MarkSomethingDownLLS icon indicating copy to clipboard operation
MarkSomethingDownLLS copied to clipboard

HTTP Proxy 文章汇总

Open moooofly opened this issue 6 years ago • 8 comments

一个简单的Golang实现的HTTP Proxy

2016年12月24日

  • ss 是 Socks 代理,想用 HTTP 代理的时候很不方便;
  • 以前在 Linux 下的时候,会安装一个 Privoxy 把 Socks 代理转换为 HTTP 代理,但是 Mac 下使用 Brew 安装的 Privoxy 就很难用;
  • 本文主要讲基于 HTTP/1.1 协议中的 CONNECT 方法建立起隧道连接,从而实现 HTTP(S) Proxy。这种代理的好处就是不用知道客户端请求的数据,只需要原封不动的转发就可以了,对于处理 HTTPS 的请求就非常方便了,不用解析他的内容,就可以实现代理。

具体步骤(略):

  • 启动代理监听
  • 代理接收请求
  • 代理解析请求,获取要访问(建立隧道)的 IP 和端口
  • 代理服务器和远程服务器建立连接(TCP 连接)
  • 数据转发
  • 运行在国外 VPS 上

moooofly avatar Jan 22 '19 05:01 moooofly

HTTP 代理原理和实现

2017-03-21

  • 代理的核心功能可以用一句话概括:接受客户端的请求,转发到后端服务器,获得应答之后返回给客户端。
  • 代理服务器根据不同的配置和使用,可能会有不同的功能,这些功能主要包括:
    • 内容过滤:代理可以根据一定的规则限制某些请求的连接。比如有些公司会设置内部网络无法访问某些购物、游戏网站,或者学校的网络不让学生访问色情暴力的网站等
    • 节省成本:代理服务器可以作为缓存使用,对于某些资源只需要第一次访问的时候去下载,以后代理直接把缓存的结果返回给客户端,节约网络带宽的开销
    • 提高性能:通过代理服务器的缓存(比如 CDN)和负载均衡(比如 nginx lb)功能,服务器端可以加速请求的访问,在更快的时间内返回结果)
    • 增加安全性:公司可以在内网和外网之间通过代理进行转发,这样不仅对外隐藏了实现的细节,而且可以在代理层对爬虫、病毒性请求进行过滤,保护内部服务
  • 代理具体可以做哪些事情呢?
    • 修改请求:url、header、body
    • 过滤请求:根据一定的规则丢弃、过滤请求
    • 决定转发到哪个后端(可以是静态定义的,也可以是动态决定)
    • 保存服务器的应答,后续的请求可以直接使用保存的应答
    • 修改应答:对应答做一些格式的转换,修改数据,甚至返回完全不一样的应答数据
    • 重试机制:如果后端服务器暂时无法响应,隔一段时间重试
    • ……

image

正向代理和反向代理

代理可以分为正向代理和反向代理两种。

正向代理需要客户端来配置,一般来说我们会通过浏览器或者操作系统提供的工具或者界面来配置。这个时候,代理对客户端不是透明的,客户端需要知道代理的地址并且手动配置。配置了代理,浏览器在发送请求的时候会对报文做特殊的修改。

反向代理对客户端是透明的,也就是说客户端一般不知道代理的存在,认为自己是直接和服务器通信。我们大部分访问的网站就是反向代理服务器,反向代理服务器会转发到真正的服务器,一般在反向代理这一层实现负载均衡和高可用的功能。而且这里也可以看到,客户端是不会知道真正服务器端的 ip 地址和端口的,这在一定程度上起到了安全保护的作用。

代理服务器怎么知道目的服务器的地址?

反向代理中,代理服务器要转发的服务器地址都是事先知道的(包括静态配置和动态配置)。比如使用 nginx 来配置负载均衡

而对于正向代理来说,客户端可能访问的服务器地址是无法事先知道的。因为 HTTP 协议活动在应用层,它无法获取网络层(IP层)信息,那么该协议要有一个地方可以拿到这个信息。HTTP 中可能保存这个信息的地方有两个:URL 和 header。默认情况下,HTTP 请求的 status line 有三部分组成:方法、uri 和协议版本,比如:

GET /index.html HTTP/1.0
User-Agent: gohttp 1.0

如果客户端(比如浏览器)知道自己在通过正向代理进行报文传输,那么它会在 status line 加上要访问服务器的真实地址。这个时候发送的报文是:

