基于 Gin 的 HTTPS 代理 Demo

news2024/11/29 20:37:00

上次写了 基于 Gin 的 HTTP 代理 Demo 之后,对这方面还是蛮感兴趣的,所以就接着继续走下去。为了这个主题的内容,我斥巨资购入了一本二手的 《HTTP 权威指南》,因为我知道这本书里面有我想要的知识。在我还在大学的时候,我就看过这本书的前面关于 HTTP 协议的基本知识,当时正好也接触了 Fiddler,所以就利用 Fiddler 进行学习。抓取协议,了解各个字段的含义,尝试用JAVA的 TCP 来模拟,因此对于 HTTP 协议有了一个基本的认识。当时看到后面的章节,我就看到了关于代理和隧道的内容,不过当时显然是看不懂的,但是这颗种子已经在我心里埋下了。后来,我已经很少使用 Fiddler 了,但是我对于它的工作原理却一直很感兴趣,现在让我们从代理的角度来理解它吧。那么首先就是明白它的工作原理,所以最好的方式就是写一个 Demo 了。

所以,我觉得自己做一个 HTTP(HTTPS) 代理服务器的 Demo,对于这种广泛使用的软件,想要做得很好是需要很大的能力和精力的,但是做一个可以运行的 Demo 还是要轻松一点的。下面就让我们尝试在 100 行之内,使用 Gin 实现一个建议的 HTTP/HTTPS 代理服务器的 Demo吧。

注1:为什么是 100 行呢,因为我在快实现的时候,发现了一个老外写的相似的内容,100 行实现一个 HTTP 代理服务器。我也吸收了它的部分代码,就是关于建立 TCP 隧道之后的读写。他直接使用了 io.Copy,而我最开始是使用的 ReadWrite 方法,老实说自己来处理网络流的读写真的是麻烦(也做不好,没有考虑各种可能的异常情况),不建议这样来做。

注2:为什么使用 Gin 呢,如果你去搜索实现一个 HTTP 代理服务器,这基本上算是一个 Netty 的入门项目了(我发现很多人都是用 Netty 写这个)。因为我现在是主要使用 Go 语言了,所以我首选是用 Go 语言来实现,还有,我想要表明的是:任何 Web 框架都能作为 一个 HTTP 代理服务器。 当然了,通常来说使用 Netty 这种框架是最好的。但是,好不好和能不能是两回事,我想对于一个 Demo 来说,只要实现能不能就行了,而且你也会学习到一些你使用 Netty 无法了解到的知识。

一、代码和演示

Talk is cheap, show me your code.

让我们直入主题,上代码吧!

1.1 代码

package main

import (
	"bufio"
	"io"
	"log"
	"net"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
)

const (
	RPOXY_SERVER  = "CrazyDragonHttpProxy"                                                             // it is just a kidding, but Only HTTP!
	TUNNEL_PACKET = "HTTP/1.1 200 Connection Established\r\nProxy-agent: CrazyDragonHttpProxy\r\n\r\n" // Don'e USE `` to surround a protocl strng, DAMN!!!
)

var proxyHttpClient = http.DefaultClient

func main() {
	r := gin.Default()
	r.NoRoute(routeProxy)   // NO Route is every Route!!!
	r.Run("localhost:8888") // I may be safer when in only run in localhost.
}

// Then I can process all routes
func routeProxy(c *gin.Context) {
	req := c.Request
	go func(req *http.Request) { // just print basic info. Remember you can't proxy youself.
		log.Printf("Method: %s, Host: %s, URL: %s, Version: %s\n", req.Method, req.Host, req.URL.Path, req.Proto)
	}(req)

	if req.Method == http.MethodConnect {
		httpsProxy(c, req) // create http tunnel to process https
	} else {
		httpProxy(c, req) // process plain http
	}
}

