Merge pull request #49 from alexsong93/bufferLimit
Increase in-memory buffer size
diff --git a/README.md b/README.md
index 4fe1392..2a59f2f 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@
This command starts an API Gateway that subscribes to the Redis instance with the specified host and port. The `REDIS_PASS` variable is optional and is required only when redis needs authentication.
-On startup, the API Gateway looks for pre-existing resources in redis, whose keys are defined as `resources:<namespace>:<resource>`, and creates nginx conf files associated with those resources. Then, it listens for any resource key changes in redis and updates nginx conf files appropriately. These conf files are stored in the running docker container at `/etc/api-gateway/managed_confs/<namespace>/<resource>.conf`.
+On startup, the API Gateway subscribes to redis and listens for changes in keys that are defined as `resources:<namespace>:<resourcePath>`.
## Routes
See [here](doc/routes.md) for the management interface for creating tenants/APIs. For detailed API policy definitions, see [here](doc/policies.md).
@@ -50,12 +50,7 @@
make docker-run PUBLIC_MANAGEDURL_HOST=<mangedurl_host> PUBLIC_MANAGEDURL_PORT=<managedurl_port> \
REDIS_HOST=<redis_host> REDIS_PORT=<redis_port> REDIS_PASS=<redis_pass>
```
-
- The main API Gateway process is exposed to port `80`. To test that the Gateway works see its `health-check`:
- ```
- $ curl http://<docker_host_ip>/health-check
- API-Platform is running!
- ```
+
### Testing
diff --git a/api-gateway-config/conf.d/managed_endpoints.conf b/api-gateway-config/conf.d/managed_endpoints.conf
index 6edacd0..37a5ed5 100644
--- a/api-gateway-config/conf.d/managed_endpoints.conf
+++ b/api-gateway-config/conf.d/managed_endpoints.conf
@@ -58,11 +58,22 @@
';
}
+ location ~ "^/api/([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\/\-\_\{\} ]+)(\\b)" {
+ set $upstream https://172.17.0.1;
+ set $tenant $1;
+ set $backendUrl '';
+ set $gatewayPath $2;
+ add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS';
+ access_by_lua_block {
+ local routing = require "routing"
+ routing.processCall()
+ }
+ proxy_pass $upstream;
+ }
+
location = /health {
proxy_pass http://0.0.0.0:9000/v1/health-check;
}
-
- include /etc/api-gateway/managed_confs/*/*.conf;
}
diff --git a/api-gateway-config/scripts/lua/lib/redis.lua b/api-gateway-config/scripts/lua/lib/redis.lua
index 6ba3853..37491ca 100644
--- a/api-gateway-config/scripts/lua/lib/redis.lua
+++ b/api-gateway-config/scripts/lua/lib/redis.lua
@@ -23,13 +23,11 @@
-- @author Alex Song (songs)
local cjson = require "cjson"
-local filemgmt = require "lib/filemgmt"
local utils = require "lib/utils"
local logger = require "lib/logger"
local request = require "lib/request"
local REDIS_FIELD = "resources"
-local BASE_CONF_DIR = "/etc/api-gateway/managed_confs/"
local _M = {}
@@ -44,7 +42,7 @@
-- @param timeout redis timeout in milliseconds
function _M.init(host, port, password, timeout)
local redis = require "resty.redis"
- local red = redis:new()
+ local red = redis:new()
red:set_timeout(timeout)
-- Connect to Redis server
local retryCount = 4
@@ -215,18 +213,19 @@
return resourceObj
end
---- Get all resource keys in redis
+--- Get all resource keys for a tenant in redis
-- @param red redis client instance
-function getAllResourceKeys(red)
+-- @param tenantId tenant id
+function _M.getAllResourceKeys(red, tenantId)
-- Find all resourceKeys in redis
- local resources, err = red:scan(0, "match", "resources:*:*")
+ local resources, err = red:scan(0, "match", utils.concatStrings({"resources:", tenantId, ":*"}))
if not resources then
request.err(500, util.concatStrings({"Failed to retrieve resource keys: ", err}))
end
local cursor = resources[1]
local resourceKeys = resources[2]
while cursor ~= "0" do
- resources, err = red:scan(cursor, "match", "resources:*:*")
+ resources, err = red:scan(cursor, "match", utils.concatStrings({"resources:", tenantId, ":*"}))
if not resources then
request.err(500, util.concatStrings({"Failed to retrieve resource keys: ", err}))
end
@@ -353,97 +352,9 @@
end
end
------------------------------------
-------- Pub/Sub with Redis --------
------------------------------------
-
-local syncStatus = false
---- Sync with redis on startup and create conf files for resources that are already in redis
--- @param red redis client instance
-function _M.syncWithRedis(red)
- logger.info("Sync with redis in progress...")
- setSyncStatus(true)
- local resourceKeys = getAllResourceKeys(red)
- for k, resourceKey in pairs(resourceKeys) do
- local prefix, tenant, gatewayPath = resourceKey:match("([^,]+):([^,]+):([^,]+)")
- local resourceObj = _M.getResource(red, resourceKey, REDIS_FIELD)
- filemgmt.createResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath), resourceObj)
- end
- os.execute("/usr/local/sbin/nginx -s reload")
- setSyncStatus(false)
- logger.info("All resources synced.")
-end
-
-function setSyncStatus(status)
- syncStatus = status
-end
-
-function getSyncStatus()
- return syncStatus
-end
-
---- Subscribe to redis
--- @param redisSubClient the redis client that is listening for the redis key changes
--- @param redisGetClient the redis client that gets the changed resource to update the conf file
-function _M.subscribe(redisSubClient, redisGetClient)
- logger.info("Subscribed to redis and listening for key changes...")
- -- Subscribe to redis using psubscribe
- local ok, err = redisSubClient:config("set", "notify-keyspace-events", "KEA")
- if not ok then
- request.err(500, utils.concatStrings({"Failed to subscribe to redis: ", err}))
- end
- ok, err = redisSubClient:psubscribe("__keyspace@0__:resources:*:*")
- if not ok then
- request.err(500, utils.concatStrings({"Failed to subscribe to redis: ", err}))
- end
- -- Update nginx conf file when redis is updated
- local redisUpdated = false
- local startTime = ngx.now()
- while true do
- local res, err = redisSubClient:read_reply()
- if not res then
- if err ~= "timeout" then
- request.err(500, utils.concatStrings({"Failed to read from redis: ", err}))
- end
- else
- -- res[3] format is "__keyspace@0__:resources:<tenantId>:<gatewayPath>"
- local keyspacePrefix, resourcePrefix, tenant, gatewayPath = res[3]:match("([^,]+):([^,]+):([^,]+):([^,]+)")
- local redisKey = utils.concatStrings({resourcePrefix, ":", tenant, ":", gatewayPath})
- -- Don't allow single quotes in the gateway path
- if string.match(gatewayPath, "'") then
- logger.debug(utils.concatStrings({"Redis key \"", redisKey, "\" contains illegal character \"'\"."}))
- else
- local resourceObj = _M.getResource(redisGetClient, redisKey, REDIS_FIELD)
- if resourceObj == nil then
- logger.debug(utils.concatStrings({"Redis key deleted: ", redisKey}))
- local fileLocation = filemgmt.deleteResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath))
- logger.debug(utils.concatStrings({"Deleted file: ", fileLocation}))
- else
- logger.debug(utils.concatStrings({"Redis key updated: ", redisKey}))
- local fileLocation = filemgmt.createResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath), resourceObj)
- logger.debug(utils.concatStrings({"Updated file: ", fileLocation}))
- end
- redisUpdated = true
- end
- end
- -- reload Nginx only if redis has been updated and it has been at least 1 second since last reload
- local timeDiff = ngx.now() - startTime
- if(redisUpdated == true and timeDiff >= 1) then
- os.execute("/usr/local/sbin/nginx -s reload")
- logger.info("Nginx reloaded.")
- redisUpdated = false
- startTime = ngx.now()
- end
- end
-end
-
---- Get gateway sync status
+--- Check health of gateway
function _M.healthCheck()
- if getSyncStatus() == true then
- request.success(503, "Status: Gateway syncing.")
- else
- request.success(200, "Status: Gateway ready.")
- end
+ request.success(200, "Status: Gateway ready.")
end
return _M
\ No newline at end of file
diff --git a/api-gateway-config/scripts/lua/lib/utils.lua b/api-gateway-config/scripts/lua/lib/utils.lua
index cdaec4e..d757c39 100644
--- a/api-gateway-config/scripts/lua/lib/utils.lua
+++ b/api-gateway-config/scripts/lua/lib/utils.lua
@@ -84,10 +84,23 @@
end)
end
+--- Check if element exists in table as value
+-- @param table table to check
+-- @param element element to check in table
+function tableContains(table, element)
+ for i, value in pairs(table) do
+ if value == element then
+ return true
+ end
+ end
+ return false
+end
+
_Utils.concatStrings = concatStrings
_Utils.serializeTable = serializeTable
_Utils.convertTemplatedPathParam = convertTemplatedPathParam
_Utils.uuid = uuid
+_Utils.tableContains = tableContains
return _Utils
diff --git a/api-gateway-config/scripts/lua/management.lua b/api-gateway-config/scripts/lua/management.lua
index 05cb3d3..ad87c0b 100644
--- a/api-gateway-config/scripts/lua/management.lua
+++ b/api-gateway-config/scripts/lua/management.lua
@@ -44,7 +44,7 @@
--------------------------
--- Add an api to the Gateway
--- PUT /APIs
+-- PUT /v1/apis
-- body:
-- {
-- "name": *(String) name of API
@@ -83,6 +83,7 @@
end
-- Format basePath
local basePath = decoded.basePath:sub(1,1) == '/' and decoded.basePath:sub(2) or decoded.basePath
+ basePath = basePath:sub(-1) == '/' and basePath:sub(1, -2) or basePath
-- Create managedUrl object
local uuid = existingAPI ~= nil and existingAPI.id or utils.uuid()
local managedUrl = utils.concatStrings({"http://", MANAGEDURL_HOST, ":", MANAGEDURL_PORT, "/api/", decoded.tenantId})
@@ -101,7 +102,8 @@
managedUrlObj = redis.addAPI(red, uuid, managedUrlObj, existingAPI)
-- Add resources to redis
for path, resource in pairs(decoded.resources) do
- local gatewayPath = utils.concatStrings({basePath, ngx.escape_uri(path)})
+ local gatewayPath = utils.concatStrings({basePath, path})
+ gatewayPath = (gatewayPath:sub(1,1) == '/') and gatewayPath:sub(2) or gatewayPath
addResource(red, resource, gatewayPath, decoded.tenantId)
end
redis.close(red)
@@ -133,63 +135,95 @@
return false, { statusCode = 404, message = utils.concatStrings({"Unknown tenant id ", object }) }
end
end
- -- Additional checks for resource object
if field == "resources" then
- local resources = object
- if next(object) == nil then
- return false, { statusCode = 400, message = "Empty resources object." }
- end
- for path, resource in pairs(resources) do
- -- Check resource path for illegal characters
- if string.match(path, "'") then
- return false, { statusCode = 400, message = "resource path contains illegal character \"'\"." }
- end
- -- Check that resource path begins with slash
- if path:sub(1,1) ~= '/' then
- return false, { statusCode = 400, message = "Resource path must begin with '/'." }
- end
- -- Check operations object
- if not resource.operations or next(resource.operations) == nil then
- return false, { statusCode = 400, message = "Missing or empty field 'operations' or in resource path object." }
- end
- for verb, verbObj in pairs(resource.operations) do
- local allowedVerbs = {GET=true, POST=true, PUT=true, DELETE=true, PATCH=true, HEAD=true, OPTIONS=true}
- if allowedVerbs[verb:upper()] == nil then
- return false, { statusCode = 400, message = utils.concatStrings({"Resource verb '", verb, "' not supported."}) }
- end
- -- Check required fields
- local requiredFields = {"backendMethod", "backendUrl"}
- for k, v in pairs(requiredFields) do
- if verbObj[v] == nil then
- return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", v, "' for '", verb, "' operation."}) }
- end
- if v == "backendMethod" then
- local backendMethod = verbObj[v]
- if allowedVerbs[backendMethod:upper()] == nil then
- return false, { statusCode = 400, message = utils.concatStrings({"backendMethod '", backendMethod, "' not supported."}) }
- end
- end
- end
- -- Check optional fields
- local policies = verbObj.policies
- if policies then
- for k, v in pairs(policies) do
- if v.type == nil then
- return false, { statusCode = 400, message = "Missing field 'type' in policy object." }
- end
- end
- end
- local security = verbObj.security
- if security and security.type == nil then
- return false, { statusCode = 400, message = "Missing field 'type' in security object." }
- end
- end
+ local res, err = checkResources(object)
+ if res ~= nil and res == false then
+ return res, err
end
end
-- All error checks passed
return true
end
+--- Error checking for resources
+-- @param resources resources object
+function checkResources(resources)
+ if next(resources) == nil then
+ return false, { statusCode = 400, message = "Empty resources object." }
+ end
+ for path, resource in pairs(resources) do
+ -- Check resource path for illegal characters
+ if string.match(path, "'") then
+ return false, { statusCode = 400, message = "resource path contains illegal character \"'\"." }
+ end
+ -- Check that resource path begins with slash
+ if path:sub(1,1) ~= '/' then
+ return false, { statusCode = 400, message = "Resource path must begin with '/'." }
+ end
+ -- Check operations object
+ local res, err = checkOperations(resource.operations)
+ if res ~= nil and res == false then
+ return res, err
+ end
+ end
+end
+
+--- Error checking for operations
+-- @param operations operations object
+function checkOperations(operations)
+ if not operations or next(operations) == nil then
+ return false, { statusCode = 400, message = "Missing or empty field 'operations' or in resource path object." }
+ end
+ local allowedVerbs = {GET=true, POST=true, PUT=true, DELETE=true, PATCH=true, HEAD=true, OPTIONS=true}
+ for verb, verbObj in pairs(operations) do
+ if allowedVerbs[verb:upper()] == nil then
+ return false, { statusCode = 400, message = utils.concatStrings({"Resource verb '", verb, "' not supported."}) }
+ end
+ -- Check required fields
+ local requiredFields = {"backendMethod", "backendUrl"}
+ for k, v in pairs(requiredFields) do
+ if verbObj[v] == nil then
+ return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", v, "' for '", verb, "' operation."}) }
+ end
+ if v == "backendMethod" then
+ local backendMethod = verbObj[v]
+ if allowedVerbs[backendMethod:upper()] == nil then
+ return false, { statusCode = 400, message = utils.concatStrings({"backendMethod '", backendMethod, "' not supported."}) }
+ end
+ end
+ end
+ -- Check optional fields
+ local res, err = checkOptionalPolicies(verbObj.policies, verbObj.security)
+ if res ~= nil and res == false then
+ return res, err
+ end
+ end
+end
+
+--- Error checking for policies and security
+-- @param policies policies object
+-- @param security security object
+function checkOptionalPolicies(policies, security)
+ if policies then
+ for k, v in pairs(policies) do
+ local validTypes = {reqMapping = true, rateLimit = true}
+ if (v.type == nil or v.value == nil) then
+ return false, { statusCode = 400, message = "Missing field in policy object. Need \"type\" and \"scope\"." }
+ elseif validTypes[v.type] == nil then
+ return false, { statusCode = 400, message = "Invalid type in policy object. Valid: \"reqMapping\", \"rateLimit\"" }
+ end
+ end
+ end
+ if security then
+ local validScopes = {tenant=true, api=true, resource=true}
+ if (security.type == nil or security.scope == nil) then
+ return false, { statusCode = 400, message = "Missing field in security object. Need \"type\" and \"scope\"." }
+ elseif validScopes[security.scope] == nil then
+ return false, { statusCode = 400, message = "Invalid scope in security object. Valid: \"tenant\", \"api\", \"resource\"." }
+ end
+ end
+end
+
--- Helper function for adding a resource to redis and creating an nginx conf file
-- @param red
-- @param resource
@@ -197,7 +231,7 @@
-- @param tenantId
function addResource(red, resource, gatewayPath, tenantId)
-- Create resource object and add to redis
- local redisKey = utils.concatStrings({"resources", ":", tenantId, ":", ngx.unescape_uri(gatewayPath)})
+ local redisKey = utils.concatStrings({"resources", ":", tenantId, ":", gatewayPath})
local apiId
local operations
for k, v in pairs(resource) do
@@ -212,7 +246,7 @@
end
--- Get one or all APIs from the gateway
--- GET /APIs
+-- GET /v1/apis
function _M.getAPIs()
local uri = string.gsub(ngx.var.request_uri, "?.*", "")
local id
@@ -289,7 +323,7 @@
end
--- Delete API from gateway
--- DELETE /APIs/<id>
+-- DELETE /v1/apis/<id>
function _M.deleteAPI()
local uri = string.gsub(ngx.var.request_uri, "?.*", "")
local index = 1
@@ -313,7 +347,8 @@
-- Delete all resources for the API
local basePath = api.basePath:sub(2)
for path, v in pairs(api.resources) do
- local gatewayPath = utils.concatStrings({basePath, ngx.escape_uri(path)})
+ local gatewayPath = utils.concatStrings({basePath, path})
+ gatewayPath = (gatewayPath:sub(1,1) == '/') and gatewayPath:sub(2) or gatewayPath
deleteResource(red, gatewayPath, api.tenantId)
end
redis.close(red)
@@ -325,7 +360,7 @@
-- @param gatewayPath path in gateway
-- @param tenantId tenant id
function deleteResource(red, gatewayPath, tenantId)
- local redisKey = utils.concatStrings({"resources:", tenantId, ":", ngx.unescape_uri(gatewayPath)})
+ local redisKey = utils.concatStrings({"resources:", tenantId, ":", gatewayPath})
redis.deleteResource(red, redisKey, REDIS_FIELD)
end
@@ -334,7 +369,7 @@
-----------------------------
--- Add a tenant to the Gateway
--- PUT /Tenants
+-- PUT /v1/tenants
-- body:
-- {
-- "namespace": *(String) tenant namespace
@@ -382,7 +417,7 @@
end
--- Get one or all tenants from the gateway
--- GET /Tenants
+-- GET /v1/tenants
function _M.getTenants()
local uri = string.gsub(ngx.var.request_uri, "?.*", "")
local id
@@ -461,7 +496,7 @@
end
--- Delete tenant from gateway
--- DELETE /Tenants/<id>
+-- DELETE /v1/tenants/<id>
function _M.deleteTenant()
local uri = string.gsub(ngx.var.request_uri, "?.*", "")
local index = 1
@@ -485,25 +520,20 @@
----- Pub/Sub with Redis -----
------------------------------
---- Sync with redis
--- GET /v1/sync
-function _M.sync()
- local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
- logger.info(utils.concatStrings({"Connected to redis at ", REDIS_HOST, ":", REDIS_PORT}))
- redis.syncWithRedis(red)
- ngx.exit(200)
-end
-
--- Subscribe to redis
-- GET /v1/subscribe
function _M.subscribe()
- local redisGetClient = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
- local redisSubClient = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
- redis.subscribe(redisSubClient, redisGetClient)
+ redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS)
+ logger.info(utils.concatStrings({"Connected to redis at ", REDIS_HOST, ":", REDIS_PORT}))
+ while true do end
ngx.exit(200)
end
---- Get gateway sync status
+----------------------------
+------- Health Check -------
+----------------------------
+
+--- Check health of gateway
function _M.healthCheck()
redis.healthCheck()
end
@@ -518,9 +548,9 @@
-- {
-- key: *(String) key for tenant/api/resource
-- scope: *(String) tenant or api or resource
--- tenant: *(String) tenant id
+-- tenantId: *(String) tenant id
-- resource: (String) url-encoded resource path
--- api: (String) api id
+-- apiId: (String) api id
-- }
function _M.addSubscription()
-- Validate body and create redisKey
@@ -539,9 +569,9 @@
-- {
-- key: *(String) key for tenant/api/resource
-- scope: *(String) tenant or api or resource
--- tenant: *(String) tenant id
+-- tenantId: *(String) tenant id
-- resource: (String) url-encoded resource path
--- api: (String) api id
+-- apiId: (String) api id
-- }
function _M.deleteSubscription()
-- Validate body and create redisKey
@@ -560,19 +590,14 @@
function validateSubscriptionBody()
-- Read in the PUT JSON Body
ngx.req.read_body()
- local args = ngx.req.get_post_args()
+ local args = ngx.req.get_body_data()
if not args then
request.err(400, "Missing request body.")
end
-- Convert json into Lua table
- local decoded
- if next(args) then
- decoded = utils.convertJSONBody(args)
- else
- request.err(400, "Request body required.")
- end
+ local decoded = cjson.decode(args)
-- Check required fields
- local requiredFieldList = {"key", "scope", "tenant"}
+ local requiredFieldList = {"key", "scope", "tenantId"}
for i, field in ipairs(requiredFieldList) do
if not decoded[field] then
request.err(400, utils.concatStrings({"\"", field, "\" missing from request body."}))
@@ -582,7 +607,7 @@
local resource = decoded.resource
local apiId = decoded.apiId
local redisKey
- local prefix = utils.concatStrings({"subscriptions:tenant:", decoded.tenant})
+ local prefix = utils.concatStrings({"subscriptions:tenant:", decoded.tenantId})
if decoded.scope == "tenant" then
redisKey = prefix
elseif decoded.scope == "resource" then
diff --git a/api-gateway-config/scripts/lua/policies/mapping.lua b/api-gateway-config/scripts/lua/policies/mapping.lua
index 6748fe3..97f7c71 100644
--- a/api-gateway-config/scripts/lua/policies/mapping.lua
+++ b/api-gateway-config/scripts/lua/policies/mapping.lua
@@ -70,7 +70,7 @@
--- Insert parameter value to header, body, or query params into request
-- @param m Parameter value to add to request
function insertParam(m)
- local v = nil
+ local v
local k = m.to.name
if m.from.value ~= nil then
v = m.from.value
@@ -81,7 +81,7 @@
elseif m.from.location == 'body' then
v = body[m.from.name]
elseif m.from.location == 'path' then
- v = ngx.var[utils.concatStrings({'path_', m.from.name})]
+ v = ngx.ctx[m.from.name]
end
-- determine to where
if m.to.location == 'header' then
@@ -208,8 +208,8 @@
function insertPath(k, v)
v = ngx.unescape_uri(v)
- local primedUri = path:gsub("%{(%w*)%}", v)
- ngx.req.set_uri(primedUri)
+ path = path:gsub(utils.concatStrings({"%{", k ,"%}"}), v)
+ ngx.req.set_uri(path)
end
function removeHeader(k)
diff --git a/api-gateway-config/scripts/lua/routing.lua b/api-gateway-config/scripts/lua/routing.lua
index d1b45ec..005e35e 100644
--- a/api-gateway-config/scripts/lua/routing.lua
+++ b/api-gateway-config/scripts/lua/routing.lua
@@ -22,27 +22,37 @@
-- Used to dynamically handle nginx routing based on an object containing implementation details
-- @author Cody Walker (cmwalker), Alex Song (songs)
-
+local cjson = require "cjson"
local utils = require "lib/utils"
local request = require "lib/request"
+local redis = require "lib/redis"
local url = require "url"
-- load policies
local security = require "policies/security"
local mapping = require "policies/mapping"
local rateLimit = require "policies/rateLimit"
+local logger = require "lib/logger"
+
+local REDIS_HOST = os.getenv("REDIS_HOST")
+local REDIS_PORT = os.getenv("REDIS_PORT")
+local REDIS_PASS = os.getenv("REDIS_PASS")
local _M = {}
--- Main function that handles parsing of invocation details and carries out implementation
--- @param obj Lua table object containing implementation details for the given resource
--- {
--- {{GatewayMethod (GET / PUT / POST / DELETE)}} = {
--- "backendMethod": (GET / PUT / POST / DELETE) - Method to use for invocation (if different from gatewayMethod),
--- "backendUrl": STRING - fully composed url of backend invocation,
--- "policies": LIST - list of table objects containing type and value fields
--- }, ...
--- }
-function processCall(obj)
+function processCall()
+ -- Get resource object from redis
+ local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
+ local redisKey = utils.concatStrings({"resources:", ngx.var.tenant, ":", ngx.var.gatewayPath})
+ local obj = redis.getResource(red, redisKey, "resources")
+ -- Check for path parameters
+ if obj == nil then
+ obj = checkForPathParams(red)
+ if obj == nil then
+ return request.err(404, 'Not found.')
+ end
+ end
+ obj = cjson.decode(obj)
local found = false
for verb, opFields in pairs(obj.operations) do
if string.upper(verb) == ngx.req.get_method() then
@@ -78,6 +88,33 @@
end
end
+--- Check redis for path parameters
+-- @param red redis client instance
+function checkForPathParams(red)
+ local resourceKeys = redis.getAllResourceKeys(red, ngx.var.tenant)
+ for i, key in pairs(resourceKeys) do
+ local res = {string.match(key, "([^,]+):([^,]+):([^,]+)")}
+ local path = res[3] -- gatewayPath portion of redis key
+ local pathParamVars = {}
+ for w in string.gfind(path, "({%w+})") do
+ w = string.gsub(w, "{", "")
+ w = string.gsub(w, "}", "")
+ pathParamVars[#pathParamVars + 1] = w
+ end
+ if next(pathParamVars) ~= nil then
+ local pathPattern, count = string.gsub(path, "%{(%w*)%}", "([^,]+)")
+ local obj = {string.match(ngx.var.gatewayPath, pathPattern)}
+ if (#obj == count) then
+ for i, v in pairs(obj) do
+ ngx.ctx[pathParamVars[i]] = v
+ end
+ return redis.getResource(red, key, "resources")
+ end
+ end
+ end
+ return nil
+end
+
--- Function to read the list of policies and send implementation to the correct backend
-- @param obj List of policies containing a type and value field. This function reads the type field and routes it appropriately.
-- @param apiKey optional subscription api key
@@ -94,34 +131,24 @@
--- Given a verb, transforms the backend request to use that method
-- @param v Verb to set on the backend request
function setVerb(v)
- if (string.lower(v) == 'post') then
- ngx.req.set_method(ngx.HTTP_POST)
- elseif (string.lower(v) == 'put') then
- ngx.req.set_method(ngx.HTTP_PUT)
- elseif (string.lower(v) == 'delete') then
- ngx.req.set_method(ngx.HTTP_DELETE)
- elseif (string.lower(v) == 'patch') then
- ngx.req.set_method(ngx.HTTP_PATCH)
- elseif (string.lower(v) == 'head') then
- ngx.req.set_method(ngx.HTTP_HEAD)
- elseif (string.lower(v) == 'options') then
- ngx.req.set_method(ngx.HTTP_OPTIONS)
+ local allowedVerbs = {'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'}
+ local verb = string.upper(v)
+ if(utils.tableContains(allowedVerbs, verb)) then
+ ngx.req.set_method(ngx[utils.concatStrings({"HTTP_", verb})])
else
ngx.req.set_method(ngx.HTTP_GET)
end
end
function getUriPath(backendPath)
- local uriPath
- local i, j = ngx.var.uri:find(ngx.var.gatewayPath)
+ local i, j = ngx.var.uri:find(ngx.unescape_uri(ngx.var.gatewayPath))
local incomingPath = ((j and ngx.var.uri:sub(j + 1)) or nil)
-- Check for backendUrl path
- if backendPath == nil or backendPath== '' or backendPath== '/' then
- uriPath = (incomingPath and incomingPath ~= '') and incomingPath or '/'
+ if backendPath == nil or backendPath == '' or backendPath == '/' then
+ return (incomingPath and incomingPath ~= '') and incomingPath or '/'
else
- uriPath = utils.concatStrings({backendPath, incomingPath})
+ return utils.concatStrings({backendPath, incomingPath})
end
- return uriPath
end
_M.processCall = processCall
diff --git a/doc/routes.md b/doc/routes.md
index 33887d0..dc9a2a5 100644
--- a/doc/routes.md
+++ b/doc/routes.md
@@ -245,9 +245,9 @@
{
"key": *(string) The api key to store to redis.
"scope": *(string) The scope to use the api key. "tenant", "resource", or "api".
- "tenant": *(string) Tenant guid.
+ "tenantId": *(string) Tenant guid.
"resource": (string) Resource path. Required if scope is "resource".
- "api": (string) API Guid. Required if scope is "API".
+ "apiId": (string) API Guid. Required if scope is "API".
}
```
@@ -264,9 +264,9 @@
{
"key": *(string) The api key to delete.
"scope": *(string) The scope to use the api key. "tenant", "resource", or "api".
- "tenant": *(string) Tenant guid.
+ "tenantId": *(string) Tenant guid.
"resource": (string) Resource path. Required if scope is "resource".
- "api": (string) API Guid. Required if scope is "API".
+ "apiId": (string) API Guid. Required if scope is "API".
}
```
diff --git a/init.sh b/init.sh
index e32c38f..aa7269f 100755
--- a/init.sh
+++ b/init.sh
@@ -55,7 +55,6 @@
sleep 1 # sleep until api-gateway is set up
tail -f /var/log/api-gateway/access.log -f /var/log/api-gateway/error.log \
-f /var/log/api-gateway/gateway_error.log -f /var/log/api-gateway/management.log &
- curl -s http://0.0.0.0:9000/v1/sync & # sync with redis
curl -s http://0.0.0.0:9000/v1/subscribe # subscribe to redis key changes for routes
else
echo "REDIS_HOST and/or REDIS_PORT not defined"