golang实现一个简单的http代理
转载于https://staight.github.io/archives/
代理是网络中的一项重要的功能,其功能就是代理网络用户去取得网络信息。形象的说:它是网络信息的中转站,对于客户端来说,代理扮演的是服务器的角色,接收请求报文,返回响应报文;对于web服务器来说,代理扮演的是客户端的角色,发送请求报文,接收响应报文。
代理具有多种类型,如果是根据网络用户划分的话,可以划分为正向代理和反向代理:
- 正向代理:将客户端作为网络用户。客户端访问服务端时,先访问代理服务器,随后代理服务器再访问服务端。此过程需客户端进行代理配置,对服务端透明。
- 反向代理:将服务端作为网络用户。访问过程与正向代理相同,不过此过程对客户端透明,需服务端进行代理配置(也可不配置)。
针对正向代理和反向代理,分别有不同的代理协议,即代理服务器和网络用户之间通信所使用的协议:
- 正向代理:
- http
- https
- socks4
- socks5
- vpn:就功能而言,vpn也可以被认为是代理
- 反向代理:
- tcp
- udp
- http
- https
接下来我们就说说http代理。
http代理概述
http代理是正向代理中较为简单的代理方式,它使用http协议作为客户端和代理服务器的传输协议。
http代理可以承载http协议,https协议,ftp协议等等。对于不同的协议,客户端和代理服务器间的数据格式略有不同。
http协议
我们先来看看http协议下客户端发送给代理服务器的HTTP Header:
1
|
// 直接连接
|
可以看到,http代理比起直接连接:
- url变成完整路径,
/
->http://staight.github.io/
Connection
字段变成Proxy-Connection
字段- 其余保持原样
为什么使用完整路径?
为了识别目标服务器。如果没有完整路径,且没有Host字段的话,代理服务器将无法得知目标服务器的地址。
为什么使用Proxy-Connection字段代替Connection字段?
为了兼容使用HTTP/1.0协议的过时的代理服务器。HTTP/1.1才开始有长连接功能,直接连接的情况下,客户端发送的HTTP Header中如果有Connection: keep-alive
字段,表示使用长连接和服务端进行http通信,但如果中间有过时的代理服务器,该代理服务器将无法与客户端和服务端进行长连接,造成客户端和服务端一直等待,白白浪费时间。因此使用Proxy-Connection
字段代替Connection
字段,如果代理服务器使用HTTP/1.1协议,能够识别Proxy-Connection
字段,则将该字段转换成Connection
再发送给服务端;如果不能识别,直接发送给服务端,因为服务端也无法识别,则使用短连接进行通信。
http代理http协议交互过程如图:
https协议
接下来我们来看看https协议下,客户端发送给代理服务器的HTTP Header:
1
|
CONNECT staight.github.io:443 HTTP/1.1
|
如上,https协议和http协议相比:
- 请求方法从
GET
变成CONNECT
- url没有protocol字段
实际上,由于https下客户端和服务端的通信除了开头的协商以外都是密文,中间的代理服务器不再承担修改http报文再转发
的功能,而是一开始就和客户端协商好服务端的地址,随后的tcp密文直接转发即可。
http代理https协议交互过程如图:
代码实现
首先,创建tcp服务,并且对于每个tcp请求,均调用handle函数:
1
|
// tcp连接,监听8080端口
|
然后将获取的数据放入缓冲区:
1
|
// 用来存放客户端数据的缓冲区
|
从缓冲区读取HTTP请求方法,URL等信息:
1
|
var method, URL, address string
|
http协议和https协议获取地址的方式不同,分别处理:
1
|
// 如果方法是CONNECT,则为https协议
|
用获取到的地址向服务端发起请求。如果是http协议,将客户端的请求直接转发给服务端;如果是https协议,发送http响应:
1
|
//获得了请求的host和port,向服务端发起tcp连接
|
最后,将所有客户端的请求转发至服务端,将所有服务端的响应转发给客户端:
1
|
//将客户端的请求转发至服务端,将服务端的响应转发给客户端。io.Copy为阻塞函数,文件描述符不关闭就不停止
|
完整的源代码:
1
|
package main
|
添加代理,然后运行:
运行成功!
参考文档
HTTP 代理原理及实现(一):https://imququ.com/post/web-proxy.html
Http 请求头中的 Proxy-Connection:https://imququ.com/post/the-proxy-connection-header-in-http-request.html
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
2020-07-20 Redis中的Scan命令踩坑记
2020-07-20 在Redis中设置了过期时间的Key,需要注意哪些问题?
2020-07-20 Linux 下如何修改密码有效期?
2020-07-20 10个很实用Linux命令,千万不要错过
2020-07-20 nc - 网络工具箱中的「瑞士军刀」
2020-07-20 zabbix的数据库优化
2020-07-20 Redis 面试常见问答