Skip to content

Commit

Permalink
fix merge conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
lonelycode committed Sep 20, 2017
2 parents 950c4fc + 1413dfd commit 55831d3
Show file tree
Hide file tree
Showing 8 changed files with 449 additions and 9 deletions.
2 changes: 2 additions & 0 deletions api_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ func processSpec(spec *APISpec, apisByListen map[string]int,
mwAppendEnabled(&chainArray, &RateCheckMW{BaseMiddleware: baseMid})
mwAppendEnabled(&chainArray, &IPWhiteListMiddleware{BaseMiddleware: baseMid})
mwAppendEnabled(&chainArray, &OrganizationMonitor{BaseMiddleware: baseMid})
mwAppendEnabled(&chainArray, &RateLimitForAPI{BaseMiddleware: baseMid})
mwAppendEnabled(&chainArray, &MiddlewareContextVars{BaseMiddleware: baseMid})
mwAppendEnabled(&chainArray, &VersionCheck{BaseMiddleware: baseMid})
mwAppendEnabled(&chainArray, &RequestSizeLimitMiddleware{baseMid})
Expand Down Expand Up @@ -453,6 +454,7 @@ func processSpec(spec *APISpec, apisByListen map[string]int,

mwAppendEnabled(&chainArray, &KeyExpired{baseMid})
mwAppendEnabled(&chainArray, &AccessRightsCheck{baseMid})
mwAppendEnabled(&chainArray, &RateLimitForAPI{BaseMiddleware: baseMid})
mwAppendEnabled(&chainArray, &RateLimitAndQuotaCheck{baseMid})
mwAppendEnabled(&chainArray, &GranularAccessMiddleware{baseMid})
mwAppendEnabled(&chainArray, &TransformMiddleware{baseMid})
Expand Down
6 changes: 6 additions & 0 deletions apidef/api_definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,12 @@ type APIDefinition struct {
EnableContextVars bool `bson:"enable_context_vars" json:"enable_context_vars"`
ConfigData map[string]interface{} `bson:"config_data" json:"config_data"`
TagHeaders map[string]interface{} `bson:"tag_headers" json:"tag_headers"`
GlobalRateLimit GlobalRateLimit `bson:"global_rate_limit" json:"global_rate_limit"`
}

type GlobalRateLimit struct {
Rate float64 `bson:"rate" json:"rate"`
Per float64 `bson:"per" json:"per"`
}

type BundleManifest struct {
Expand Down
1 change: 1 addition & 0 deletions apidef/importer/importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type APIImporter interface {
LoadFrom(io.Reader) error
ConvertIntoApiVersion(bool) (apidef.VersionInfo, error)
InsertIntoAPIDefinitionAsVersion(apidef.VersionInfo, *apidef.APIDefinition, string) error
ToAPIDefinition(string, string, bool) (*apidef.APIDefinition, error)
}

type APIImporterSource string
Expand Down
198 changes: 198 additions & 0 deletions apidef/importer/importer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package importer

import (
"bytes"
"testing"
)

func TestToAPIDefinition_Swagger(t *testing.T) {
imp, err := GetImporterForSource(SwaggerSource)
if err != nil {
t.Fatal(err)
}

buff := bytes.NewBufferString(petstoreJSON)

err = imp.LoadFrom(buff)
if err != nil {
t.Fatal(err)
}

def, err := imp.ToAPIDefinition("testOrg", "http://test.com", false)

if err != nil {
t.Fatal(err)
}

if def.VersionData.NotVersioned {
t.Fatal("Swagger import must always be versioned")
}

if len(def.VersionData.Versions) > 1 {
t.Fatal("THere should only be one version")
}

v, ok := def.VersionData.Versions["1.0.0"]
if !ok {
t.Fatal("Version could not be found")
}

if len(v.ExtendedPaths.TrackEndpoints) != 3 {
t.Fatalf("Expected 3 endpoints, found %v\n", len(v.ExtendedPaths.TrackEndpoints))
}

}

var petstoreJSON string = `{
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore",
"license": {
"name": "MIT"
}
},
"host": "petstore.swagger.io",
"basePath": "/v1",
"schemes": [
"http"
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/pets": {
"get": {
"summary": "List all pets",
"operationId": "listPets",
"tags": [
"pets"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "How many items to return at one time (max 100)",
"required": false,
"type": "integer",
"format": "int32"
}
],
"responses": {
"200": {
"description": "An paged array of pets",
"headers": {
"x-next": {
"type": "string",
"description": "A link to the next page of responses"
}
},
"schema": {
"$ref": "#/definitions/Pets"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"post": {
"summary": "Create a pet",
"operationId": "createPets",
"tags": [
"pets"
],
"responses": {
"201": {
"description": "Null response"
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
"/pets/{petId}": {
"get": {
"summary": "Info for a specific pet",
"operationId": "showPetById",
"tags": [
"pets"
],
"parameters": [
{
"name": "petId",
"in": "path",
"required": true,
"description": "The id of the pet to retrieve",
"type": "string"
}
],
"responses": {
"200": {
"description": "Expected response to a valid request",
"schema": {
"$ref": "#/definitions/Pets"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
}
},
"definitions": {
"Pet": {
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"tag": {
"type": "string"
}
}
},
"Pets": {
"type": "array",
"items": {
"$ref": "#/definitions/Pet"
}
},
"Error": {
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
}
}
}
}`
13 changes: 5 additions & 8 deletions apidef/importer/swagger.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,13 @@ func (s *SwaggerAST) ConvertIntoApiVersion(asMock bool) (apidef.VersionInfo, err

versionInfo.UseExtendedPaths = true
versionInfo.Name = s.Info.Version
versionInfo.ExtendedPaths.WhiteList = make([]apidef.EndPointMeta, 0)
versionInfo.ExtendedPaths.TrackEndpoints = make([]apidef.TrackEndpointMeta, 0)

if len(s.Paths) == 0 {
return versionInfo, errors.New("no paths defined in swagger file")
}
for pathName, pathSpec := range s.Paths {
newEndpointMeta := apidef.EndPointMeta{}
newEndpointMeta.MethodActions = make(map[string]apidef.EndpointMethodMeta)
newEndpointMeta := apidef.TrackEndpointMeta{}
newEndpointMeta.Path = pathName

// We just want the paths here, no mocks
Expand All @@ -112,12 +111,10 @@ func (s *SwaggerAST) ConvertIntoApiVersion(asMock bool) (apidef.VersionInfo, err
if len(m.Responses) == 0 && m.Description == "" && m.OperationID == "" {
continue
}
methodAction := apidef.EndpointMethodMeta{}
methodAction.Action = apidef.NoAction
newEndpointMeta.MethodActions[methodName] = methodAction
}

versionInfo.ExtendedPaths.WhiteList = append(versionInfo.ExtendedPaths.WhiteList, newEndpointMeta)
newEndpointMeta.Method = methodName
versionInfo.ExtendedPaths.TrackEndpoints = append(versionInfo.ExtendedPaths.TrackEndpoints, newEndpointMeta)
}
}

return versionInfo, nil
Expand Down
75 changes: 75 additions & 0 deletions mw_api_rate_limit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"errors"
"fmt"
"net/http"

"github.com/Sirupsen/logrus"
)

// RateLimitAndQuotaCheck will check the incomming request and key whether it is within it's quota and
// within it's rate limit, it makes use of the SessionLimiter object to do this
type RateLimitForAPI struct {
BaseMiddleware
keyName string
apiSess *SessionState
}

func (k *RateLimitForAPI) Name() string {
return "RateLimitForAPI"
}

func (k *RateLimitForAPI) EnabledForSpec() bool {
if k.Spec.DisableRateLimit || k.Spec.GlobalRateLimit.Rate == 0 {
return false
}

// We'll init here
k.keyName = fmt.Sprintf("apilimiter-%s%s", k.Spec.OrgID, k.Spec.APIID)
k.apiSess = &SessionState{
Rate: k.Spec.GlobalRateLimit.Rate,
Per: k.Spec.GlobalRateLimit.Per,
LastUpdated: "na",
}

return true
}

func (k *RateLimitForAPI) handleRateLimitFailure(r *http.Request, token string) (error, int) {
log.WithFields(logrus.Fields{
"path": r.URL.Path,
"origin": requestIP(r),
"key": token,
}).Info("API rate limit exceeded.")

// Fire a rate limit exceeded event
k.FireEvent(EventRateLimitExceeded, EventRateLimitExceededMeta{
EventMetaDefault: EventMetaDefault{Message: "API Rate Limit Exceeded", OriginatingRequest: EncodeRequestToEvent(r)},
Path: r.URL.Path,
Origin: requestIP(r),
Key: token,
})

// Report in health check
reportHealthValue(k.Spec, Throttle, "-1")

return errors.New("API Rate limit exceeded"), 429
}

// ProcessRequest will run any checks on the request on the way through the system, return an error to have the chain fail
func (k *RateLimitForAPI) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) {
storeRef := k.Spec.SessionManager.Store()
reason := sessionLimiter.ForwardMessage(k.apiSess,
k.keyName,
storeRef,
true,
false)

if reason == sessionFailRateLimit {
return k.handleRateLimitFailure(r, k.keyName)
}

// Request is valid, carry on
return nil, 200
}
Loading

0 comments on commit 55831d3

Please sign in to comment.