Golang 实现一个简单的 http 代理

开发 后端
本文详细介绍了Golang 实现 http 代理的实现,在实际业务中有需求的同学可以学起来了!

本文详细介绍了Golang 实现 http 代理的实现,在实际业务中有需求的同学可以学起来了!

代理是网络中的一项重要的功能,其功能就是代理网络用户去取得网络信息。形象的说:它是网络信息的中转站,对于客户端来说,代理扮演的是服务器的角色,接收请求报文,返回响应报文;对于 web 服务器来说,代理扮演的是客户端的角色,发送请求报文,接收响应报文。

代理具有多种类型,如果是根据网络用户划分的话,可以划分为正向代理和反向代理:

  •  正向代理:将客户端作为网络用户。客户端访问服务端时,先访问代理服务器,随后代理服务器再访问服务端。此过程需客户端进行代理配置,对服务端透明。
  •  反向代理:将服务端作为网络用户。访问过程与正向代理相同,不过此过程对客户端透明,需服务端进行代理配置(也可不配置)。

针对正向代理和反向代理,分别有不同的代理协议,即代理服务器和网络用户之间通信所使用的协议:

  •  正向代理:
    •  http
    •  https
    •  socks4
    •  socks5
  •  反向代理:
    •  tcp
    •  udp
    •  http
    •  https

接下来我们就说说 http 代理。

http 代理概述

http 代理是正向代理中较为简单的代理方式,它使用 http 协议作为客户端和代理服务器的传输协议。

http 代理可以承载 http 协议,https 协议,ftp 协议等等。对于不同的协议,客户端和代理服务器间的数据格式略有不同。

http 协议

我们先来看看 http 协议下客户端发送给代理服务器的 HTTP Header: 

  1. // 直接连接  
  2. GET / HTTP/1.1  
  3. Host: staight.github.io  
  4. Connection: keep-alive  
  5. // http 代理  
  6. GET http://staight.github.io/ HTTP/1.1  
  7. Host: staight.github.io  
  8. Proxy-Connection: keep-alive 

可以看到,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 协议交互过程如图:

http 代理 http 协议

https 协议

接下来我们来看看 https 协议下,客户端发送给代理服务器的 HTTP Header: 

  1. CONNECT staight.github.io:443 HTTP/1.1  
  2. Host: staight.github.io:443  
  3. Proxy-Connection: keep-alive 

如上,https 协议和 http 协议相比:

  •  请求方法从GET变成CONNECT
  •  url 没有 protocol 字段

实际上,由于 https 下客户端和服务端的通信除了开头的协商以外都是密文,中间的代理服务器不再承担修改 http 报文再转发的功能,而是一开始就和客户端协商好服务端的地址,随后的 tcp 密文直接转发即可。

http 代理 https 协议交互过程如图:

http 代理 https 协议

代码实现

首先,创建 tcp 服务,并且对于每个 tcp 请求,均调用 handle 函数: 

  1. // tcp 连接,监听 8080 端口  
  2. l, err :net.Listen("tcp", ":8080")  
  3. if err != nil {  
  4.  log.Panic(err)  
  5.  
  6. // 死循环,每当遇到连接时,调用 handle  
  7. for {  
  8.  client, err :l.Accept()  
  9.  if err != nil {  
  10.   log.Panic(err)  
  11.  }   
  12.  go handle(client)  
  13.    }  
  14. 然后将获取的数据放入缓冲区:  
  15. // 用来存放客户端数据的缓冲区  
  16. var b [1024]byte  
  17. //从客户端获取数据  
  18. n, err :client.Read(b[:])  
  19. if err != nil {  
  20.  log.Println(err)  
  21.  return  
  22.    } 

从缓冲区读取 HTTP 请求方法,URL 等信息: 

  1. var method, URL, address string  
  2. // 从客户端数据读入 method,url  
  3. fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &URL)  
  4. hostPortURL, err :url.Parse(URL)  
  5. if err != nil { 
  6.  log.Println(err) 
  7.   return  
  8.    } 

http 协议和 https 协议获取地址的方式不同,分别处理: 

  1. // 如果方法是 CONNECT,则为 https 协议  
  2. if method == "CONNECT" {  
  3.  address = hostPortURL.Scheme + ":" + hostPortURL.Opaque  
  4. } else { //否则为 http 协议  
  5.  address = hostPortURL.Host  
  6.  // 如果 host 不带端口,则默认为 80  
  7.  if strings.Index(hostPortURL.Host, ":") == -1 { //host 不带端口, 默认 80  
  8.   address = hostPortURL.Host + ":80"  
  9.  }  
  10.    } 

