簡單說說反向代理
信號監聽方式啟動兩個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) }

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

package util import "net/http" func CloneHeader(src http.Header,dest *http.Header) { for k,v:=range src{ dest.Set(k,v[0]) } }
怎么獲取真實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() } } } }

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) // 寫入響應給客戶端 }

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

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

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) }
使用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的負載均衡算法和限流相關知識
- 最簡單的隨機算法實現負載均衡
- 負載均衡算法之ip_hash
- 負載均衡算法之加權隨機算法
- 負載均衡算法之輪詢算法
- 負載均衡算法之加權輪詢算法
- 負載均衡算法之平滑加權輪詢算法
- 簡易健康檢查:http服務定時檢查
- 簡易FailOver機制: 普通輪詢算法下的計數器機制
- 普通加權輪詢算法下的降權機制
- 平滑加權輪詢算法下的降權機制
- 限流(令牌桶算法),熔斷機制,
- 微服務框架學習 go-kit和 go-micro的學習(學習這么多最終還是會使用官方的微服務框架,學習之前請先熟悉grpc和服務注冊,服務發現相關知識)
路漫漫其修遠兮吾將上下而求所