• golang网关之手动实现反向代理


    简单说说反向代理

     

     信号监听方式启动两个web服务,分别是9091 9092 分别返回 web1 web2

    webmain.go

    type web1handler struct {}
    func(web1handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
        writer.Write([]byte("web1"))
    }
    type web2handler struct {}
    func(web2handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
        writer.Write([]byte("web2"))
    }
    
    func main()  {
        c:=make(chan os.Signal)
        go(func() {
            http.ListenAndServe(":9091",web1handler{})
        })()
    
        go(func() {
            http.ListenAndServe(":9092",web2handler{})
        })()
        signal.Notify(c,os.Interrupt)
        s:=<-c
        log.Println(s)
    }

    Httpclient 初步使用(转发)

    myproxy.go

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "log"
        "net/http"
    )
    
    type ProxyHandler struct {}
    func(* ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    
        //fmt.Println(r.RequestURI)
        //    /a?b=123
        defer func() {
            if err := recover();err != nil{
                w.WriteHeader(500)
                log.Println(err)
            }
        }()
        fmt.Println(r.URL) //    /a?b=123
        fmt.Println(r.URL.Path)  //   /a
        if r.URL.Path == "/a"{
            newreq,_ := http.NewRequest(r.Method,"http://localhost:9091",r.Body)
            newresponse,_ := http.DefaultClient.Do(newreq)
            res_cont,_ := ioutil.ReadAll(newresponse.Body)
            w.Write(res_cont)
            return
        }
        w.Write([]byte("default index"))
    }
    
    
    func main()  {
        http.ListenAndServe(":8080",&ProxyHandler{})
    
    }

     

      

     

    在httpserver中实现Basic Auth的认证和解析

    GO实现

    1、在头 里设置WWW-Authenticate

    2、返回401 writer.Header().Set("WWW-Authenticate", `Basic realm="您必须输入用户名和密码"`)

    writer.WriteHeader(http.StatusUnauthorized) return

    客户端应答

    1、假设用户名和密码是 sunlong和123

    2、那么把两者拼接成 sunlong:123

    3、然后base64编码 变成 c2hlbnlpOjEyMw==

    4、发送请求时 添加头 Authorization: Basic c2hlbnlpOjEyMw==

    服务器判断

    auth:=request.Header.Get("Authorization")
        if auth==""{
         writer.Header().Set("WWW-Authenticate", `Basic realm="您必须输入用户名和密码"`)
         writer.WriteHeader(http.StatusUnauthorized)
         return
      }

    让”反向代理”支持Basic Auth验证框弹出

    拷贝头

    import "net/http"
    
    func CloneHeader(dest *http.Header,src http.Header)   {
        for k, vv := range src {
            dest.Set(k,vv[0])
        }
    }
    
    
    然后主函数里加入
    h:=w.Header()CloneHeader(&h,newresponse.Header)

    会发现还是没用。。。。。

    看下响应头

    w.WriteHeader(newresponse.StatusCode)

    一句话搞定,有木有~~~

    完整代码

    package main
    
    import (
        "encoding/base64"
        "fmt"
        "log"
        "net/http"
        "os"
        "os/signal"
        "strings"
    )
    
    type web1handler struct {
    
    }
    
    func(web1handler) ServeHTTP(writer http.ResponseWriter,request *http.Request){
        auth := request.Header.Get("Authorization")
        fmt.Println(auth)
        if auth == ""{
            writer.Header().Set("WWW-Authenticate",`Basic realm="您必须输入用户名和密码"`)
            writer.WriteHeader(http.StatusUnauthorized)
            return
        }
        //fmt.Println(auth)
        // Basic c3VubG9uZzoxMjM=
        auth_list :=  strings.Split(auth," ")
        if len(auth_list) == 2  && auth_list[0] == "Basic"{
            res,err := base64.StdEncoding.DecodeString(auth_list[1])
            if err == nil && string(res) == "sunlong:123456" {
                writer.Write([]byte("<h1>web1</h1>"))
                return
            }
        }
        writer.Write([]byte("用户名密码错误"))
    }
    
    type web2handler struct {}
    
    func(web2handler) ServeHTTP(writer http.ResponseWriter, request *http.Request)  {
        writer.Write([]byte("<h1>web2</h1>"))
    }
    
    
    func main(){
        c :=  make(chan os.Signal)
        go(func() {
            http.ListenAndServe(":9091",web1handler{})
        })()
    
        go(func() {
            http.ListenAndServe(":9092",web2handler{})
        })()
    
        signal.Notify(c,os.Interrupt)
        s := <- c
        log.Println(s)
    }
    webmian.go
    package main
    
    import (
        "fmt"
        "io/ioutil"
        "log"
        "net/http"
        "go-networks/util"
    )
    
    type ProxyHandler struct {}
    func(* ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    
        //fmt.Println(r.RequestURI)
        //    /a?b=123
        defer func() {
            if err := recover();err != nil{
                w.WriteHeader(500)
                log.Println(err)
            }
        }()
        fmt.Println(r.URL) //    /a?b=123
        fmt.Println(r.URL.Path)  //   /a
        if r.URL.Path == "/a"{
            newreq,_ := http.NewRequest(r.Method,"http://localhost:9091",r.Body)
            util.CloneHeader(r.Header,&newreq.Header)
            newresponse,_ := http.DefaultClient.Do(newreq)
            getHeader := w.Header()
    
            util.CloneHeader(newresponse.Header,&getHeader)
            defer newresponse.Body.Close()
            w.WriteHeader(newresponse.StatusCode)
    
            res_cont,_ := ioutil.ReadAll(newresponse.Body)
            w.Write(res_cont)
            return
        }
        w.Write([]byte("default index"))
    }
    
    
    func main()  {
        http.ListenAndServe(":8080",&ProxyHandler{})
    
    }
    myproxy.go
    package util
    
    import "net/http"
    
    func CloneHeader(src http.Header,dest *http.Header)  {
       for k,v:=range src{
           dest.Set(k,v[0])
       }
    
    }
    unil/functions.go

     怎么获取真实IP?

    writer.Write([]byte(fmt.Sprintf("<h1>web1,来自于:%s</h1>", request.RemoteAddr))) (可以用net.SplitHostPort()进行分割)

     很不幸的是 得到是 代理服务器IP

    X-Forwarded-For

    X-Forwarded-For 是一个 HTTP 扩展 HTTP/1.1(RFC 2616)协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP。如今它已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension)标准之中。

    写个获取IP方法
    
    func(web1handler) GetIP(request *http.Request) string{
        ips:=request.Header.Get("x-forwarded-for")
        if ips!=""{     
        ips_list:= strings.Split(ips, ",")  
            if len(ips_list)>0 && ips_list[0]!=""{   
                return ips_list[0]
            }
        }
        return request.RemoteAddr
    }
    
    以上代码来自Beego改编

    设计ini配置文件格式、配置”反向代理”路径映射

    package util
    
    import (
        "github.com/go-ini/ini"
        "log"
        "os"
    )
    
    var ProxyConfigs map[string]string
    type EnvConfig *os.File
    
    func init(){
        ProxyConfigs = make(map[string]string)
        EnvConfig,err:=  ini.Load("env")
        if err!=nil{
            log.Println(err)
            return
        }
        proxy,_:=EnvConfig.GetSection("proxy") //假设是固定的 分区
        if proxy!=nil{
            secs:=proxy.ChildSections() //获取子分区
            for _,sec := range secs{
                path,_ := sec.GetKey("path")
                pass,_:= sec.GetKey("pass")//固定Key
                if path!=nil && pass !=nil{
                    ProxyConfigs[path.Value()]=pass.Value()
                }
            }
        }
    }
    configs.go
    package util
    
    import (
        "io/ioutil"
        "net/http"
    )
    
    func CloneHeader(src http.Header,dest *http.Header)  {
       for k,v:=range src{
           dest.Set(k,v[0])
       }
    
    }
    
    
    func RequestUrl(w http.ResponseWriter, r *http.Request,url string)  {
        newreq,_:=http.NewRequest(r.Method,url,r.Body)
        CloneHeader(r.Header,&newreq.Header)
        newreq.Header.Add("x-forwarded-for",r.RemoteAddr)
    
        newresponse,_:=http.DefaultClient.Do(newreq)
        getHeader:=w.Header()
        CloneHeader(newresponse.Header,&getHeader) //拷贝响应头 给客户端
    
        w.WriteHeader(newresponse.StatusCode) // 写入http status
    
        defer newresponse.Body.Close()
        res_cont,_:=ioutil.ReadAll(newresponse.Body)
        w.Write(res_cont)  // 写入响应给客户端
    }
    functions.go
    [proxy]
    
    [proxy.a]
    path=/web1
    pass=http://127.0.0.1:9091
    
    [proxy.b]
    path=/web2
    pass=http://127.0.0.1:9092
    env
    package main
    
    import (
        "fmt"
        "io/ioutil"
        "log"
        "net/http"
        . "go-networks/util"
        "regexp"
    )
    
    type ProxyHandler struct {}
    func(* ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    
        //fmt.Println(r.RequestURI)
        //    /a?b=123
        defer func() {
            if err := recover();err != nil{
                w.WriteHeader(500)
                log.Println(err)
            }
        }()
        fmt.Println(r.URL) //    /a?b=123
        fmt.Println(r.URL.Path)  //   /a
    
        for k,v := range ProxyConfigs{
            fmt.Println(k)
            fmt.Println(v)
            if matched,_:= regexp.MatchString(k,r.URL.Path);matched == true{
                RequestUrl(w,r,v)
                return
            }
        }
    
        if r.URL.Path == "/a"{
            newreq,_ := http.NewRequest(r.Method,"http://localhost:9091",r.Body)
            CloneHeader(r.Header,&newreq.Header)
            newreq.Header.Set("x-forwarded-for",r.RemoteAddr)
    
            newresponse,_ := http.DefaultClient.Do(newreq)
            getHeader := w.Header()
            CloneHeader(newresponse.Header,&getHeader)//拷贝响应头给客户端
    
    
            defer newresponse.Body.Close()
            w.WriteHeader(newresponse.StatusCode)// 写入http status
            res_cont,_ := ioutil.ReadAll(newresponse.Body)
            w.Write(res_cont)
            return
        }
        w.Write([]byte("default index"))
    }
    
    
    func main()  {
        http.ListenAndServe(":8080",&ProxyHandler{})
    
    }
    myproxy.go
    package main
    
    import (
        "encoding/base64"
        "fmt"
        "log"
        "net/http"
        "os"
        "os/signal"
        "strings"
    )
    
    type web1handler struct {
    
    }
    
    func (web1handler) GetIp(request *http.Request) string{
        ips := request.Header.Get("x-forwarded-for")
        if ips != ""{
            ips_list := strings.Split(ips,",")
            if len(ips_list) > 0 && ips_list[0] != ""{
                return ips_list[0]
            }
        }
        return request.RemoteAddr
    }
    
    func(this web1handler) ServeHTTP(writer http.ResponseWriter,request *http.Request){
        auth := request.Header.Get("Authorization")
        fmt.Println(auth)
        if auth == ""{
            writer.Header().Set("WWW-Authenticate",`Basic realm="您必须输入用户名和密码"`)
            writer.WriteHeader(http.StatusUnauthorized)
            return
        }
        //fmt.Println(auth)
        // Basic c3VubG9uZzoxMjM=
        auth_list :=  strings.Split(auth," ")
        if len(auth_list) == 2  && auth_list[0] == "Basic"{
            res,err := base64.StdEncoding.DecodeString(auth_list[1])
            if err == nil && string(res) == "sunlong:123456" {
                writer.Write([]byte(fmt.Sprintf("<h1>web1,来自:%s</h1>",this.GetIp(request))))
                return
            }
        }
        writer.Write([]byte("用户名密码错误"))
    }
    
    type web2handler struct {}
    
    func(web2handler) ServeHTTP(writer http.ResponseWriter, request *http.Request)  {
        writer.Write([]byte("<h1>web2</h1>"))
    }
    
    
    func main(){
        c :=  make(chan os.Signal)
        go(func() {
            http.ListenAndServe(":9091",web1handler{})
        })()
    
        go(func() {
            http.ListenAndServe(":9092",web2handler{})
        })()
    
        signal.Notify(c,os.Interrupt)
        s := <- c
        log.Println(s)
    }
    webmain.go

    使用Transport来进行反代请求、go内置的反向代理函数

    实现的方式是用了

    Transport

    最终真正产生响应结果的是 transport的 RoundTrip方法(大约在client.go ,252行),而httpclient 已经帮我们把譬如cookie、重定向、timeout等http请求的整个过程(事务)机制都包含了且有默认值

    DefaultTransport

    http.DefaultTransport.RoundTrip(newreq)

    利用Transport实现反向代理

    func RequestUrl(w http.ResponseWriter, r *http.Request,url string)  {
        newreq,_:=http.NewRequest(r.Method,url,r.Body)
        CloneHeader(r.Header,&newreq.Header)
        newreq.Header.Add("x-forwarded-for",r.RemoteAddr)
    
        dt := &http.Transport{
            DialContext: (&net.Dialer{
                Timeout:   30 * time.Second,
                KeepAlive: 30 * time.Second,
                DualStack: true,
            }).DialContext,
            MaxIdleConns:          100,
            IdleConnTimeout:       90 * time.Second,
            TLSHandshakeTimeout:   10 * time.Second,
            ExpectContinueTimeout: 1 * time.Second,
            ResponseHeaderTimeout: 3*time.Second,
        }
        newresponse,err := dt.RoundTrip(newreq)
        if err != nil{
            log.Println(err)
            return
        }
    
        //newresponse,_:=http.DefaultClient.Do(newreq)
        getHeader:=w.Header()
        CloneHeader(newresponse.Header,&getHeader) //拷贝响应头 给客户端
    
        w.WriteHeader(newresponse.StatusCode) // 写入http status
    
        defer newresponse.Body.Close()
        res_cont,_:=ioutil.ReadAll(newresponse.Body)
        w.Write(res_cont)  // 写入响应给客户端
    }

    最后介绍go内置的反代函数

    上面写了那么多代码,只是为了了解反向代理内部实现机制,其实go也有内置的反向代理函数,三句话搞定

    target,_:=url.Parse(v)
     proxy:=httputil.NewSingleHostReverseProxy(target)
     proxy.ServeHTTP(w,r)

    下面是官方内置反向代理源码,有兴趣可以继续分许

    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.WithContext(ctx) // includes shallow copies of maps, but okay
        if req.ContentLength == 0 {
            outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
        }
    
        outreq.Header = cloneHeader(req.Header)
    
        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.
            if prior, ok := outreq.Header["X-Forwarded-For"]; ok {
                clientIP = strings.Join(prior, ", ") + ", " + clientIP
            }
            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)
            }
        }
    }

    最终代码:

    https://github.com/sunlongv520/go-network

    下面继续会学习go的负载均衡算法和限流相关知识

    1. 最简单的随机算法实现负载均衡
    2. 负载均衡算法之ip_hash
    3. 负载均衡算法之加权随机算法
    4. 负载均衡算法之轮询算法
    5. 负载均衡算法之加权轮询算法
    6. 负载均衡算法之平滑加权轮询算法
    7. 简易健康检查:http服务定时检查
    8. 简易FailOver机制: 普通轮询算法下的计数器机制
    9. 普通加权轮询算法下的降权机制
    10. 平滑加权轮询算法下的降权机制
    11. 限流(令牌桶算法),熔断机制,
    12. 微服务框架学习    go-kit和 go-micro的学习(学习这么多最终还是会使用官方的微服务框架,学习之前请先熟悉grpc和服务注册,服务发现相关知识)

    路漫漫其修远兮吾将上下而求所

  • 相关阅读:
    eclipse历史版本下载地址
    注解@SuppressWarnings
    VB创建文件夹
    VB学习生成JavaBean
    C++下的强制转换类型
    Django扩展
    数据查询操作
    深入剖析C++多态、VPTR指针、虚函数表
    快速排序一步一步优化
    cookie与session
  • 原文地址:https://www.cnblogs.com/sunlong88/p/12443233.html
Copyright © 2020-2023  润新知