golang 实现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) }
上面的代码运行之后,会在本地的 8080
端口启动代理服务。修改浏览器的代理为 127.0.0.1::8080
再访问http网站,可以验证代理正常工作,也能看到它在终端打印出所有的请求信息。
如果了解 HTTPS 协议的话,你会明白这种模式下是无法完成 HTTPS 握手的,虽然代理可以和真正的服务器建立连接(知道了对方的公钥和证书),但是代理无法代表服务器和客户端建立连接,因为代理服务器无法知道真正服务器的私钥。
反向代理
编写反向代理按照上面的思路当然没有问题,只需要在第二步的时候,根据之前的配置修改 outReq
的 URL Host 地址可以了。不过 Golang 已经给我们提供了编写代理的框架: httputil.ReverseProxy
。我们可以用非常简短的代码来实现自己的代理,而且内部的细节问题都已经被很好地处理了。
package main import ( "log" "net/http" "net/http/httputil" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { director := func(req *http.Request) { req = r req.URL.Scheme = "http" req.URL.Host = r.Host } proxy := &httputil.ReverseProxy{Director: director} proxy.ServeHTTP(w, r) }) log.Fatal(http.ListenAndServe(":8888", nil)) }
感觉用法与正向代理没区别,也是设置浏览器代理地址后,用于访问http网站
用代理访问HTTPS
可以在远程服务器上用http.Get 或client.Do获取http或https网页内容。然后用http.ListenAndServe向本地返回获取到的内容。
package main import ( "fmt" "io/ioutil" "net/http" ) func main() { resp, _ := http.Get("https://www.baidu.com") defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) }
或
package main import ( "fmt" "io/ioutil" "net/http" ) func main() { client := &http.Client{} req, _ := http.NewRequest("GET", "https://www.baidu.com", nil) //req.Header.Set("Content-Type", "application/x-www-form-urlencoded") //req.Header.Set("Cookie", "name=any") resp, err := client.Do(req) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { // handle error } fmt.Println(string(body)) }
其实也不必用这种土办法,直接在远程服务器上装ss就好了。(下面的参考中也有一个“用不到 100 行的 Golang 代码实现 HTTP(S) 代理”的代码)
参考:
https://blog.csdn.net/mengxinghuiku/article/details/65448600
https://www.jianshu.com/p/f868c88b45e1
https://studygolang.com/articles/11967?fr=sidebar
https://studygolang.com/articles/12525?fr=sidebar
https://segmentfault.com/a/1190000003735562
https://www.cnblogs.com/freecast/p/9687733.html