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

摘要:
b=123defefunc(){ifer:=recover();err!=nil{w.WriteHeader(500)log.Println(err)}}()fmt.Println(r.URL)///a?
简单说说反向代理

golang网关之手动实现反向代理第1张

 golang网关之手动实现反向代理第2张

 信号监听方式启动两个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{})

}

 golang网关之手动实现反向代理第3张

  

golang网关之手动实现反向代理第4张

 

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

golang网关之手动实现反向代理第5张

golang网关之手动实现反向代理第6张

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)

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

看下响应头

golang网关之手动实现反向代理第7张

w.WriteHeader(newresponse.StatusCode)

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

完整代码

golang网关之手动实现反向代理第8张golang网关之手动实现反向代理第9张
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
golang网关之手动实现反向代理第10张golang网关之手动实现反向代理第11张
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
golang网关之手动实现反向代理第12张golang网关之手动实现反向代理第13张
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

golang网关之手动实现反向代理第14张

 怎么获取真实IP?

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

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

golang网关之手动实现反向代理第15张

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配置文件格式、配置”反向代理”路径映射

golang网关之手动实现反向代理第16张golang网关之手动实现反向代理第17张
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
golang网关之手动实现反向代理第18张golang网关之手动实现反向代理第19张
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
golang网关之手动实现反向代理第20张golang网关之手动实现反向代理第21张
[proxy]

[proxy.a]
path=/web1
pass=http://127.0.0.1:9091

[proxy.b]
path=/web2
pass=http://127.0.0.1:9092
env
golang网关之手动实现反向代理第22张golang网关之手动实现反向代理第23张
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
golang网关之手动实现反向代理第24张golang网关之手动实现反向代理第25张
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内置的反向代理函数

实现的方式是用了

golang网关之手动实现反向代理第26张

Transport

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

DefaultTransport

golang网关之手动实现反向代理第27张

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和服务注册,服务发现相关知识)

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

免责声明:文章转载自《golang网关之手动实现反向代理》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇使用visual studio生成事件,生成固定文件夹和dll位置SendMessage模拟按键所需的虚拟键码下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

JetBrains全家桶,pycharm、golang等最新版安装激活注册教程

插件介绍 JetBrains全家桶,支持注册激活IntelliJ IDEA , Pycharm , Webstorm , PhpStorm , Rider Clion , RubyMine, AppCode, Goland, DataGrip , Kotlin等15款产品。可以注册激活2020.2.4最新版本,可以永久激活最新版本。 注意:这里以gola...

code-server nginx 反向代理

这里根据官网所给出的配置项进行了简单修改,仅作参考 关于nginx配置项有不了解的可以看这里 反向代理配置内容 location / { proxy_pass http://127.0.0.1:8082; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade;...

Golang基础编程(四)-Map(集合)、Slice(切片)、Range

一、Map ·Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。 ·Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。 Map-声明&初始化 Map使用前必须初始化 Ma...

nginx的反向代理缓存

假设有两台物理机,他们分属于不同的域名,以webgame的游戏运营商和开发商为例,运营商需要获取游戏中的排行数据,开发商为了过多对游戏服务器的请求,会在发起请求的机器上设置缓存(或是让运营商定时过来抓取一次数据) nginx设置web缓存,需要用到的相关指令: 1、proxy_cache 2、proxy_cache_path 3、proxy_cache_v...

Nginx反向代理WebSocket链接失败问题

问题记录:本地socket测试无误后部署发现 WebSocket connection to "xxx/xxx" failed  解决方案: 在nginx.conf的http模块添加如下内容 map $http_upgrade $connection_upgrade { default upgrade; '' close; } 其...

JAVA获取客户端请求的当前网络ip地址(附:Nginx反向代理后获取客户端请求的真实IP)

1. JAVA获取客户端请求的当前网络ip地址: /** * 获取客户端请求的当前网络ip * @param request * @return */ public static String getIpAddr(HttpServletRequest request){ S...