美文网首页
ReverseProxy 反向代理

ReverseProxy 反向代理

作者: COMS | 来源:发表于2020-10-11 23:02 被阅读0次

ReverseProxy 反向代理

go 内置反向代理 ReverseProxy , 使用 ReverseProxy 实现反向代理服务器!反向代理服务优点:

  1. 提高网络速度:因为数据会存在代理服务中,然后对其进行转发。具有一定的存储功能!也可以根据相关负载均衡对策进行资源的利用

  2. 其到防火墙作用:可以过滤请求,过滤某些不安全信息。防止重要的服务器受到攻击出现问题!

  3. 通过代理服务器访问不能访问的目标站点
    互联网上有许多开发的代理服务器,客户机可访问受限时,可通过不受限的代理服务器 访问目标站点,通俗说,我们使用的翻墙浏览器就是利用了代理服务器,可直接访问外网。

ReverseProxy 功能点

  1. 更改内容支持
  2. 错误信息回调
  3. 支持自定义负载均衡
  4. url重写
  5. 支持协议升级 (websocket , https)

简单代码实例

实现简单的代码实例!

// reverse_proxy.go
package main

import (
    "bytes"
    "cs/rep/algorithm"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/httputil"
    "net/url"
    "strings"
)

// 需要直接写一个 tcp server , 具体看 net.Listen 函数的官方实例
var addr = "http://127.0.0.1:2003"

func NewSingleHostReverseProxy(lb algorithm.LoadBalance) *httputil.ReverseProxy {
        
    // 对其进行url重写
    director := func(r *http.Request) {
        // 对addr 进行解析
        rurl ,err := url.Parse(addr)
        if err != nil {
            return
        }
        tr := rurl.RawQuery
        r.URL.Host = rurl.Host
        r.URL.Scheme= rurl.Scheme

        r.URL.Path = singleJoiningSlash(rurl.Path,r.URL.Path)
        // r.url = 127.0.0.1:8081/?id=123
        // tr = 127.0.0.1:8082/?coms=23
        if tr == "" || r.URL.RawQuery != "" {
            r.URL.RawQuery = tr + r.URL.RawQuery
        } else {
            r.URL.RawQuery = tr + "&" + r.URL.RawQuery
        }

        if _, ok := r.Header["User-Agent"]; !ok {
            r.Header.Set("User-Agent","")
        }
    }

       // 修改response 相关信息
    modifyresponse := func(response *http.Response) error {
        if response.StatusCode != 200 {
            b , err := ioutil.ReadAll(response.Body)
            if err != nil {
                return err
            }

            by := []byte("coms is good" + string(b))
            response.Body = ioutil.NopCloser(bytes.NewBuffer(by))
            response.ContentLength = int64(len(by))
            response.Header.Set("Content-Length",fmt.Sprintf("%v",response.ContentLength))
        }
        return nil
    }

       // 错误捕捉器
    errhandler := func(w http.ResponseWriter, re *http.Request, err error) {
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte(err.Error()))
    }
    return &httputil.ReverseProxy{Director:director,ModifyResponse:modifyresponse,ErrorHandler:errhandler}
}

// url 匹配模块
func singleJoiningSlash(dist,src string) string {
    // 获取 dist 的后缀 , 比如 : 【127.0.0.1/】就是匹配 “/” 是否存在
    s := strings.HasSuffix(dist,"/")
   // 获取 src 的前缀 ,比如:【/coms】匹配 “/” 是否存在
    p := strings.HasPrefix(src,"/")

    switch {
    case s && p:
        return dist + src[1:]
    case !s && !p:
        return dist + "/" + src
    }

    return dist + src
}
// main.go
func main() {
    http.Handle("/hijack",NewSingleHostReverseProxy())
    http.ListenAndServe(":8085",nil)
}

以上是对 ReverseProxy 的使用,今天目的对 ReverseProxy 源码进行分析

  1. 对ReverseProxy struct 进行粗略分析
type ReverseProxy struct {
    // 修改控制,就是对协议进行修改重写,或者对 url 进行重写转发等...
    Director func(*http.Request)

      // 获取 roundtripper 进行下游请求
    Transport http.RoundTripper

    // 错误日志处理
    ErrorLog *log.Logger

    // BufferPool optionally specifies a buffer pool to
    // get byte slices for use by io.CopyBuffer when
    // copying HTTP response bodies.

    // bufferpool 是buffer 缓存池
    BufferPool BufferPool

    // ModifyResponse is an optional function that modifies the
    // Response from the backend. It is called if the backend
    // returns a response at all, with any HTTP status code.
    // If the backend is unreachable, the optional ErrorHandler is
    // called without any call to ModifyResponse.
    //
    // If ModifyResponse returns an error, ErrorHandler is called
    // with its error value. If ErrorHandler is nil, its default
    // implementation is used.

    // 修改相应头,执行拦截操作,比如 nginx 403 拦截,就可以在这里设置
    ModifyResponse func(*http.Response) error

    // ErrorHandler is an optional function that handles errors
    // reaching the backend or errors from ModifyResponse.
    //
    // If nil, the default is to log the provided error and return
    // a 502 Status Bad Gateway response.

    // 错误捕捉,当ModifyResponse return err 的时候,对这个错误进行捕捉,实现 server 500 的相关操作
    ErrorHandler func(http.ResponseWriter, *http.Request, error)
}

对 ReverseProxy 分析完成后,根据 Handle 挂载到 http.ListenAndServe

func main() {
    http.Handle("/hijack",NewSingleHostReverseProxy())
    http.ListenAndServe(":8085",nil)
}

根据 ListenAndServe 映射执行 ServeHTTP

