0. 前言

  • 前面的介绍我们理解了数字签名等知识,同时学习了 OpenSSL 生成私钥和证书并验证
  • 之前提过我们基于 BitTorrent 协议开发了一个 docker 镜像分发加速插件
  • 中间涉及到了配置 docker 的代理
  • 下面我们简单介绍下 Golang 的 http.transport 配置了网络代理后的网络行为并编写一个简单的代理转发,加深理解代理转发行为

1. http.Transport 配置代理

  • http 代理配置代码如下:
func TLSTransport(caFile string) (*http.Transport, error) {
tr := &http.Transport{TLSClientConfig: &tls.Config{}, Proxy: http.ProxyFromEnvironment}
if len(caFile) == {
tr.TLSClientConfig.InsecureSkipVerify = true
return tr, nil
} ca, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, fmt.Errorf("read CA file failed: %v", err)
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(ca) tr.TLSClientConfig.RootCAs = pool return tr, nil
}
  • 上述代码制定了 Proxy 为 http.ProxyFromEnvironment
  • 我们跟踪一下 http.ProxyFromEnvironment 的代码
func ProxyFromEnvironment(req *Request) (*url.URL, error) {
return envProxyFunc()(req.URL)
}
/////////////////////////////////////////////////////////////////////////
func envProxyFunc() func(*url.URL) (*url.URL, error) {
envProxyOnce.Do(func() {
envProxyFuncValue = httpproxy.FromEnvironment().ProxyFunc()
})
return envProxyFuncValue
}
/////////////////////////////////////////////////////////////////////////
func FromEnvironment() *Config {
return &Config{
HTTPProxy: getEnvAny("HTTP_PROXY", "http_proxy"),
HTTPSProxy: getEnvAny("HTTPS_PROXY", "https_proxy"),
NoProxy: getEnvAny("NO_PROXY", "no_proxy"),
CGI: os.Getenv("REQUEST_METHOD") != "",
}
}
/////////////////////////////////////////////////////////////////////////
func (cfg *Config) ProxyFunc() func(reqURL *url.URL) (*url.URL, error) {
// Preprocess the Config settings for more efficient evaluation.
cfg1 := &config{
Config: *cfg,
}
cfg1.init()
return cfg1.proxyForURL
}
/////////////////////////////////////////////////////////////////////////
func (cfg *config) proxyForURL(reqURL *url.URL) (*url.URL, error) {
var proxy *url.URL
if reqURL.Scheme == "https" {
proxy = cfg.httpsProxy
}
fmt.Printf("WangAo test: proxy: %+v", proxy)
if proxy == nil {
proxy = cfg.httpProxy
if proxy != nil && cfg.CGI {
return nil, errors.New("refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy")
}
}
if proxy == nil {
return nil, nil
}
if !cfg.useProxy(canonicalAddr(reqURL)) {
return nil, nil
} return proxy, nil
}
  • proxy 指定返回给定请求的代理的函数
  • 如果函数返回一个非 nil 错误,请求将因提供的错误而中止
  • 代理类型由 URL scheme 决定:支持 http、https 和 socks5
  • 如果 scheme 为空,则假定为 http
  • 如果 proxy 为 nil 或返回 nil 的 *url.URL 类型,则不使用 proxy
  • envProxyFunc 返回一个函数,函数读取环境变量确定代理地址
  • FromEnvironment 可以看出代码主要读取 HTTP_PROXY、HTTPS_PROXY、NO_PROXY 和 REQUEST_METHOD
  • ProxyFunc 中调用 config.init 方法解析环境变量,并返回实际解析 URL 并返回代理地址的函数
  • 在 proxyForURL 中我们发现,对于 https 请求首选是采用 https 代理地址,若 https 代理地址为空或者请求为其他请求则采用 http 地址
  • 若配置了 http 代理地址同时配置了 REQUEST_METHOD,返回空代理地址和错误信息
  • 如果 http 代理也没有配置则返回空代理地址
  • 解析请求信息若为 localhost 或者为回环地址不使用代理地址,否则返回配置的代理地址

2. 测试网络行为

  • 上述我们简单读取了 http.ProxyFromEnvironment 读取环境变量确定代理地址的行为
  • 下面我们简单介绍下测试代码
  • 首先是 Server 端:
package main