func httpsProxy(c *gin.Context, req *http.Request) {
	// established connect tunnel
	address := req.URL.Host // it contains the port
	tunnelConn, err := net.Dial("tcp", address)
	if err != nil {
		log.Println(err)
		return
	}
	log.Printf("try to established Connect Tunnel to: %s has been successfully.\n", address)
	tunnelrw := bufio.NewReadWriter(bufio.NewReader(tunnelConn), bufio.NewWriter(tunnelConn))
	// c.Status(200)
	// c.Writer.WriteHeaderNow()

	// And We need to take over the http connection, Then make it become a TCP connection.
	hj, ok := c.Writer.(http.Hijacker)
	if !ok {
		http.Error(c.Writer, "webserver doesn't support hijacking", http.StatusInternalServerError)
		return
	}
	clientConn, bufrw, err := hj.Hijack()
	if err != nil {
		http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
		return
	}
	// 建立隧道之后,发送一个连接建立的响应(其实,只要状态码是 200 就可以了)
	if _, err = clientConn.Write([]byte(TUNNEL_PACKET)); err != nil {
		log.Printf("Response Failed: %v", err.Error())
	} else {
		log.Println("Response Success.")
	}
	// data flow direction: client <---> tunnel <---> server
	defer clientConn.Close()
	defer tunnelConn.Close()
	done := make(chan struct{})
	go transfer(bufrw, tunnelrw, done) // client --> proxy --> server
	go transfer(tunnelrw, bufrw, done) //server --> proxy --> client
	<-done
}

func httpProxy(c *gin.Context, req *http.Request) {
	req.RequestURI = "" // Must create a new Req or empty this.
	resp, err := proxyHttpClient.Do(req)
	if err != nil {
		log.Println(err)
		return
	}
	defer resp.Body.Close()
	c.Status(resp.StatusCode) // change the status code, default is 404 !!!
	for k, v := range resp.Header {
		c.Header(k, strings.Join(v, ",")) // write Header
	}
	c.Header("Server", RPOXY_SERVER) // haha, it just a kidding!!!
	io.Copy(c.Writer, resp.Body)     // and response data to client
}

// tunnel transfer data.
func transfer(from io.Reader, to io.Writer, ch chan<- struct{}) {
	io.Copy(to, from)
	ch <- struct{}{}
}

1.2 启动代理服务器 Demo

我已经把它交叉编译成 Windows 的可执行文件了,因为我是在镜像内开发的,所以要拿出来运行(或者可能要配置 Docker 的网络,不过那就变麻烦了。)

在这里插入图片描述

在这里插入图片描述

一定要注意是先启动代理,然后再配置系统代理,不然在配置系统代理到启动代理服务器的这段时间内,你是断网的。我相信,使用过代理上网的大部分人都遇到过代理服务器关闭了,但是系统代理没有关闭,导致自己上不了网,然后还看不懂浏览器的报错提示吧,哈哈!

在这里插入图片描述

1.3 系统代理配置

http=127.0.0.1:8888;https=127.0.0.1:8888 因为我见 Fiddler 是指定了 HTTP 和 HTTPS 协议,所以我就复制它的来配置吧。

在这里插入图片描述

1.4 运行效果

在这里插入图片描述

我写这篇文章就是在开启了代理的情况下,所以我插入图片,可以看到 csdn 的链接,而且证明了它是没有什么问题的。

在这里插入图片描述

注意:如果你可能注意到了这里大量的 404 请求日志。我一开始也感觉到很困惑,不过在我一番探索之后发现。它是因为我的请求都是在 NotRoute 中处理的,它默认是绑定了 404 的 handler(打印日志时,应该是依赖了状态码)。但是你可以看见下面的 200,你能看到它的请求方法(不是 CONNECT)说明它是 HTTP 请求。在那里,我是手动设置了状态码。但是 HTTPS 请求,因为劫持之后就不是 HTTP 连接了,退化成了 TCP 连接了,所以我就改不了了(那个时候已经脱离 gin 或者说 http 服务器的控制了)。

在这里插入图片描述

实际上,我也可以改的,那就是不在隧道建立之后发送响应,而是提前发送:把这两行代码放开,然后建立连接之后的写入连接建立成功的那段代码注释掉就可以了。效果的话就像下图一样,不过我感觉这样不符合代理服务器实现的时序逻辑了,而且实际上返回的是 200 OK 而不是 200 Connection Established。但是因为状态码才是最重要的,这个短语是给人看的,所以不是那么重要,不过为了合乎逻辑,我选择了后者,所以就让它显示 404 吧。

