go入门实践五-实现一个https服务
文章目录
- 前言
- 生成证书
- 申请免费的证书
- 使用Go语言生成自签CA证书
- https的客户端和服务端
- 服务端代码
- 客户端代码
- tls的客户端和服务端
- 服务端
- 客户端
前言
在公网中,我想加密传输的数据。(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 socatda1234cao@vultr:~$ curl https://get.acme.sh | sh -s email=da1234cao@163.com% Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed
100 1032 0 1032 0 0 4384 0 --:--:-- --:--:-- --:--:-- 4410% Total % Received % Xferd Average Speed Time Time Time CurrentDload 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-SHA384Issuer: C = AT, O = ZeroSSL, CN = ZeroSSL ECC Domain Secure Site CAValidityNot Before: Aug 11 00:00:00 2023 GMTNot After : Nov 9 23:59:59 2023 GMTSubject: CN = *.da1234cao.top <-----# 查看key信息
openssl ec -noout -text -in '*.da1234cao.top.key'
read EC key
Private-Key: (256 bit)
使用Go语言生成自签CA证书
这里有比较详细的介绍:使用Go语言生成自签CA证书
package certificateimport ("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.ListenPortlistenIp := Conf.ListenIpif 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
服务端
func TLSDataHandle(conn net.Conn) {for {// 读取requestioBuf := 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))// 构建一个responsebuf := 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)// 发送responsebuf.WriteTo(conn)}
}func TLSStart() error {listenPort := Conf.ListenPortlistenIp := Conf.ListenIpif 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服务
文章目录 前言生成证书申请免费的证书使用Go语言生成自签CA证书 https的客户端和服务端服务端代码客户端代码 tls的客户端和服务端服务端客户端 前言 在公网中,我想加密传输的数据。(1)很自然,我想到了把数据放到http的请求中,然后通过tls确…...

面试之快速学习STL-set
set 和 map、multimap 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等使用 set 容器存储的各个元素的值必须各不相同从语法上讲 set 容器并没有强制对存储元素的类型做 const 修饰, 即 set 容器中存储的元素的值是可以修改的。…...

leetcode 1614.括号的最大嵌套深度
⭐️ 题目描述 🌟leetcode链接:括号的最大嵌套深度 ps: 使用数据结构栈来存储 ( 在使用 maxDepth 变量记录栈顶 top 的最大值,当遇到 ) 时删除栈顶元素。举个例子 (1)((2))(((3))),当遇到第一个 ( 时 top 1ÿ…...

Ajax 笔记(四)—— Ajax 进阶
笔记目录 4. Ajax 进阶4.1 同步代码和异步代码4.2 回调函数地狱4.2.1 解决方法一:Promise 链式调用4.2.2 解决方法二:async 函数和 await 4.3 Promise.all 静态方法4.4 事件循环4.4.1 事件循环4.4.2 宏任务与微任务 4.5 案例4.5.1 案例一-商品分类4.5.2 …...

Linux 5种网络IO模型
Linux IO模型 网络IO的本质是socket的读取,socket在linux系统被抽象为流,IO可以理解为对流的操作。刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操…...

Linux多线程【初识线程】
✨个人主页: 北 海 🎉所属专栏: Linux学习之旅 🎃操作环境: CentOS 7.6 阿里云远程服务器 文章目录 🌇前言🏙️正文1、什么是线程?1.1、基本概念1.2、线程理解1.3、进程与线程的关系…...

Python爬虫的应用场景与技术难点:如何提高数据抓取的效率与准确性
作为专业爬虫程序员,我们在数据抓取过程中常常面临效率低下和准确性不高的问题。但不用担心!本文将与大家分享Python爬虫的应用场景与技术难点,并提供一些实际操作价值的解决方案。让我们一起来探索如何提高数据抓取的效率与准确性吧…...

Spring Cloud Gateway系例—参数配置(CORS 配置、SSL、元数据)
一、CORS 配置 你可以配置网关来控制全局或每个路由的 CORS 行为。两者都提供同样的可能性。 1. Global CORS 配置 “global” CORS配置是对 Spring Framework CorsConfiguration 的URL模式的映射。下面的例子配置了 CORS。 Example 77. application.yml spring:cloud:gat…...

QT:UI控件(按设计师界面导航界面排序)
基础部分 创建新项目:QWidget,QMainWindow,QDialog QMainWindow继承自QWidget,多了菜单栏; QDialog继承自QWidget,多了对话框 QMainWindow 菜单栏和工具栏: Bar: 菜单栏:QMenuBar࿰…...

AtCoder Beginner Contest 314-A/B/C
A - 3.14 题目要求输出圆周率保留小数几位后的结果 用字符串来存储长串的圆周率,截取字符串就可以了。 #include<iostream> using namespace std; int main() {string s"3.1415926535897932384626433832795028841971693993751058209749445923078164062…...

讯飞星火、文心一言和通义千问同时编“贪吃蛇”游戏,谁会胜出?
同时向讯飞星火、文心一言和通义千问三个国产AI模型提个相同的问题: “python 写一个贪吃蛇的游戏代码” 看哪一家AI写的程序直接能用,谁就胜出! 讯飞星火 讯飞星火给出的代码: import pygame import sys import random# 初…...

数学建模之“聚类分析”原理详解
一、聚类分析的概念 1、聚类分析(又称群分析)是研究样品(或指标)分类问题的一种多元统计法。 2、主要方法:系统聚类法、有序样品聚类法、动态聚类法、模糊聚类法、图论聚类法、聚类预报法等。这里主要介绍系统聚类法…...

【面试问题】当前系统查询接口需要去另外2个系统库中实时查询返回结果拼接优化思路
文章目录 场景描述优化思路分享资源 场景描述 接口需要从系统1查询数据,查出的每条数据需要从另一个系统2中再去查询某些字段, 比如:从系统1中查出100条数据,每条数据需要去系统2中再去查询出行数据,可能系统1一条数…...

Scada和lloT有什么区别?
人们经常混淆SCADA(监督控制和数据采集)和IIoT(工业物联网)。虽然SCADA系统已经存在多年,但IIoT是一种相对较新的技术,由于其能够收集和分析来自各种设备的大量数据而越来越受欢迎。SCADA和IIoT都用于提高工…...

Conda(Python管理工具)
1.简介 Conda是一个开源的包管理器和环境管理器,主要用于管理Python,但也可以用于其他语言。它主要用于安装、管理和更新软件包及其依赖项,以及创建、保存、加载和切换不同的开发环境。Conda可以在Windows、MacOS和Linux系统上使用ÿ…...

(14)嵌套列表,Xpath路径表达式,XML增删查改,Implicit,Operator,Xml序列化,浅拷贝与深拷贝
一、作业问题 1、问:listbox1.items[i]返回的object是指的字符串吗? 答:items是真正的对象集合,在Add时加的是Person对象p,则里面的item就是Person对象p。 但是,在listbox1显…...

软考笔记 信息管理师 高级
文章目录 介绍考试内容与时间教材 预习课程一些例子课本结构考试内容 1 信息与信息化1.1 信息与信息化1.1.1 信息1.1.2 信息系统1.1.3 信息化 1.2 现代化基础设施1.2.1 新型基础建设1.2.2 工业互联网1.2.3 车联网: 1.3 现代化创新发展1.3.1 农业农村现代化1.3.2 两化…...

124、SpringMVC处理一个请求的流程是怎样的?
SpringMVC处理一个请求的流程是怎样的? 一、处理流程二、流程图三、额外扩展(可不看)一、处理流程 Tomcat接收到一个请求后,会交给DispatcherServlet进行处理DispatcherServlet会根据请求的path找到对应的HandlerHandler就是一个加了@RequestMapping的方法,然后就利用反射…...

低成本高收益,五金店小程序的秘密武器
如今,随着移动互联网的快速发展,小程序成为了许多企业进行线上业务拓展的重要方式之一。对于那些不懂代码的人来说,制作一个小程序可能会让人觉得困难重重。但是,现在,借助乔拓云平台,不懂代码的人也能轻松…...

C语言宏定义详解
文章目录 宏定义无参宏定义带参宏定义固定参数宏可变参数宏 多语句宏处理连接符条件判断常见预定义宏 宏在C语言中是一段有名称的代码片段(使用#define定义),在预处理阶段会把程序中的宏名替换为对应的代码片段,然后才进入编译阶段…...

SwiftUI 动画进阶:实现行星绕圆周轨道运动
0. 概览 SwiftUI 动画对于优秀 App 可以说是布帛菽粟。利用美妙的动画我们不仅可以活跃界面元素,更可以单独打造出一整套生动有机的世界,激活无限可能。 如上图所示,我们用动画粗略实现了一个小太阳系:8大行星围绕太阳旋转,而卫星们围绕各个行星旋转。 在本篇博文中,您将…...

物理试题-空气净化器
详细解释...

Es、kibana安装教程-ES(二)
上篇文章介绍了ES负责数据存储,计算和搜索,他与传统数据库不同,是基于倒排索引来解决问题的。Kibana是es可视化工具。 分布式搜索ElasticSearch-ES(一) 一、ElasticSearch安装 官网下载地址:https://www…...

leetcode 917.仅仅反转字母
⭐️ 题目描述 🌟 leetcode链接:仅仅反转字母 ps: 这道题思路很简单,只需要一个下标在前一个下标在后,分别找是字母的字符,找到之后交换即可。 代码: class Solution { public:bool isAlpha …...

有没有推荐的golang的练手项目?
前言 下面是github上的golang项目,适合练手,可以自己选择一些项目去练习,整理不易,希望能多多点赞收藏一下!废话少说,我们直接进入正题>>> 先推荐几个教程性质的项目(用于新手学习、巩…...

springBoot的日志文件
日志是程序的重要组成部分,主要可以用来定位和排查问题。除此之外,还可以用来: 1. 记录用户的登录日志,方便分析用户是正常登录还是恶意破解; 2. 记录系统的操作日志,方便数据恢复和定位操作人;…...

Linux学习之iptables的nat表
iptables -t nat 命令 规则链 规则是格式命令。 PREROUTING一般用于内网,用于目的地址转换。 POSTROUTING一般用于外网,用于源地址转换。 iptables -t nat -A PREROUTING -i eth0 -d 114.115.116.117 -p tcp --dport 80 -j DNAT --to-destination 10.0.0…...

【数据结构】 ArrayList简介与实战
文章目录 什么是ArrayListArrayList相关说明 ArrayList使用ArrayList的构造无参构造指定顺序表初始容量利用其他 Collection 构建 ArrayListArrayList常见操作获取list有效元素个数获取和设置index位置上的元素在list的index位置插入指定元素删除指定元素删除list中index位置上…...

您的网站不应该只提供一套通用 API
后端应该提供两套 API,一套是外部使用的通用 API,服务特定的数据,另一套是自家使用的应用 API,服务特定的页面。 在当今的web开发中,构建一个提供JSON服务的后端和一个渲染应用程序的前端是很流行的。我不太喜欢&…...

vue tree禁用和多选变为单选
禁用的话和后台协调一下,参数中多返回一个disabled 多选变单选 在tree结构中加入一个方法 <el-treeaccordion:data"deptOptions":props"defaultProps"show-checkbox:expand-on-click-node"false":filter-node-method"filte…...