Golang 實現 API 網關實戰

【導讀】本文介紹了 Go 實現微服務網管的實踐。

在最近的一個項目中,採用了微服務架構 -go-kit進行後端的開發。在微服務架構風格中,一個大應用被拆分成爲了多個小的服務系統提供出來,這些小的系統他們可以自成體系,也就是說這些小系統可以擁有自己的數據庫,框架甚至語言等,因此我們需要設計一個 API 網關(API Gataway),其實網上已經有較多現成的實現框架,但是本項目的需求是比較簡單的,因此將使用Golang自行實現。

實現

API 網關是一個服務器,是系統的唯一入口。從面向對象設計的角度看,它與外觀模式類似。API 網關封裝了系統內部架構,爲每個客戶端提供一個定製的 API。它可能還具有其它職責,如身份驗證、監控、負載均衡、緩存、請求分片與管理、靜態響應處理。

用於實現 API 網關的技術有很多,大致分爲這麼幾類:

API 網關最基本的功能就是反向代理。其實現方式有很多,本文將基於標準庫net/http/httputil包中的ReverseProxy類型來實現實現一個簡單的反向代理。反向代理的實現主要涉及到func NewSingleHostReverseProxy(target *url.URL) *ReverseProxytype ReverseProxy

func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy
// NewSingleHostReverseProxy returns a new ReverseProxy that routes
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
// NewSingleHostReverseProxy does not rewrite the Host header.
// To rewrite Host headers, use ReverseProxy directly with a custom
// Director policy.
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
 targetQuery := target.RawQuery
 director := func(req *http.Request) {
  req.URL.Scheme = target.Scheme
  req.URL.Host = target.Host
  req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
  if targetQuery == "" || req.URL.RawQuery == "" {
   req.URL.RawQuery = targetQuery + req.URL.RawQuery
  } else {
   req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
  }
  if _, ok := req.Header["User-Agent"]; !ok {
   // explicitly disable User-Agent so it's not set to default value
   req.Header.Set("User-Agent""")
  }
 }
 return &ReverseProxy{Director: director}
}

NewSingleHostReverseProxy返回一個新的ReverseProxy,將URLs請求路由到targe的指定的scheme, host, base path

// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client.
type ReverseProxy struct {
 // Director must be a function which modifies
 // the request into a new request to be sent
 // using Transport. Its response is then copied
 // back to the original client unmodified.
 // Director must not access the provided Request
 // after returning.
 Director func(*http.Request)

 Transport http.RoundTripper

 FlushInterval time.Duration

 ErrorLog *log.Logger

 BufferPool BufferPool

 // ModifyResponse is an optional function that modifies the
 // Response from the backend. It is called if the backend
 // returns a response at all, with any HTTP status code.
 // If the backend is unreachable, the optional ErrorHandler is
 // called without any call to ModifyResponse.
 //
 // If ModifyResponse returns an error, ErrorHandler is called
 // with its error value. If ErrorHandler is nil, its default
 // implementation is used.
 ModifyResponse func(*http.Response) error

 ErrorHandler func(http.ResponseWriter, *http.Request, error)
}

ReverseProxy類型有兩個重要的屬性,分別是DirectorModifyResponse,這兩個屬性都是函數類型,在接收到客戶端請求時,ServeHTTP函數首先調用Director函數對接受到的請求體進行修改,例如修改請求的目標地址、請求頭等;然後使用修改後的請求體發起新的請求,接收到響應後,調用ModifyResponse函數對響應進行修改,最後將修改後的響應體拷貝並響應給客戶端,這樣就實現了反向代理的整個流程。

NewSingleHostReverseProxy中源碼已經對傳入的URLs進行解析並且完成了Director的修改,我們只需要調用NewSingleHostReverseProxy函數並且傳入目標服務器的 URL 即可,一個簡單的反向代理就完成了啦。

代碼

實例代碼只涉及微服務中 userauth模塊,可以根據實際需求自行修改部分

package main

import (
 "fmt"
 "log"
 "net/http"
 "net/http/httputil"
 "net/url"
 "strings"
)

type handle struct {
 host string
 port string
}

type Service struct {
 auth *handle
 user *handle
}

func (this *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 var remote *url.URL
 if strings.Contains(r.RequestURI, "api/auth") {
  remote, _ = url.Parse("http://" + this.auth.host + ":" + this.auth.port)
 } else if strings.Contains(r.RequestURI, "api/user") {
  remote, _ = url.Parse("http://" + this.user.host + ":" + this.user.port)
 } else {
  fmt.Fprintf(w, "404 Not Found")
  return
 }
 proxy := httputil.NewSingleHostReverseProxy(remote)
 proxy.ServeHTTP(w, r)
}

func startServer() {
 // 註冊被代理的服務器 (host, port)
 service := &Service{
  auth: &handle{host: "127.0.0.1", port: "8081"},
  user: &handle{host: "127.0.0.1", port: "8082"},
 }
 err := http.ListenAndServe(":8888", service)
 if err != nil {
  log.Fatalln("ListenAndServe: ", err)
 }
}

func main() {
 startServer()
}

轉自:

blog.csdn.net/lllllyt/article/details/89528785

Go 開發大全

參與維護一個非常全面的 Go 開源技術資源庫。日常分享 Go, 雲原生、k8s、Docker 和微服務方面的技術文章和行業動態。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/2gJ_os_aCzy3txpH9woKYA