GET http://www.marys-antiques.com/index.html HTTP/1.0
User-Agent: gohttp 1.0

代理路径

客户端不管是通过代理服务器,还是直接访问后端服务器对于最终的结果是没有区别的,也就是说大多数情况下客户端根本不关心它访问的到底是什么,只需要(准确快速地)拿到想要的信息就够了。但是有时候,我们还是希望知道请求到底在中间经历了哪些代理,比如用来调试网络异常,或者做数据统计,而 HTTP 协议也提供了响应的功能。

虽然 RFC 2616 定义了 Via 头部字段来跟踪 HTTP 请求经过的代理路径,但在实际中用的更多的还是 X-Forwarded-For 字段,X-Forwarded-For 是 Squid 缓存代理服务软件引入的,目前已经在规范化在 RFC 7239 文档。

X-Forwarded-For 头部格式也比较简单,比如某个服务器接受到请求的对应头部可能是:

X-Forwarded-For: client, proxy1, proxy2

对应的值有多个字段,每个字段代表中间的一个节点,它们之间由逗号和空格隔开,从左到右距离当前节点越来越近

每个代理服务器会在 X-Forwarded-For 头部填上前一个节点的 ip 地址,这个地址可以通过 TCP 请求的 remote address 获取。为什么每个代理服务器不填写自己的 ip 地址呢?有两个原因,如果由代理服务器填写自己的 ip 地址,那么代理可以很简单地伪造这个地址,而上一个节点的 remote address 是根据 TCP 连接获取的(如果不建立正确的 TCP 连接是无法进行 HTTP 通信的);另外一个原因是如果由当前节点填写 X-Forwarded-For,那么很多情况客户端无法判断自己是否会通过代理的。

NOTE:

  • 最终客户端或者服务器端接受的请求,X-Forwarded-For 是没有最邻近节点的 ip 地址的,而这个地址可以通过 remote address 获取
  • 每个节点(不管是客户端、代理服务器、真实服务器)都可以随便更改 X-Forwarded-For 的值,因此这个字段只能作为参考

代理服务器实现

这个部分我们会介绍如何用 golang 来实现 HTTP 代理服务器,需要读者了解一些 HTTP 服务器端编程的知识,可以参考我之前的文章:go http 服务器编程

正向代理

按照我们之前介绍的代理原理,我们可以编写出这样的代码:

package main

import (
    "fmt"
    "io"
    "net"
    "net/http"
    "strings"
)

type Pxy struct {}

func (p *Pxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    fmt.Printf("Received request %s %s %s\n", req.Method, req.Host, req.RemoteAddr)

    transport :=  http.DefaultTransport

    // step 1
    outReq := new(http.Request)
    *outReq = *req // this only does shallow copies of maps

    if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
        if prior, ok := outReq.Header["X-Forwarded-For"]; ok {
            clientIP = strings.Join(prior, ", ") + ", " + clientIP
        }
        outReq.Header.Set("X-Forwarded-For", clientIP)
    }

    // step 2
    res, err := transport.RoundTrip(outReq)
    if err != nil {
        rw.WriteHeader(http.StatusBadGateway)
        return
    }

    // step 3
    for key, value := range res.Header {
        for _, v := range value {
            rw.Header().Add(key, v)
        }
    }

    rw.WriteHeader(res.StatusCode)
    io.Copy(rw, res.Body)
    res.Body.Close()
}

func main() {
    fmt.Println("Serve on :8080")
    http.Handle("/", &Pxy{})
    http.ListenAndServe("0.0.0.0:8080", nil)
}

这段代码比较直观,只包含了最核心的代码逻辑,完全按照最上面的代理图例进行组织。一共分成几个步骤:

  1. 代理接收到客户端的请求,复制了原来的请求对象,并根据数据配置新请求的各种参数(添加上 X-Forward-For 头部等)
  2. 把新请求发送到服务器端,并接收到服务器端返回的响应
  3. 代理服务器对响应做一些处理,然后返回给客户端

上面的代码运行之后,会在本地的 8080 端口启动代理服务。修改浏览器的代理为 127.0.0.1:8080 再访问网站,可以验证代理正常工作,也能看到它在终端打印出所有的请求信息。

