go http-proxy

news/2024/10/21 7:44:03/

        我们这里主要讲使用HTTP/1.1协议中的CONNECT方法建立起来的隧道连接,实现的HTTP Proxy。这种代理的好处就是不用知道客户端请求的数据,只需要原封不动的转发就可以了,对于处理HTTPS的请求就非常方便了,不用解析他的内容,就可以实现代理。

  • 启动代理监听

        要想做一个HTTP Proxy,我们需要启动一个服务器,监听一个端口,用于接收客户端的请求。Golang给我们提供了强大的net包供我们使用,我们启动一个代理服务器监听非常方便。

l, err := net.Listen("tcp", ":8080") if err != nil {log.Panic(err)}

        以上代理我们就实现了一个在8080端口上监听的服务器,我们这里没有写ip地址,默认在所有ip地址上进行监听。如果你只想本机适用,可以使用127.0.0.1:8080,这样机器就访问不了你的代理服务器了。

  • 监听接收代理请求

        启动了代理服务器,就可以开始接受不了代理请求了,有了请求,我们才能做进一步的处理。

for {client, err := l.Accept() if err != nil {log.Panic(err)} go handleClientRequest(client)}

        Listener接口的Accept方法,会接受客户端发来的连接数据,这是一个阻塞型的方法,如果客户端没有连接数据发来,他就是阻塞等待。接收来的连接数据,会马上交给handleClientRequest方法进行处理,这里使用一个go关键字开一个goroutine的目的是不阻塞客户端的接收,代理服务器可以马上接收下一个连接请求。

  • 解析请求,获取要访问的IP和端口

        有了客户端的代理请求了,我们还得从请求里提取客户端要访问的远程主机的IP和端口,这样我们的代理服务器才可以建立和远程主机的连接,代理转发。

        HTTP协议的头信息里就包含有我们需要的主机名(IP)和端口信息,并且是明文的,协议很规范,类似于:

CONNECT www.google.com:443 HTTP/1.1

Host: www.google.com:443

Proxy-Connection: keep-alive

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36

        可以看到我们需要的在第一行,第一个行的信息以空格分开,第一部分CONNECT是请求方法,这里是CONNECT,除此之外还有GET,POST等,都是HTTP协议的标准方法。

        第二部分是URL,https的请求只有host和port,http的请求是一个完成的url,等下会看个样例,就明白了。

        第三部是HTTP的协议和版本,这个我们不用太关注。

        以上是一个https的请求,我们看下http的:

GET http://www.flysnow.org/ HTTP/1.1

Host: www.flysnow.org

Proxy-Connection: keep-alive

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36

可以看到htt的,没有端口号(默认是80);比https多了schame—http://。

有了分析,下面我们就可以从HTTP头信息中获取请求的url和method信息了。

var b [1024]byten, err := client.Read(b[:]) 
if err != nil {log.Println(err) return} 
var method, host, address stringfmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &host)hostPortURL, err := url.Parse(host) if err != nil {log.Println(err) return}

然后需要进一步对url进行解析,获取我们需要的远程服务器信息

