Golang實(shí)現(xiàn)反向代理的示例代碼
背景
當(dāng)我們談到反向代理時(shí),可以將其比喻為一個(gè)“中間人”。想象一下,你是一個(gè)用戶,你想要訪問某個(gè)網(wǎng)站。但是,這個(gè)網(wǎng)站并不直接向你提供服務(wù),而是委托了一個(gè)代理來處理你的請求。這個(gè)代理就是反向代理。
你可以把反向代理想象成一個(gè)非常聰明的助手,它可以幫助你與網(wǎng)站進(jìn)行交流。當(dāng)你發(fā)送請求時(shí),它會(huì)接收到你的請求,并將其轉(zhuǎn)發(fā)給網(wǎng)站。然后,網(wǎng)站會(huì)將響應(yīng)發(fā)送給反向代理,反向代理再將響應(yīng)發(fā)送給你。這樣,你就可以與網(wǎng)站進(jìn)行交互,而不需要直接與網(wǎng)站通信。
net/http 包里面已經(jīng)幫我們內(nèi)置了具有反向代理能力 ReverseProxy 對象, 但是它的能力有限, 從工程能力上面還有很多自行實(shí)現(xiàn).
本文包含了講述官方代碼內(nèi)部實(shí)現(xiàn), 同時(shí)結(jié)合自身需求講述改造后對象代碼邏輯
由于筆者能力和精力有限, 因本文包含了大段代碼, 不免閱讀起來第一感覺較為繁瑣復(fù)雜, 但大部分代碼都進(jìn)行了詳細(xì)的注釋標(biāo)注, 可業(yè)務(wù)中用到時(shí)再回來詳讀代碼部分.
大家也可閱讀底部參考鏈接部分, 選擇的質(zhì)量都很精簡, 相信大家肯定能有所收獲.
官方代碼分析
簡單使用
首先我們看下入口實(shí)現(xiàn), 只需要幾行代碼, 就將所有流量代理到了 www.domain.com 上
// 設(shè)置要轉(zhuǎn)發(fā)的地址 target, err := url.Parse("http://www.domain.com") if err != nil { panic(err) } // 實(shí)例化 ReverseProxy 包 proxy := httputil.NewSingleHostReverseProxy(target) //http.HandleFunc("/", proxy.ServeHTTP) // 啟動(dòng)服務(wù) log.Fatal(http.ListenAndServe(":8082", proxy))
本地啟動(dòng) 127.0.0.1:8082 后會(huì)攜帶相關(guān)客戶端相關(guān)請求信息到 www.domain.com 域下.
但是通常上述是無法滿足我們需求的, 比如有鑒權(quán)、超時(shí)控制、鏈路傳遞、請求日志記錄等常見需求, 這樣我們怎么來實(shí)現(xiàn)呢? 在開始之前, 我們先了解下官方內(nèi)置了哪些能力, 具體是怎么工作的.
底層結(jié)構(gòu)
官方的 ReverseProxy 提供的結(jié)構(gòu):
type ReverseProxy struct { // 對請求內(nèi)容進(jìn)行修改 (對象是業(yè)務(wù)傳入req的一個(gè)副本) Director func(*http.Request) // 連接池復(fù)用連接,用于執(zhí)行請求, 默認(rèn)為http.DefaultTransport Transport http.RoundTripper // 定時(shí)刷新內(nèi)容到客戶端的時(shí)間間隔(流式/無內(nèi)容此參數(shù)忽略) FlushInterval time.Duration // 默認(rèn)為std.err,用于記錄內(nèi)部錯(cuò)誤日志 ErrorLog *log.Logger // 用于執(zhí)行 copyBuffer 復(fù)制響應(yīng)體時(shí),利用的bytes內(nèi)存池化 BufferPool BufferPool // 如果配置后, 可修改目標(biāo)代理的響應(yīng)結(jié)果(響應(yīng)頭和內(nèi)容) // 如果此方法返回error, 將調(diào)用 ErrorHandler 方法 ModifyResponse func(*http.Response) error // 配置后代理執(zhí)行過程中, 發(fā)生錯(cuò)誤均會(huì)回調(diào)此方法 // 默認(rèn)邏輯不響應(yīng)任務(wù)內(nèi)容, 狀態(tài)碼返回502 ErrorHandler func(http.ResponseWriter, *http.Request, error) }
在開始的demo里, 我們第一步實(shí)例化了 ReverseProxy 對象, 首先我們分析下NewSingleHostReverseProxy 方法做了什么
// 實(shí)例化 ReverseProxy 包 proxy := httputil.NewSingleHostReverseProxy(target)
初始化部分
初始化對象, 設(shè)置代理請求的request結(jié)構(gòu)值
// 實(shí)例化 ReverseProxy 對象 // 初始化 Director 對象, 將請求地址轉(zhuǎn)換為代理目標(biāo)地址. // 對請求header頭進(jìn)行處理 func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy { targetQuery := target.RawQuery director := func(req *http.Request) { req.URL.Scheme = target.Scheme req.URL.Host = target.Host req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL) if targetQuery == "" || req.URL.RawQuery == "" { req.URL.RawQuery = targetQuery + req.URL.RawQuery } else { req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery } if _, ok := req.Header["User-Agent"]; !ok { // explicitly disable User-Agent so it's not set to default value req.Header.Set("User-Agent", "") } } return &ReverseProxy{Director: director} }
小貼士:
大家可能對 User-Agent 處理比較奇怪, 為什么不存在后要設(shè)置一個(gè)空字符串呢?
這塊代碼源自于的 issues 為: https://github.com/golang/go/issues/15524目的是為了避免請求頭User-Agent被污染, 在http底層包發(fā)起請求時(shí), 如果未設(shè)置 User-Agent 將會(huì)使用 Go-http-client/1.1 代替
發(fā)起請求部分
http.ListenAndServe(":8082", proxy) 啟動(dòng)服務(wù)時(shí), 處理請求的工作主要是 Handler 接口ServeHTTP 方法.
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
ReverseProxy 中默認(rèn)已實(shí)現(xiàn)此接口, 以下是處理請求的核心邏輯
我們來看下代碼是怎么處理的
// 服務(wù)請求處理方法 func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // 檢測是否設(shè)置http.Transport對象 // 如果未設(shè)置則使用默認(rèn)對象 transport := p.Transport if transport == nil { transport = http.DefaultTransport } // 檢測請求是否被終止 // 終止請求或是正常結(jié)束請求等 notifyChan 都會(huì)收到請求結(jié)束通知, 之后進(jìn)行cancel ctx := req.Context() if cn, ok := rw.(http.CloseNotifier); ok { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() notifyChan := cn.CloseNotify() go func() { select { case <-notifyChan: cancel() case <-ctx.Done(): } }() } // 對外部傳入的http.Request對象進(jìn)行克隆 // outreq 是給代理服務(wù)器傳入的請求對象 outreq := req.Clone(ctx) if req.ContentLength == 0 { // 主要修復(fù) ReverseProxy 與 http.Transport 重試不兼容性問題 // 如果請求方法為 GET、HEAD、OPTIONS、TRACE, 同時(shí)body為nil情況下, 將會(huì)發(fā)生重試 // 避免因?yàn)閺?fù)制傳入的request創(chuàng)建傳入代理的請求內(nèi)容, 導(dǎo)致無法發(fā)生重試. // https://github.com/golang/go/issues/16036 outreq.Body = nil } if outreq.Body != nil { // 避免因panic問題導(dǎo)致請求未正確關(guān)閉, 其他協(xié)程繼續(xù)從中讀取 // https://github.com/golang/go/issues/46866 defer outreq.Body.Close() } if outreq.Header == nil { // Issue 33142: historical behavior was to always allocate outreq.Header = make(http.Header) } // 調(diào)用實(shí)現(xiàn)的 Director 方法修改請求代理的request對象 p.Director(outreq) if outreq.Form != nil { outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery) } outreq.Close = false // 升級http協(xié)議,HTTP Upgrade // 判斷header Connection 中是否有Upgrade reqUpType := upgradeType(outreq.Header) // 根據(jù)《網(wǎng)絡(luò)交換的 ASCII 格式》規(guī)范, 升級協(xié)議中是否包含禁止使用的字符 // https://datatracker.ietf.org/doc/html/rfc20#section-4.2 if !ascii.IsPrint(reqUpType) { // 調(diào)用 ReverseProxy 對象的 ErrorHandler 方法 p.getErrorHandler()( rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType)) return } // 請求下游移除Connetion頭 // https://datatracker.ietf.org/doc/html/rfc7230#section-6.1 removeConnectionHeaders(outreq.Header) // 請求下游根據(jù)RFC規(guī)范移除協(xié)議頭 for _, h := range hopHeaders { outreq.Header.Del(h) } // Transfer-Encoding: chunked 分塊傳輸編碼 if httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") { outreq.Header.Set("Te", "trailers") } // 請求下游指定協(xié)議升級, 例如 websockeet if reqUpType != "" { outreq.Header.Set("Connection", "Upgrade") outreq.Header.Set("Upgrade", reqUpType) } // 添加 X-Forwarded-For 頭 // 最開始的是離服務(wù)端最遠(yuǎn)的設(shè)備 IP,然后是每一級代理設(shè)備的 IP // 類似于 X-Forwarded-For: client, proxy1, proxy2 if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { prior, ok := outreq.Header["X-Forwarded-For"] // 如果header頭 X-Forwarded-For 設(shè)置為nil, 則不再 X-Forwarded-For // 這個(gè)參數(shù)下面我們將詳細(xì)說明 omit := ok && prior == nil if len(prior) > 0 { clientIP = strings.Join(prior, ", ") + ", " + clientIP } if !omit { outreq.Header.Set("X-Forwarded-For", clientIP) } } // 使用transport對象中維護(hù)的鏈接池, 向下游發(fā)起請求 res, err := transport.RoundTrip(outreq) if err != nil { p.getErrorHandler()(rw, outreq, err) return } // 處理下游響應(yīng)的升級協(xié)議請求 // Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc) if res.StatusCode == http.StatusSwitchingProtocols { if !p.modifyResponse(rw, res, outreq) { return } p.handleUpgradeResponse(rw, outreq, res) return } // 根據(jù)協(xié)議規(guī)范刪除響應(yīng) Connection 頭 removeConnectionHeaders(res.Header) // 下游響應(yīng)根據(jù)RFC規(guī)范移除協(xié)議頭 for _, h := range hopHeaders { res.Header.Del(h) } // 如有設(shè)置 modifyResponse, 則修改響應(yīng)內(nèi)容 // 調(diào)用 ReverseProxy 對象 modifyResponse 方法 if !p.modifyResponse(rw, res, outreq) { return } // 拷貝響應(yīng)Header到上游response對象 copyHeader(rw.Header(), res.Header) // 分塊傳輸部分協(xié)議 header 頭設(shè)置, 已跳過 // 寫入響應(yīng)碼到上游response對象 rw.WriteHeader(res.StatusCode) // 拷貝結(jié)果到上游 // flushInterval將響應(yīng)定時(shí)刷新到緩沖區(qū) err = p.copyResponse(rw, res.Body, p.flushInterval(res)) if err != nil { defer res.Body.Close() // ... 調(diào)用errorHandler panic(http.ErrAbortHandler) } // 關(guān)閉響應(yīng)body res.Body.Close() // chunked 分塊傳輸編碼調(diào)用flush刷新到客戶端 if len(res.Trailer) > 0 { // Force chunking if we saw a response trailer. // This prevents net/http from calculating the length for short // bodies and adding a Content-Length. if fl, ok := rw.(http.Flusher); ok { fl.Flush() } } // 以下為分塊傳輸編碼相關(guān)header設(shè)置 if len(res.Trailer) == announcedTrailers { copyHeader(rw.Header(), res.Trailer) return } for k, vv := range res.Trailer { k = http.TrailerPrefix + k for _, v := range vv { rw.Header().Add(k, v) } } }
以上是代理請求的核心處理流程, 我們可以看到主要是對傳入 request 對象轉(zhuǎn)成下游代理請求對象, 請求后返回響應(yīng)頭和內(nèi)容, 進(jìn)行處理.
內(nèi)容補(bǔ)充
1. 為什么請求下游移除Connetion頭
Connection 通用標(biāo)頭控制網(wǎng)絡(luò)連接在當(dāng)前會(huì)話完成后是否仍然保持打開狀態(tài)。如果發(fā)送的值是 keep-alive,則連接是持久的,不會(huì)關(guān)閉,允許對同一服務(wù)器進(jìn)行后續(xù)請求。
這個(gè)頭設(shè)置解決的是客戶端和服務(wù)端鏈接方式, 而不應(yīng)該透傳給代理的下游服務(wù).
所以再RFC中有以下明確規(guī)定:
“Connection”頭字段允許發(fā)送者指示所需的連接 當(dāng)前連接的控制選項(xiàng)。為了避免混淆下游接收者,代理或網(wǎng)關(guān)必須刪除或在轉(zhuǎn)發(fā)之前替換任何收到的連接選項(xiàng)信息。
RFC: https://datatracker.ietf.org/doc/html/rfc7230#section-6.1
2. X-Forwarded-For 作用
X-Forwarded-For(XFF)請求標(biāo)頭是一個(gè)事實(shí)上的用于標(biāo)識(shí)通過代理服務(wù)器連接到 web 服務(wù)器的客戶端的原始 IP 地址的標(biāo)頭(很容易被篡改)。
當(dāng)客戶端直接連接到服務(wù)器時(shí),其 IP 地址被發(fā)送給服務(wù)器(并且經(jīng)常被記錄在服務(wù)器的訪問日志中)。但是如果客戶端通過正向或反向代理服務(wù)器進(jìn)行連接,服務(wù)器就只能看到最后一個(gè)代理服務(wù)器的 IP 地址,這個(gè) IP 通常沒什么用。如果最后一個(gè)代理服務(wù)器是與服務(wù)器安裝在同一臺(tái)主機(jī)上的負(fù)載均衡服務(wù)器,則更是如此。X-Forwarded-For 的出現(xiàn),就是為了向服務(wù)器提供更有用的客戶端 IP 地址。
X-Forwarded-For: <client>, <proxy1>, <proxy2>
<client>
客戶端的 IP 地址。
<proxy1>, <proxy2>
如果請求經(jīng)過多個(gè)代理服務(wù)器,每個(gè)代理服務(wù)器的 IP 地址會(huì)依次出現(xiàn)在列表中。
這意味著,如果客戶端和代理服務(wù)器行為良好,最右邊的 IP 地址會(huì)是最近的代理服務(wù)器的 IP 地址,
最左邊的 IP 地址會(huì)是原始客戶端的 IP 地址。
引用: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/X-Forwarded-For
實(shí)際應(yīng)用落地
實(shí)際落地過程中, 我們不僅要考慮轉(zhuǎn)發(fā)能力, 還要有相對應(yīng)的日志、超時(shí)、優(yōu)雅錯(cuò)誤處理等能力,
下面將講解怎么基于官方內(nèi)置的 ReverseProxy 對象的代理能力來實(shí)現(xiàn)這些功能.
設(shè)計(jì)思路: 對外實(shí)現(xiàn) Proxy ServerHttp版的接口, 在內(nèi)部利用 ReverseProxy 對象代理能力基礎(chǔ)上設(shè)計(jì).
1. 定義proxy ServeHTTP對象
type ServeHTTP struct { // 代理鏈接地址 targetUrl string // net/http 內(nèi)置的 ReverseProxy 對象 reverseProxy *httputil.ReverseProxy // 代理錯(cuò)誤處理 proxyErrorHandler ProxyErrorHandler // 日志對象 logger log.Logger }
下面我們實(shí)例化對象
// NewServeHTTP 初始化代理對象 func NewServeHTTP(targetUrl string, logger log.Logger) *ServeHTTP { target, err := url.Parse(targetUrl) if err != nil { panic(err) } // 重新設(shè)置 Director 復(fù)制請求處理 proxy := &httputil.ReverseProxy{Director: func(req *http.Request) { req.URL.Scheme = target.Scheme req.URL.Host = target.Host req.Host = target.Host if _, ok := req.Header["User-Agent"]; !ok { req.Header.Set("User-Agent", "") } if req.Header.Get("Content-Length") == "0" { req.Header.Del("Content-Length") } req.Header["X-Forwarded-For"] = nil for _, name := range removeRequestHeaders { req.Header.Del(name) } }} serveHttp := &ServeHTTP{ targetUrl: targetUrl, logger: logger, reverseProxy: proxy, proxyErrorHandler: DefaultProxyErrorHandler, } // 設(shè)置trasport處理對象(主要調(diào)配鏈接池大小和超時(shí)時(shí)間) serveHttp.reverseProxy.Transport = HttpTransportDefault() // 定義錯(cuò)誤處理 serveHttp.reverseProxy.ErrorHandler = serveHttp.getErrorHandler(logger) // 定義響應(yīng)處理 serveHttp.reverseProxy.ModifyResponse = serveHttp.getResponseHandler(logger) return serveHttp } // SetProxyErrorFunc 設(shè)置錯(cuò)誤處理函數(shù) func (s *ServeHTTP) SetProxyErrorFunc(handler ProxyErrorHandler) *ServeHTTP { s.proxyErrorHandler = handler return s }
2. 我們重寫了 reverseProxy 的 Director方法
1.我們不希望轉(zhuǎn)發(fā) X-Forwarded-For 到代理層, 通過手動(dòng)賦值為nil方式解決
原因是網(wǎng)絡(luò)防火墻對源IP進(jìn)行了驗(yàn)證, X-Forwarded-For是可選項(xiàng)之一, 但通常 X-Forwarded-For 不安全且容易造成本地聯(lián)通性問題, 不建議通過此參數(shù)進(jìn)行驗(yàn)證, 故將此移除.
2.移除指定的 removeRequestHeaders 頭
常見的鑒權(quán)類頭等
3. 覆蓋官方默認(rèn)的 HttpTransportDefault
在 http.Transport 對象中, MaxIdleConnsPerHost、MaxIdleConns 參數(shù)在 http1.1 下非常影響性能, 默認(rèn) 同host 建立的鏈接池內(nèi)連接數(shù)只有2個(gè), 下面我們統(tǒng)一修改為200
netHttp.Transport{ Proxy: proxyURL, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, ForceAttemptHTTP2: true, MaxIdleConns: 200, MaxIdleConnsPerHost: 200, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, }
4. 定義請求處理部分
考慮到在請求 reverseProxy 對象轉(zhuǎn)發(fā)邏輯時(shí),需要攔截請求進(jìn)行前置參數(shù)處理, 不能直接使用 reverseProxy 對象, 所以就由自定義 proxy 實(shí)現(xiàn) handler 接口的 ServeHTTP 方法, 對 reverseProxy 鏈接處理進(jìn)行一層包裝.
邏輯如下:
// ServeHTTP 服務(wù)轉(zhuǎn)發(fā) func (s *ServeHTTP) ServeHTTP(writer http.ResponseWriter, request *http.Request) { var ( reqBody []byte err error // 生成traceId traceId = s.getTraceId(request) ) // 前置獲取請求頭, 放入context中 // 調(diào)用結(jié)束后請求 body 將會(huì)被關(guān)閉, 后面將無法再獲取 if request.Body != nil { reqBody, err = io.ReadAll(request.Body) if err == nil { request.Body = io.NopCloser(bytes.NewBuffer(reqBody)) } } // header 設(shè)置 traceId和超時(shí)時(shí)間傳遞 request.Header.Set(utils.TraceKey, traceId) request.Header.Set(utils.Timeoutkey, cast.ToString(s.getTimeout(request))) // 計(jì)算獲取超時(shí)時(shí)間, 發(fā)起轉(zhuǎn)發(fā)請求 ctx, cancel := context.WithTimeout( request.Context(), time.Duration(s.getTimeout(request))*time.Millisecond, ) defer cancel() // 設(shè)置請求體 ctx = context.WithValue(ctx, ctxReqBody, string(reqBody)) // 設(shè)置請求時(shí)間, 用于響應(yīng)結(jié)束后計(jì)算請求耗時(shí) ctx = context.WithValue(ctx, ctxReqTime, time.Now()) // context 設(shè)置traceId, 用于鏈路日志打印 ctx = context.WithValue(ctx, utils.TraceKey, traceId) request = request.WithContext(ctx) // 調(diào)用 reverseProxy ServeHTTP, 處理轉(zhuǎn)發(fā)邏輯 s.reverseProxy.ServeHTTP(writer, request) }
以上代碼均有詳細(xì)注釋, 下面我們看下 traceId和請求耗時(shí)函數(shù)邏輯, 比較簡單.
// getTraceId 獲取traceId // header頭中不存在則生成 func (s *ServeHTTP) getTraceId(request *http.Request) string { traceId := request.Header.Get(utils.TraceKey) if traceId != "" { return traceId } return uuid.NewV4().String() } // getTimeout 獲取超時(shí)時(shí)間 // header中不存在timeoutKey, 返回默認(rèn)超時(shí)時(shí)間 // header頭存在, 則判斷是否大于默認(rèn)超時(shí)時(shí)間, 大于則使用默認(rèn)超時(shí)時(shí)間 // 否則返回header設(shè)置的超時(shí)時(shí)間 func (s *ServeHTTP) getTimeout(request *http.Request) uint32 { timeout := request.Header.Get(utils.Timeoutkey) if timeout == "" { return DefaultTimeoutMs } headerTimeoutMs := cast.ToUint32(timeout) if headerTimeoutMs > DefaultTimeoutMs { return DefaultTimeoutMs } return cast.ToUint32(timeout) }
5. 定義響應(yīng)部分和錯(cuò)誤處理部分
從一開始我們就了解 ReverseProxy 功能, 可以設(shè)置 ModifyResponse、ErrorHandler, 下面我們看下具體是怎么實(shí)現(xiàn)的.
ErrorHandler
// getErrorHandler 記錄錯(cuò)誤記錄 func (s *ServeHTTP) getErrorHandler(logger log.Logger) ErrorHandler { return func(writer http.ResponseWriter, request *http.Request, e error) { var ( reqBody []byte err error ) if request.Body != nil { reqBody, err = io.ReadAll(request.Body) if err == nil { request.Body = io.NopCloser(bytes.NewBuffer(reqBody)) } } // 初始化時(shí)確認(rèn)proxyErrorHandler具體處理方法 // 調(diào)用 proxyErrorHandler,處理響應(yīng)部分 s.proxyErrorHandler(writer, e) // 獲取必要信息, 記錄錯(cuò)誤日志 scheme := s.getSchemeDataByRequest(request) _ = log.WithContext(request.Context(), logger).Log(log.LevelError, "x_module", "proxy/server/error", "x_component", scheme.kind, "x_error", e, "x_header", request.Header, "x_action", scheme.operation, "x_param", string(reqBody), "x_trace_id", request.Context().Value(utils.TraceKey), ) } } // 具體代理業(yè)務(wù)錯(cuò)誤處理 // 包含默認(rèn)錯(cuò)誤響應(yīng)和具體代理業(yè)務(wù)錯(cuò)誤響應(yīng). // 以下為某個(gè)業(yè)務(wù)響應(yīng) func XXXProxyErrorHandler(writer http.ResponseWriter, err error) { resp := HttpXXXResponse{ ErrCode: 1, ErrMsg: err.Error(), Data: struct{}{}, } writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.Header().Set("Connection", "keep-alive") writer.Header().Set("Cache-Control", "no-cache") // 設(shè)置狀態(tài)碼為200 writer.WriteHeader(http.StatusOK) // 將響應(yīng)值序列化 respByte, _ := json.Marshal(resp) // 將response數(shù)據(jù)寫入writer, 刷新到Flush // 關(guān)于Flush部分, 一般是不需要主動(dòng)刷新的, 請求結(jié)束后會(huì)自動(dòng)Flush _, _ = fmt.Fprintf(writer, string(respByte)) if f, ok := writer.(http.Flusher); ok { f.Flush() } }
以上有一個(gè)值的關(guān)注的地方, 設(shè)置響應(yīng)頭一定要在設(shè)置響應(yīng)碼之前, 否則將無效
設(shè)置響應(yīng)內(nèi)容一定在最后, 否則將設(shè)置失敗并返回錯(cuò)誤.
ModifyResponse 處理邏輯
// getResponseHandler 獲取響應(yīng)數(shù)據(jù) func (s *ServeHTTP) getResponseHandler(logger log.Logger) func(response *http.Response) error { return func(response *http.Response) error { var ( duration float64 logLevel = log.LevelInfo header http.Header ) // 獲取請求體 reqBody := response.Request.Context().Value(ctxReqBody) // 獲取開始請求時(shí)間, 計(jì)算請求耗時(shí) startTime := response.Request.Context().Value(ctxReqBody) if startTime != nil { _, ok := startTime.(time.Time) if ok { duration = time.Since(startTime.(time.Time)).Seconds() } } // 獲取響應(yīng)數(shù)據(jù) // 如果響應(yīng)碼非200, 調(diào)整日志等級 scheme := s.getSchemeDataByResponse(response) if response.StatusCode != http.StatusOK { logLevel = log.LevelError header = scheme.header } // 記錄日志 _ = log.WithContext(response.Request.Context(), logger).Log(logLevel, "x_module", "proxy/server/resp", "x_component", "http", "x_code", scheme.code, "x_header", header, "x_action", scheme.operation, "x_params", reqBody, "x_response", scheme.responseData, "x_duration", duration, "x_trace_id", response.Request.Context().Value(utils.TraceKey), ) // 設(shè)置響應(yīng)頭 response.Header.Set("Content-Type", "application/json; charset=utf-8") return nil } }
默認(rèn)代理服務(wù)器是不設(shè)置響應(yīng)頭的, 則為默認(rèn)的響應(yīng)頭。
響應(yīng)頭必須手動(dòng)設(shè)置
6. 使用自定義的 proxy 代理請求
urlStr := "https://" + targetHost proxy := utilsProxy.NewServeHTTP(urlStr, logger).SetProxyErrorFunc(utilsProxy.XXXProxyErrorHandler) log.Fatal(http.ListenAndServe(":8082", proxy))
以上就是Golang實(shí)現(xiàn)反向代理的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Go反向代理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
細(xì)細(xì)探究Go 泛型generic設(shè)計(jì)
這篇文章主要帶大家細(xì)細(xì)探究了Go 泛型generic設(shè)計(jì)及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Golang迭代如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解
這篇文章主要為大家介紹了Golang迭代之如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10GoLang中socket心跳檢測的實(shí)現(xiàn)
本文主要介紹了GoLang中socket心跳檢測的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02Go實(shí)現(xiàn)MD5加密的三種方法小結(jié)
本文主要介紹了Go實(shí)現(xiàn)MD5加密的三種方法小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03