import (
"bufio"
"context"
"fmt"
"git.tencent.com/tke/p2p/pkg/util"
"github.com/elazarl/goproxy"
"github.com/gorilla/mux"
"io"
"k8s.io/klog"
"log"
"net"
"net/http"
) func main() {
go func() {
log.Println("Starting httpServer")
router := mux.NewRouter().SkipClean(true)
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
req.URL.Host = req.Host
req.URL.Scheme = "http"
proxy.ServeHTTP(w, req)
})
proxy.OnRequest(goproxy.ReqHostIs("test.openssl.com:1213")).HijackConnect(func(req *http.Request, client net.Conn, _ *goproxy.ProxyCtx) {
var err error log.Printf("getHijhack: %+v", req.URL)
defer func() {
if err != nil {
klog.Errorf("Transfer HTTP CONNECT request failed: %+v, %v", req, err)
if _, writeErr := client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n")); err != nil {
klog.Errorf("Write CONNECT failing header failed: %v", writeErr)
}
}
if closeErr := client.Close(); closeErr != nil {
klog.Errorf("Close client connection failed: %v", closeErr)
}
}() log.Println("before connectDial")
remote, err := connectDial(proxy, "tcp", "127.0.0.1:1213")
if remote != nil {
log.Printf("==============> remote: %+v>%+v\n", remote.LocalAddr(), remote.RemoteAddr())
}
if err != nil {
return
} bufferedRemote := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))
bufferedClient := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client)) errCh := make(chan error, )
go func() {
defer close(errCh)
if _, reverseErr := io.Copy(bufferedRemote, bufferedClient); reverseErr != nil {
klog.Errorf("Transfer remote to client failed: %v", reverseErr)
errCh <- reverseErr
}
}() if _, transferErr := io.Copy(bufferedClient, bufferedRemote); transferErr != nil {
klog.Errorf("Transfer client to remote failed: %v", transferErr)
err = transferErr
} if reverseErr := <-errCh; reverseErr != nil {
err = reverseErr
}
})
router.HandleFunc("/http", func(w http.ResponseWriter, r *http.Request) {
log.Printf("1--------------------->http: /http >>>>>> req.URL: %+v", r.URL)
cnt, err := w.Write([]byte(fmt.Sprintf("http: /http return response of req: %+v", r)))
log.Printf("/http write: cnt: %v, err: %v", cnt, err)
})
router.HandleFunc("/https", func(w http.ResponseWriter, r *http.Request) {
log.Printf("2--------------------->http: /https >>>>>>req.URL: %+v", r.URL)
cnt, err := w.Write([]byte(fmt.Sprintf("http: /https return response of req: %+v", r)))
log.Printf("/http write: cnt: %v, err: %v", cnt, err)
//proxy.ServeHTTP(w, r)
})
router.NotFoundHandler = proxy
if err := http.ListenAndServe(":1212", router); err != nil {
log.Printf("httpServer err: %+v", err)
}
}()
go func() {
log.Println("Starting httpsServer")
router := mux.NewRouter().SkipClean(true)
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
req.URL.Host = req.Host
req.URL.Scheme = "https"
proxy.ServeHTTP(w, req)
})
if tr, err := util.TLSTransport("/home/ao/Documents/certs/review/server.crt"); err == nil {
proxy.Tr = tr
}
router.HandleFunc("/https", func(w http.ResponseWriter, r *http.Request) {
log.Printf("3--------------------->https: req: %+v", r)
cnt, err := w.Write([]byte(fmt.Sprintf("https: /https return response of req: %+v", r)))
log.Printf("/http write: cnt: %v, err: %v", cnt, err)
})
if err := http.ListenAndServeTLS(":1213", "/home/ao/Documents/certs/review/server.crt", "/home/ao/Documents/certs/review/server.key", router); err != nil {
log.Printf("httsServer err: %+v", err)
}
}()
select {}
} func dial(proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) {
if proxy.Tr.DialContext != nil {
return proxy.Tr.DialContext(context.Background(), network, addr)
}
return net.Dial(network, addr)
} func connectDial(proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) {
if proxy.ConnectDial == nil {
return dial(proxy, network, addr)
}
return proxy.ConnectDial(network, addr)
}
  • 服务端启动了两个 goroutine,分别监听 http 和 https 请求
  • http 监听地址为配置的代理地址
  • https 为请求实际请求的地址,同时我们设置了拦截 CONNECT 方法的目标域名
  • 在拦截 CONNECT 方法之后的回调函数我们看到此时会和 https 监听地址交换数据转发给 https 地址
  • 然后我们看一下 Client 端:
package main

import (
"fmt"
"git.tencent.com/tke/p2p/pkg/util"
"io/ioutil"
"net/http"
) func main() {
tr, _ := util.TLSTransport("/home/ao/Documents/certs/review/server.crt")
client := &http.Client{Transport: tr} req, _ := http.NewRequest("GET", "https://test.openssl.com:1213/https", nil)
resp, err := client.Do(req)
if err != nil {
fmt.Printf("err: %+v", err)
} else {
body, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("resp: %+v=>%+v", resp.StatusCode, string(body))
}
}
  • Client 端很简单,我们只是制定了证书发送一个 https 请求
  • 分别启动 Server 端和 Client 端我们看一下结果:
$ go run server.go
// :: Starting httpsServer
// :: Starting httpServer
// :: [] INFO: Running CONNECT handlers
// :: [] INFO: on 0th handler: &{ 0x69b280 <nil>} test.openssl.com:
// :: [] INFO: Hijacking CONNECT to test.openssl.com:
// :: getHijhack: //test.openssl.com:1213
// :: before connectDial
// :: ==============> remote: 127.0.0.1:>127.0.0.1:
// :: --------------------->https: req: &{Method:GET URL:/https Proto:HTTP/1.1 ProtoMajor: ProtoMinor: Header:map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]] Body:{} GetBody:<nil> ContentLength: TransferEncoding:[] Close:false Host:test.openssl.com: Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr:127.0.0.1: RequestURI:/https TLS:0xc0000ae6e0 Cancel:<nil> Response:<nil> ctx:0xc000142510}
// :: /http write: cnt: , err: <nil>
// :: [] INFO: Running CONNECT handlers
// :: [] INFO: on 0th handler: &{ 0x69b280 <nil>} test.openssl.com:
// :: [] INFO: Hijacking CONNECT to test.openssl.com:
// :: getHijhack: //test.openssl.com:1213
// :: before connectDial
// :: ==============> remote: 127.0.0.1:>127.0.0.1:
// :: --------------------->https: req: &{Method:GET URL:/https Proto:HTTP/1.1 ProtoMajor: ProtoMinor: Header:map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]] Body:{} GetBody:<nil> ContentLength: TransferEncoding:[] Close:false Host:test.openssl.com: Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr:127.0.0.1: RequestURI:/https TLS:0xc000160160 Cancel:<nil> Response:<nil> ctx:0xc000154510}
// :: /http write: cnt: , err: <nil> $ HTTP_PROXY=http://127.0.0.1:1212 go run connect.go
resp: =>return response of req: &{Method:GET URL:http://test.openssl.com:1213/https Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]] Body:{} GetBody:<nil> ContentLength:0 TransferEncoding:[] Close:false Host:test.openssl.com:1213 Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr:127.0.0.1:34024 RequestURI:http://test.openssl.com:1213/https TLS:<nil> Cancel:<nil> Response:<nil> ctx:0xc00011cba0}% $ HTTPS_PROXY=http://127.0.0.1:1212 go run connect.go
resp: =>return response of req: &{Method:GET URL:http://test.openssl.com:1213/https Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]] Body:{} GetBody:<nil> ContentLength:0 TransferEncoding:[] Close:false Host:test.openssl.com:1213 Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr:127.0.0.1:34024 RequestURI:http://test.openssl.com:1213/https TLS:<nil> Cancel:<nil> Response:<nil> ctx:0xc00011cba0}%
  • 设定 HTTPS_PROXY 和 HTTP_PROXY 启动得到的结果都是一样的
  • 当我们把 Client 端请求的结果设为 http 时,我们再看一下 Server 和 Client 的输出:

$ go run server.go
// :: Starting httpsServer
// :: Starting httpServer
// :: --------------------->http: /https >>>>>>req.URL: http://test.openssl.com:1213/https
// :: /http write: cnt: , err: <nil> $ HTTP_PROXY=http://127.0.0.1:1212 go run client.go
resp: =>http: /https return response of req: &{Method:GET URL:http://test.openssl.com:1213/https Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]] Body:{} GetBody:<nil> ContentLength:0 TransferEncoding:[] Close:false Host:test.openssl.com:1213 Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr:127.0.0.1:34630 RequestURI:http://test.openssl.com:1213/https TLS:<nil> Cancel:<nil> Response:<nil> ctx:0xc00007bdd0}% $ HTTPS_PROXY=http://127.0.0.1:1212 go run client.go
resp: =>Client sent an HTTP request to an HTTPS server.
  • 同样分别设定 HTTP_PROXY 和 HTTPS_PROXY 发送 http 请求得到结果是不同的
  • 配置了 HTTP_PROXY 的请求被代理收到但是没有发出 CONNECT 方法,而是以 http 方式直接请求的
  • 配置了 HTTPS_PROXY 的请求没有被代理收到,但是由于 https 服务端同样的 IP 地址,被 https 服务端直接收到。但由于是 http 请求直接返回 400 了

3. 小结

  • 本文主要介绍了 http.ProxyFromEnvironment 配置下的代理行为和相关测试代码
  • 欢迎各位给出意见

Golang(十二)TLS 相关知识(三)理解并模拟简单代理的更多相关文章

  1. Golang(十)TLS 相关知识(一)基本概念原理

    0. 前言 最近参与一个基于 BitTorrent 协议的 Docker 镜像分发加速插件的开发,主要参与补充 https 协议 学习了 TLS 相关知识,下面对之前的学习做一下简单总结 参考文献:T ...

  2. Golang(十一)TLS 相关知识(二)OpenSSL 生成证书

    0. 前言 接前一篇文章,上篇文章我们介绍了数字签名.数字证书等基本概念和原理 本篇我们尝试自己生成证书 参考文献:TLS完全指南(二):OpenSSL操作指南 1. OpenSSL 简介 OpenS ...

  3. 【.NET Core项目实战-统一认证平台】第十二章 授权篇-深入理解JWT生成及验证流程

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了基于Ids4密码授权模式,从使用场景.原理分析.自定义帐户体系集成完整的介绍了密码授权模式的内容,并最后给出了三个思考问题,本 ...

  4. 网站开发进阶(二十二)HTML UI知识汇总(更新中...)

    HTML知识汇总(更新中...) 1.<iframe> 标签 浏览器支持 所有浏览器都支持 <iframe> 标签. 定义和用法 iframe 元素会创建包含另外一个文档的内联 ...

  5. golang中并发的相关知识

    golang中done channel理解:https://segmentfault.com/a/1190000006261218 golang并发模型之使用Context:https://segme ...

  6. html5 之 canvas 相关知识(三)API-strokeStyle-shadow相关

    strokeStyle定义和用法 strokeStyle 属性设置或返回用于笔触的颜色.渐变或模式. context.strokeStyle=color|gradient|pattern;//指示绘图 ...

  7. AngularJS进阶(十二)AngularJS常用知识汇总(不断更新中....)

    AngularJS常用知识汇总(不断更新中....) 注:请点击此处进行充电! app.controller('editCtrl',['$http','$location','$rootScope', ...

  8. Java学习之路(十二):IO流<三>

    复习:序列流 序列流可以把多个字节输入整合成一个,从序列流中读取到数据时,将从被整合的第一个流开始读取,读完这个后,然后开始读取第二个流,依次向后推. 详细见上一篇文章 ByteArrayOutput ...

  9. golang的项目结构 相关知识

    ### 项目结构 ``` ├── bin │   ├── login │   └── main ├── pkg │   └── darwin_amd64 │   └── login │   └── a ...

随机推荐

  1. Eclipse 常用快捷键-java

    (转自https://www.runoob.com/w3cnote/eclipse-shortcut-keys.html) Eclipse有强大的编辑功能, 工欲善其事,必先利其器, 掌握Eclips ...

  2. LinuxShell——正则表达式

    LinuxShell——正则表达式 摘要:本文主要学习了Shell中的正则表达式. 简介 含义 正则表达式,也称作正规表示法,是用于描述字符排列和匹配模式的一种语法规则,它主要用于字符串的模式分割.匹 ...

  3. Python进阶(二)

    1.模块和包的概念 python的解决方案是把同名的模块放到不同的包中 1.1,导入模块 要使用一个模块,我们必须首先导入该模块.Python使用import语句导入一个模块.例如,导入系统自带的模块 ...

  4. Python基础24

    import 与 from import 知乎上说的简洁明了,zhihu.com/question/38857862 from import, 导入之后就能拿来用了,直接用!到处用!

  5. electron——ipcMain模块、ipcRenderer模块

    ipcMain 从 主进程 到 渲染进程 的异步通信. ipcMain模块是EventEmitter类的一个实例. 当在主进程中使用时,它处理从渲染器进程(网页)发送出来的异步和同步信息. 从渲染器进 ...

  6. 【推荐】全球最全面的Telegram组群频道的集合网站 持续收集中

    全球最全面的Telegram组群频道的集合网站 https://www.telegramgroup.org Telegram 组群频道分享 可搜索自己想找的组群频道 从小白到大神,一个 telegra ...

  7. JavaScript函数式编程究竟是什么?

    摘要: 理解函数式编程. 作者:前端小智 原文:JS中函数式编程基本原理简介 Fundebug经授权转载,版权归原作者所有. 在长时间学习和使用面向对象编程之后,咱们退一步来考虑系统复杂性. 在做了一 ...

  8. mysql update运行超时解决方案

    问题描述: 今天update(修改)mysql数据库中一张表时,发现时间很长,而且会失败.报错:Error Code: 1205. Lock wait timeout exceeded; try re ...

  9. Windows | Ubuntu 16.04/18.04 安装Pycharm并永久破解以及安装配置Anaconda3

    Ubuntu 18.04下 1.安装python 2._版本,输入  sudo apt install python 命令行输入 python或python3会打开对应的版本. 输入 exit()或C ...

  10. JAVA的toString方法的一个小例子

    Object是一个抽象类,他有很有方法,其中的toString方法是我们常见的一个方法,我们可以看这段代码 package com.com.day1; public class ToStringTes ...