// c.Status(200)
// c.Writer.WriteHeaderNow()

在这里插入图片描述

不过这么多 404,看起来真的挺烦人的,还是来改一下吧,只把下面这一行放出来就行,我看源码这个应该是先写入一个缓冲区的,不是写入连接的,所以也不影响后续的读写,这样就只影响记录日志时的状态了。

c.Status(200)

这样就好多了,不过还是有一些 404,但是它们是 HTTP 的状态码了。这个情况还是不一样的,我去查了一下,这几个 URL 是和证书认证有关的,似乎是问我要认证证书的,我怎么会有这种东西呢,哈哈,索性就不管它们了。

在这里插入图片描述

二、HTTPS 代理时序图

强烈推荐阅读《HTTP权威指南》第 6 章和第 8 章,如果你也对这一块感兴趣的话,必然会大有收获了。下面是一个简单的 HTTPS 连接代理的时序图:

在这里插入图片描述

因为这里的代理是 HTTP 服务器,它是无法处理 HTTPS 连接的,没法进行 TLS 握手。所以客户端会使用 HTTP 协议发送一个 CONNECT 连接,代理会去连接服务器建立一个隧道(一个 TCP 连接)。如果建立成功,它就会向客户端响应一个连接已经建立的请求报文,然后客户端直接向代理发送 TCP 上的数据,代理虽然无法理解,但是可以转发它。这就相当于客户端到代理,代理到服务器都是一个 TCP 连接,它不需要管它们直接发送的是什么,只需要盲目的转发数据即可。从而实现了不同协议之间的通讯。这里不止可以传递 HTTPS 流量,其它类型的协议也是可以的,稍后我们会提到一个众所周知的协议。

三、使用 Gin 实现的技术难点

这里使用 Gin 来做,其实还是蛮方便的,第一个难点是如何处理所有的连接。这个在上一篇文章中已经介绍过了,就是通过处理 404 请求,没有路由就是等于全部的路由了。第二个难点是客户端和代理之间的 TCP 连接,刚开始的时候,它是一个 HTTP 连接,然后要在它上面传输 TCP 流量。这个可是难倒了我,我去看源码发现底层的 TCP 连接是不导出的变量,没有办法直接操作它。

在这里插入图片描述

不过,最后我还是找到了 hijacker,这个方法我以前见过,不明白这玩意干嘛的,一眼而过。不过,现在它可真是我的大救星了,哈哈。看来,如果不了解一些其它的知识,是无法了解代码的用处的。这个接口被 ResponseWriters 实现,允许我们去接管底层的 TCP 连接。所以,你在我的代码里面可以看到,我调用了 Hijacke 方法,然后就在那上面转发 HTTPS 的流量了。

注:我认识 hijack 这个单词比见到 Hijack() 方法 可能要早,所以就更有意思了(这个单词的意思是 劫持,打劫)。

在这里插入图片描述

这个 Hijack 还是很有趣的,你可能不明白为什么会提供一个这样的方法呢?让我们去看一看大名鼎鼎的 Gorilla/websocket 是怎么实现的吧!是的,没错,它就是依靠 Hijacke 实现的。这里你需要简单了解一下,WebSocket 也是通过一个 HTTP/HTTPS 请求建立的,然后它会劫持这个连接,获取底层的 TCP 连接,然后会返回一个 101 的状态码(Connection: Upgrade),之后这个连接就是 WebSocket 连接,你就可以在连接上进行双向的数据传输了。

在这里插入图片描述

下一步展望

我这里的文字描述可能比较少,因为这个确实需要你有一点网络的知识了,特别是有代理的使用经验,会更有助于你理解的。那么下一步还能做什么呢?这个程序可以沿着这个思路往下继续走下去,我大致有两个想法:

在这里插入图片描述
如果尝试做一个抓包软件的话,需要解决 HTTPS 报文解密的问题,这个我也在考虑,不过这个东西的意义就不大了。因为抓包软件都蛮成熟的了,不过如果是为了再深入了解抓包软件解密的原理,也是蛮有意思的,这个我可能会尝试去做一下解密的这一块,稍微了解一下就行了。
然后,是下面这个上网行为分析,记录一下自己日常看了哪些网站,然后统计一下数据或者只是简单记录一下,我感觉更有意思一点或者说更实用一些吧,我还是对自己日常主要看哪些网站比较感兴趣的(我平时刷 B 站比较多一些,哈哈)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1253805.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kerberos 高可用配置和验证

