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 WhiteApis map[string]string ClearCORS bool }
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 }
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 }
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 = "/" } 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) }
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": 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 } default: bodyBytes, err = io.ReadAll(resp.Body) if err != nil { err = fmt.Errorf("read response body failed: %w", err) return } } return }
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) }
func (p *ReverseProxy) findWhitelistedAPI(path string) (string, bool) { if method, ok := p.config.WhiteApis[path]; ok { return method, true } return "", false }
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) } }
|