使用 Golang 创建反向代理

为了让 API 连接正常工作,我需要在请求头中提供客户端 ID,基于用户名或客户端证书,然后将流量重定向到它。听起来像是专用反向代理软件 Nginx 或 Traefik 要执行的任务。现在我们来探索用 Golang 来实现反向代理的功能。

单主机反向代理

得益于 Golang 标准库,一个开箱即用的单主机反向代理可以很容易地实现。

1
2
rp := httputil.NewSingleHostReverseProxy(targetUrl)
rp.ServeHTTP(w, r)

这段代码应该插入标准的 HTTP 处理程序中。这里的“单一主机”更多指的是反向代理功能,没有提供负载均衡功能。

携带 header 的反向代理

这里会实现一个 HTTP 处理程序,读取客户端证书,并根据 CN 名称选择一个客户端 ID 值,在 HTTP 头部中更新它,并将流量重定向到目标 URL。

给出以下示例配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mapping:
- name: client1-app1
certCN: user1
clientID: 2a3977e9c4dd4631c9233f2e9387a103

- name: client2-app1
certCN: user2
clientID: 939f15e518e75d3c251a1245141c1c48

server:
port: 8443
certFile: ../certs/server.pem
keyFile: ../certs/server-key.pem

reverseProxy:
targetUrl: https://apis-gw-gateway-apic.apps.dev-ocp414.ibmcloud.io.cpak/porg/mycat/myapi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
config.WithOptions(config.ParseEnv, func(opt *config.Options) {
opt.DecoderConfig.TagName = "yaml"
})
config.AddDriver(yaml.Driver)
try.E(config.LoadFiles("config.yaml"))

var mapping []CnToClientID
try.E(config.BindStruct("mapping", &mapping))

targetUrl := try.E1(url.Parse(config.String("reverseProxy.targetUrl")))

http.HandleFunc("/", rpHandler(mapping, targetUrl))

srv := &http.Server{
Addr: fmt.Sprintf(":%d", config.Int("server.port")),
TLSConfig: &tls.Config{
ClientAuth: tls.RequireAnyClientCert,
},
}

try.E(srv.ListenAndServeTLS(config.String("server.certFile"), config.String("server.keyFile")))
}

我们使用带有证书和密钥的基 于HTTPs 的服务器。请注意 TLS 选项,我们将要求客户端提供其证书以提取其 CN 名称,尽管我们不执行mTLS。

可以通过以下方式实现http处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
func rpHandler(mapping []CnToClientID, targetUrl *url.URL) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
dump, _ := httputil.DumpRequest(r, true)
log.Printf("request: %s", dump)

if r.TLS == nil {
log.Printf("Request must be over TLS")
http.Error(w, "Request must be over TLS", http.StatusForbidden)
return
}
if len(r.TLS.PeerCertificates) == 0 {
log.Printf("Request must contain a client certificate")
http.Error(w, "Request must contain a client certificate", http.StatusForbidden)
return
}

cnName := r.TLS.PeerCertificates[0].Subject.CommonName
log.Printf("Client CN: %s", cnName)
for _, v := range mapping {
if v.CertCN == cnName {
rp := httputil.NewSingleHostReverseProxy(targetUrl)

r.Header.Set("X-IBM-Client-Id", v.ClientID)
rp.ServeHTTP(w, r)

return
}
}

// if no match found
http.Error(w, "client id not found", http.StatusForbidden)
}
}

在处理程序中,首先我们读取客户端证书以获取其 CN 名称,然后循环遍历配置以找到客户端 ID 值,将其添加到请求头中,然后创建一个反向代理对象,目标 URL 是让它处理该请求。

自定义 Round Trip

我们需要一些自定义来解决反向代理的问题,可能需要跳过证书验证,因为目标端可能使用自签名证书。

这些可以通过 RoundTrip 的接口实现。

RoundTripper是一个接口,代表着执行单个HTTP事务的能力,获取给定请求的响应。

1
2
3
4
5
6
7
8
9
10
11
type MyRoundTripper struct{}

func (MyRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
dump, _ := httputil.DumpRequest(r, true)
log.Printf("request to proxy: %s", dump)

insecureTransport := http.DefaultTransport.(*http.Transport).Clone()
insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}

return insecureTransport.RoundTrip(r)
}

Roundtrip() 中,我们调用 httputil.DumpRequest 来为了调试目的而转储请求。

然后重置其 TLS 配置以跳过证书验证,以允许自签名证书通过。然后我们调用原始的 RoundTrip() 来处理请求。

然后可以使用自定义的 roundtrip 更新 http 处理程序。

1
2
3
4
5
6
7
8
...

rp := httputil.NewSingleHostReverseProxy(targetUrl)
rp.Transport = &MyRoundTripper{}

r.Header.Set("X-IBM-Client-Id", v.ClientID)
rp.ServeHTTP(w, r)
...

-------------The End-------------

本文标题:使用 Golang 创建反向代理

文章作者:cloud sjhan

发布时间:2024年05月17日 - 22:05

最后更新:2024年05月17日 - 22:05

原始链接:https://cloudsjhan.github.io/2024/05/17/使用-Golang-创建反向代理/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

cloud sjhan wechat
subscribe to my blog by scanning my public wechat account
坚持原创技术分享,您的支持将鼓励我继续创作!
0%
;