用获取到的地址向服务端发起请求。如果是 http 协议,将客户端的请求直接转发给服务端;如果是 https 协议,发送 http 响应: 

  1. //获得了请求的 host 和 port,向服务端发起 tcp 连接  
  2. server, err :net.Dial("tcp", address)  
  3. if err != nil {  
  4.  log.Println(err)  
  5.  return  
  6.  
  7. //如果使用 https 协议,需先向客户端表示连接建立完毕  
  8. if method == "CONNECT" {  
  9.  fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n\r\n")  
  10. } else { //如果使用 http 协议,需将从客户端得到的 http 请求转发给服务端  
  11.  server.Write(b[:n])  
  12.    } 

最后,将所有客户端的请求转发至服务端,将所有服务端的响应转发给客户端: 

  1. //将客户端的请求转发至服务端,将服务端的响应转发给客户端。io.Copy 为阻塞函数,文件描述符不关闭就不停止  
  2. go io.Copy(server, client)  
  3.    io.Copy(client, server 

完整的源代码: 

  1. package main  
  2. import (  
  3.  "bytes"  
  4.  "fmt"  
  5.  "io"  
  6.  "log"  
  7.  "net"  
  8.  "net/url"  
  9.  "strings"  
  10.   
  11. func main() {  
  12.  // tcp 连接,监听 8080 端口  
  13.  l, err :net.Listen("tcp", ":8080")  
  14.  if err != nil {  
  15.   log.Panic(err)  
  16.  }   
  17.  // 死循环,每当遇到连接时,调用 handle  
  18.  for {  
  19.   client, err :l.Accept()  
  20.   if err != nil {  
  21.    log.Panic(err)  
  22.   }  
  23.   go handle(client)  
  24.  }  
  25.  
  26. func handle(client net.Conn) { 
  27.  if client == nil {  
  28.   return  
  29.  }  
  30.  defer client.Close()  
  31.  log.Printf("remote addr: %v\n", client.RemoteAddr())  
  32.  // 用来存放客户端数据的缓冲区  
  33.  var b [1024]byte  
  34.  //从客户端获取数据  
  35.  n, err :client.Read(b[:])  
  36.  if err != nil {  
  37.   log.Println(err)  
  38.   return 
  39.   
  40.  var method, URL, address string  
  41.  // 从客户端数据读入 method,url  
  42.  fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &URL)  
  43.  hostPortURL, err :url.Parse(URL)  
  44.  if err != nil {  
  45.   log.Println(err)  
  46.   return  
  47.  }  
  48.  // 如果方法是 CONNECT,则为 https 协议  
  49.  if method == "CONNECT" {  
  50.   address = hostPortURL.Scheme + ":" + hostPortURL.Opaque  
  51.  } else { //否则为 http 协议  
  52.   address = hostPortURL.Host  
  53.   // 如果 host 不带端口,则默认为 80  
  54.   if strings.Index(hostPortURL.Host, ":") == -1 { //host 不带端口, 默认 80  
  55.    address = hostPortURL.Host + ":80"  
  56.   }  
  57.  }  
  58.  //获得了请求的 host 和 port,向服务端发起 tcp 连接  
  59.  server, err :net.Dial("tcp", address)  
  60.  if err != nil {  
  61.   log.Println(err)  
  62.   return  
  63.  }  
  64.  //如果使用 https 协议,需先向客户端表示连接建立完毕  
  65.  if method == "CONNECT" {  
  66.   fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n\r\n")  
  67.  } else { //如果使用 http 协议,需将从客户端得到的 http 请求转发给服务端  
  68.   server.Write(b[:n])  
  69.  }  
  70.  //将客户端的请求转发至服务端,将服务端的响应转发给客户端。io.Copy 为阻塞函数,文件描述符不关闭就不停止  
  71.  go io.Copy(server, client)  
  72.  io.Copy(client, server)  

添加代理,然后运行:

添加代理

运行 

 

责任编辑:庞桂玉 来源: 马哥Linux运维
相关推荐

2021-12-14 09:00:42

Swift HTTP 代理服务器

2024-01-08 08:36:29

HTTPGo代理服务器

2015-12-02 14:10:56

HTTP网络协议代理原理

2018-09-18 10:11:21

前端vue.jsjavascript

2016-11-08 18:53:08

编译器

2022-09-08 06:23:37

C++HTTP 服务器

2017-12-27 09:49:35

HTTP服务器反向

2022-11-29 17:34:43

虚拟形象系统

2020-06-04 12:55:44

PyTorch分类器神经网络

2019-04-24 15:06:37

Http服务器协议

2021-05-20 07:56:35

Bean容器Spring

2019-12-11 10:45:08

Python 开发编程语言

2015-12-02 15:29:32

HTTP网络协议代理原理

2022-04-08 08:26:03

JavaHTTP请求

2011-03-24 09:34:41

SPRING

2022-09-19 08:01:45

数据库SQLitePostgreSQL

2022-10-21 14:21:46

JavaScript笔记技能

2022-12-29 12:06:28

2011-05-17 15:13:59

oracle分页存储

2009-07-14 16:02:42

JDBC例子
点赞
收藏

51CTO技术栈公众号