虽然这段代码非常简短,但是你可以添加更多的逻辑实现非常有用的功能。比如在请求发送之前进行过滤,根据一定的规则直接阻止某些请求的访问;或者对请求进行限流,某个客户端在一定的时间里执行的请求有最大限额;统计请求的数据进行分析等等。

这个代理目前不支持 HTTPS 协议,因为它只提供了 HTTP 请求的转发功能,并没有处理证书和认证有关的内容。如果了解 HTTPS 协议的话,你会明白这种模式下是无法完成 HTTPS 握手的,虽然代理可以和真正的服务器建立连接(知道了对方的公钥和证书),但是代理无法代表服务器和客户端建立连接,因为代理服务器无法知道真正服务器的私钥。

反向代理

编写反向代理按照上面的思路当然没有问题,只需要在第二步的时候,根据之前的配置修改 outReq 的 URL Host 地址可以了。不过 Golang 已经给我们提供了编写代理的框架:httputil.ReverseProxy。我们可以用非常简短的代码来实现自己的代理,而且内部的细节问题都已经被很好地处理了。

这部分我们会实现一个简单的反向代理,它能够对请求实现负载均衡,随机地把请求发送给某些配置好的后端服务器。使用 httputil.ReverseProxy 编写反向代理最重要的就是实现自己的 Director 对象,这是 GoDoc 对它的介绍:

Director must be a function which modifies the request into a new request to be sent using Transport. Its response is then copied back to the original client unmodified. Director must not access the provided Request after returning.

简单翻译的话,Director 是一个函数,它接受一个请求作为参数,然后对其进行修改。修改后的请求会实际发送给服务器端,因此我们编写自己的 Director 函数,每次把请求的 SchemeHost 修改成某个后端服务器的地址,就能实现负载均衡的效果(其实上面的正向代理也可以通过相同的方法实现)。看代码:

package main

import (
        "log"
        "math/rand"
        "net/http"
        "net/http/httputil"
        "net/url"
)

func NewMultipleHostsReverseProxy(targets []*url.URL) *httputil.ReverseProxy {
        director := func(req *http.Request) {
                target := targets[rand.Int()%len(targets)]
                req.URL.Scheme = target.Scheme
                req.URL.Host = target.Host
                req.URL.Path = target.Path
        }
        return &httputil.ReverseProxy{Director: director}
}

func main() {
        proxy := NewMultipleHostsReverseProxy([]*url.URL{
                {
                        Scheme: "http",
                        Host:   "localhost:9091",
                },
                {
                        Scheme: "http",
                        Host:   "localhost:9092",
                },
        })
        log.Fatal(http.ListenAndServe(":9090", proxy))
}

NOTE:这段代码来自 http://blog.charmes.net/2015/07/reverse-proxy-in-go.html。

我们让代理监听在 9090 端口,在后端启动两个返回不同响应的服务器分别监听在 9091 和 9092 端口,通过 curl 访问,可以看到多次请求会返回不同的结果。

➜  curl http://127.0.0.1:9090
116064a9eb83
➜  curl http://127.0.0.1:9090
8f7ccc11718f

同样的,这段代码也只是一个 demo,存在着很多问题,比如没有错误处理机制,如果后端某个服务器挂了,代理会返回 502 错误,更好的做法是把请求转发到另外的可用服务器。当然也可以添加更多的特性让它更好用,比如动态地添加后端服务器列表;根据后端服务器的负载情况进行负载转发等等。

moooofly avatar Jan 22 '19 05:01 moooofly

HTTP 隧道代理原理和实现

2017-03-22

HTTP 隧道代理简介

在上一篇文章中我们介绍了 HTTP 普通代理的原理和 golang 简单实现,也提到过普通代理的局限,比如无法代理 HTTPS 的报文,此外普通代理也只能代理 HTTP 协议报文无法支持其他协议的数据转发。当然有问题就有解决方案,对于这些问题 HTTP 可以通过隧道(tunnel)代理来解决。

隧道代理的原因也可以用一句话来总结:

代理服务器和真正的服务器之间建立起 TCP 连接,然后在客户端和真正服务器端进行数据的直接转发。

《HTTP 权威指南》对隧道描述的原话是:

The CONNECT method asks a tunnel gateway to create a TCP connection to an arbitrary destination server and port and to blindly relay subsequent data between client and server.

意思和我之前的那句话一样,只不过有了更多的细节说明。

