go入门实践五-实现一个https服务

这篇具有很好参考价值的文章主要介绍了go入门实践五-实现一个https服务。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

在公网中,我想加密传输的数据。(1)很自然,我想到了把数据放到http的请求中,然后通过tls确保数据安全。(2)更进一步,只要数据可以解析,则无需http协议,直接通过tls协议加密传输即可。本文分别尝试了这两个方案。

尝试实现方案之前,我们考虑需要实现哪些内容。(1)如何获取证书。(2)golang中如何实现一个https的客户端和服务器。(3)golang中如何实现一个tls的客户端和服务器。(4)http的request和response的构建,发送和解析。(5)对于客户端, 应用层(http)是否应该复用网络层(tcp)的连接; 哪些需求下不能复用; (6)不考虑传输层的网络细节。

注:本文不涉及相关内容的背景知识介绍。本文完整代码见仓库。


生成证书

如果有已经购买的域名,可以申请一个免费的通配符证书,便于日常使用。

没有域名的话:可以通过命令行生成证书,见:windows和linux上证书的增删查。也可以通过go代码来创建证书。


申请免费的证书

首先是安装acme.sh

sudo apt-get -y install socat

da1234cao@vultr:~$ curl https://get.acme.sh | sh -s email=da1234cao@163.com
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1032    0  1032    0     0   4384      0 --:--:-- --:--:-- --:--:--  4410
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  216k  100  216k    0     0   715k      0 --:--:-- --:--:-- --:--:--  715k
[Fri Aug 11 06:17:18 AM UTC 2023] Installing from online archive.
[Fri Aug 11 06:17:18 AM UTC 2023] Downloading https://github.com/acmesh-official/acme.sh/archive/master.tar.gz
[Fri Aug 11 06:17:19 AM UTC 2023] Extracting master.tar.gz
[Fri Aug 11 06:17:19 AM UTC 2023] Installing to /home/da1234cao/.acme.sh
[Fri Aug 11 06:17:19 AM UTC 2023] Installed to /home/da1234cao/.acme.sh/acme.sh
[Fri Aug 11 06:17:19 AM UTC 2023] Installing alias to '/home/da1234cao/.bashrc'
[Fri Aug 11 06:17:19 AM UTC 2023] OK, Close and reopen your terminal to start using acme.sh
[Fri Aug 11 06:17:19 AM UTC 2023] Installing cron job
8 0 * * * "/home/da1234cao/.acme.sh"/acme.sh --cron --home "/home/da1234cao/.acme.sh" > /dev/null
[Fri Aug 11 06:17:19 AM UTC 2023] Good, bash is found, so change the shebang to use bash as preferred.
[Fri Aug 11 06:17:20 AM UTC 2023] OK
[Fri Aug 11 06:17:20 AM UTC 2023] Install success!

为了使用方便,我这里申请一个泛域名证书。我的域名是在阿里云购买的,所以本文仅尝试获取阿里云的泛域名证书。

参考:阿里云域名使用ACME自动申请免费的通配符https域名证书、acme.sh 使用泛域名|阿里云DNS |免费申请证书。

大概过程是:AccessKey管理->创建子用户->允许open API访问->添加DNS管理权限。将获取到的AccessKey 和 Secret 写到acme.sh.env配置文件里面。

export Ali_Key="*****"
export Ali_Secret="*******"

执行source ~/.bashrc

然后开始申请证书。

sudo ufw status
sudo ufw allow 80

# --debug 参数查看执行过程
# 没有web服务,80端口空闲, acme.sh 还能假装自己是一个webserver, 临时听在80 端口, 完成验证
## 如果执行报错;稍等等会再尝试;
acme.sh --issue --dns dns_ali -d *.da1234cao.top --standalone --debug

