Merge pull request #47 from alexsong93/refactor

Get resources directly from redis without conf files
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"