下图是《HTTP 权威指南》书中的插图,它讲解了客户端通过隧道代理连接 HTTPS 服务器的过程。

  • (a)客户端先发送 CONNECT 请求到隧道代理服务器,告诉它建立和服务器的 TCP 连接(因为是 TCP 连接,只需要 ip 和端口就行,不需要关注上层的协议类型)
  • (b,c)代理服务器成功和后端服务器建立 TCP 连接
  • (d)代理服务器返回 HTTP 200 Connection Established 报文,告诉客户端连接已经成功建立
  • (e)这个时候就建立起了连接,所有发给代理的 TCP 报文都会直接转发,从而实现服务器和客户端的通信

image

CONNECT 请求

CONNECT 请求的内容和其他 HTTP 方法的语法一样,只不过它在状态栏(status line)指定了真正服务器的地址。请求 URI 替换成了 hostname 和 port 的字符串,比如:

CONNECT realserver.com:443 HTTP/1.0
User-Agent: GoProxy

而其他 HTTP 请求的状态栏对应位置是路径地址,比如:

GET /about HTTP/1.0
User-Agent: GoProxy

知道了 hostname 和 port,代理服务器就能正确地建立,才能够继续后面的访问。需要注意的是,客户端应该尽量少地暴露其他信息,最好只有状态栏一行的内容,因为 CONNECT 请求是没有经过加密的。如果想通过这种方式进行 HTTPS 安全访问,那么不要在 CONNECT 请求中暴露敏感数据(比如 cookie)是必须的

如果代理服务器正确接受了 CONNECT 请求,并且成功建立了和后端服务器的 TCP 连接,它应该返回 200 状态码的应答,按照大多数的约定为 200 Connection Establised。应答也不需要包含其他的头部和 body,因为后续的数据传输都是直接转发的,代理不会分析其中的内容。

代码实现

有了上面的理论分析,我们可以写出下面的代码:

package main

import (
    "fmt"
    "io"
    "net"
    "net/http"
)

type Pxy struct {}

func NewProxy() *Pxy {
    return &Pxy{}
}

// ServeHTTP is the main handler for all requests.
func (p *Pxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    fmt.Printf("Received request %s %s %s\n",
        req.Method,
        req.Host,
        req.RemoteAddr,
    )

    if req.Method != "CONNECT" {
        rw.WriteHeader(http.StatusMethodNotAllowed)
        rw.Write([]byte("This is a http tunnel proxy, only CONNECT method is allowed."))
        return
    }

    // Step 1
    host := req.URL.Host
    hij, ok := rw.(http.Hijacker)
    if !ok {
        panic("HTTP Server does not support hijacking")
    }

    client, _, err := hij.Hijack()
    if err != nil {
        return
    }

    // Step 2
    server, err := net.Dial("tcp", host)
    if err != nil {
        return
    }
    client.Write([]byte("HTTP/1.0 200 Connection Established\r\n\r\n"))

    // Step 3
    go io.Copy(server, client)
    io.Copy(client, server)
}

func main() {
    proxy := NewProxy()
    http.ListenAndServe("0.0.0.0:8080", proxy
}

这段代码和前一篇文章的大致框架相同,但是处理的逻辑有些许的区别。

首先接收到客户端的请求之后,代理服务器需要获得底层的 TCP 连接,这样才能转发数据,所以这里看到了 Hijacker 类型转换和 Hijack() 调用,它们最终的目的是拿到客户端的 TCP 连接(net.TCPConn) 然后代理服务器调用 net.Dial 函数和真正的服务器端建立 TCP 连接,并在成功后返回给客户端 200 应答 最后就是在客户端和服务器端直接转发数据

NOTE:默认的 ServeMux 不支持 CONNECT 方法的请求,请直接使用自己编写的 Proxy 作为 Mux。

稍加修改我们就能实现同时支持两种代理的代码,我已经把最终的代码放到了 github 上面。

moooofly avatar Jan 22 '19 06:01 moooofly

Reverse Proxy in Go

Jul 10, 2015

moooofly avatar Jan 22 '19 06:01 moooofly

HTTP(S) Proxy in Golang in less than 100 lines of code

Oct 29, 2017

The goal is to implement a proxy server for HTTP and HTTPS. Handling of HTTP is a matter of parsing request, passing such request to destination server, reading response and passing it back to the client. All we need for that is built-in HTTP server and client (net/http). HTTPS is different as it’ll use technique called HTTP CONNECT tunneling. First client sends request using HTTP CONNECT method to set up the tunnel between the client and destination server. When such tunnel consisting of two TCP connections is ready, client starts regular TLS handshake with destination server to establish secure connection and later send requests and receive responses.

这里清晰的描述了 HTTP proxy 和 HTTPS proxy 的主要实现原理;

Certificates

Our proxy will be an HTTPS server (when --proto https will be used) so we need certificate and private key. For the purpose of this post let’s use self-signed certificate. To generate one use such script:

这里的意思是,该 proxy 还会作为 HTTPS server 使用,因此才需要证书;

#!/usr/bin/env bash
case `uname -s` in
    Linux*)     sslConfig=/etc/ssl/openssl.cnf;;
    Darwin*)    sslConfig=/System/Library/OpenSSL/openssl.cnf;;
