èÂÂæ¯
å½ÂæÂÂ们è°Âå°åÂÂÃ¥ÂÂ代çÂÂæ¶ï¼Âå¯以å°Âå ¶æ¯Âå»为ä¸Â个âÂÂä¸Âé´人âÂÂãÂÂæ³象ä¸Âä¸Âï¼Âä½ æ¯ä¸Â个ç¨æ·ï¼Âä½ æ³è¦Â访é®æÂÂ个ç½Âç«ÂãÂÂä½Âæ¯ï¼Âè¿Â个ç½Âç«Â并ä¸Âç´æÂ¥åÂÂä½ æÂÂä¾ÂæÂÂå¡ï¼ÂèÂÂæ¯å§ÂæÂÂäºÂä¸Â个代çÂÂæÂ¥å¤ÂçÂÂä½ çÂÂ请æ±ÂãÂÂè¿Â个代çÂÂå°±æ¯åÂÂÃ¥ÂÂ代çÂÂãÂÂ
ä½ å¯以æÂÂÃ¥ÂÂÃ¥ÂÂ代çÂÂæ³象æÂÂä¸Â个éÂÂ常èªæÂÂçÂÂå©æÂÂï¼Âå®Âå¯以帮å©你ä¸Âç½Âç«Âè¿Âè¡Â交æµÂãÂÂå½Âä½ åÂÂéÂÂ请æ±Âæ¶ï¼Âå®Âä¼ÂæÂ¥æ¶å°你çÂÂ请æ±Âï¼Â并å°Â堶转åÂÂç»Âç½Âç«ÂãÂÂç¶åÂÂï¼Âç½Âç«Âä¼Âå°ÂÃ¥ÂÂåºÂÃ¥ÂÂéÂÂç»ÂÃ¥ÂÂÃ¥ÂÂ代çÂÂï¼ÂÃ¥ÂÂÃ¥ÂÂ代çÂÂÃ¥ÂÂå°ÂÃ¥ÂÂåºÂÃ¥ÂÂéÂÂç»Âä½ ãÂÂè¿Âæ ·ï¼Âä½ å°±å¯以ä¸Âç½Âç«Âè¿Âè¡Â交äºÂï¼ÂèÂÂä¸ÂéÂÂè¦Âç´æÂ¥ä¸Âç½Âç«ÂéÂÂä¿¡ãÂÂ
net/http å éÂÂé¢已ç»Â帮æÂÂ们å 置äºÂå ·æÂÂÃ¥ÂÂÃ¥ÂÂ代çÂÂè½å ReverseProxy 对象, ä½Âæ¯å®ÂçÂÂè½åÂÂæÂÂéÂÂ, ä»Âå·¥ç¨Âè½åÂÂä¸Âé¢è¿ÂæÂÂå¾Âå¤Âèªè¡Âå®Âç°.
æ¾ÂÂå å«äºÂ讲述å®Âæ¹代ç Âå é¨å®Âç°, Ã¥ÂÂæ¶ç»ÂÃ¥ÂÂèª身éÂÂæ±Â讲述æ¹é åÂÂ对象代ç Âé»è¾Â
ç±äºÂç¬Âè è½åÂÂÃ¥ÂÂç²¾åÂÂæÂÂéÂÂ, å æ¾ÂÂå å«äºÂ大段代ç Â, ä¸Âå Âé 读起æ¥第ä¸ÂæÂÂè§Âè¾Â为ç¹ÂçÂÂå¤ÂæÂÂ, ä½Â大é¨åÂÂ代ç Âé½è¿Âè¡ÂäºÂ详ç»ÂçÂÂ注éÂÂæ Â注, å¯ä¸Âå¡ä¸Âç¨å°æ¶åÂÂÃ¥ÂÂæ¥详读代ç Âé¨åÂÂ.
大家ä¹Âå¯é 读åºÂé¨åÂÂèÂÂé¾æÂ¥é¨åÂÂ, éÂÂæ©çÂÂè´¨éÂÂé½å¾Âç²¾ç®Â, ç¸信大家è¯å®Âè½æÂÂæÂÂæ¶è·.
å®Âæ¹代ç ÂÃ¥ÂÂæÂÂ
ç®ÂÃ¥ÂÂ使ç¨
é¦Âå ÂæÂÂ们çÂÂä¸Âå ¥å£å®Âç°, åªéÂÂè¦Âå è¡Â代ç Â, å°±å°ÂæÂÂæÂÂæµÂéÂÂ代çÂÂå°亠www.domain.com ä¸Â
// 设置è¦Â转åÂÂçÂÂå°åÂÂ
target, err := url.Parse("http://www.domain.com")
if err != nil {
panic(err)
}
// å®Âä¾Âå ReverseProxy Ã¥ÂÂ
proxy := httputil.NewSingleHostReverseProxy(target)
//http.HandleFunc("/", proxy.ServeHTTP)
// å¯å¨æÂÂå¡
log.Fatal(http.ListenAndServe(":8082", proxy))
æΡå¯å¨ 127.0.0.1:8082 Ã¥ÂÂä¼Âæº带ç¸堳客æ·端ç¸堳请æ±Âä¿¡æ¯å° www.domain.com Ã¥ÂÂä¸Â.
ä½Âæ¯éÂÂ常ä¸Âè¿°æ¯æ æ³Â满足æÂÂ们éÂÂæ±ÂçÂÂ, æ¯Âå¦ÂæÂÂé´æÂÂãÂÂ趠æ¶æ§å¶ãÂÂé¾路传éÂÂãÂÂ请æ±ÂæÂ¥å¿Âè®°å½ÂçÂÂ常è§ÂéÂÂæ±Â, è¿Âæ ·æÂÂ们æÂÂä¹ÂæÂ¥å®Âç°å¢? å¨å¼Âå§Âä¹ÂÃ¥ÂÂ, æÂÂ们å ÂäºÂ解ä¸Âå®Âæ¹å 置äºÂåªäºÂè½åÂÂ, å ·ä½Âæ¯æÂÂä¹Âå·¥ä½ÂçÂÂ.
åºÂå±Âç»ÂæÂÂ
å®Âæ¹ç ReverseProxy æÂÂä¾ÂçÂÂç»ÂæÂÂ:
type ReverseProxy struct {
// 对请æ±ÂÃ¥ÂÂ
容è¿Âè¡Âä¿®æ¹ (对象æ¯ä¸Âå¡传åÂ
Â¥reqçÂÂä¸Â个å¯æ¬)
Director func(*http.Request)
// è¿Âæ¥池å¤Âç¨è¿ÂæÂ¥ï¼Âç¨äºÂæ§è¡Â请æ±Â, é»Â认为http.DefaultTransport
Transport http.RoundTripper
// å®Âæ¶å·æ°åÂÂ
容å°客æ·端çÂÂæ¶é´é´éÂÂ(æµÂå¼Â/æ åÂÂ
容æ¤åÂÂæ°忽çÂÂ¥)
FlushInterval time.Duration
// é»Â认为std.errï¼Âç¨äºÂè®°å½ÂÃ¥ÂÂ
é¨éÂÂ误æÂ¥å¿Â
ErrorLog *log.Logger
// ç¨äºÂæ§衠copyBuffer å¤Âå¶åÂÂåºÂä½Âæ¶ï¼Âå©ç¨çÂÂbytesÃ¥ÂÂ
Ã¥ÂÂæ± åÂÂ
BufferPool BufferPool
// å¦ÂæÂÂéÂ
Âç½®åÂÂ, å¯修æ¹ç®æ Â代çÂÂçÂÂÃ¥ÂÂåºÂç»ÂæÂÂ(Ã¥ÂÂåºÂ头åÂÂÃ¥ÂÂ
容)
// å¦ÂæÂÂæ¤æ¹æ³Âè¿ÂÃ¥ÂÂerror, å°Âè°Âç¨ ErrorHandler æ¹æ³Â
ModifyResponse func(*http.Response) error
// éÂ
Âç½®åÂÂ代çÂÂæ§è¡Âè¿Âç¨Âä¸Â, Ã¥ÂÂçÂÂéÂÂ误åÂÂä¼ÂÃ¥ÂÂè°Âæ¤æ¹æ³Â
// é»Â认é»è¾Âä¸ÂÃ¥ÂÂåºÂä»»å¡åÂÂ
容, ç¶æÂÂç Âè¿ÂÃ¥ÂÂ502
ErrorHandler func(http.ResponseWriter, *http.Request, error)
}
å¨å¼Âå§ÂçÂÂdemoéÂÂ, æÂÂ们第ä¸ÂæÂ¥å®Âä¾ÂÃ¥ÂÂ亠ReverseProxy 对象, é¦Âå ÂæÂÂ们åÂÂæÂÂä¸ÂNewSingleHostReverseProxy æ¹æ³ÂÃ¥ÂÂäºÂä»Âä¹Â
// å®Âä¾Âå ReverseProxy Ã¥ÂÂ
proxy := httputil.NewSingleHostReverseProxy(target)
Ã¥ÂÂå§ÂÃ¥ÂÂé¨åÂÂ
Ã¥ÂÂå§ÂÃ¥ÂÂ对象, 设置代çÂÂ请æ±ÂçÂÂrequestç»ÂæÂÂå¼
// å®Âä¾Âå ReverseProxy 对象
// Ã¥ÂÂå§Âå Director 对象, å°Â请æ±Âå°åÂÂ转æ¢为代çÂÂç®æ Âå°åÂÂ.
// 对请æ±Âheader头è¿Âè¡Âå¤ÂçÂÂ
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 å¤ÂçÂÂæ¯Âè¾Âå¥Âæª, 为ä»Âä¹Âä¸ÂÃ¥ÂÂå¨åÂÂè¦Â设置ä¸Â个空åÂÂ符串å¢?
è¿ÂÃ¥ÂÂ代ç ÂæºÂèªäºÂç issues 为: github.com/golang/go/iâ¦
ç®çÂÂæ¯为äºÂé¿å Â请æ±Â头User-Agent被污æÂÂ, å¨httpåºÂå±Âå åÂÂ起请æ±Âæ¶, å¦ÂæÂÂæª设置 User-Agent å°Âä¼Â使ç¨ Go-http-client/1.1 代æ¿堷ä½Â代ç Âå°åÂÂ: github.com/golang/go/bâ¦
Ã¥ÂÂ起请æ±Âé¨åÂÂ
http.ListenAndServe(“:8082”, proxy) å¯å¨æÂÂå¡æ¶, å¤ÂçÂÂ请æ±ÂçÂÂå·¥ä½Â主è¦Âæ¯ Handler æÂ¥å£ServeHTTP æ¹æ³Â.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ReverseProxy ä¸Âé»Â认已å®Âç°æ¤æÂ¥å£, 以ä¸Âæ¯å¤ÂçÂÂ请æ±ÂçÂÂæ ¸å¿Âé»è¾Â
æÂÂ们æÂ¥çÂÂä¸Â代ç Âæ¯æÂÂä¹Âå¤ÂçÂÂçÂÂ
// æÂÂå¡请æ±Âå¤ÂçÂÂæ¹æ³Â
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// æ£ÂæµÂæ¯å¦设置http.Transport对象
// å¦ÂæÂÂæª设置åÂÂ使ç¨é»Â认对象
transport := p.Transport
if transport == nil {
transport = http.DefaultTransport
}
// æ£ÂæµÂ请æ±Âæ¯å¦被ç»Âæ¢
// ç»Âæ¢请æ±ÂæÂÂæ¯æ£常ç»ÂæÂÂ请æ±Âç notifyChan é½ä¼Âæ¶å°请æ±Âç»ÂæÂÂéÂÂçÂÂ¥, ä¹ÂÃ¥ÂÂè¿Âè¡Â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对象è¿Âè¡ÂÃ¥Â
ÂéÂÂ
// outreq æ¯ç»Â代çÂÂæÂÂå¡å¨传åÂ
¥çÂÂ请æ±Â对象
outreq := req.Clone(ctx)
if req.ContentLength == 0 {
// 主è¦Â修夠ReverseProxy 丠http.Transport éÂÂè¯Âä¸ÂÃ¥Â
¼å®¹æ§é®é¢Â
// å¦ÂæÂÂ请æ±Âæ¹æ³Â为 GETãÂÂHEADãÂÂOPTIONSãÂÂTRACE, Ã¥ÂÂæ¶body为nilæÂÂ
åµä¸Â, å°Âä¼ÂÃ¥ÂÂçÂÂéÂÂè¯Â
// é¿åÂ
Âå 为å¤Âå¶传åÂ
¥çÂÂrequestÃ¥ÂÂ建传åÂ
¥ä»£çÂÂçÂÂ请æ±ÂÃ¥ÂÂ
容, 导è´æ æ³ÂÃ¥ÂÂçÂÂéÂÂè¯Â.
// https://github.com/golang/go/issues/16036
outreq.Body = nil
}
if outreq.Body != nil {
// é¿åÂ
Âå panicé®é¢Â导è´请æ±Âæªæ£确åÂ
³éÂÂ, Ã¥Â
¶ä»ÂÃ¥ÂÂç¨Â继ç»Âä»Âä¸Â读åÂÂ
// 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)
}
// è°Âç¨å®Âç°ç Director æ¹æ³Âä¿®æ¹请æ±Â代çÂÂçÂÂrequest对象
p.Director(outreq)
if outreq.Form != nil {
outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery)
}
outreq.Close = false
// Ã¥ÂÂ级httpÃ¥ÂÂè®®ï¼ÂHTTP Upgrade
// å¤æÂÂheader Connection ä¸Âæ¯å¦æÂÂUpgrade
reqUpType := upgradeType(outreq.Header)
// æ ¹æ®ãÂÂç½Âç»Â交æ¢ç ASCII æ ¼å¼ÂãÂÂè§ÂèÂÂ, Ã¥ÂÂ级åÂÂè®®ä¸Âæ¯å¦åÂÂ
å«ç¦Âæ¢使ç¨çÂÂÃ¥ÂÂ符
// https://datatracker.ietf.org/doc/html/rfc20#section-4.2
if !ascii.IsPrint(reqUpType) {
// è°Âç¨ 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)
// 请æ±Âä¸Â游根æ®RFCè§ÂèÂÂ移é¤åÂÂ议头
for _, h := range hopHeaders {
outreq.Header.Del(h)
}
// Transfer-Encoding: chunked Ã¥ÂÂÃ¥ÂÂä¼ è¾Âç¼Âç Â
if httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") {
outreq.Header.Set("Te", "trailers")
}
// 请æ±Âä¸Â游æÂÂå®ÂÃ¥ÂÂè®®åÂÂ级, ä¾Â妠websockeet
if reqUpType != "" {
outreq.Header.Set("Connection", "Upgrade")
outreq.Header.Set("Upgrade", reqUpType)
}
// æ·»å X-Forwarded-For 头
// æÂÂå¼Âå§ÂçÂÂæ¯离æÂÂå¡端æÂÂè¿ÂçÂÂ设夠IPï¼Âç¶åÂÂæ¯æ¯Âä¸Â级代çÂÂ设å¤Âç 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 设置为nil, Ã¥ÂÂä¸Âå X-Forwarded-For
// è¿Â个åÂÂæ°ä¸Âé¢æÂÂ们å°Â详ç»Â说æÂÂ
omit := ok && prior == nil
if len(prior) > 0 {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
if !omit {
outreq.Header.Set("X-Forwarded-For", clientIP)
}
}
// 使ç¨transport对象ä¸Âç»´æ¤çÂÂé¾æ¥池, Ã¥ÂÂä¸Â游åÂÂ起请æ±Â
res, err := transport.RoundTrip(outreq)
if err != nil {
p.getErrorHandler()(rw, outreq, err)
return
}
// å¤ÂçÂÂä¸Â游åÂÂåºÂçÂÂÃ¥ÂÂ级åÂÂ议请æ±Â
// 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
}
// æ ¹æ®åÂÂè®®è§ÂèÂÂå é¤åÂÂ庠Connection 头
removeConnectionHeaders(res.Header)
// ä¸Â游åÂÂåºÂæ ¹æ®RFCè§ÂèÂÂ移é¤åÂÂ议头
for _, h := range hopHeaders {
res.Header.Del(h)
}
// å¦ÂæÂÂ设置 modifyResponse, Ã¥ÂÂä¿®æ¹åÂÂåºÂÃ¥ÂÂ
容
// è°Âç¨ ReverseProxy 对象 modifyResponse æ¹æ³Â
if !p.modifyResponse(rw, res, outreq) {
return
}
// æ·è´ÂÃ¥ÂÂåºÂHeaderå°ä¸Â游response对象
copyHeader(rw.Header(), res.Header)
// Ã¥ÂÂÃ¥ÂÂä¼ è¾Âé¨åÂÂÃ¥ÂÂè®® header 头设置, 已跳è¿Â
// Ã¥ÂÂÃ¥Â
¥åÂÂåºÂç Âå°ä¸Â游response对象
rw.WriteHeader(res.StatusCode)
// æ·è´Âç»ÂæÂÂå°ä¸Â游
// flushIntervalå°ÂÃ¥ÂÂåºÂå®Âæ¶å·æ°å°ç¼Âå²åº
err = p.copyResponse(rw, res.Body, p.flushInterval(res))
if err != nil {
defer res.Body.Close()
// ... è°Âç¨errorHandler
panic(http.ErrAbortHandler)
}
// Ã¥Â
³éÂÂÃ¥ÂÂåºÂbody
res.Body.Close()
// chunked Ã¥ÂÂÃ¥ÂÂä¼ è¾Âç¼Âç Âè°Âç¨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()
}
}
// 以ä¸Â为åÂÂÃ¥ÂÂä¼ è¾Âç¼Âç Âç¸åÂ
³header设置
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 对象转æÂÂä¸Â游代çÂÂ请æ±Â对象, 请æ±ÂÃ¥ÂÂè¿ÂÃ¥ÂÂÃ¥ÂÂåºÂ头åÂÂå 容, è¿Âè¡Âå¤ÂçÂÂ.
å 容补å Â
1. 为ä»Âä¹Â请æ±Âä¸Â游移é¤Connetion头
Connection éÂÂç¨æ Â头æ§å¶ç½Âç»Âè¿ÂæÂ¥å¨å½ÂÃ¥ÂÂä¼Âè¯Âå®ÂæÂÂÃ¥ÂÂæ¯å¦ä»Âç¶ä¿ÂæÂÂæÂÂå¼Âç¶æÂÂãÂÂå¦ÂæÂÂÃ¥ÂÂéÂÂçÂÂå¼æ¯ keep-aliveï¼ÂÃ¥ÂÂè¿ÂæÂ¥æ¯æÂÂä¹ çÂÂï¼Âä¸Âä¼Âå ³éÂÂï¼Âå Â许对åÂÂä¸ÂæÂÂå¡å¨è¿Âè¡ÂÃ¥ÂÂç»Â请æ±ÂãÂÂ
è¿Â个头设置解å³çÂÂæ¯客æ·端åÂÂæÂÂå¡端é¾æÂ¥æ¹å¼Â, èÂÂä¸ÂåºÂ该éÂÂä¼ ç»Â代çÂÂçÂÂä¸Â游æÂÂå¡.
æÂÂ以åÂÂRFCä¸ÂæÂÂ以ä¸ÂæÂÂç¡®è§Âå®Â:
âÂÂConnectionâÂÂ头åÂÂ段å Â许åÂÂéÂÂè æÂÂ示æÂÂéÂÂçÂÂè¿ÂæÂÂ¥ å½ÂÃ¥ÂÂè¿ÂæÂ¥çÂÂæ§å¶éÂÂ项ãÂÂ为äºÂé¿å Âæ··æ·Âä¸Â游æÂ¥æ¶è ï¼Â代çÂÂæÂÂç½Â堳忠须å é¤æÂÂå¨转åÂÂä¹ÂÃ¥ÂÂæ¿æ¢任ä½Âæ¶å°çÂÂè¿ÂæÂ¥éÂÂ项信æ¯ãÂÂ
RFC: datatracker.ietf.org/doc/html/rfâ¦
2. X-Forwarded-For ä½Âç¨
X-Forwarded-Forï¼ÂXFFï¼Â请æ±Âæ Â头æ¯ä¸Â个äºÂå®Âä¸ÂçÂÂç¨äºÂæ Âè¯ÂéÂÂè¿Â代çÂÂæÂÂå¡å¨è¿ÂæÂ¥å° web æÂÂå¡å¨çÂÂ客æ·端çÂÂÃ¥ÂÂ姠IP å°åÂÂçÂÂæ Â头(å¾Â容æÂÂ被篡æ¹)ãÂÂ
å½Â客æ·端ç´æÂ¥è¿ÂæÂ¥å°æÂÂå¡å¨æ¶ï¼Âå ¶ IP å°åÂÂ被åÂÂéÂÂç»ÂæÂÂå¡å¨ï¼Â并ä¸Âç»Â常被记å½Âå¨æÂÂå¡å¨çÂÂ访é®æÂ¥å¿Âä¸Âï¼ÂãÂÂä½Âæ¯å¦ÂæÂÂ客æ·端éÂÂè¿Âæ£åÂÂæÂÂÃ¥ÂÂÃ¥ÂÂ代çÂÂæÂÂå¡å¨è¿Âè¡Âè¿ÂæÂ¥ï¼ÂæÂÂå¡å¨就åªè½çÂÂå°æÂÂÃ¥ÂÂä¸Â个代çÂÂæÂÂå¡å¨ç IP å°åÂÂï¼Âè¿Â个 IP éÂÂ常没ä»Âä¹Âç¨ãÂÂå¦ÂæÂÂæÂÂÃ¥ÂÂä¸Â个代çÂÂæÂÂå¡å¨æ¯ä¸ÂæÂÂå¡å¨å®Â裠å¨åÂÂä¸Âå°主æºä¸ÂçÂÂè´Âè½½åÂÂè¡¡æÂÂå¡å¨ï¼ÂÃ¥ÂÂæ´æ¯å¦Âæ¤ãÂÂX-Forwarded-For çÂÂåºç°ï¼Âå°±æ¯为äºÂÃ¥ÂÂæÂÂå¡å¨æÂÂä¾Âæ´æÂÂç¨çÂÂ客æ·端 IP å°åÂÂãÂÂ
X-Forwarded-For: <client>, <proxy1>, <proxy2>
<client>
客æ·端ç IP å°åÂÂãÂÂ
<proxy1>, <proxy2>
å¦ÂæÂÂ请æ±Âç»Âè¿Âå¤Â个代çÂÂæÂÂå¡å¨ï¼Âæ¯Â个代çÂÂæÂÂå¡å¨ç IP å°åÂÂä¼Âä¾Â次åºç°å¨åÂÂ表ä¸ÂãÂÂ
è¿ÂæÂÂå³çÂÂï¼Âå¦ÂæÂÂ客æ·端åÂÂ代çÂÂæÂÂå¡å¨è¡Â为è¯好ï¼ÂæÂÂå³边ç IP å°åÂÂä¼Âæ¯æÂÂè¿ÂçÂÂ代çÂÂæÂÂå¡å¨ç IP å°åÂÂï¼Â
æÂÂ左边ç IP å°åÂÂä¼Âæ¯åÂÂå§Â客æ·端ç IP å°åÂÂãÂÂå¼Âç¨: developer.mozilla.org/zh-CN/docs/â¦
å®Âé åºÂç¨è½å°
å®Âé è½å°è¿Âç¨Âä¸Â, æÂÂ们ä¸Âä» è¦ÂèÂÂèÂÂ转åÂÂè½åÂÂ, è¿Âè¦ÂæÂÂç¸对åºÂçÂÂæÂ¥å¿ÂãÂÂ趠æ¶ãÂÂä¼Âé éÂÂ误å¤ÂçÂÂçÂÂè½åÂÂ,
ä¸Âé¢å°Â讲解æÂÂä¹ÂåºäºÂå®Âæ¹å 置ç ReverseProxy 对象çÂÂ代çÂÂè½åÂÂæÂ¥å®Âç°è¿ÂäºÂÃ¥ÂÂè½.
设计æÂÂè·¯: 对å¤Âå®Âç° Proxy ServerHttpçÂÂçÂÂæÂ¥å£, å¨å é¨å©ç¨ ReverseProxy 对象代çÂÂè½åÂÂåºç¡Âä¸Â设计.
1. å®Âä¹Âproxy ServeHTTP对象
type ServeHTTP struct {
// 代çÂÂé¾æÂ¥å°åÂÂ
targetUrl string
// net/http Ã¥ÂÂ
ç½®ç ReverseProxy 对象
reverseProxy *httputil.ReverseProxy
// 代çÂÂéÂÂ误å¤ÂçÂÂ
proxyErrorHandler ProxyErrorHandler
// æÂ¥å¿Â对象
logger log.Logger
}
ä¸Âé¢æÂÂ们å®Âä¾ÂÃ¥ÂÂ对象
// NewServeHTTP Ã¥ÂÂå§ÂÃ¥ÂÂ代çÂÂ对象
func NewServeHTTP(targetUrl string, logger log.Logger) *ServeHTTP {
target, err := url.Parse(targetUrl)
if err != nil {
panic(err)
}
// éÂÂæ°设置 Director å¤Âå¶请æ±Âå¤ÂçÂÂ
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,
}
// 设置trasportå¤ÂçÂÂ对象(主è¦Âè°ÂéÂ
Âé¾æ¥池大å°ÂÃ¥ÂÂè¶Â
æ¶æ¶é´)
serveHttp.reverseProxy.Transport = HttpTransportDefault()
// å®Âä¹ÂéÂÂ误å¤ÂçÂÂ
serveHttp.reverseProxy.ErrorHandler = serveHttp.getErrorHandler(logger)
// å®Âä¹ÂÃ¥ÂÂåºÂå¤ÂçÂÂ
serveHttp.reverseProxy.ModifyResponse = serveHttp.getResponseHandler(logger)
return serveHttp
}
// SetProxyErrorFunc 设置éÂÂ误å¤ÂçÂÂå½æ°
func (s *ServeHTTP) SetProxyErrorFunc(handler ProxyErrorHandler) *ServeHTTP {
s.proxyErrorHandler = handler
return s
}
2. æÂÂ们éÂÂÃ¥ÂÂ亠reverseProxy ç Directoræ¹æ³Â
-
æÂÂ们ä¸Âå¸ÂæÂÂ转å X-Forwarded-For å°代çÂÂå±Â, éÂÂè¿ÂæÂÂå¨èµÂå¼为nilæ¹å¼Â解å³
Ã¥ÂÂå æ¯ç½Âç»Âé²ç«å¢Â对æºÂIPè¿Âè¡ÂäºÂéªÂè¯Â, X-Forwarded-Foræ¯å¯éÂÂ项ä¹Âä¸Â, ä½ÂéÂÂ常 X-Forwarded-For ä¸Âå®Âå ¨ä¸Â容æÂÂé æÂÂæΡèÂÂéÂÂæ§é®é¢Â, ä¸Â建议éÂÂè¿Âæ¤åÂÂæ°è¿Âè¡ÂéªÂè¯Â, æ å°Âæ¤移é¤. -
移é¤æÂÂå®Âç removeRequestHeaders 头
常è§ÂçÂÂé´æÂÂ类头çÂÂ
3. è¦ÂçÂÂå®Âæ¹é»Â认ç HttpTransportDefault
å¨ http.Transport 对象ä¸Â, MaxIdleConnsPerHostãÂÂMaxIdleConns Ã¥ÂÂæ°å¨ http1.1 ä¸ÂéÂÂ常影åÂÂæ§è½, é»Â认 Ã¥ÂÂhost 建ç«ÂçÂÂé¾æ¥池å è¿ÂæÂ¥æ°åªæÂÂ2个, ä¸Âé¢æÂÂ们ç»Âä¸Âä¿®æ¹为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 对象转åÂÂé»è¾Âæ¶ï¼ÂéÂÂè¦Âæ¦æª请æ±Âè¿Âè¡ÂÃ¥ÂÂç½®åÂÂæ°å¤ÂçÂÂ, ä¸Âè½ç´æ¥使ç¨ reverseProxy 对象, æÂÂ以就ç±èªå®Âä¹ proxy å®Âç° handler æÂ¥å£ç ServeHTTP æ¹æ³Â, 对 reverseProxy é¾æÂ¥å¤ÂçÂÂè¿Âè¡Âä¸Âå±Âå 裠.
é»è¾Âå¦Âä¸Â:
// ServeHTTP æÂÂå¡转åÂÂ
func (s *ServeHTTP) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
var (
reqBody []byte
err error
// çÂÂæÂÂtraceId
traceId = s.getTraceId(request)
)
// Ã¥ÂÂç½®è·åÂÂ请æ±Â头, æ¾åÂ
Â¥contextä¸Â
// è°Âç¨ç»ÂæÂÂÃ¥ÂÂ请池body å°Âä¼Â被åÂ
³éÂÂ, Ã¥ÂÂé¢å°Âæ æ³ÂÃ¥ÂÂè·åÂÂ
if request.Body != nil {
reqBody, err = io.ReadAll(request.Body)
if err == nil {
request.Body = io.NopCloser(bytes.NewBuffer(reqBody))
}
}
// header 设置 traceIdÃ¥ÂÂè¶Â
æ¶æ¶é´传éÂÂ
request.Header.Set(utils.TraceKey, traceId)
request.Header.Set(utils.Timeoutkey, cast.ToString(s.getTimeout(request)))
// 计ç®Âè·åÂÂè¶Â
æ¶æ¶é´, Ã¥ÂÂ起转åÂÂ请æ±Â
ctx, cancel := context.WithTimeout(
request.Context(),
time.Duration(s.getTimeout(request))*time.Millisecond,
)
defer cancel()
// 设置请æ±Âä½Â
ctx = context.WithValue(ctx, ctxReqBody, string(reqBody))
// 设置请æ±Âæ¶é´, ç¨äºÂÃ¥ÂÂåºÂç»ÂæÂÂÃ¥ÂÂ计ç®Â请æ±ÂèÂÂæ¶
ctx = context.WithValue(ctx, ctxReqTime, time.Now())
// context 设置traceId, ç¨äºÂé¾路æÂ¥å¿ÂæÂÂå°
ctx = context.WithValue(ctx, utils.TraceKey, traceId)
request = request.WithContext(ctx)
// è°Âç¨ reverseProxy ServeHTTP, å¤ÂçÂÂ转åÂÂé»è¾Â
s.reverseProxy.ServeHTTP(writer, request)
}
以ä¸Â代ç ÂÃ¥ÂÂæÂÂ详ç»Â注éÂÂ, ä¸Âé¢æÂÂ们çÂÂ丠traceIdÃ¥ÂÂ请æ±ÂèÂÂæ¶å½æ°é»è¾Â, æ¯Âè¾Âç®ÂÃ¥ÂÂ.
// 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 è·åÂÂè¶Â
æ¶æ¶é´
// headerä¸Âä¸ÂÃ¥ÂÂå¨timeoutKey, è¿ÂÃ¥ÂÂé»Â认è¶Â
æ¶æ¶é´
// header头åÂÂå¨, Ã¥ÂÂå¤æÂÂæ¯å¦大äºÂé»Â认è¶Â
æ¶æ¶é´, 大äºÂÃ¥ÂÂ使ç¨é»Â认è¶Â
æ¶æ¶é´
// å¦åÂÂè¿ÂÃ¥ÂÂheader设置çÂÂè¶Â
æ¶æ¶é´
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. å®Âä¹ÂÃ¥ÂÂåºÂé¨åÂÂÃ¥ÂÂéÂÂ误å¤ÂçÂÂé¨åÂÂ
ä»Âä¸Âå¼Âå§ÂæÂÂ们就äºÂ解 ReverseProxy Ã¥ÂÂè½, å¯以设置 ModifyResponseãÂÂErrorHandler, ä¸Âé¢æÂÂ们çÂÂä¸Âå ·ä½Âæ¯æÂÂä¹Âå®Âç°çÂÂ.
ErrorHandler
// getErrorHandler è®°å½ÂéÂÂ误记å½Â
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))
}
}
// Ã¥ÂÂå§ÂÃ¥ÂÂæ¶确认proxyErrorHandlerÃ¥Â
·ä½Âå¤ÂçÂÂæ¹æ³Â
// è°Âç¨ proxyErrorHandler,å¤ÂçÂÂÃ¥ÂÂåºÂé¨åÂÂ
s.proxyErrorHandler(writer, e)
// è·åÂÂå¿Â
è¦Âä¿¡æ¯, è®°å½ÂéÂÂ误æÂ¥å¿Â
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),
)
}
}
// Ã¥Â
·ä½Â代çÂÂä¸Âå¡éÂÂ误å¤ÂçÂÂ
// Ã¥ÂÂ
å«é»Â认éÂÂ误åÂÂåºÂÃ¥ÂÂÃ¥Â
·ä½Â代çÂÂä¸Âå¡éÂÂ误åÂÂåºÂ.
// 以ä¸Â为æÂÂ个ä¸Âå¡åÂÂåºÂ
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")
// 设置ç¶æÂÂç Â为200
writer.WriteHeader(http.StatusOK)
// å°ÂÃ¥ÂÂåºÂå¼åºÂÃ¥ÂÂÃ¥ÂÂ
respByte, _ := json.Marshal(resp)
// å°Âresponseæ°æ®åÂÂÃ¥Â
Â¥writer, å·æ°å°Flush
// Ã¥Â
³äºÂFlushé¨åÂÂ, ä¸Â辯ä¸ÂéÂÂè¦Â主å¨å·æ°çÂÂ, 请æ±Âç»ÂæÂÂÃ¥ÂÂä¼Âèªå¨Flush
_, _ = fmt.Fprintf(writer, string(respByte))
if f, ok := writer.(http.Flusher); ok {
f.Flush()
}
}
以ä¸ÂæÂÂä¸Â个å¼çÂÂ堳注çÂÂå°æ¹, 设置åÂÂåºÂ头ä¸Âå®Âè¦Âå¨设置åÂÂåºÂç Âä¹ÂÃ¥ÂÂ, å¦åÂÂå°Âæ æÂÂ
设置åÂÂåºÂå 容ä¸Âå®Âå¨æÂÂÃ¥ÂÂ, å¦åÂÂå°Â设置失败并è¿ÂÃ¥ÂÂéÂÂ误.
ModifyResponse å¤ÂçÂÂé»è¾Â
// getResponseHandler è·åÂÂÃ¥ÂÂåºÂæ°æ®
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)
// è·åÂÂå¼Âå§Â请æ±Âæ¶é´, 计ç®Â请æ±ÂèÂÂæ¶
startTime := response.Request.Context().Value(ctxReqBody)
if startTime != nil {
_, ok := startTime.(time.Time)
if ok {
duration = time.Since(startTime.(time.Time)).Seconds()
}
}
// è·åÂÂÃ¥ÂÂåºÂæ°æ®
// å¦ÂæÂÂÃ¥ÂÂåºÂç ÂéÂÂ200, è°Âæ´æÂ¥å¿ÂçÂÂ级
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),
)
// 设置åÂÂåºÂ头
response.Header.Set("Content-Type", "application/json; charset=utf-8")
return nil
}
}
é»Â认代çÂÂæÂÂå¡å¨æ¯ä¸Â设置åÂÂåºÂ头çÂÂ, Ã¥ÂÂ为é»Â认çÂÂÃ¥ÂÂåºÂ头ãÂÂ
Ã¥ÂÂåºÂ头忠须æÂÂå¨设置
6. 使ç¨èªå®Âä¹Âç proxy 代çÂÂ请æ±Â
urlStr := "https://" + targetHost
proxy := utilsProxy.NewServeHTTP(urlStr, logger).SetProxyErrorFunc(utilsProxy.XXXProxyErrorHandler)
log.Fatal(http.ListenAndServe(":8082", proxy))
Ã¥ÂÂèÂÂé¾æÂÂ¥
ãÂÂgolangç®ÂÃ¥ÂÂèÂÂ强大çÂÂÃ¥ÂÂÃ¥ÂÂ代çÂÂã h1z3y3.me/posts/simplâ¦
ãÂÂGolang ReverseProxy å¦Âä½Âå®Âç°åÂÂÃ¥ÂÂ代çÂÂï¼ÂãÂÂjuejin.cn/post/697330â¦
ãÂÂgolangÃ¥ÂÂÃ¥ÂÂ代çÂÂæºÂç Â解æÂÂãÂÂwww.cnblogs.com/FengZeng666â¦
ãÂÂgolang x-forwared-for issuesãÂÂgithub.com/golang/go/iâ¦
ãÂÂgolang User-Agent issuesãÂÂgithub.com/golang/go/iâ¦
ãÂÂgolang req body issusesãÂÂgithub.com/golang/go/iâ¦
ãÂÂgolang header头设置çÂÂÃ¥ÂÂãÂÂblog.alovn.cn/2020/01/20/â¦
ãÂÂhttpÃ¥ÂÂè®®ãÂÂdeveloper.mozilla.org/zh-CN/docs/â¦
ãÂÂrfc ConnectionÃ¥ÂÂ议头è§ÂèÂÂãÂÂdatatracker.ietf.org/doc/html/rfâ¦
ãÂÂrfc Ã¥ÂÂè®®ç½Âç»ÂÃ¥ÂÂ符è§ÂèÂÂãÂÂdatatracker.ietf.org/doc/html/rfâ¦