ReverseProxy 反向代理
go 内置反向代理 ReverseProxy , 使用 ReverseProxy 实现反向代理服务器!反向代理服务优点:
-
提高网络速度:因为数据会存在代理服务中,然后对其进行转发。具有一定的存储功能!也可以根据相关负载均衡对策进行资源的利用
-
其到防火墙作用:可以过滤请求,过滤某些不安全信息。防止重要的服务器受到攻击出现问题!
-
通过代理服务器访问不能访问的目标站点
互联网上有许多开发的代理服务器,客户机可访问受限时,可通过不受限的代理服务器 访问目标站点,通俗说,我们使用的翻墙浏览器就是利用了代理服务器,可直接访问外网。
ReverseProxy 功能点
- 更改内容支持
- 错误信息回调
- 支持自定义负载均衡
- url重写
- 支持协议升级 (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 源码进行分析
- 对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)
以上就反向代理最简单基本执行流程!里面很多模块都没有对其说明!由于内容过多,下期文章在对其进行全方面了吧!
网友评论