参考 https://cloud.tencent.com/developer/article/1078314 https://mp.weixin.qq.com/s?__bizMzI4OTY3MTUyNg&mid2247485861&idx1&snbb930a497f63ac5e63ed20c64643eec5 机器准备 Kerberos主 ip-172-31-22-86.ap-southeast-1.compute.internal 7.common2.hado…

美国季节性干旱数据集

美国季节性干旱数据集 美国干旱展望栅格数据集由国家气象局气候预测中心生成。它在每个月的最后一天发布&#xff0c;提供下个月的干旱前景信息。“美国季节性干旱展望”数据集每月发布一次&#xff0c;特别是每月的第三个星期四。该数据集对美国不同地区发生干旱的可能性进行…

Linux加强篇005-用户身份与文件权限

目录 前言 1. 用户身份与能力 2. 文件权限与归属 3. 文件的特殊权限 4. 文件的隐藏属性 5. 文件访问控制列表 6. su命令与sudo服务 前言 悟已往之不谏&#xff0c;知来者之可追。实迷途其未远&#xff0c;觉今是而昨非。舟遥遥以轻飏&#xff0c;风飘飘而吹衣。问征夫以…

AIGC创作系统ChatGPT网站源码、支持最新GPT-4-Turbo模型、GPT-4图片对话能力+搭建部署教程

一、AI创作系统 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI…

居家适老化设计第三十一条---卫生间水龙头

以上产品图片均来源于淘宝 侵权联系删除 居家适老化中&#xff0c;水龙头是一个非常重要的设备。水龙头的选择应该考虑到老年人的特点和需求。首先&#xff0c;水龙头的操作应该简单方便&#xff0c;老年人手部灵活性可能不如年轻人&#xff0c;因此水龙头应该设计成易于转动和…

计算机网络常考计算题之循环冗余校验(宝典教学)

文章目录 奇偶效验循环冗余校验例题四步走另一种题型 本文讲述了计算机考研中易出现的循环冗余校验&#xff0c;点赞关注收藏不迷路哦 我是一名双非计算机本科生&#xff0c;希望我的文章可以帮助到你。 奇偶效验 奇偶校验&#xff1a;也可以检测数据在传输过程中是否出现错误…

Ceph----RBD块存储的使用:详细实践过程实战版

RBD 方式的 工作 流程&#xff1a; 1、客户端创建一个pool&#xff0c;并指定pg数量&#xff0c;创建 rbd 设备并map 到文件系统&#xff1b; 2、用户写入数据&#xff0c;ceph进行对数据切块&#xff0c;每个块的大小默认为 4M&#xff0c;每个 块名字是 object序号&#xff…

【UnLua】在 Lua 中定义 UE 反射类型

【UnLua】在 Lua 中定义 UE 反射类型 用法 启动编辑器时遍历 Defines 目录下 lua 脚本来加载 UE 反射类型&#xff08;开个临时的 Lua VM 即可&#xff09;直接像 -- define a uenum in lua UEnum.EEnumGuestSomethingElse {Value1 1;Value2 2; }-- use it like a native …

算法基础之单链表

单链表 核心思想&#xff1a; 用数组模拟链表(new节点非常慢 用数组模拟快) e[N] 表示节点value ne[N]表示next指针指向 (空节点为-1) #include<iostream>using namespace std;const int N100010;//head头结点的指针//e[N] 表示节点value ne[N]表示next指针指向 //idx…

聚簇索引和非聚簇索引的区别;什么是回表

聚簇索引和非聚簇索引的区别 什么是聚簇索引&#xff1f;&#xff08;重点&#xff09; 聚簇索引就是将数据(一行一行的数据)跟索引结构放到一块&#xff0c;InnoDB存储引擎使用的就是聚簇索引&#xff1b; 注意点&#xff1a; 1、InnoDB使用的是聚簇索引&#xff08;聚簇索…

学习.NET验证模块FluentValidation的基本用法(续2:其它常见用法)

