如果您向客户提供银行服务,则可能必须与第三方(例如其他银行和支付提供商)集成。为了保证高级别的安全性,参与方通常要求:
对于Web服务:
但是,如果您的服务托管在平台即服务(PaaS)提供程序(例如Heroku)上,则必须考虑其他集成工作,以满足起源限制。这是HTTPS代理用来提供封装的。
代理是一种服务器或应用程序,可充当从客户端到目的地请求的中介。HTTP/S代理是支持HTTP请求转发(forwarding)和HTTP隧道(tunneling)的代理。它通常在开放系统互连(OSI)第7层上运行,这与通常在OSI第3层上运行的网络地址转换(NAT)应用程序区分开来。像HTTP隧道这种代理,有很多备用名称,例如网关(gateway)或透明代理(transparent proxy)。要代理HTTP/S请求,可以在两种类型之间进行广泛选择:转发代理(Forwarding Proxy)和反向代理(Reverse Proxy)。
转发代理可以有两种变体。
一种变体称为复制(replication),其中代理端终止传入的客户端请求,创建自己的到目的地的传出请求,然后以目的地回复来响应客户端。这适用于HTTP,但不适用于HTTPS,因为它会附带一个重要的副作用,即能够检查(和修改)客户端的请求,这会破坏TLS完整性。
另一个变种称为隧道(tunneling),其中代理端通过HTTP CONNECT方法使用HTTP隧道功能。这样,代理端就接受了一个初始CONNECT请求,该请求将整个URL当作HOST值,而不仅仅是主机地址。然后,代理端打开到目的地的TCP连接,并透明地将原始通信从客户端转发到目的地。这允许代理端仅检查和评估来自客户端的初始CONNECT请求,例如授权。但是,任何进一步的通信都不会被拦截,因此代理端无法读取TLS通信。这允许初始的连接代理端的CONNECT方法是HTTP或HTTPS,然后是通过代理端的HTTPS到目的地。
HTTP会话示例
> CONNECT example.host.com:443 HTTP/1.1 > Host: example.host.com:443 > Proxy-Authorization: Basic base64-encoded-proxy-credentials > Proxy-Connection: Keep-Alive < HTTP/1.1 200 OK > GET /foo/bar?baz#qux HTTP/1.1 > Host: example.host.com > Authorization: Basic base64-encoded-destination-credentials < HTTP/1.1 200 OK < Connection: close
在这两种变体中,客户端都会直接影响目的地的选择,因为涉及两个不同的URL:代理端的URL和目的地的URL。
在计算机网络中,反向代理是一种代理服务器,它代表一个客户端从一个或多个服务器检索资源。然后,将这些资源返回给客户端,就好像它们源自Web服务器本身一样。
反向代理接受来自客户端的传入请求,并根据该请求将它们路由到特定的目的地。目的地选择的区分因素可能是请求的主机,路径,查询参数,任何标头,甚至是请求体的有效负载。无论如何,反向代理都会拦截并终止HTTP和HTTPS连接,并向目的地创建新请求。因此,在使用反向代理时,客户端不会直接影响目的地选择。
现在我们知道了不同的HTTP/S代理类型和变体,可以评估我们的要求并选择适当的代理实现:如果希望完全控制目的地的选择,那么选择转发代理,并且要保证高度的隐私和完整性,因此选择HTTP隧道。
Golang的net/http
包中有客户端-服务器通信所需的大多数实现。您需要做的就是了解功能,并在需要时使用它。
#!/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
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)) } } // 升级版 package main import ( "crypto/tls" "fmt" "net/http" "net/http/httputil" "net/url" ) func main() { u, err := url.Parse("https://localhost:8888") if err != nil { panic(err) } tr := &http.Transport{ Proxy: http.ProxyURL(u), // Disable HTTP/2. TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), } client := &http.Client{Transport: tr} resp, err := client.Get("https://google.com") if err != nil { panic(err) } defer resp.Body.Close() dump, err := httputil.DumpResponse(resp, true) if err != nil { panic(err) } fmt.Printf("%q", dump) }
Chrome --proxy-server=https://localhost:8888 curl -Lv --proxy https://localhost:8888 --proxy-cacert server.pem https://google.com
已经有singleHostReverseProxy函数,在httputil包中。
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy
我们只需要发送一个URL到返回reverseProxy的函数即可。
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request)
然后,如果我们调用ServeHTTP函数,它将http请求发送到适当的服务。
假设有一个可通过某个端口的http访问的服务,但是不希望公开该服务,或者想要添加一些自定义规则,或者想要在界面上进行一些性能分析,则可以使用反向代理。
在此示例中,我们将通过将所有Web请求从一台服务器(运行在任意的9090端口上)转发到某处的另一台服务器来演示反向代理的作用,例如 <http://127.0.0.1:8080>
:
ioutil
包的ReadAll和NopCloser函数,该函数有助于在不修改请求缓冲区的情况下复制请求正文。创建一个称为Prox的简单结构,它将处理反向代理的业务逻辑。
type Prox struct { target *url.URL proxy *httputil.ReverseProxy } func NewProxy(target string) *Prox { url, _ := url.Parse(target) return &Prox{ target: url, proxy: httputil.NewSingleHostReverseProxy(url) } }
如您所见,没有太多代码。我们只需要发送目标url,NewProxy函数将返回Prox结构对象。
func (p *Prox) handle(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-GoProxy", "GoProxy") p.proxy.Transport = &myTransport{} p.proxy.ServeHTTP(w, r) }
由于我们要捕获请求/响应主体或为每个请求添加一些自定义规则,因此我们不得不更改传输接口。因此,我们为Transport创建了自己的RoundTrip函数并将其分配给我们之前创建的proxy.Transport
。
type myTransport struct { } func (t *myTransport) RoundTrip(request *http.Request) (*http.Response, error) { buf, _ := ioutil.ReadAll(request.Body) rdr1 := ioutil.NopCloser(bytes.NewBuffer(buf)) rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf)) fmt.Println("Request body : ", rdr1) request.Body = rdr2 response, err := http.DefaultTransport.RoundTrip(request) if err != nil { print("\n\ncame in error resp here", err) return nil, err //Server is not reachable. Server not working } body, err := httputil.DumpResponse(response, true) if err != nil { print("\n\nerror in dumb response") // copying the response body did not work return nil, err } log.Println("Response Body : ", string(body)) return response, err }
借助Go的time包,我们可以测量函数所花费的时间,在这种情况下,我们可以测量原始服务器的响应时间。我们修改了RoundTrip函数,以测量每个路径所花费的时间。我们添加了代码,以便对于特定路径,我们可以得到进行http调用的次数,所有调用花费的时间,平均时间等。
var globalMap = make(map[string]Montioringpath) func (t *myTransport) RoundTrip(request *http.Request) (*http.Response, error) { start := time.Now() response, err := http.DefaultTransport.RoundTrip(request) if err != nil { print("\n\ncame in error resp here", err) return nil, err //Server is not reachable. Server not working } elapsed := time.Since(start) key := request.Method + "-" + request.URL.Path // for example for POST Method with /path1 as url path key=POST-/path1 if val, ok := globalMap[key]; ok { val.Count = val.Count + 1 val.Duration += elapsed.Nanoseconds() val.AverageTime = val.Duration / val.Count globalMap[key] = val //do something here } else { var m Montioringpath m.Path = request.URL.Path m.Count = 1 m.Duration = elapsed.Nanoseconds() m.AverageTime = m.Duration / m.Count globalMap[key] = m } b, err := json.MarshalIndent(globalMap, "", " ") if err != nil { fmt.Println("error:", err) } body, err := httputil.DumpResponse(response, true) if err != nil { print("\n\nerror in dumb response") // copying the response body did not work return nil, err } log.Println("Response Body : ", string(body)) log.Println("Response Time:", elapsed.Nanoseconds()) }
Go的Flag 包使得在运行程序时接受命令行参数。我们可以通过命令行参数设置http端口(将在其中运行代理服务器的位置)和重定向url(需要将http请求路由到的位置)。
func main() { const ( defaultPort = ":9090" defaultPortUsage = "default server port, ':9090'" defaultTarget = "http://127.0.0.1:8080" defaultTargetUsage = "default redirect url, 'http://127.0.0.1:8080'" ) // flags port = flag.String("port", defaultPort, defaultPortUsage) redirecturl = flag.String("url", defaultTarget, defaultTargetUsage) flag.Parse() fmt.Println("server will run on :", *port) fmt.Println("redirecting to :", *redirecturl) // proxy proxy := NewProxy(*redirecturl) http.HandleFunc("/proxyServer", ProxyServer) // server redirection http.HandleFunc("/", proxy.handle) log.Fatal(http.ListenAndServe(":"+*port, nil)) }
果在运行程序时未设置参数,则默认端口设置为9090,并且请求将路由到http://127.0.0.1:8080。
func main() { //New functionality written in Go http.HandleFunc("/new", func(w http.ResponseWriter, r *http.Request){ fmt.Fprint(w, "New function") }) //gospot.mchampaneri.in ===> localhost:8085 u1, _ := url.Parse("http://localhost:8085/") http.Handle("gospot.mchampaneri.in/", httputil.NewSingleHostReverseProxy(u1)) // www.mchampaneri.in ===> localhost:8081 u2, _ := url.Parse("http://localhost:8081/") http.Handle("www.mchampaneri.in/", httputil.NewSingleHostReverseProxy(u2)) // Start the server http.ListenAndServe(":80", nil) }