func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    transport := p.Transport
    if transport == nil {
        transport = http.DefaultTransport
    }

    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():
            }
        }()
    }

    outreq := req.Clone(ctx)
    if req.ContentLength == 0 {
        outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
    }
    if outreq.Header == nil {
        outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
    }

    p.Director(outreq)
    outreq.Close = false

    reqUpType := upgradeType(outreq.Header)
    removeConnectionHeaders(outreq.Header)

    // Remove hop-by-hop headers to the backend. Especially
    // important is "Connection" because we want a persistent
    // connection, regardless of what the client sent to us.
    for _, h := range hopHeaders {
        hv := outreq.Header.Get(h)
        if hv == "" {
            continue
        }
        if h == "Te" && hv == "trailers" {
            // Issue 21096: tell backend applications that
            // care about trailer support that we support
            // trailers. (We do, but we don't go out of
            // our way to advertise that unless the
            // incoming client request thought it was
            // worth mentioning)
            continue
        }
        outreq.Header.Del(h)
    }

    // After stripping all the hop-by-hop connection headers above, add back any
    // necessary for protocol upgrades, such as for websockets.
    if reqUpType != "" {
        outreq.Header.Set("Connection", "Upgrade")
        outreq.Header.Set("Upgrade", reqUpType)
    }

    if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
        // If we aren't the first proxy retain prior
        // X-Forwarded-For information as a comma+space
        // separated list and fold multiple headers into one.
        prior, ok := outreq.Header["X-Forwarded-For"]
        omit := ok && prior == nil // Issue 38079: nil now means don't populate the header
        if len(prior) > 0 {
            clientIP = strings.Join(prior, ", ") + ", " + clientIP
        }
        if !omit {
            outreq.Header.Set("X-Forwarded-For", clientIP)
        }
    }

    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
    }

    removeConnectionHeaders(res.Header)

    for _, h := range hopHeaders {
        res.Header.Del(h)
    }

    if !p.modifyResponse(rw, res, outreq) {
        return
    }

    copyHeader(rw.Header(), res.Header)

    // The "Trailer" header isn't included in the Transport's response,
    // at least for *http.Transport. Build it up from Trailer.
    announcedTrailers := len(res.Trailer)
    if announcedTrailers > 0 {
        trailerKeys := make([]string, 0, len(res.Trailer))
        for k := range res.Trailer {
            trailerKeys = append(trailerKeys, k)
        }
        rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
    }

    rw.WriteHeader(res.StatusCode)

    err = p.copyResponse(rw, res.Body, p.flushInterval(req, res))
    if err != nil {
        defer res.Body.Close()
        // Since we're streaming the response, if we run into an error all we can do
        // is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
        // on read error while copying body.
        if !shouldPanicOnCopyError(req) {
            p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
            return
        }
        panic(http.ErrAbortHandler)
    }
    res.Body.Close() // close now, instead of defer, to populate res.Trailer

    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()
        }
    }

    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)
        }
    }
}

以上是 ReverseProxy 源代码,在此我不对其进行全部讲解,如果有时间有能力可以全部专研一下。之后我陆续全部讲解

获取协议类型
//  获取升级协议头,当需要使用 websocket的时候,我们需要 Connection ,当 Connection 是 Upgrade , 说明是对其协议进行升级
reqUpType := upgradeType(outreq.Header)
func upgradeType(h http.Header) string {
        // 判断 Connection == Upgrade ,如果是返回所有 Upgrade
    if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
        return ""
    }
       // Upgrade 升级协议的模式,当使用 websocket 对其协议升级时,
       // h.Get("Upgrade") = "websocket"
    return strings.ToLower(h.Get("Upgrade"))
}
对协议进行升级
    // After stripping all the hop-by-hop connection headers above, add back any
    // necessary for protocol upgrades, such as for websockets.

    // 对 response.header 进行写入,回复浏览器协议升级
    if reqUpType != "" {
        outreq.Header.Set("Connection", "Upgrade")
        outreq.Header.Set("Upgrade", reqUpType)
    }
对用来表示 HTTP 请求端任务截取
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
        // If we aren't the first proxy retain prior
        // X-Forwarded-For information as a comma+space
        // separated list and fold multiple headers into one.

       // 获取 "X-Forwarder-For" 对其进行写入,目的记录client端的ip地址
       // nginx 的白名单是是根据 X-Forwarder-For 记录的ip地址进行匹配的
      // 但是 "X-Forwarder-For" 是可以进行伪造,就可以对其进行绕过!
  
        prior, ok := outreq.Header["X-Forwarded-For"]
        omit := ok && prior == nil // Issue 38079: nil now means don't populate the header
        if len(prior) > 0 {
         // "X-Forwarder-For" 的格式是 "X-Forwarder-For" : client,proxy1,proxy2,proxyn....
            clientIP = strings.Join(prior, ", ") + ", " + clientIP
        }
        if !omit {
            outreq.Header.Set("X-Forwarded-For", clientIP)
        }
    }

对其向下游请求,就是把请求转发给指定一个服务器

// 向下游服务进行请求
res, err := transport.RoundTrip(outreq)
    if err != nil {
        p.getErrorHandler()(rw, outreq, err)
        return
    }

    // 对其实协议进行修改,判断modifyResponse函数是否注册,有就执行 modifyResponse

    // 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
    }

最后写入状态码

rw.WriteHeader(res.StatusCode)

以上就反向代理最简单基本执行流程!里面很多模块都没有对其说明!由于内容过多,下期文章在对其进行全方面了吧!

相关文章

网友评论

      本文标题:ReverseProxy 反向代理

      本文链接:https://www.haomeiwen.com/subject/uwlqpktx.html