golang實現正/反向代理服務

1 概念

1.1 正向代理

一種客戶端代理技術,用於幫助客戶端訪問沒法直接訪問的網絡資源,並隱藏客戶端IP,常見的場景有***、瀏覽器HTTP代理nginx

1.2 反向代理

一種服務端代理技術,用於隱藏真實服務端節點,並實現負載均衡、緩存、安全校驗、協議轉換等,常見的有LVS、nginx後端

2 實踐

2.1 實現一個正向代理服務

2.1.1 基本思路
  • 代理接收客戶端請求,複製該請求對象,並根據實際須要配置請求參數
  • 構造新的請求,發送到服務端,並獲取服務端的響應內容
  • 接收到響應內容後返回給客戶端
2.1.2 具體實現
package main

import (
    "fmt"
    "io"
    "log"
    "net"
    "net/http"
    "strings"
)

type proxy struct {}

func (p *proxy)ServeHTTP(w http.ResponseWriter,r *http.Request){
    fmt.Printf("Received request: %s %s %s\n",r.Method,r.Host,r.RemoteAddr)
    transport := http.DefaultTransport

    // 淺拷貝一個request 對象,避免後續修影響了源對象
    req := new(http.Request)
    *req = *r

    // 設置X-Forward-For 頭部
    if clientIp,_,err := net.SplitHostPort(r.RemoteAddr);err ==nil{
        if prior,ok := req.Header["X-Forward-For"];ok{
            clientIp = strings.Join(prior,", ") + ", " + clientIp
        }
        req.Header.Set("X-Forward-For",clientIp)
    }

    // 構造新請求
    response,err:=transport.RoundTrip(req)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    // 獲取響應數據並返回
    for k,v := range response.Header{
        for _,v1 := range v{
            w.Header().Add(k,v1)
        }
    }
    w.WriteHeader(response.StatusCode)
    io.Copy(w,response.Body)
    response.Body.Close()

}

func main() {
    //mux := http.NewServeMux()
    server := &http.Server{
        Addr: ":9090",
        Handler: &proxy{},
    }
    if err:=server.ListenAndServe();err != nil{
        log.Fatal("Http proxy server start failed.")
    }
}

2.2 Go實現一個反向代理服務

2.2.1 基本思路
2.2.2 具體實現

Http real Server 實現:監聽8081和8082端口,返回請求的URL瀏覽器

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

type realServer struct {
    Addr string
}

func (rs *realServer) HelloHandler(w http.ResponseWriter,r *http.Request){
    reqUrl := fmt.Sprintf("http://%s%s\n",rs.Addr,r.RequestURI)
    w.Write([]byte(reqUrl))
}

func (rs *realServer) Run(){
    fmt.Println("Http server tart to serve at :",rs.Addr)
    mux := http.NewServeMux()
    mux.HandleFunc("/",rs.HelloHandler)
    server := &http.Server{
        Addr: rs.Addr,
        Handler: mux,
        WriteTimeout: time.Second * 3,
    }
    go func(){
        if err := server.ListenAndServe();err != nil{
            log.Fatal("Start http server failed,err:",err)
        }
    }()
}

func main() {
    rs1 := &realServer{Addr:"127.0.0.1:8081"}
    rs2 := &realServer{Addr:"127.0.0.1:8082"}

    go rs1.Run()
    go rs2.Run()

    doneCh := make(chan os.Signal)
    signal.Notify(doneCh,syscall.SIGINT,syscall.SIGTERM)
    <- doneCh
}

模擬請求緩存

// output
$ curl http://127.0.0.1:8081/hellotest?id=1 -s
http://127.0.0.1:8081/hellotest?id=1

$ curl http://127.0.0.1:8082/hellotest?id=2 -s
http://127.0.0.1:8082/hellotest?id=2

反向代理服務端實現安全

package main

import (
    "bufio"
    "fmt"
    "log"
    "net/http"
    "net/url"
    "time"
)
var serverPort = "8888"

// 爲了測試,簡單的經過當前時間戳取餘的方式模擬隨機訪問後端rs
func GetRandServer()string{
    ports := []string{"8081","8082"}
    n := time.Now().Unix() % 2
    return ports[n]
}

func handler(w http.ResponseWriter,r *http.Request){
  // 解析並修改代理服務
    port := GetRandServer()
    proxyAddr := "http://127.0.0.1:" + port
    proxy ,err := url.Parse(proxyAddr)
    if err != nil{
        log.Println(err)
        return
    }
    r.URL.Scheme = proxy.Scheme
    r.URL.Host = proxy.Host

    // 代理請求
    transport := http.DefaultTransport
    resp ,err := transport.RoundTrip(r)
    if err != nil{
        log.Println(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    // 將響應結果返回
    for key,value := range resp.Header{
        for _,v := range value{
            w.Header().Add(key,v)
        }
    }
    defer resp.Body.Close()
    bufio.NewReader(resp.Body).WriteTo(w)
}

func main() {
    http.HandleFunc("/",handler)
    fmt.Println("Http reverse proxy server start at : 127.0.0.1:",serverPort)
    if err := http.ListenAndServe(":"+serverPort,nil);err != nil{
        log.Fatal("Start server failed,err:",err)
    }
}

測試網絡

$ for i in {0..9};do curl http://127.0.0.1:8888/reversetest?id=111 -s;done
http://127.0.0.1:8082/reversetest?id=111
http://127.0.0.1:8082/reversetest?id=111
http://127.0.0.1:8082/reversetest?id=111
http://127.0.0.1:8082/reversetest?id=111
http://127.0.0.1:8081/reversetest?id=111
http://127.0.0.1:8081/reversetest?id=111
http://127.0.0.1:8081/reversetest?id=111
http://127.0.0.1:8081/reversetest?id=111
http://127.0.0.1:8081/reversetest?id=111
http://127.0.0.1:8082/reversetest?id=111
相關文章
相關標籤/搜索