esac
openssl req \
    -newkey rsa:2048 \
    -x509 \
    -nodes \
    -keyout server.key \
    -new \
    -out server.pem \
    -subj /CN=localhost \
    -reqexts SAN \
    -extensions SAN \
    -config <(cat $sslConfig \
        <(printf '[SAN]\nsubjectAltName=DNS:localhost')) \
    -sha256 \
    -days 3650

It’s required to convince your OS to trust such certificate. In OS X it can be done with Keychain Access — https://tosbourn.com/getting-os-x-to-trust-self-signed-ssl-certificates/.

HTTP

To support HTTP we’ll use built-in HTTP server and client. The role of proxy is to handle HTTP request, pass such request to destination server and send response back to the client.

image

HTTP CONNECT tunneling

Suppose client wants to use either HTTPS or WebSockets in order to talk to server. Client is aware of using proxy. Simple HTTP request / response flow cannot be used since client needs to e.g. establish secure connection with server (HTTPS) or wants to use other protocol over TCP connection (WebSockets). Technique which works is to use HTTP CONNECT method. It tells the proxy server to establish TCP connection with destination server and when done to proxy the TCP stream to and from the client. This way proxy server won’t terminate SSL but will simply pass data between client and destination server so these two parties can establish secure connection.

image

Implementation

package main
import (
    "crypto/tls"
    "flag"
    "io"
    "log"
    "net"
    "net/http"
    "time"
)
func handleTunneling(w http.ResponseWriter, r *http.Request) {
    dest_conn, err := net.DialTimeout("tcp", r.Host, 10*time.Second)
    if err != nil {
        http.Error(w, err.Error(), http.StatusServiceUnavailable)
        return
    }
    w.WriteHeader(http.StatusOK)
    hijacker, ok := w.(http.Hijacker)
    if !ok {
        http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
        return
    }
    client_conn, _, err := hijacker.Hijack()
    if err != nil {
        http.Error(w, err.Error(), http.StatusServiceUnavailable)
    }
    go transfer(dest_conn, client_conn)
    go transfer(client_conn, dest_conn)
}
func transfer(destination io.WriteCloser, source io.ReadCloser) {
    defer destination.Close()
    defer source.Close()
    io.Copy(destination, source)
}
func handleHTTP(w http.ResponseWriter, req *http.Request) {
    resp, err := http.DefaultTransport.RoundTrip(req)
    if err != nil {
        http.Error(w, err.Error(), http.StatusServiceUnavailable)
        return
    }
    defer resp.Body.Close()
    copyHeader(w.Header(), resp.Header)
    w.WriteHeader(resp.StatusCode)
    io.Copy(w, resp.Body)
}
func copyHeader(dst, src http.Header) {
    for k, vv := range src {
        for _, v := range vv {
            dst.Add(k, v)
        }
    }
}
func main() {
    var pemPath string
    flag.StringVar(&pemPath, "pem", "server.pem", "path to pem file")
    var keyPath string
    flag.StringVar(&keyPath, "key", "server.key", "path to key file")
    var proto string
    flag.StringVar(&proto, "proto", "https", "Proxy protocol (http or https)")
    flag.Parse()
    if proto != "http" && proto != "https" {
        log.Fatal("Protocol must be either http or https")
    }
    server := &http.Server{
        Addr: ":8888",
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if r.Method == http.MethodConnect {
                handleTunneling(w, r)
            } else {
                handleHTTP(w, r)
            }
        }),
        // Disable HTTP/2.
        TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
    }
    if proto == "http" {
        log.Fatal(server.ListenAndServe())
    } else {
        log.Fatal(server.ListenAndServeTLS(pemPath, keyPath))
    }
}