[Fri Aug 11 07:07:43 AM UTC 2023] Your cert is in: /home/da1234cao/.acme.sh/*.da1234cao.top_ecc/*.da1234cao.top.cer
[Fri Aug 11 07:07:43 AM UTC 2023] Your cert key is in: /home/da1234cao/.acme.sh/*.da1234cao.top_ecc/*.da1234cao.top.key
[Fri Aug 11 07:07:43 AM UTC 2023] The intermediate CA cert is in: /home/da1234cao/.acme.sh/*.da1234cao.top_ecc/ca.cer
[Fri Aug 11 07:07:43 AM UTC 2023] And the full chain certs is there: /home/da1234cao/.acme.sh/*.da1234cao.top_ecc/fullchain.cer
[Fri Aug 11 07:07:43 AM UTC 2023] _on_issue_success
# 查看证书信息
openssl x509 -noout -text -in '*.da1234cao.top.cer'
 Signature Algorithm: ecdsa-with-SHA384
        Issuer: C = AT, O = ZeroSSL, CN = ZeroSSL ECC Domain Secure Site CA
        Validity
            Not Before: Aug 11 00:00:00 2023 GMT
            Not After : Nov  9 23:59:59 2023 GMT
        Subject: CN = *.da1234cao.top  <-----

# 查看key信息
openssl ec -noout -text -in '*.da1234cao.top.key'
read EC key
Private-Key: (256 bit)

使用Go语言生成自签CA证书

这里有比较详细的介绍:使用Go语言生成自签CA证书

package certificate

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"io/ioutil"
	"math/big"
	"time"
)

func Gencertificate(output string) error {
	// ref: https://foreverz.cn/go-cert

	// 生成私钥
	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return err
	}

	// x509证书内容
	var csr = &x509.Certificate{
		Version:      3,
		SerialNumber: big.NewInt(time.Now().Unix()),
		Subject: pkix.Name{
			Country:            []string{"CN"},
			Province:           []string{"Shanghai"},
			Locality:           []string{"Shanghai"},
			Organization:       []string{"httpsDemo"},
			OrganizationalUnit: []string{"httpsDemo"},
			CommonName:         "da1234cao.top",
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(1, 0, 0),
		BasicConstraintsValid: true,
		IsCA:                  false,
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
	}

	// 证书签名
	certDer, err := x509.CreateCertificate(rand.Reader, csr, csr, priv.Public(), priv)
	if err != nil {
		return err
	}

	// 二进制证书解析
	interCert, err := x509.ParseCertificate(certDer)
	if err != nil {
		return err
	}

	// 证书写入文件
	pemData := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: interCert.Raw,
	})
	if err = ioutil.WriteFile(output+"cert.pem", pemData, 0644); err != nil {
		panic(err)
	}

	// 私钥写入文件
	keyData := pem.EncodeToMemory(&pem.Block{
		Type:  "EC PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(priv),
	})

	if err = ioutil.WriteFile(output+"key.pem", keyData, 0644); err != nil {
		return err
	}

	return nil
}

https的客户端和服务端

轮子已经有了,net/http。我没有看到net/http很好的入门教程。只能看下官方文档,网上翻翻一些简单的示例。

下面是一个示例。其中,必须一提的是,http请求结束后,连接可以仍然存在,放到闲置的连接池中。便于后续请求,复用之前的连接。我到源码里面去看了下,没太看懂,可见:Golang Http RoundTrip解析。


服务端代码

启动服务端后,对于/reflect路径的request,构建一个response。注意其中的header和body内容的填充。

func reflect(w http.ResponseWriter, r *http.Request) {
	log.Println("handle reflect")
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	bodyByte, _ := io.ReadAll(r.Body)
	log.Println("recv:", string(bodyByte))
	w.Write(bodyByte)
}

func Start() error {
	listenPort := Conf.ListenPort
	listenIp := Conf.ListenIp
	if listenPort <= 0 || listenPort > 65535 {
		log.Println("invalid listen port:", listenPort)
		return errors.New("invalid listen port")
	}

	http.HandleFunc("/reflect", reflect)
	err := http.ListenAndServeTLS(listenIp+":"+strconv.Itoa(listenPort), Conf.Protocol.Https.Certificate, Conf.Protocol.Https.Key, nil)
	return err
}

客户端代码

客户端可以选择是否验证服务端的证书。验证证书不重要,因为我信任这个域名解析过程(如果DNS没有被污染的话)。数据可以加密传输即可。代码中使用http.NewRequest构建一个请求,然后通过http.Client.Do发送请求(可能会复用之前的连接)。

func New() *http.Client {
	cli := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: Conf.SkipVerify},
		},
	}
	return cli
}

func DoRequest(cli *http.Client, data []byte) (*http.Response, error) {
	req, err := http.NewRequest("POST", Conf.Protocol+"://"+Conf.ServerIp+":"+strconv.Itoa(Conf.ServerPort)+"/reflect", bytes.NewBuffer(data))
	if err != nil {
		log.Println("fail to consstruct request", err)
	}
	return cli.Do(req)
}

func PrintResponse(resp *http.Response, err error) {
	if err == nil {
		if resp.StatusCode == http.StatusOK {
			body, _ := ioutil.ReadAll(resp.Body)
			log.Print(string(body))
		}
		log.Println("http status code:", resp.StatusCode)
	} else {
		log.Print(err)
	}
}

tls的客户端和服务端

当我们的应用层不需要http协议,只需要对应用层的数据进行加密传输。我们尝试下面的代码(下面代码中,我手动构建了http的request和response,是为了保证接收到完整的数据后再处理,仅此而已)。 使用的库是crypto/tls文章来源地址https://www.toymoban.com/news/detail-652631.html

服务端

func TLSDataHandle(conn net.Conn) {
	for {
		// 读取request
		ioBuf := bufio.NewReader(conn)
		req, err := http.ReadRequest(ioBuf)
		if err != nil {
			log.Println(err)
			return
		}
		defer req.Body.Close()
		defer conn.Close()
		bodyByte, _ := io.ReadAll(req.Body)
		log.Println("recv: ", string(bodyByte))

		// 构建一个response
		buf := bytes.NewBuffer(nil)
		buf.WriteString("HTTP/1.1 200 OK\r\n")
		buf.WriteString("Content-Length: " + strconv.Itoa(len(bodyByte)) + "\r\n")
		buf.WriteString("\r\n")
		buf.Write(bodyByte)

		// 发送response
		buf.WriteTo(conn)
	}
}

func TLSStart() error {
	listenPort := Conf.ListenPort
	listenIp := Conf.ListenIp
	if listenPort <= 0 || listenPort > 65535 {
		log.Println("invalid listen port:", listenPort)
		return errors.New("invalid listen port")
	}

	cert, err := tls.LoadX509KeyPair(Conf.Protocol.Https.Certificate, Conf.Protocol.Https.Key)
	if err != nil {
		log.Println("fail to laod x509 key pair", err)
	}

	config := &tls.Config{Certificates: []tls.Certificate{cert}}
	listener, _ := tls.Listen("tcp", listenIp+":"+strconv.Itoa(listenPort), config)
	for {
		conn, _ := listener.Accept()
		go TLSDataHandle(conn)
	}
}

客户端

func NewTlsConn() (net.Conn, error) {
	config := &tls.Config{InsecureSkipVerify: Conf.SkipVerify}
	return tls.Dial("tcp", Conf.ServerIp+":"+strconv.Itoa(Conf.ServerPort), config)
}

func SendRequest(conn net.Conn, data []byte) {
	// 构造一个请求
	buf := bytes.NewBuffer(nil)
	buf.WriteString("POST /no_thing")
	buf.WriteString(" HTTP/1.1\r\n")
	buf.WriteString("Content-Length: " + strconv.Itoa(len(data)) + "\r\n")
	buf.WriteString("\r\n")
	buf.Write(data)

	// 发送请求
	buf.WriteTo(conn)

	// 读取回复
	ioBuf := bufio.NewReader(conn)
	res, err := http.ReadResponse(ioBuf, nil)
	if err != nil {
		log.Println(err)
		return
	}
	defer res.Body.Close()
	bodyByte, _ := io.ReadAll(res.Body)
	log.Println(string(bodyByte))
}

到了这里,关于go入门实践五-实现一个https服务的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Golang 实现一个简单的 RPC 服务

    分享一个简单的 rpc 服务框架 一、服务端实现 二、客户端实现

    2024年04月10日
    浏览(45)
  • 【Golang星辰图】Go语言云计算SDK全攻略:深入Go云存储SDK实践

    在当今数字化时代,云计算和存储服务扮演着至关重要的角色,为应用程序提供高效、可靠的基础设施支持。本文将介绍几种流行的Go语言SDK,帮助开发者与AWS、Google Cloud、Azure、MinIO、 阿里云和腾讯云等各大云服务提供商的平台进行交互。 欢迎订阅专栏:Golang星辰图 1.1 提供

    2024年03月17日
    浏览(52)
  • 学习如何在VS Code中创建一个Golang/Go项目,并运行一个简单的Golang程序

     学习如何在VS Code中创建一个Golang项目,并运行一个简单的Golang程序。 在VS Code 手动输入命令创建一个Golang项目 在VS Code 不输入命令创建一个Golang项目 1. 在VS Code 手动输入命令创建一个Golang项目 步骤1:在VS Code中创建一个新文件夹,用于存放Golang项目文件。 步骤2:打开VS

    2024年02月14日
    浏览(61)
  • 【GoLang入门教程】Go语言工程结构详述

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站:人工智能 前言 当创建一个Go语言项目时,良好的工程结构是确保项目可维护性、可扩展性和清晰性的关键。 虽然Go本身没有强制性的项目结构要求,但是采用一致性

    2024年01月24日
    浏览(80)
  • 猜谜游戏、彩云词典爬虫、SOCKS5代理的 Go(Golang) 小实践,附带全代码解释

    猜谜游戏在编程语言实践都已经和 HelloWord 程序成为必不可少的新手实践环节,毕竟,它能够让我们基本熟悉 for 循环、变量定义、打印、if else 语句等等的使用,当我们基本熟悉该语言基础之后,就要学会其优势方面的程序实践,比如 Golang 所具备的爬虫及其并发优势。我们

    2024年02月05日
    浏览(31)
  • Golang快速入门到实践学习笔记

    Go程序设计的一些规则 Go之所以会那么简洁,是因为它有一些默认的行为: 大写字母开头的变量是可导出的,也就是其它包可以读取 的,是公用变量;小写字母开头的就是不可导出的,是私有变量。 大写字母开头的函数也是一样,相当于class 中的带public的公有函数;

    2024年02月20日
    浏览(56)
  • 【Golang入门教程】Go语言变量的初始化

    强烈推荐 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站: 人工智能 推荐一个个人工作,日常中比较常用的人工智能工具,无需魔法,忍不住分享一下给大家。点击跳转到网站: 人工智能工具 引言 在Go语言中,变量

    2024年04月17日
    浏览(79)
  • 【Go语言】Golang保姆级入门教程 Go初学者chapter2

    setting的首选项 一个程序就是一个世界 变量是程序的基本组成单位 变量的使用步骤 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuxG8imp-1691479164956)(https://cdn.staticaly.com/gh/hudiework/img@main/image-20230726152905139.png)] 变量表示内存中的一个存储区 注意:

    2024年02月14日
    浏览(129)
  • 【Go语言】Golang保姆级入门教程 Go初学者chapter3

    下划线“_”本身在Go中一个特殊的标识符,成为空标识符。可以代表任何其他的标识符,但是他对应的值就会被忽略 仅仅被作为站维度使用, 不能作为标识符使用 因为Go语言中没有private public 所以标记变量首字母大写代表其他包可以使用 小写就是不可使用的 注意:Go语言中

    2024年02月13日
    浏览(62)
  • golang redis第三方库github.com/go-redis/redis/v8实践

    这里示例使用 go-redis v8 ,不过 go-redis latest 是 v9 安装v8:go get github.com/go-redis/redis/v8 Redis 5 种基本数据类型:  string 字符串类型;list列表类型;hash哈希表类型;set集合类型;zset有序集合类型   最基本的Set/Get操作 # setget.go package  main import  ( \\\"context\\\" \\\"fmt\\\" \\\"time\\\" \\\"github.com/go-re

    2024年02月12日
    浏览(71)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包