• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
孙龙 程序员
少时总觉为人易,华年方知立业难
博客园    首页    新随笔    联系   管理    订阅  订阅
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和服务注册,服务发现相关知识)

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

本文来自博客园,作者:孙龙-程序员,转载请注明原文链接:https://www.cnblogs.com/sunlong88/p/12443233.html

posted on 2020-03-08 16:35  孙龙-程序员  阅读(2398)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3