FluentValidation模块支持调用When和Unless函数设置验证规则的执行条件&#xff0c;其中when函数设置的是满足条件时执行&#xff0c;而Unless函数则是满足条件时不执行&#xff0c;这两个函数的使用示例如及效果如下所示&#xff1a; public AppInfoalidator() {RuleFor(x>…

CodeTON Round 7(D、E)

D - Ones and Twos 题意&#xff1a;给你一个长度为 n 的 数组 a &#xff0c;其中每个元素都是 1 或 2。请处理以下两种类型的询问 "1 s"&#xff1a;检查是否存在 a的子数组 &#xff0c;其总和等于 s。 "2 i v"&#xff1a;将 ai改为v。 如果数组 …

MySQL性能优化,SQL调优,SQL调优的手段

文章目录 对MySQL性能的优化的理解硬件和操作系统层面的优化架构设计层面的优化MySQL程序配置优化SQL优化 SQL调优有哪几种方式1.EXPLAIN2.SQL语句中IN包含的值不应过多3.SELECT语句务必指明字段名称4.当只需要一条数据的时候&#xff0c;使用limit 15.如果排序字段没有用到索引…

(附源码)SSM环卫人员管理平台 计算机毕设36412

目 录 摘要 1 绪论 1.1背景及意义 1.2国内外研究概况 1.3研究内容 1.4 ssm框架介绍 1.5论文结构与章节安排 2 环卫人员管理平台系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1数据增加流程 2.2.2数据修改流程 2.2.3数据删除流程 2.3 系统功能分析 2.3.1 功能性…

面对困境时的力量——《难不难》与歌手荆涛的坚持

歌手荆涛演唱的《难不难》不仅是一首歌曲&#xff0c;更是一种精神的呈现。它告诉我们&#xff0c;面对问题时&#xff0c;只要我们坚持并勇往直前&#xff0c;一切困难都会变得简单。无论前方有多少险阻&#xff0c;总有过去的那一天&#xff0c;只要我们不放弃&#xff0c;就…

SAP smartforms二维码输出

此方法需要SAP_BASIS版本在731以上 TCODE-SE73 选择’系统条形码’点击 ‘更改’ 按步骤创建一个系统条形码 Module Size 调节二维码的尺寸 进入smartforms 创建样式 填入条形码名称 创建一张表单测试二维码&#xff0c;填入创建好的样式 测试结果&#xff1a;

快递批量查询高手软件,让你轻松掌握多家快递物流信息,提升工作效率

随着电子商务的繁荣和智能化物流的普及&#xff0c;快递行业在全球范围内迅速发展。在这个快节奏的时代&#xff0c;为了提高工作效率并更好地管理物流信息&#xff0c;一款强大的工具——快递批量查询高手软件应运而生。这款软件可以让你轻松掌握多家快递公司的物流信息&#…

CorelDRAW X7最新下载安装详细图文步骤教程

CorelDRAW X7是加拿大Corel公司推出的向量图形制作工具&#xff0c;经历二十多年的发展与蜕变&#xff0c;CorelDRAW系列已发布了17个版本&#xff0c;而CorelDRAW X7是此系列中的新版本。其完善的内容环境和强大的平面设计功能为设计师提供充分的施展舞台&#xff0c;是向量绘…

防火墙命令行基础配置实验(H3C模拟器)

嘿&#xff0c;这里是目录&#xff01; ⭐ H3C模拟器资源链接1. 实验示意图2. 要求3. 当前配置3.1 PC配置3.2 FW配置&#xff08;防火墙&#xff09;[^7][^8]3.2.1 FW1配置3.2.2 FW2配置 3.3 R配置3.3.1 R1配置3.3.2 R2配置 3.4 SW配置3.4.1 SW1配置3.4.2 SW2配置3.4.3 SW3配置…

【漏洞复现】Array VPN任意文件读取漏洞

漏洞描述 华耀(中国)科技有限公司(简称:Array)于2003年创建于北京,是优秀的网络功能平台解决方案提供商,也是应用交付解决方案、移动应用接入(SSL VPN)解决方案的全球领导者。华耀现有员工200余人,其中研发团队占到100余人,总部位于北京。并在北京、上海、广州、杭…