反向代理是一种设置在 Web 服务器前端的服务器,负责接收并转发来自客户端(如 Web 浏览器)的请求。通过这种方式,反向代理能够控制客户端的请求和服务器端的响应。利用这一特性,我们可以实现多种功能,例如增加缓存机制、提升网站的安全性等。

在进一步探讨反向代理的具体细节之前,我们先来简要对比一下普通代理(又称正向代理)与反向代理的区别。

正向代理的架构中,代理服务器代表原始客户端从目标网站获取数据。它位于客户端(通常是浏览器)和互联网之间,确保客户端不直接与后端服务器通信。所有客户端的请求都会经过代理服务器,由代理服务器转发给目标网站。因此,对于目标网站而言,代理服务器似乎是请求的发起者(即客户端),从而隐藏了真实客户端的 identity。

6l1z2hapin-20241226165746.png

另一方面,反向代理部署在后端服务器的前端,其作用是确保客户端不会直接与后端服务器进行通信。所有来自客户端的请求都会先经过反向代理,由它负责将这些请求转发给后端服务器。因此,客户端在整个过程中只会与反向代理服务器交互,而不会与实际的后端服务器产生任何直接联系。 在这种情况下,代理可以隐藏后端服务器。 几个常见的反向代理有 Nginx, HAProxy。

zjgm6eip85-20241226165815.png

反向代理使用场景

负载均衡(Load Balancing):
反向代理可作为负载均衡器,将 incoming 请求合理分配到多个后端服务器上,避免单一服务器承受过大压力,从而提升系统的稳定性和响应效率。

增强安全性:
通过反向代理,实际的后端服务器无需暴露其公共 IP 地址。这意味着像 DDoS 之类的攻击只能瞄准代理服务器,而后端服务器则可以得到更好的保护,免受直接的安全威胁。

内容缓存(Caching):
如果你的源服务器与终端用户地理距离较远,反向代理可以在靠近用户的地方部署,缓存常用的内容并直接为本地用户提供服务,从而减少延迟,提升用户体验。

SSL 加密处理:
SSL 加密通信会消耗大量的计算资源。为减轻后端服务器的负担,反向代理可以负责所有 SSL 相关处理,包括加密和解密操作,这样就能够释放源服务器的计算资源,使其专注于处理核心业务逻辑。

Golang 实现

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package proxy  

import (
"bytes"
"compress/gzip" "fmt" "git.woa.com/pdata/slog" "github.com/gin-gonic/gin" "io" "net/http" "net/http/httputil" "net/url" "strings")

type ReverseProxyConfig struct {
// 目标服务地址
TargetHost string
// 路由前缀
RouterPrefix string
// API白名单 key:路径 value:HTTP方法
WhiteApis map[string]string
ClearCORS bool
}

// ReverseProxy 反向代理
type ReverseProxy struct {
config *ReverseProxyConfig
proxy *httputil.ReverseProxy
}

func NewReverseProxy(config *ReverseProxyConfig) (*ReverseProxy, error) {
if config == nil {
return nil, fmt.Errorf("proxy config cannot be nil")
}

targetURL, err := url.Parse(config.TargetHost)
if err != nil {
return nil, err
}
proxy := &ReverseProxy{
config: config,
proxy: httputil.NewSingleHostReverseProxy(targetURL),
}
proxy.setupProxyHandlers()
return proxy, nil
}

// setupProxyHandlers 设置代理处理器
func (p *ReverseProxy) setupProxyHandlers() {
originalDirector := p.proxy.Director
p.proxy.Director = func(req *http.Request) {
originalDirector(req)
p.modifyRequest(req)
}

p.proxy.ModifyResponse = p.modifyResponse
p.proxy.ErrorHandler = p.handleError
}

