From 98f5ef2b266e4d0fc5b994ef133ddc925865cb42 Mon Sep 17 00:00:00 2001 From: eoLinker API Management Date: Mon, 14 May 2018 16:18:19 +0800 Subject: [PATCH] V2.0.3 --- README.md | 24 +- release/goku_ce_2.0.1.zip | Bin 18153 -> 0 bytes release/goku_ce_2.0.2.zip | Bin 18147 -> 0 bytes release/goku_ce_2.0.3.zip | Bin 0 -> 25079 bytes source_code/conf/conf.go | 10 +- source_code/conf/parse_config.go | 18 +- source_code/config/gateway/test/api.conf | 5 +- source_code/config/gateway/test/backend.conf | 2 +- source_code/goku-ce.go | 3 +- source_code/goku/context.go | 41 ++ source_code/goku/goku.go | 128 +--- source_code/goku/path.go | 87 +++ source_code/goku/router.go | 305 +++++++++ source_code/goku/tree.go | 619 +++++++++++++++++++ source_code/goku/utils.go | 12 - source_code/middleware/auth.go | 13 +- source_code/middleware/backend.go | 18 - source_code/middleware/ip_limit.go | 32 +- source_code/middleware/rate.go | 64 +- source_code/middleware/request_mapping.go | 198 +++--- source_code/request/request.go | 6 +- source_code/request/response.go | 12 +- 22 files changed, 1242 insertions(+), 355 deletions(-) delete mode 100644 release/goku_ce_2.0.1.zip delete mode 100644 release/goku_ce_2.0.2.zip create mode 100644 release/goku_ce_2.0.3.zip create mode 100644 source_code/goku/context.go create mode 100644 source_code/goku/path.go create mode 100644 source_code/goku/router.go create mode 100644 source_code/goku/tree.go delete mode 100644 source_code/goku/utils.go delete mode 100644 source_code/middleware/backend.go diff --git a/README.md b/README.md index 373c0fdf..ac35709a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ GoKu API Gateway CE,支持OpenAPI与微服务管理,支持私有云部署, 6. **IP黑白名单**:支持全局IP白名单、也可自定义某个接口的IP白名单。 -7. **数据整形**:支持参数的转换与绑定,支持formdata、raw数据、json。 +7. **数据整形**:支持参数的转换与绑定,支持formdata、raw数据。 8. **配置文件**:支持配置文件修改网关配置。 @@ -32,15 +32,13 @@ GoKu API Gateway CE,支持OpenAPI与微服务管理,支持私有云部署, 1. **UI界面**:支持通过UI界面修改网关配置。 -2. **API支持**:支持通过API对网关进行操作。 +2. **兼容eoLinker-AMS**:可与国内最大的接口管理平台打通。 -3. **兼容eoLinker-AMS**:可与国内最大的接口管理平台打通。 +3. **支持Restful**:支持rest路由。 -4. **支持Restful**:支持rest路由。 +4. **告警设置**:当系统达到预设告警条件时,邮件通知运维人员。 -6. **告警设置**:当系统达到预设告警条件时,邮件通知运维人员。 - -7. **超时设置**:配置访问超时时间,网关控制超时后立即返回,防止系统雪崩。 +5. **超时设置**:配置访问超时时间,网关控制超时后立即返回,防止系统雪崩。 **……** @@ -73,10 +71,20 @@ GoKu API Gateway CE,支持OpenAPI与微服务管理,支持私有云部署, ## 更新日志 +#### V2.0.3(2018/5/14) +新增: + +1. 支持form-data格式下文件传输; +2. 接口配置文件弃用proxy_body_type、proxy_body_desc字段,启用is_raw字段用于支持raw数据转发。 + +优化: + +1. 基于HttpRouter优化路由转发性能。 + #### V2.0.2(2018/5/7) 修复: -1. 修复请求路径带参数时,匹配路径失败; +1. 修复请求路径带query参数时,路径匹配失败的问题; 2. 修复proxy_method配置必须大写的问题,现支持不区分大小写。 #### V2.0.1(2018/5/4) diff --git a/release/goku_ce_2.0.1.zip b/release/goku_ce_2.0.1.zip deleted file mode 100644 index bf94fcb958d32b104415e8d697fa27b666a7009d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18153 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 R5vP|xVDLad1FYXa{U2#0A>aT2 diff --git a/release/goku_ce_2.0.3.zip b/release/goku_ce_2.0.3.zip new file mode 100644 index 0000000000000000000000000000000000000000..c38d78608d2e33acecbf6f74b1b6dd9c774dcca6 GIT binary patch literal 25079 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 W5}<|Q4%NU}ar#f-;E?McKK&n@NzF_E literal 0 HcmV?d00001 diff --git a/source_code/conf/conf.go b/source_code/conf/conf.go index 00c782f7..6d04cd01 100644 --- a/source_code/conf/conf.go +++ b/source_code/conf/conf.go @@ -33,6 +33,9 @@ type GatewayInfo struct { StrategyList Strategy ApiList Api BackendList Backend + UpdateTime string `json:"update_time" yaml:"update_time"` + CreateTime string `json:"create_time" yaml:"create_time"` + GroupList ApiGroup } type Strategy struct { @@ -50,6 +53,8 @@ type StrategyInfo struct { IPWhiteList []string `json:"ip_white_list" yaml:"ip_white_list"` IPBlackList []string `json:"ip_black_list" yaml:"ip_black_list"` RateLimitList []RateLimitInfo `json:"rate_limit_list" yaml:"rate_limit_list"` + UpdateTime string `json:"update_time" yaml:"update_time"` + CreateTime string `json:"create_time" yaml:"create_time"` } type RateLimitInfo struct { @@ -67,7 +72,7 @@ type ApiGroupInfo struct { } type ApiGroup struct { - Group ApiGroupInfo `json:"group" yaml:"group"` + Group []ApiGroupInfo `json:"group" yaml:"group"` } type Api struct { @@ -82,8 +87,7 @@ type ApiInfo struct { BackendID int `json:"backend_id" yaml:"backend_id"` ProxyURL string `json:"proxy_url" yaml:"proxy_url"` ProxyMethod string `json:"proxy_method" yaml:"proxy_method"` - ProxyBodyType string `json:"proxy_body_type" yaml:"proxy_body_type"` - ProxyBody string `json:"proxy_body" yaml:"proxy_body"` + IsRaw bool `json:"is_raw" yaml:"is_raw"` ProxyParams []Param `json:"proxy_params" yaml:"proxy_params"` ConstantParams []ConstantParam `json:"constant_params" yaml:"constant_params"` } diff --git a/source_code/conf/parse_config.go b/source_code/conf/parse_config.go index 1387751f..cba029be 100644 --- a/source_code/conf/parse_config.go +++ b/source_code/conf/parse_config.go @@ -13,11 +13,11 @@ func ParseConfInfo() GlobalConfig { var g GlobalConfig err := yaml.Unmarshal([]byte(Configure),&g) if err != nil { - panic("Error global config!") + panic("Global Config Error!") } path,err := GetDir(g.GatewayConfPath) if err != nil { - panic("Error gateway config path!") + panic("Gateway Config Path Error!") } fmt.Println(path) gatewayList := getGatewayList(path) @@ -33,11 +33,11 @@ func getGatewayList(path []string) []GatewayInfo { c,err := ioutil.ReadFile(p + PthSep + "gateway.conf") if err != nil { - panic("Error gateway config path! Error path: " + p) + panic("Gateway Config Path Error! Error path: " + p) } err = yaml.Unmarshal(c,&gateway) if err != nil { - panic("Error gateway config! Error path: " + p) + panic("Gateway Config Error! Error path: " + p) } if gateway.GatewayStatus != "on" { continue @@ -54,11 +54,11 @@ func getStrategyList(path string) Strategy { var strategy Strategy c,err := ioutil.ReadFile(path) if err != nil { - panic("Error strategy config path! Error path: " + path) + panic("Strategy Config Path Error! Error path: " + path) } err = yaml.Unmarshal(c,&strategy) if err != nil { - panic("Error strategy config! Error path: " + path) + panic("Strategy Config Error! Error path: " + path) } return strategy } @@ -67,11 +67,11 @@ func getApiList(path string) Api { var api Api c,err := ioutil.ReadFile(path) if err != nil { - panic("Error api config path! Error path: " + path) + panic("Api Config Path Error! Error path: " + path) } err = yaml.Unmarshal(c,&api) if err != nil { - panic("Error api config! Error path: " + path) + panic("Api Config Error! Error path: " + path) } return api } @@ -80,7 +80,7 @@ func getBackendList(path string) Backend { var backend Backend c,err := ioutil.ReadFile(path) if err != nil { - panic("Error backend config path! Error path: " + path) + panic("Backend Config Path Error! Error path: " + path) } err = yaml.Unmarshal(c,&backend) if err != nil { diff --git a/source_code/config/gateway/test/api.conf b/source_code/config/gateway/test/api.conf index 7a91f6a6..7fea9a57 100644 --- a/source_code/config/gateway/test/api.conf +++ b/source_code/config/gateway/test/api.conf @@ -1,6 +1,6 @@ apis: # api列表 - api_name: 全国油价 # 接口名称 - group_id: 0 # 接口所属分组,0代表无分组 + group_id: 0 # 接口所属分组 backend_id: 1 # 后端服务 request_url: /common/oil/getOilPriceToday # 网关请求路径 request_method: # 请求方法:get/post/put/delete/options/patch/head @@ -8,8 +8,7 @@ apis: # api列表 - post proxy_url: /common/oil/getOilPriceToday # 后端请求路径 proxy_method: post # 非数组,后端请求方法:get/post/put/delete/options/patch/head - proxy_body_type: formdata # body类型:formdata/raw/json - proxy_body: "" # raw内容 + is_raw: false # 请求body是否为raw数据:true/false proxy_params: # 请求参数映射列表 - key: province # 网关接口参数名 key_position: body # 网关参数位置:query/body/header diff --git a/source_code/config/gateway/test/backend.conf b/source_code/config/gateway/test/backend.conf index ad6d2ccd..51971400 100644 --- a/source_code/config/gateway/test/backend.conf +++ b/source_code/config/gateway/test/backend.conf @@ -1,4 +1,4 @@ backend: # 后端服务列表 -- backend_id: 1 # 后端服务id,0代表无API分组 +- backend_id: 1 # 后端服务id backend_name: 测试后端 # 后端服务名称 backend_path: https://api.apishop.net # 后端服务地址 \ No newline at end of file diff --git a/source_code/goku-ce.go b/source_code/goku-ce.go index 96e9af57..84894c63 100644 --- a/source_code/goku-ce.go +++ b/source_code/goku-ce.go @@ -8,10 +8,9 @@ import ( func main() { server := goku.New() - server.Use(middleware.Mapping) + server.RegisterRouter(server.ServiceConfig,middleware.Mapping) server.Listen() server.Run() } - diff --git a/source_code/goku/context.go b/source_code/goku/context.go new file mode 100644 index 00000000..0b564c9d --- /dev/null +++ b/source_code/goku/context.go @@ -0,0 +1,41 @@ +package goku + +import ( + "goku-ce/conf" +) +type Context struct { + GatewayInfo Gateway + StrategyInfo Strategy + ApiInfo Api + Rate map[string]Rate +} + +type Gateway struct { + GatewayAlias string `json:"gateway_alias" yaml:"gateway_alias"` + GatewayStatus string `json:"gateway_status" yaml:"gateway_status"` + IPLimitType string `json:"ip_limit_type" yaml:"ip_limit_type"` + IPWhiteList []string `json:"ip_white_list" yaml:"ip_white_list"` + IPBlackList []string `json:"ip_black_list" yaml:"ip_black_list"` +} + +type Strategy struct { + StrategyID string `json:"strategy_id" yaml:"strategy_id"` + Auth string `json:"auth" yaml:"auth"` + BasicUserName string `json:"basic_user_name" yaml:"basic_user_name"` + BasicUserPassword string `json:"basic_user_password" yaml:"basic_user_password"` + ApiKey string `json:"api_key" yaml:"api_key"` + IPLimitType string `json:"ip_limit_type" yaml:"ip_limit_type"` + IPWhiteList []string `json:"ip_white_list" yaml:"ip_white_list"` + IPBlackList []string `json:"ip_black_list" yaml:"ip_black_list"` + RateLimitList []conf.RateLimitInfo `json:"rate_limit_list" yaml:"rate_limit_list"` +} + +type Api struct { + RequestURL string `json:"request_url" yaml:"request_url"` + BackendPath string `json:"backend_path" yaml:"backend_path"` + ProxyURL string `json:"proxy_url" yaml:"proxy_url"` + ProxyMethod string `json:"proxy_method" yaml:"proxy_method"` + IsRaw bool `json:"is_raw" yaml:"is_raw"` + ProxyParams []conf.Param `json:"proxy_params" yaml:"proxy_params"` + ConstantParams []conf.ConstantParam `json:"constant_params" yaml:"constant_params"` +} diff --git a/source_code/goku/goku.go b/source_code/goku/goku.go index da9b7711..bc3c3bd6 100644 --- a/source_code/goku/goku.go +++ b/source_code/goku/goku.go @@ -8,55 +8,44 @@ import ( "net/http" "log" "os" - "sync/atomic" - "time" ) type GokuServer interface{ Run() error - Use(handler ...Handler) Address() string Listener() net.Listener Listen() error } -type Handler interface{} - -type Injector interface { - Get(reflect.Type) reflect.Value - Map(interface{}) Injector -} - type classicGoku struct { *Goku } + type Goku struct{ - handlers []Handler + *Router index int ServiceConfig conf.GlobalConfig logger *log.Logger listener *net.Listener address string values map[reflect.Type]reflect.Value - parent Injector cClose chan bool isStopping bool activeCount int32 - Rate map[string]Rate } -func (i *Goku) Map(val interface{}) Injector { - i.values[reflect.TypeOf(val)] = reflect.ValueOf(val) - return i -} // 启动一个Goku实例 -func New() GokuServer{ - g := &Goku{values: make(map[reflect.Type]reflect.Value),logger:log.New(os.Stdout, "[Goku]", 0),Rate:make(map[string]Rate)} +func New() *Goku{ + g := &Goku{ + Router:NewRouter(), + values: make(map[reflect.Type]reflect.Value), + logger:log.New(os.Stdout, "[Goku]", 0), + } + g.ServiceConfig = conf.ParseConfInfo() - g.Map(g) - return &classicGoku{g} + return g } func (g *Goku) Run() error{ @@ -106,100 +95,3 @@ func (g *Goku) Listener() net.Listener{ return *g.listener } -func (g *Goku) run() { - for g.index < len(g.handlers) { - handle := g.handlers[g.index] - _, err := g.Invoke(handle) - if err != nil { - panic(err) - } - g.index += 1 - - } - g.index = 0 - return -} - - - -func (g *Goku) Use(handler ...Handler) { - for _,h := range handler{ - ValidateHandler(h) - g.handlers = append(g.handlers,h) - } -} - -func (i *Goku) Get(t reflect.Type) reflect.Value { - val := i.values[t] - - if val.IsValid() { - return val - } - - if t.Kind() == reflect.Interface { - for k, v := range i.values { - if k.Implements(t) { - val = v - break - } - } - } - - // Still no type found, try to look it up on the parent - if !val.IsValid() && i.parent != nil { - val = i.parent.Get(t) - } - - return val - -} - -// 调用函数 -func (g *Goku) Invoke(handler Handler) ([]reflect.Value,error) { - ValidateHandler(handler) - t := reflect.TypeOf(handler) - var in = make([]reflect.Value, t.NumIn()) - for i := 0; i < t.NumIn(); i++ { - argType := t.In(i) - val := g.Get(argType) - if !val.IsValid() { - return nil, fmt.Errorf("Value not found for type %v", argType) - } - - in[i] = val - } - return reflect.ValueOf(handler).Call(in),nil -} - -func (c *Goku) IsStopped() bool { - return !(c.index < len(c.handlers)) -} - - -func (g *Goku) ServeHTTP(res http.ResponseWriter, req *http.Request) { - if(req.RequestURI == "/favicon.ico"){ - return - } - g.Map(res) - g.Map(req) - activeCount := atomic.AddInt32(&g.activeCount, -1) - if g.isStopping && activeCount == 0 { - time.Sleep(1) - g.cClose <- true - } - g.run() -} - -func InterfaceOf(value interface{}) reflect.Type { - t := reflect.TypeOf(value) - - for t.Kind() == reflect.Ptr { - t = t.Elem() - } - - if t.Kind() != reflect.Interface { - panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)") - } - - return t -} diff --git a/source_code/goku/path.go b/source_code/goku/path.go new file mode 100644 index 00000000..a070c0b5 --- /dev/null +++ b/source_code/goku/path.go @@ -0,0 +1,87 @@ +package goku + +func CleanPath(p string) string { + if p == "" { + return "/" + } + + n := len(p) + var buf []byte + + r := 1 + w := 1 + + if p[0] != '/' { + r = 0 + buf = make([]byte, n+1) + buf[0] = '/' + } + + trailing := n > 1 && p[n-1] == '/' + + + for r < n { + switch { + case p[r] == '/': + r++ + + case p[r] == '.' && r+1 == n: + trailing = true + r++ + + case p[r] == '.' && p[r+1] == '/': + r++ + + case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): + r += 2 + + if w > 1 { + w-- + + if buf == nil { + for w > 1 && p[w] != '/' { + w-- + } + } else { + for w > 1 && buf[w] != '/' { + w-- + } + } + } + + default: + if w > 1 { + bufApp(&buf, p, w, '/') + w++ + } + + for r < n && p[r] != '/' { + bufApp(&buf, p, w, p[r]) + w++ + r++ + } + } + } + + if trailing && w > 1 { + bufApp(&buf, p, w, '/') + w++ + } + + if buf == nil { + return p[:w] + } + return string(buf[:w]) +} + +func bufApp(buf *[]byte, s string, w int, c byte) { + if *buf == nil { + if s[w] == c { + return + } + + *buf = make([]byte, len(s)) + copy(*buf, s[:w]) + } + (*buf)[w] = c +} diff --git a/source_code/goku/router.go b/source_code/goku/router.go new file mode 100644 index 00000000..d19afb57 --- /dev/null +++ b/source_code/goku/router.go @@ -0,0 +1,305 @@ +package goku + +import ( + "net/http" + "goku-ce/conf" + "strings" +) + +// Handle是一个可以被注册到路由中去处理http请求,类似于HandlerFunc,但是有第三个参数值 +type Handle func(http.ResponseWriter, *http.Request, Params,*Context) +// Param is a single URL parameter, consisting of a key and a value. +type Param struct { + Key string + Value string +} + +// Params是一个参数切片,作为路由的返回结果,这个切片是有序的 +// 第一个URL参数会作为第一个切片值,因此通过索引来读值是安全的 +type Params []Param + +// ByName returns the value of the first Param which key matches the given name. +// If no matching Param is found, an empty string is returned. +func (ps Params) ByName(name string) string { + for i := range ps { + if ps[i].Key == name { + return ps[i].Value + } + } + return "" +} + +// Router是一个可以被用来调度请求去不同处理函数的Handler +type Router struct { + trees map[string]*node + + context *Context + + handle Handle + + RedirectTrailingSlash bool + + RedirectFixedPath bool + + HandleMethodNotAllowed bool + + HandleOPTIONS bool + + NotFound http.Handler + + MethodNotAllowed http.Handler + + PanicHandler func(http.ResponseWriter, *http.Request, interface{}) +} + +func NewRouter() *Router { + return &Router{ + RedirectTrailingSlash: true, + RedirectFixedPath: true, + HandleMethodNotAllowed: true, + HandleOPTIONS: true, + } +} + + +func (r *Router) Use(handle Handle) { + r.handle = handle +} + + +func (r *Router) Handle(method, path string, handle Handle,context Context) { + if path[0] != '/' { + panic("path must begin with '/' in path '" + path + "'") + } + + if r.trees == nil { + r.trees = make(map[string]*node) + } + + root := r.trees[method] + if root == nil { + root = new(node) + r.trees[method] = root + } + root.addRoute(path, handle,context) +} + +// // HandlerFunc 是一个适配器允许使用http.HandleFunc函数作为一个请求处理器 +// func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) { +// r.Handler(method, path, handler) +// } + +func (r *Router) recv(w http.ResponseWriter, req *http.Request) { + if rcv := recover(); rcv != nil { + r.PanicHandler(w, req, rcv) + } +} +// 查找允许手动查找方法 + 路径组合。 +// 这对于构建围绕此路由器的框架非常有用。 +// 如果找到路径, 它将返回句柄函数和路径参数值 +// 否则, 第三个返回值指示是否应执行与附加/不带尾随斜线的同一路径的重定向 +func (r *Router) Lookup(method, path string) (Handle, Params, *Context, bool) { + if root := r.trees[method]; root != nil { + return root.getValue(path) + } + return nil, nil,&Context{}, false +} + +func (r *Router) allowed(path, reqMethod string) (allow string) { + if path == "*" { // server-wide + for method := range r.trees { + if method == "OPTIONS" { + continue + } + + // 将请求方法添加到允许的方法列表中 + if len(allow) == 0 { + allow = method + } else { + allow += ", " + method + } + + } + } else { // 特定路径 + for method := range r.trees { + // 跳过请求的方法-我们已经尝试过这一项 + if method == reqMethod || method == "OPTIONS" { + continue + } + + handle, _, _,_ := r.trees[method].getValue(path) + if handle != nil { + // 将请求方法添加到允许的方法列表中 + if len(allow) == 0 { + allow = method + } else { + allow += ", " + method + } + } + } + } + if len(allow) > 0 { + allow += ", OPTIONS" + } + return +} + +// ServeHTTP使用路由实现http.Handler接口 +func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if r.PanicHandler != nil { + + defer r.recv(w, req) + } + // now := time.Now() + path := req.URL.Path + pathArray := strings.Split(path,"/") + + if len(pathArray) == 2 { + w.WriteHeader(500) + if pathArray[1] == "" { + w.Write([]byte("Missing Gateway Alias")) + } else { + w.Write([]byte("Missing StrategyID")) + } + return + } else if len(pathArray) == 3 { + w.WriteHeader(500) + if pathArray[2] == "" { + w.Write([]byte("Missing StrategyID")) + } else { + w.Write([]byte("Invalid URI")) + } + return + } + + if root := r.trees[req.Method]; root != nil { + handle, ps, context,tsr := root.getValue(path); + if handle != nil { + handle(w, req, ps,context) + return + } else if req.Method != "CONNECT" && path != "/" { + code := 301 + if req.Method != "GET" { + code = 307 + } + + if tsr && r.RedirectTrailingSlash { + if len(path) > 1 && path[len(path)-1] == '/' { + req.URL.Path = path[:len(path)-1] + } else { + req.URL.Path = path + "/" + } + http.Redirect(w, req, req.URL.String(), code) + return + } + // 尝试修复请求路径 + if r.RedirectFixedPath { + fixedPath, found := root.findCaseInsensitivePath( + CleanPath(path), + r.RedirectTrailingSlash, + ) + if found { + req.URL.Path = string(fixedPath) + http.Redirect(w, req, req.URL.String(), code) + return + } + } + } + } + + if req.Method == "OPTIONS" && r.HandleOPTIONS { + // Handle OPTIONS requests + if allow := r.allowed(path, req.Method); len(allow) > 0 { + w.Header().Set("Allow", allow) + return + } + } else { + // Handle 405 + if r.HandleMethodNotAllowed { + if allow := r.allowed(path, req.Method); len(allow) > 0 { + w.Header().Set("Allow", allow) + if r.MethodNotAllowed != nil { + r.MethodNotAllowed.ServeHTTP(w, req) + } else { + http.Error(w, + http.StatusText(http.StatusMethodNotAllowed), + http.StatusMethodNotAllowed, + ) + } + return + } + } + } + + // Handle 404 + if r.NotFound != nil { + r.NotFound.ServeHTTP(w, req) + } else { + w.WriteHeader(404) + w.Write([]byte("Invalid URI")) + } +} + +// 注册路由 +func (r *Router) RegisterRouter(c conf.GlobalConfig,handle Handle) { + r.handle = handle + for _, g := range c.GatewayList { + if g.GatewayStatus != "on" { + continue + } + gateway := Gateway{ + GatewayAlias: g.GatewayAlias, + GatewayStatus: g.GatewayStatus, + IPLimitType: g.IPLimitType, + IPWhiteList: g.IPWhiteList, + IPBlackList: g.IPBlackList, + } + for _, s := range g.StrategyList.Strategy { + strategy := Strategy{ + StrategyID: s.StrategyID, + Auth: s.Auth, + ApiKey: s.ApiKey, + BasicUserName: s.BasicUserName, + BasicUserPassword: s.BasicUserPassword, + IPLimitType: s.IPLimitType, + IPWhiteList: s.IPWhiteList, + IPBlackList: s.IPBlackList, + RateLimitList:s.RateLimitList, + } + for _, api := range g.ApiList.Apis { + path := "/" + g.GatewayAlias + "/" + s.StrategyID + api.RequestURL + backendPath := "" + flag := false + // 获取后端请求路径 + for _,b := range g.BackendList.Backend { + if b.BackendID == api.BackendID{ + backendPath = b.BackendPath + flag =true + break + } + } + if !flag && api.BackendID != -1{ + continue + } + apiInfo := Api{ + RequestURL: api.RequestURL, + BackendPath: backendPath, + ProxyURL: api.ProxyURL, + IsRaw:api.IsRaw, + ProxyMethod:api.ProxyMethod, + ProxyParams:api.ProxyParams, + ConstantParams:api.ConstantParams, + } + context := Context{ + GatewayInfo:gateway, + StrategyInfo:strategy, + ApiInfo:apiInfo, + Rate:make(map[string]Rate), + } + for _,method := range api.RequestMethod { + r.Handle(strings.ToUpper(method),path,r.handle,context) + } + } + } + } +} diff --git a/source_code/goku/tree.go b/source_code/goku/tree.go new file mode 100644 index 00000000..e228bf10 --- /dev/null +++ b/source_code/goku/tree.go @@ -0,0 +1,619 @@ +package goku + +import ( + "strings" + "unicode" + "unicode/utf8" + "fmt" +) + +func min(a, b int) int { + if a <= b { + return a + } + return b +} + + +// 计算路径中参数数量 +func countParams(path string) uint8 { + var n uint + for i := 0; i < len(path); i++ { + if path[i] != ':' && path[i] != '*' { + continue + } + n++ + } + if n >= 255 { + return 255 + } + return uint8(n) +} + +type nodeType uint8 + +const ( + static nodeType = iota // default + root + param + catchAll +) + +type node struct { + path string + wildChild bool + nType nodeType + maxParams uint8 + indices string + children []*node + handle Handle + priority uint32 + context *Context +} + + +// 在必要时给定子项和排序的优先级 +func (n *node) incrementChildPrio(pos int) int { + n.children[pos].priority++ + prio := n.children[pos].priority + + newPos := pos + for newPos > 0 && n.children[newPos-1].priority < prio { + n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1] + + newPos-- + } + + if newPos != pos { + n.indices = n.indices[:newPos] + // unchanged prefix, might be empty + n.indices[pos:pos+1] + // the index char we move + n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos' + } + + return newPos +} + +// addRoute将具有给定句柄的节点添加到路径中 +func (n *node) addRoute(path string, handle Handle,context Context) { + fullPath := path + n.priority++ + numParams := countParams(path) + + if len(n.path) > 0 || len(n.children) > 0 { + walk: + for { + // 更新当前节点的 maxParams + if numParams > n.maxParams { + n.maxParams = numParams + } + + i := 0 + max := min(len(path), len(n.path)) + for i < max && path[i] == n.path[i] { + i++ + } + + if i < len(n.path) { + child := node{ + path: n.path[i:], + wildChild: n.wildChild, + nType: static, + indices: n.indices, + children: n.children, + handle: n.handle, + context: n.context, + priority: n.priority - 1, + } + + for i := range child.children { + if child.children[i].maxParams > child.maxParams { + child.maxParams = child.children[i].maxParams + } + } + + n.children = []*node{&child} + n.indices = string([]byte{n.path[i]}) + n.path = path[:i] + n.handle = nil + n.context = &Context{} + n.wildChild = false + } + + if i < len(path) { + path = path[i:] + + if n.wildChild { + n = n.children[0] + n.priority++ + + if numParams > n.maxParams { + n.maxParams = numParams + } + numParams-- + + if len(path) >= len(n.path) && n.path == path[:len(n.path)] && + (len(n.path) >= len(path) || path[len(n.path)] == '/') { + continue walk + } else { + var pathSeg string + if n.nType == catchAll { + pathSeg = path + } else { + pathSeg = strings.SplitN(path, "/", 2)[0] + } + prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path + panic("'" + pathSeg + + "' in new path '" + fullPath + + "' conflicts with existing wildcard '" + n.path + + "' in existing prefix '" + prefix + + "'") + } + } + + c := path[0] + + if n.nType == param && c == '/' && len(n.children) == 1 { + n = n.children[0] + n.priority++ + continue walk + } + + for i := 0; i < len(n.indices); i++ { + if c == n.indices[i] { + i = n.incrementChildPrio(i) + n = n.children[i] + continue walk + } + } + + if c != ':' && c != '*' { + n.indices += string([]byte{c}) + child := &node{ + maxParams: numParams, + } + n.children = append(n.children, child) + n.incrementChildPrio(len(n.indices) - 1) + n = child + } + n.insertChild(numParams, path, fullPath, handle,context) + return + + } else if i == len(path) { + if n.handle != nil { + panic("a handle is already registered for path '" + fullPath + "'") + } + n.handle = handle + n.context = &context + } + return + } + } else { + n.insertChild(numParams, path, fullPath, handle,context) + n.nType = root + } +} + +func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle,context Context) { + var offset int // already handled bytes of the path + + for i, max := 0, len(path); numParams > 0; i++ { + c := path[i] + if c != ':' && c != '*' { + continue + } + + end := i + 1 + for end < max && path[end] != '/' { + switch path[end] { + case ':', '*': + panic("only one wildcard per path segment is allowed, has: '" + + path[i:] + "' in path '" + fullPath + "'") + default: + end++ + } + } + + if len(n.children) > 0 { + panic("wildcard route '" + path[i:end] + + "' conflicts with existing children in path '" + fullPath + "'") + } + + if end-i < 2 { + panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") + } + + if c == ':' { // param + if i > 0 { + n.path = path[offset:i] + offset = i + } + + child := &node{ + nType: param, + maxParams: numParams, + } + n.children = []*node{child} + n.wildChild = true + n = child + n.priority++ + numParams-- + + if end < max { + n.path = path[offset:end] + offset = end + + child := &node{ + maxParams: numParams, + priority: 1, + } + n.children = []*node{child} + n = child + } + + } else { // catchAll + if end != max || numParams > 1 { + panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") + } + + if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { + panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") + } + + i-- + if path[i] != '/' { + panic("no / before catch-all in path '" + fullPath + "'") + } + + n.path = path[offset:i] + + child := &node{ + wildChild: true, + nType: catchAll, + maxParams: 1, + } + n.children = []*node{child} + n.indices = string(path[i]) + n = child + n.priority++ + + child = &node{ + path: path[i:], + nType: catchAll, + maxParams: 1, + handle: handle, + context: &context, + priority: 1, + } + n.children = []*node{child} + + return + } + } + + n.path = path[offset:] + n.handle = handle + n.context = &context +} + +func (n *node) getValue(path string) (handle Handle, p Params, context *Context, tsr bool) { +walk: // outer loop for walking the tree + for { + if len(path) > len(n.path) { + if path[:len(n.path)] == n.path { + path = path[len(n.path):] + if !n.wildChild { + c := path[0] + for i := 0; i < len(n.indices); i++ { + if c == n.indices[i] { + n = n.children[i] + continue walk + } + } + tsr = (path == "/" && n.handle != nil) + fmt.Println(123) + return + } + + // handle wildcard child + n = n.children[0] + switch n.nType { + case param: + // find param end (either '/' or path end) + end := 0 + for end < len(path) && path[end] != '/' { + end++ + } + + // save param value + if p == nil { + // lazy allocation + p = make(Params, 0, n.maxParams) + } + i := len(p) + p = p[:i+1] // expand slice within preallocated capacity + p[i].Key = n.path[1:] + p[i].Value = path[:end] + + // we need to go deeper! + if end < len(path) { + if len(n.children) > 0 { + path = path[end:] + n = n.children[0] + continue walk + } + + // ... but we can't + tsr = (len(path) == end+1) + fmt.Println(456) + return + } + context = n.context + if handle = n.handle; handle != nil { + return + } else if len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists for TSR recommendation + n = n.children[0] + tsr = (n.path == "/" && n.handle != nil) + } + + return + + case catchAll: + // save param value + if p == nil { + // lazy allocation + p = make(Params, 0, n.maxParams) + } + i := len(p) + p = p[:i+1] // expand slice within preallocated capacity + p[i].Key = n.path[2:] + p[i].Value = path + + handle = n.handle + context = n.context + return + + default: + panic("invalid node type") + } + } + } else if path == n.path { + // We should have reached the node containing the handle. + // Check if this node has a handle registered. + context = n.context + if handle = n.handle; handle != nil { + return + } + + if path == "/" && n.wildChild && n.nType != root { + tsr = true + return + } + + // No handle found. Check if a handle for this path + a + // trailing slash exists for trailing slash recommendation + for i := 0; i < len(n.indices); i++ { + if n.indices[i] == '/' { + n = n.children[i] + tsr = (len(n.path) == 1 && n.handle != nil) || + (n.nType == catchAll && n.children[0].handle != nil) + fmt.Println(541) + return + } + } + return + } + // Nothing found. We can recommend to redirect to the same URL with an + // extra trailing slash if a leaf exists for that path + tsr = (path == "/") || + (len(n.path) == len(path)+1 && n.path[len(path)] == '/' && + path == n.path[:len(n.path)-1] && n.handle != nil) + return + } +} + +// Makes a case-insensitive lookup of the given path and tries to find a handler. +// It can optionally also fix trailing slashes. +// It returns the case-corrected path and a bool indicating whether the lookup +// was successful. +func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) { + return n.findCaseInsensitivePathRec( + path, + strings.ToLower(path), + make([]byte, 0, len(path)+1), // preallocate enough memory for new path + [4]byte{}, // empty rune buffer + fixTrailingSlash, + ) +} + +// shift bytes in array by n bytes left +func shiftNRuneBytes(rb [4]byte, n int) [4]byte { + switch n { + case 0: + return rb + case 1: + return [4]byte{rb[1], rb[2], rb[3], 0} + case 2: + return [4]byte{rb[2], rb[3]} + case 3: + return [4]byte{rb[3]} + default: + return [4]byte{} + } +} + +// recursive case-insensitive lookup function used by n.findCaseInsensitivePath +func (n *node) findCaseInsensitivePathRec(path, loPath string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) ([]byte, bool) { + loNPath := strings.ToLower(n.path) + +walk: // outer loop for walking the tree + for len(loPath) >= len(loNPath) && (len(loNPath) == 0 || loPath[1:len(loNPath)] == loNPath[1:]) { + // add common path to result + ciPath = append(ciPath, n.path...) + + if path = path[len(n.path):]; len(path) > 0 { + loOld := loPath + loPath = loPath[len(loNPath):] + + // If this node does not have a wildcard (param or catchAll) child, + // we can just look up the next child node and continue to walk down + // the tree + if !n.wildChild { + // skip rune bytes already processed + rb = shiftNRuneBytes(rb, len(loNPath)) + + if rb[0] != 0 { + // old rune not finished + for i := 0; i < len(n.indices); i++ { + if n.indices[i] == rb[0] { + // continue with child node + n = n.children[i] + loNPath = strings.ToLower(n.path) + continue walk + } + } + } else { + // process a new rune + var rv rune + + // find rune start + // runes are up to 4 byte long, + // -4 would definitely be another rune + var off int + for max := min(len(loNPath), 3); off < max; off++ { + if i := len(loNPath) - off; utf8.RuneStart(loOld[i]) { + // read rune from cached lowercase path + rv, _ = utf8.DecodeRuneInString(loOld[i:]) + break + } + } + + // calculate lowercase bytes of current rune + utf8.EncodeRune(rb[:], rv) + // skipp already processed bytes + rb = shiftNRuneBytes(rb, off) + + for i := 0; i < len(n.indices); i++ { + // lowercase matches + if n.indices[i] == rb[0] { + // must use a recursive approach since both the + // uppercase byte and the lowercase byte might exist + // as an index + if out, found := n.children[i].findCaseInsensitivePathRec( + path, loPath, ciPath, rb, fixTrailingSlash, + ); found { + return out, true + } + break + } + } + + // same for uppercase rune, if it differs + if up := unicode.ToUpper(rv); up != rv { + utf8.EncodeRune(rb[:], up) + rb = shiftNRuneBytes(rb, off) + + for i := 0; i < len(n.indices); i++ { + // uppercase matches + if n.indices[i] == rb[0] { + // continue with child node + n = n.children[i] + loNPath = strings.ToLower(n.path) + continue walk + } + } + } + } + + // Nothing found. We can recommend to redirect to the same URL + // without a trailing slash if a leaf exists for that path + return ciPath, (fixTrailingSlash && path == "/" && n.handle != nil) + } + + n = n.children[0] + switch n.nType { + case param: + // find param end (either '/' or path end) + k := 0 + for k < len(path) && path[k] != '/' { + k++ + } + + // add param value to case insensitive path + ciPath = append(ciPath, path[:k]...) + + // we need to go deeper! + if k < len(path) { + if len(n.children) > 0 { + // continue with child node + n = n.children[0] + loNPath = strings.ToLower(n.path) + loPath = loPath[k:] + path = path[k:] + continue + } + + // ... but we can't + if fixTrailingSlash && len(path) == k+1 { + return ciPath, true + } + return ciPath, false + } + + if n.handle != nil { + return ciPath, true + } else if fixTrailingSlash && len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists + n = n.children[0] + if n.path == "/" && n.handle != nil { + return append(ciPath, '/'), true + } + } + return ciPath, false + + case catchAll: + return append(ciPath, path...), true + + default: + panic("invalid node type") + } + } else { + // We should have reached the node containing the handle. + // Check if this node has a handle registered. + if n.handle != nil { + return ciPath, true + } + + // No handle found. + // Try to fix the path by adding a trailing slash + if fixTrailingSlash { + for i := 0; i < len(n.indices); i++ { + if n.indices[i] == '/' { + n = n.children[i] + if (len(n.path) == 1 && n.handle != nil) || + (n.nType == catchAll && n.children[0].handle != nil) { + return append(ciPath, '/'), true + } + return ciPath, false + } + } + } + return ciPath, false + } + } + + // Nothing found. + // Try to fix the path by adding / removing a trailing slash + if fixTrailingSlash { + if path == "/" { + return ciPath, true + } + if len(loPath)+1 == len(loNPath) && loNPath[len(loPath)] == '/' && + loPath[1:] == loNPath[1:len(loPath)] && n.handle != nil { + return append(ciPath, n.path...), true + } + } + return ciPath, false +} diff --git a/source_code/goku/utils.go b/source_code/goku/utils.go deleted file mode 100644 index 250c8dd5..00000000 --- a/source_code/goku/utils.go +++ /dev/null @@ -1,12 +0,0 @@ -package goku - -import ( - "reflect" -) - -// 判定handler是否是函数类型 -func ValidateHandler(handler Handler) { - if reflect.TypeOf(handler).Kind() != reflect.Func { - panic("goku handler must be a callable func") - } -} diff --git a/source_code/middleware/auth.go b/source_code/middleware/auth.go index 09cea971..027ed47a 100644 --- a/source_code/middleware/auth.go +++ b/source_code/middleware/auth.go @@ -1,24 +1,25 @@ package middleware import ( - "goku-ce/conf" + "goku-ce/goku" "net/http" "strings" "encoding/base64" ) -func Auth(c conf.StrategyInfo,res http.ResponseWriter, req *http.Request) (bool,string) { - if c.Auth == "basic" { +func Auth(context *goku.Context,res http.ResponseWriter, req *http.Request) (bool,string) { + c := context.StrategyInfo + if strings.ToLower(c.Auth) == "basic" { authStr := []byte(c.BasicUserName + ":" + c.BasicUserPassword) authorization := "Basic " + base64.StdEncoding.EncodeToString(authStr) auth := strings.Join(req.Header["Authorization"],", ") if authorization != auth { - return false, "Error username or userpassword" + return false, "Username or UserPassword Error" } - } else if c.Auth == "apikey" { + } else if strings.ToLower(c.Auth) == "apikey" { apiKey := strings.Join(req.Header["Apikey"],", ") if c.ApiKey != apiKey { - return false,"Error apiKey" + return false,"Invalid ApiKey" } } return true,"" diff --git a/source_code/middleware/backend.go b/source_code/middleware/backend.go deleted file mode 100644 index e95cf8fb..00000000 --- a/source_code/middleware/backend.go +++ /dev/null @@ -1,18 +0,0 @@ -package middleware - -import ( - "goku-ce/conf" -) - -func GetBackendInfo(backendID int,b conf.Backend) (bool,conf.BackendInfo) { - flag := false - var backendInfo conf.BackendInfo - for _,i := range b.Backend { - if i.BackendID == backendID { - flag = true - backendInfo = i - break - } - } - return flag,backendInfo -} \ No newline at end of file diff --git a/source_code/middleware/ip_limit.go b/source_code/middleware/ip_limit.go index c2500bd6..c2c352c5 100644 --- a/source_code/middleware/ip_limit.go +++ b/source_code/middleware/ip_limit.go @@ -1,35 +1,33 @@ package middleware import ( - "goku-ce/conf" + "goku-ce/goku" "net/http" "strings" ) -func IPLimit(g conf.GatewayInfo,d conf.StrategyInfo,res http.ResponseWriter, req *http.Request) (bool,string) { +func IPLimit(g *goku.Context,res http.ResponseWriter, req *http.Request) (bool,string) { remoteAddr := req.RemoteAddr remoteIP := InterceptIP(remoteAddr, ":") if !globalIPLimit(g,remoteIP){ - res.WriteHeader(404) - return false,"[global] Illegal ip" - } else if globalIPLimit(g,remoteIP) && !strategyIPLimit(d,remoteIP) { - res.WriteHeader(404) - return false,"[strategy] Illegal ip" + return false,"[Global] Illegal IP" + } else if globalIPLimit(g,remoteIP) && !strategyIPLimit(g,remoteIP) { + return false,"[Strategy] Illegal IP" } return true,"" } -func globalIPLimit(g conf.GatewayInfo,remoteIP string) bool{ - if g.IPLimitType == "black"{ - for _,ip := range g.IPBlackList{ +func globalIPLimit(g *goku.Context,remoteIP string) bool{ + if g.GatewayInfo.IPLimitType == "black"{ + for _,ip := range g.GatewayInfo.IPBlackList{ if ip == remoteIP { return false } } return true - } else if g.IPLimitType == "white" { - for _,ip := range g.IPWhiteList{ + } else if g.GatewayInfo.IPLimitType == "white" { + for _,ip := range g.GatewayInfo.IPWhiteList{ if ip == remoteIP { return true } @@ -39,16 +37,16 @@ func globalIPLimit(g conf.GatewayInfo,remoteIP string) bool{ return true } -func strategyIPLimit(d conf.StrategyInfo,remoteIP string) bool { - if d.IPLimitType == "black" { - for _,ip := range d.IPBlackList{ +func strategyIPLimit(g *goku.Context,remoteIP string) bool { + if g.StrategyInfo.IPLimitType == "black" { + for _,ip := range g.StrategyInfo.IPBlackList{ if ip == remoteIP { return false } } return true - } else if d.IPLimitType == "white" { - for _,ip := range d.IPWhiteList{ + } else if g.StrategyInfo.IPLimitType == "white" { + for _,ip := range g.StrategyInfo.IPWhiteList{ if ip == remoteIP { return true } diff --git a/source_code/middleware/rate.go b/source_code/middleware/rate.go index f75beb50..0f18b469 100644 --- a/source_code/middleware/rate.go +++ b/source_code/middleware/rate.go @@ -1,12 +1,14 @@ package middleware import ( + "fmt" "time" "goku-ce/conf" "goku-ce/goku" ) -func getStrategyRate(c conf.StrategyInfo) (bool,[]conf.RateLimitInfo) { +func getStrategyRate(context *goku.Context) (bool,[]conf.RateLimitInfo) { + c := context.StrategyInfo now := time.Now() flag := false rateLimitList := make([]conf.RateLimitInfo,0) @@ -63,16 +65,18 @@ func timeInPeriod(c conf.RateLimitInfo,now int) bool { return false } -func RateLimit(g *goku.Goku,c conf.StrategyInfo) (bool,string) { - value, ok := g.Rate[c.StrategyID] +func RateLimit(context *goku.Context) (bool,string) { + c := context.StrategyInfo + g := context.Rate + value, ok := g[c.StrategyID] if !ok { var w goku.Rate - g.Rate[c.StrategyID] = w + g[c.StrategyID] = w } if !value.IsInit{ - flag,r := getStrategyRate(c) + flag,r := getStrategyRate(context) if flag == false { - return false,"Don't allow visit!" + return false,"Forbidden Request" } for _,i := range r { if i.Period == "sec" { @@ -87,9 +91,9 @@ func RateLimit(g *goku.Goku,c conf.StrategyInfo) (bool,string) { } value.IsInit = true } else if value.SecLimit.IsNeedReset(){ - flag,r := getStrategyRate(c) + flag,r := getStrategyRate(context) if flag == false { - return false,"Don't allow visit!" + return false,"Forbidden Request" } for _,i := range r { if i.Period == "sec" { @@ -103,58 +107,58 @@ func RateLimit(g *goku.Goku,c conf.StrategyInfo) (bool,string) { } } } - + fmt.Println(time.Now().Format("2006-01-02 15:04:05")) if value.Limit == "day" { if !value.DayLimit.DayLimit() { value.Limit = "day" - g.Rate[c.StrategyID] = value - return false,"Day visit limit exceeded" + g[c.StrategyID] = value + return false,"API Rate Limit of Day Exceeded" } value.Limit = "" } else if value.Limit == "hour" { if !value.HourLimit.HourLimit() { value.Limit = "hour" - g.Rate[c.StrategyID] = value - return false,"Hour visit limit exceeded" + g[c.StrategyID] = value + return false,"API Rate Limit of Hour Exceeded" }else if !value.DayLimit.DayLimit() { value.Limit = "day" - g.Rate[c.StrategyID] = value - return false,"Day visit limit exceeded" + g[c.StrategyID] = value + return false,"API Rate Limit of Day Exceeded" } value.Limit = "" } else if value.Limit == "minute" { if !value.MinuteLimit.MinLimit() { value.Limit = "minute" - g.Rate[c.StrategyID] = value - return false,"Minute visit limit exceeded" + g[c.StrategyID] = value + return false,"API Rate Limit of Minute Exceeded" }else if !value.HourLimit.HourLimit() { value.Limit = "hour" - g.Rate[c.StrategyID] = value - return false,"Hour visit limit exceeded" + g[c.StrategyID] = value + return false,"API Rate Limit of Hour Exceeded" }else if !value.DayLimit.DayLimit() { value.Limit = "day" - g.Rate[c.StrategyID] = value - return false,"Day visit limit exceeded" + g[c.StrategyID] = value + return false,"API Rate Limit of Day Exceeded" } value.Limit = "" } else { if !value.SecLimit.SecLimit() { - g.Rate[c.StrategyID] = value - return false,"Second visit limit exceeded" + g[c.StrategyID] = value + return false,"API Rate Limit of Second Exceeded" }else if !value.MinuteLimit.MinLimit() { value.Limit = "minute" - g.Rate[c.StrategyID] = value - return false,"Minute visit limit exceeded" + g[c.StrategyID] = value + return false,"API Rate Limit of Minute Exceeded" }else if !value.HourLimit.HourLimit() { value.Limit = "hour" - g.Rate[c.StrategyID] = value - return false,"Hour visit limit exceeded" + g[c.StrategyID] = value + return false,"API Rate Limit of Hour Exceeded" }else if !value.DayLimit.DayLimit() { value.Limit = "day" - g.Rate[c.StrategyID] = value - return false,"Day visit limit exceeded" + g[c.StrategyID] = value + return false,"API Rate Limit of Day Exceeded" } } - g.Rate[c.StrategyID] = value + g[c.StrategyID] = value return true,"" } \ No newline at end of file diff --git a/source_code/middleware/request_mapping.go b/source_code/middleware/request_mapping.go index 2ff38ba5..5ea40790 100644 --- a/source_code/middleware/request_mapping.go +++ b/source_code/middleware/request_mapping.go @@ -1,135 +1,90 @@ package middleware import ( - "fmt" "net/http" "strings" "goku-ce/goku" - "goku-ce/conf" "goku-ce/request" + "io/ioutil" ) -func Mapping(g *goku.Goku,res http.ResponseWriter, req *http.Request) (bool,string){ - url := InterceptURL(req.RequestURI,"?") - requestURI := strings.Split(url,"/") - if len(requestURI) == 2 { - if requestURI[1] == "" { - res.WriteHeader(404) - res.Write([]byte("Lack gatewayAlias")) - return false,"Lack gatewayAlias" - } else { - res.WriteHeader(404) - res.Write([]byte("Lack StrategyID")) - return false,"Lack StrategyID" - } +func Mapping(res http.ResponseWriter, req *http.Request,param goku.Params,context *goku.Context) { + // 验证IP是否合法 + f,s := IPLimit(context,res,req) + if !f { + res.WriteHeader(403) + res.Write([]byte(s)) + return } - fmt.Println(url) - gatewayAlias := requestURI[1] - StrategyID := requestURI[2] - urlLen := len(gatewayAlias) + len(StrategyID) + 2 - flag := false - for _,m := range g.ServiceConfig.GatewayList{ - if m.GatewayAlias == gatewayAlias{ - for _,i := range m.StrategyList.Strategy{ - if i.StrategyID == StrategyID{ - flag = true - f,r := IPLimit(m,i,res,req) - if !f { - res.Write([]byte(r)) - return false,r - } - - f,r = Auth(i,res,req) - if !f { - res.Write([]byte(r)) - return false,r - } - - f,r = RateLimit(g,i) - if !f { - res.Write([]byte(r)) - return false,r - } - break - } - } - } - if flag { - for _,i := range m.ApiList.Apis{ - if i.RequestURL == url[urlLen:]{ - // 验证请求 - if !validateRequest(i,req){ - res.WriteHeader(404) - res.Write([]byte("Error Request Method!")) - return false,"Error Request Method!" - } - - // 验证后端信息是否存在 - f,r := GetBackendInfo(i.BackendID,m.BackendList) - if !f { - res.WriteHeader(404) - res.Write([]byte("Backend config is not exist!")) - return false,"Backend config is not exist!" - } - - - _,response,httpResponseHeader := CreateRequest(i,r,req,res) - for key, values := range httpResponseHeader { - for _, value := range values { - res.Header().Add(key,value) - } - } - res.Write(response) - return true,string(response) - } - } - } - } - res.Write([]byte("URI Not Found")) - return false,"URI Not Found" -} - -// 验证协议及请求参数 -func validateRequest(api conf.ApiInfo, req *http.Request) bool{ - flag := false - for _,method := range api.RequestMethod{ - if !(strings.ToUpper(method) == req.Method){ - flag = true - break + f,s = Auth(context,res,req) + if !f { + res.WriteHeader(403) + res.Write([]byte(s)) + return + } + f,s = RateLimit(context) + if !f { + res.WriteHeader(403) + res.Write([]byte(s)) + return + } + statusCode,body,headers := CreateRequest(context,req,res) + for key,values := range headers { + for _,value := range values { + res.Header().Add(key,value) } } - return flag + res.WriteHeader(statusCode) + res.Write(body) + return } // 将请求参数写入请求中 -func CreateRequest(api conf.ApiInfo,i conf.BackendInfo,httpRequest *http.Request,httpResponse http.ResponseWriter) (int,[]byte,map[string][]string) { +func CreateRequest(g *goku.Context,httpRequest *http.Request,httpResponse http.ResponseWriter) (int,[]byte,map[string][]string) { + api := g.ApiInfo var backendHeaders map[string][]string = make(map[string][]string) var backendQueryParams map[string][]string = make(map[string][]string) var backendFormParams map[string][]string = make(map[string][]string) err := httpRequest.ParseForm() if err != nil { - return 500,[]byte("Fail to Parse Args"),make(map[string][]string) + return 500,[]byte("Parsing Arguments Fail"),make(map[string][]string) } backendMethod := strings.ToUpper(api.ProxyMethod) - backenDomain := i.BackendPath + api.ProxyURL + backenDomain := api.BackendPath + api.ProxyURL requ,err := request.Method(backendMethod,backenDomain) for _, reqParam := range api.ProxyParams { - var param []string + var param []string + isFile := false switch reqParam.KeyPosition { - case "header": - param = httpRequest.Header[reqParam.Key] + case "header": + key := parseHeader(reqParam.Key) + param = httpRequest.Header[key] case "body": if httpRequest.Method == "POST" || httpRequest.Method == "PUT" || httpRequest.Method == "PATCH" { param = httpRequest.PostForm[reqParam.Key] + if param == nil { + f,fh,err := httpRequest.FormFile(reqParam.Key) + if err != nil { + panic(err) + } + defer f.Close() + body,err := ioutil.ReadAll(f) + if err != nil { + panic(err) + } + requ.AddFile(reqParam.ProxyKey,fh.Filename,body) + isFile = true + } } else { continue } case "query": param = httpRequest.Form[reqParam.Key] } + if param == nil { - if reqParam.NotEmpty { + if reqParam.NotEmpty && !isFile { return 400, []byte("Missing required parameters"),make(map[string][]string) } else { continue @@ -137,8 +92,9 @@ func CreateRequest(api conf.ApiInfo,i conf.BackendInfo,httpRequest *http.Request } switch reqParam.ProxyKeyPosition { - case "header": - backendHeaders[reqParam.ProxyKey] = param + case "header": + key := parseHeader(reqParam.ProxyKey) + backendHeaders[key] = param case "body": if backendMethod == "POST" || backendMethod == "PUT" || backendMethod == "PATCH" { backendFormParams[reqParam.ProxyKey] = param @@ -150,16 +106,17 @@ func CreateRequest(api conf.ApiInfo,i conf.BackendInfo,httpRequest *http.Request for _, constParam := range api.ConstantParams { switch constParam.Position { - case "header": - backendHeaders[constParam.Key] = []string{constParam.Key} + case "header": + backendHeaders[constParam.Key] = []string{constParam.Value} case "body": - if backendMethod == "POST" || backendMethod == "PUT" || backendMethod == "PATCH" { backendFormParams[constParam.Key] = []string{constParam.Value} } else { backendQueryParams[constParam.Key] = []string{constParam.Value} - } - } + } + case "query": + backendQueryParams[constParam.Key] = []string{constParam.Value} + } } if err != nil{ @@ -170,28 +127,20 @@ func CreateRequest(api conf.ApiInfo,i conf.BackendInfo,httpRequest *http.Request requ.SetHeader(key, values...) } for key, values := range backendQueryParams { - fmt.Println(key) - fmt.Println(values) requ.SetQueryParam(key, values...) } for key, values := range backendFormParams { - fmt.Println(key) - fmt.Println(values) requ.SetFormParam(key, values...) } - if api.ProxyBodyType == "raw" { - requ.SetRawBody([]byte(api.ProxyBody)) - } else if api.ProxyBodyType == "json" { - requ.SetJSON(api.ProxyBody) - } + if api.IsRaw { + body,_ := ioutil.ReadAll(httpRequest.Body) + requ.SetRawBody([]byte(body)) + } - cookies := make(map[string]string) for _, cookie := range httpRequest.Cookies() { cookies[cookie.Name] = cookie.Value } - // requ.SetHeader("Cookie",cookies) - res, err := requ.Send() if err != nil { return 500,[]byte(""),make(map[string][]string) @@ -208,13 +157,16 @@ func CreateRequest(api conf.ApiInfo,i conf.BackendInfo,httpRequest *http.Request return res.StatusCode(), res.Body(),httpResponseHeader } -func InterceptURL(str, substr string) string { - result := strings.Index(str, substr) - var rs string - if result != -1{ - rs = str[:result] - }else { - rs = str +// 修饰请求头 +func parseHeader(header string) string { + headerArray := strings.Split(header,"-") + result := "" + for i,h := range headerArray { + h = strings.Replace(h,"_","",-1) + result += strings.ToUpper(h[0:1]) + strings.ToLower(h[1:]) + if i + 1 < len(headerArray) { + result += "-" + } } - return rs + return result } \ No newline at end of file diff --git a/source_code/request/request.go b/source_code/request/request.go index 24f821b4..c694ff62 100644 --- a/source_code/request/request.go +++ b/source_code/request/request.go @@ -78,7 +78,7 @@ func (this *request) SetQueryParam(key string, values ...string) Request { func Method(method string, urlPath string) (Request, error) { if method != "GET" && method != "POST" && method != "PUT" && method != "DELETE" && method != "HEAD" && method != "OPTIONS" && method != "PATCH" { - return nil, errors.New("method not supported") + return nil, errors.New("Unsupported Request Method") } return newRequest(method, urlPath) } @@ -228,16 +228,14 @@ func (this *request) Send() (res Response, err error) { fmt.Println(err) return } + req.Header.Set("Accept-Encoding", "gzip") req.Header = parseHeaders(this.headers) httpResponse, err := this.client.Do(req) if err != nil { - httpResponse.Body.Close() - fmt.Println(err) return } res, err = newResponse(httpResponse) if err != nil { - fmt.Println(err) return } return diff --git a/source_code/request/response.go b/source_code/request/response.go index 3a94c729..3ec6f6cb 100644 --- a/source_code/request/response.go +++ b/source_code/request/response.go @@ -3,6 +3,8 @@ package request import ( "io/ioutil" "net/http" + "io" + "compress/gzip" ) type Response interface { @@ -24,7 +26,15 @@ type response struct { func newResponse(httpResponse *http.Response) (Response, error) { defer httpResponse.Body.Close() var headers map[string][]string = httpResponse.Header - body, err := ioutil.ReadAll(httpResponse.Body) + var reader io.ReadCloser + switch httpResponse.Header.Get("Content-Encoding") { + case "gzip": + reader, _ = gzip.NewReader(httpResponse.Body) + defer reader.Close() + default: + reader = httpResponse.Body + } + body, err := ioutil.ReadAll(reader) content_length := int64(len(body)) if err != nil { return nil, err -- GitLab