Presented code is not a production-grade solution. It lacks e.g. handling hop-by-hop headers, setting up timeouts while copying data between two connections or the ones exposed by net/http — more on this in "The complete guide to Go net/http timeouts".

Our server while getting request will take one of two paths: handling HTTP or handling HTTP CONNECT tunneling. This is done with:

http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if r.Method == http.MethodConnect {
        handleTunneling(w, r)
    } else {
        handleHTTP(w, r)
    }
})

Function to handle HTTP — handleHTTP is self-explanatory so let’s focus on handling tunneling. The first part of handleTunneling is about setting connection to destination server:

dest_conn, err := net.DialTimeout("tcp", r.Host, 10*time.Second)
if err != nil {
    http.Error(w, err.Error(), http.StatusServiceUnavailable)
    return
 }
 w.WriteHeader(http.StatusOK)

Next we’ve a part to hijack connection maintained by HTTP server:

hijacker, ok := w.(http.Hijacker)
    if !ok {
        http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
        return
    }
    client_conn, _, err := hijacker.Hijack()
    if err != nil {
        http.Error(w, err.Error(), http.StatusServiceUnavailable)
    }

Hijacker interface allows to take over the connection. After that the caller is responsible to manage such connection (HTTP library won’t do it anymore).

Once we’ve two TCP connections (client→proxy, proxy→destination server) we need to set tunnel up:

go transfer(dest_conn, client_conn)
go transfer(client_conn, dest_conn)

In two goroutines data is copied in two directions: from the client to the destination server and backward.

Testing

To test our proxy you can use e.g. Chrome:

> Chrome --proxy-server=https://localhost:8888

or Curl:

> curl -Lv --proxy https://localhost:8888 --proxy-cacert server.pem https://google.com

通过 curl 测试 HTTP tunnel 很有用

curl needs to be built with HTTPS-proxy support (introduced in 7.52.0).

HTTP/2

In our server HTTP/2 support has been deliberately removed because then hijacking is not possible. More on this in #14797.

moooofly avatar Jan 22 '19 06:01 moooofly

Hop-by-hop headers

These headers are meaningful only for a single transport-level connection and must not be retransmitted by proxies or cached. Such headers are: Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer, Transfer-Encoding and Upgrade. Note that only hop-by-hop headers may be set using the Connection general header.

moooofly avatar Jan 22 '19 06:01 moooofly

Golang动手写一个Http Proxy

16 September 2017

本文主要使用Golang实现一个可用但不够标准,支持 basic authentication 的http代理服务。

**为何说不够标准?**在HTTP/1.1 RFC中,有些关于代理实现标准的条目在本文中不考虑。

Http Proxy 是如何代理我们的请求

Http 请求的代理如下图,Http Proxy只需要将接收到的请求转发给服务器,然后把服务器的响应,转发给客户端即可。

image

Https 请求的代理如下图,客户端首先需要发送一个Http CONNECT请求到Http Proxy,Http Proxy建立一条TCP连接到指定的服务器,然后响应200告诉客户端连接建立完成,之后客户端就可以与服务器进行SSL握手和传输加密的Http数据了。

image

为何需要CONNECT请求? 因为Http Proxy不是真正的服务器,没有www.foo.com的证书,不可能以www.foo.com的身份与客户端完成SSL握手从而建立Https连接。 所以需要通过CONNECT请求告诉Http Proxy,让Http Proxy与服务器先建立好TCP连接,之后客户端就可以将SSL握手消息发送给Http Proxy,再由Http Proxy转发给服务器,完成SSL握手,并开始传输加密的Http数据。

Basic Authentication

为了保护Http Proxy不被未授权的客户端使用,可以要求客户端带上认证信息。这里以Basic Authentication为例。

客户端在与Http Proxy建立连接时,Http请求头中需要带上:

Proxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l

如果服务端验证通过,则正常建立连接,否则响应:

HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm="*"

所需要开发的功能模块

  • 连接处理
  • 从客户端请求中获取服务器连接信息
  • 基本认证
  • 请求转发

完整代码可查看:https://github.com/yangxikun/gsproxy

moooofly avatar Jan 22 '19 07:01 moooofly