// modifyRequest 修改请求
func (p *ReverseProxy) modifyRequest(req *http.Request) {
proxyApiPath := strings.TrimPrefix(req.URL.Path, p.config.RouterPrefix)
targetHost, _ := url.Parse(p.config.TargetHost)

req.Host = targetHost.Host
req.URL.Path = proxyApiPath
if req.URL.Path == "" {
req.URL.Path = "/"
}

// 设置HTTP方法
if method, ok := p.findWhitelistedAPI(proxyApiPath); ok {
req.Method = method
}

var bodyStr string
if req.Body != nil {
bodyBytes, _ := io.ReadAll(req.Body)
bodyStr = string(bodyBytes)
req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
}

// 记录请求日志
proxyUrl := fmt.Sprintf("%s://%s%s ", req.URL.Scheme, req.URL.Host, req.URL.Path)
slog.Info("代理 API 请求 - 开始",
"visitor", req.Header.Get("Visitor"),
"method", req.Method,
"proxyUrl", proxyUrl,
"query", req.URL.RawQuery,
"body", bodyStr,
"remote_addr", req.RemoteAddr)
}

// modifyResponse 修改响应
func (p *ReverseProxy) modifyResponse(resp *http.Response) error {
bodyBytes, err := p.readRespBody(resp)
if err != nil {
slog.Error(err, "modifyResponse read response body failed.")
return err
}

// 清除跨域相关的头信息
if p.config.ClearCORS {
resp.Header.Del("Access-Control-Allow-Origin")
resp.Header.Del("Access-Control-Allow-Methods")
resp.Header.Del("Access-Control-Allow-Headers")
resp.Header.Del("Access-Control-Allow-Credentials")
resp.Header.Del("Access-Control-Max-Age")
resp.Header.Del("Access-Control-Expose-Headers")
}

slog.Info("代理 API 请求 - 结束",
"status_code", resp.StatusCode,
"ContentType", resp.Header.Get("Content-Type"),
"response_body", string(bodyBytes))
return nil
}

func (p *ReverseProxy) readRespBody(resp *http.Response) (bodyBytes []byte, err error) {
if resp.Body == nil {
return
}

// 检查是否是压缩数据
switch resp.Header.Get("Content-Encoding") {
case "gzip":
// 解压 gzip 数据
var gzipReader *gzip.Reader
gzipReader, err = gzip.NewReader(resp.Body)
if err != nil {
err = fmt.Errorf("create gzip reader failed: %w", err)
return
}
defer gzipReader.Close()

bodyBytes, err = io.ReadAll(gzipReader)
if err != nil {
err = fmt.Errorf("read gzip data failed: %w", err)
return
}

// 已经解压缩了数据,需要移除 Content-Encoding resp.Header.Del("Content-Encoding")

default:
// 未压缩数据直接读取
bodyBytes, err = io.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("read response body failed: %w", err)
return
}
}

// 重新设置 body resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
return
}

// handleError 处理错误
func (p *ReverseProxy) handleError(w http.ResponseWriter, r *http.Request, err error) {
slog.Error(err, "ProxyRequestHandler proxy error.", "path", r.URL.Path)
http.Error(w, "proxy error", http.StatusBadGateway)
}

// findWhitelistedAPI 查找白名单中的API
func (p *ReverseProxy) findWhitelistedAPI(path string) (string, bool) {
if method, ok := p.config.WhiteApis[path]; ok {
return method, true
}
return "", false
}

// Handler 返回 Gin 处理器
func (p *ReverseProxy) Handler() gin.HandlerFunc {
return func(c *gin.Context) {
path := strings.TrimPrefix(c.Request.URL.Path, p.config.RouterPrefix)
method, ok := p.findWhitelistedAPI(path)
if !ok {
c.String(http.StatusNotFound, "404 API not found in whitelist")
return
}
if c.Request.Method != method {
c.String(http.StatusMethodNotAllowed, "405 Method Not Allowed")
return
}
p.proxy.ServeHTTP(c.Writer, c.Request)
}
}
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
// main.go  

func ProxyRealTimeApi() gin.HandlerFunc {
config := &proxy.ReverseProxyConfig{
TargetHost: "http://kl.do",
RouterPrefix: "/api/proxy",
WhiteApis: proxy.ReverserProxyRealtimeApis,
ClearCORS: true,
}
reverseProxy, err := proxy.NewReverseProxy(config)
if err != nil {
panic(err)
}
return reverseProxy.Handler()
}



func main() {
r := gin.Default()

// 注册代理路由
r.Any("/api/proxy/*path", ProxyRealTimeApi())

r.Run(":8080")
}