if hostPortURL.Opaque == "443" { //https访问address = hostPortURL.Scheme + ":443"} else { //http访问if strings.Index(hostPortURL.Host, ":") == -1 { //host不带端口, 默认80address = hostPortURL.Host + ":80"} else {address = hostPortURL.Host}

这样就完整了获取了要请求服务器的信息,他们可能是以下几种格式

ip:port

hostname:port

domainname:port

就是有可能是ip(v4orv6),有可能是主机名(内网),有可能是域名(dns解析)

  • 代理服务器和远程服务器建立连接

有了远程服务器的信息了,就可以进行拨号建立连接了,有了连接,才可以通信。

//获得了请求的host和port,就开始拨号吧

server, err := net.Dial("tcp", address) if err != nil {log.Println(err) return}

数据转发

拨号成功后,就可以进行数据代理传输了

if method == "CONNECT" {fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n")} else {server.Write(b[:n])} //进行转发go io.Copy(server, client)io.Copy(client, server)

其中对CONNECT方法有单独的回应,客户端说要建立连接,代理服务器要回应建立好了,然后才可以像HTTP一样请求访问。

完整代码

到这里,我们的代理服务器全部开发完成了,下面是完整的源代码:

package mainimport ( "bytes""fmt""io""log""net""net/url""strings")
func main() {log.SetFlags(log.LstdFlags|log.Lshortfile)l, err := net.Listen("tcp", ":8081") if err != nil {log.Panic(err)} for {client, err := l.Accept() if err != nil {log.Panic(err)} go handleClientRequest(client)}}func handleClientRequest(client net.Conn) { if client == nil { return} defer client.Close() var b [1024]byten, err := client.Read(b[:]) if err != nil {log.Println(err) return} var method, host, address stringfmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &host)hostPortURL, err := url.Parse(host) if err != nil {log.Println(err) return} if hostPortURL.Opaque == "443" { //https访问address = hostPortURL.Scheme + ":443"} else { //http访问if strings.Index(hostPortURL.Host, ":") == -1 { //host不带端口, 默认80address = hostPortURL.Host + ":80"} else {address = hostPortURL.Host}} //获得了请求的host和port,就开始拨号吧server, err := net.Dial("tcp", address) if err != nil {log.Println(err) return} if method == "CONNECT" {fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n")} else {server.Write(b[:n])} //进行转发go io.Copy(server, client)io.Copy(client, server)}


http://www.ppmy.cn/news/1069264.html

相关文章

图片上传返回淘宝链接

简要描述 图片上传 请求URL http://xxx.xxx.xxx.xxx:xxxx/ali.uploads 请求方式 get 请求Query参数 参数名示例值必选类型说明imghttps://img.alicdn.com/imgextra/i2/2207375088936/O1CN016vjZiH2FsjsI2gx7c_!!2207375088936.jpg是string图片链接tokenadmin.api是strin…

【Map】Map集合有序与无序测试案例(121)

简单介绍常用的三种Map:不足之处,欢迎指正! HashMap:put数据是无序的; TreeMap:key值按一定的顺序排序;数字做key,put数据是有序,非数字字符串做key,put数据…

前端面试:【系统设计与架构】组件化、模块化、微前端的现代前端架构演进

前端架构和设计一直在不断演进,以满足不断变化的需求和应对日益复杂的前端应用程序。在本文中,我们将探讨三个重要的现代前端架构概念:组件化、模块化和微前端,以及它们在构建可维护、可扩展的前端应用程序中的作用。 1. 组件化&a…

ViewPager+ Fragment结合的setUserVisibleHint()调用时机

最近的项目使用到了ViewPager Fragment的模式,要求在每次Fragment获取显示的时候来刷新数据,该项目下ViewPager有5个子fragment,在onCreateView及fragment的**setUserVisibleHint(bool isVisibleToUser)**中的isVisibleToUser为t…

初学者必看!我的第一个Invideo人工智能文字生成视频

这是一个使用人工智能生成视频的在线平台。 主要功能包括: - 视频脚本自动生成:可以通过输入主题,由AI自动生成视频故事剧本。 - 人声合成:支持上传脚本,AI会合成自然的人声进行朗读。 - 视频制作:有多种视频模板可选择,支持上传自己的素材,一键生成完整视频。 - 特效和增…

Mysql--技术文档--B树-数据结构的认知

阿丹解读: B树(B tree)和B树(B-tree)都是常见的自平衡搜索树数据结构,用于在存储和检索大量数据时提供高效的操作。 基本概念-B树/B树 B树(B-tree)和B树(B tree&#x…

webassembly010 ggml的其他封装:Python,Rust ,javascript

其他封装Tensor library for machine learning, inspired by ggmlhttps://github.com/candrewlee14/zgmlBleeding edge low level Rust binding for GGMLhttps://github.com/KerfuffleV2/ggml-sys-bleedingedgeYour customized AI characters - personal assistants on any har…

Elasticsearch中倒排索引、分词器、DSL语法使用介绍

🍓 简介:java系列技术分享(👉持续更新中…🔥) 🍓 初衷:一起学习、一起进步、坚持不懈 🍓 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正🙏 🍓 希望这篇文章对你有所帮助,欢…