基于Go语言实现的简易api网关 (新手轻喷)
api网关核心功能是实现http转发和重新功能,http代理的结构和思路很简单,通过浏览器发送请求,proxy拿到浏览器的请求去请求目标地址,然后获得结果它再发送给浏览器。对于Go语言来说,实现转发只需要简单的一行代码即可实现,如下所示:
httputil. NewSingleHostReverseProxy ( address)
基于此功能,进行简单包装,实现从远端admin管理中心获取需要转发的路由信息或者可以从本地配置文件中获取,实现动态转发。后续可以根据业务情况,可以实现如下功能:
开发接口,实现动态添加代理规则,进行转发 过滤不合法的接口 接口限流 统一日志记录 …
代码如下:
package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/gin-gonic/gin"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
)
type Respond struct {
Success bool
Status string
Data [ ] Proxy
}
type Proxy struct {
Remark string
Prefix string
Upstream string
RewritePrefix string
}
var (
InfoLog * log. Logger
ErrorLog * log. Logger
proxyMap = make ( map [ string ] Proxy)
)
var adminUrl = flag. String ( "adminUrl" , "" , "admin的地址" )
var profile = flag. String ( "profile" , "" , "环境" )
var proxyFile = flag. String ( "proxyFile" , "" , "测试环境的数据" )
func initLog ( ) {
errFile, err := os. OpenFile ( "errors.log" , os. O_CREATE| os. O_WRONLY| os. O_APPEND, 0666 )
infoFile, err := os. OpenFile ( "info.log" , os. O_CREATE| os. O_WRONLY| os. O_APPEND, 0666 )
if err != nil {
log. Fatalln ( "打开日志文件失败:" , err)
}
InfoLog = log. New ( io. MultiWriter ( os. Stderr, infoFile) , "Info:" , log. LstdFlags| log. Lmicroseconds| log. Lshortfile)
ErrorLog = log. New ( io. MultiWriter ( os. Stderr, errFile) , "Error:" , log. LstdFlags| log. Lmicroseconds| log. Lshortfile)
}
func main ( ) {
router := gin. Default ( )
flag. Parse ( )
initLog ( )
if * profile != "" {
InfoLog. Printf ( "加载远端数据: %s " , * adminUrl)
initProxyList ( )
} else {
InfoLog. Printf ( "加载本地配置数据: %s" , * proxyFile)
loadProxyListFromFile ( )
}
router. Any ( "/*action" , Forward)
router. Run ( ":8000" )
}
func initProxyList ( ) {
resp, _ := http. Get ( * adminUrl)
if resp != nil && resp. StatusCode == 200 {
bytes, err := ioutil. ReadAll ( resp. Body)
defer resp. Body. Close ( )
if err != nil {
fmt. Println ( "ioutil.ReadAll err=" , err)
return
}
var respond Respond
err = json. Unmarshal ( bytes, & respond)
if err != nil {
fmt. Println ( "json.Unmarshal err=" , err)
return
}
proxyList := respond. Data
for _ , proxy := range proxyList {
/ / 追加 反斜杠,为了动态匹配的时候 防止 / proxy/ test / proxy/ test1 无法正确转发
proxyMap[ proxy. Prefix+ "/" ] = proxy
}
}
}
func Forward ( c * gin. Context) {
HostReverseProxy ( c. Writer, c. Request)
}
func HostReverseProxy ( w http. ResponseWriter, r * http. Request) {
if r. RequestURI == "/favicon.ico" {
io. WriteString ( w, "Request path Error" )
return
}
/ / 从内存里面获取转发的url
var upstream = ""
if value, ok := proxyMap[ r. RequestURI] ; ok {
/ / 如果转发的地址是 / 开头的, 需要去掉
if strings. HasSuffix ( value. Upstream, "/" ) {
upstream += strings. TrimRight ( value. Upstream, "/" )
} else {
upstream += value. Upstream
}
/ / 如果首位不是/ 开头,则需要追加
if ! strings. HasPrefix ( value. RewritePrefix, "/" ) {
upstream += "/" + value. RewritePrefix
} else {
upstream += value. RewritePrefix
}
/ / 去掉开头
r. URL. Path = strings. ReplaceAll ( r. URL. Path, r. RequestURI, "" )
}
/ / parse the url
remote, err := url. Parse ( upstream)
InfoLog. Printf ( "RequestURI %s upstream %s remote %s" , r. RequestURI, upstream, remote)
if err != nil {
panic ( err)
}
r. URL. Host = remote. Host
r. URL. Scheme = remote. Scheme
r. Header. Set ( "X-Forwarded-Host" , r. Header. Get ( "Host" ) )
r. Host = remote. Host
httputil. NewSingleHostReverseProxy ( remote) . ServeHTTP ( w, r)
}
func loadProxyListFromFile ( ) {
file, err := os. Open ( * proxyFile)
if err != nil {
ErrorLog. Println ( "err:" , err)
}
var respond Respond
/ / 创建json解码器
decoder := json. NewDecoder ( file)
err = decoder. Decode ( & respond)
if err != nil {
fmt. Println ( "LoadProxyListFromFile failed" , err. Error ( ) )
}
proxyList := respond. Data
for _ , proxy := range proxyList {
proxyMap[ proxy. Prefix+ "/" ] = proxy
}
}
proxy_data.json 格式如下:
{
"success" : true ,
"status" : "ok" ,
"data" : [
{
"remark" : "测试环境" ,
"prefix" : "/division" ,
"upstream" : "http://test.xxxxx.cn/" ,
"rewritePrefix" : "/api/division"
} ,
{
"remark" : "测试环境1" ,
"prefix" : "/division1" ,
"upstream" : "http://test.xxxx.cn/" ,
"rewritePrefix" : ""
} ,
{
"remark" : "测试环境2" ,
"prefix" : "/division3" ,
"upstream" : "http://test.xxxxxx.cn/" ,
"rewritePrefix" : "/api/division"
}
]
}
启动脚本
## 加载本地配置文件数据
go run proxy_agent. go - proxyFile . / proxy_data. json
## 启动从配置中心获取数据
go run proxy_agent. go - profile prod - adminUrl http: / / localhost: 3000 / proxy/ findAll