Best practices for a strange proxy use case?

Here is something an API Provider team came to us with:

Proxy Path Desired:
/api/benefits/claims/v4.5/claimstatus.json

Gets mapped to this URL in the backend:
http://claims45-apiprod.company.com:80/benefits/claims/v4.5/claimstatus.json

So essentially we are tasked with taking /api/benefits/claims/v4.5/claimstatus.json and make it claims45 on the backend. I assume there is nothing already in Kong available to do this via regex or plugins in existence to handle such a strange use case.

I only guess the reason they want this is because rather than hosting a tomcat server with variety of micro-services broken out by uri’s they simple run different instances of stand alone spring boot api’s or something of that nature on different hosts. And with all of those hosts they would like to have one proxy that can route to lots of hosts in this manner.

So maybe just write a plugin that gets the uri elements and regex’s out the string needed to plop into a backend URL, a placeholder spot in the backend url set on the kong “service” to be replaced dynamically via the plugin. Would be nice to make this plugin extensible but I don’t see a really great way to do so, seems super specific/demanding but sometimes to seal the deal you just have to do it :slight_smile: .

https://github.com/pearsontechnology/kong-rewrite-plugin can do this. It allows you to provide a Lua script that is executed against each part of the request/response (headers, method, path, etc…).

We open sourced it a while back and use it daily in our environment, so while it may look stagnant it is actively maintained just very stable.

HI @jdarling - I don’t see anything in this plugin which allows changes to the upstream host. Which is the functionality we need.

Here’s what I’m doing:
In routes, I’m using a regex to populate ngx.ctx.router_matches.uri_captures.resource - and writing a very small plugin, which is capable of taking in a string of parameters to populate the host, which will read from these variables and also allow for static strings.

Careful changing the upstream. I’ve found that Kong actually cache’s the outbound and will deliver stale results. Putting together a case to submit a bug report on it.

But, the code you need is as follows (well kinda, of course you will have to modify it)

local function buildHostHeader(newHost)
  local u = url.parse(newHost)
  local hostHeader = u.host
  if u.port then
    hostHeader = hostHeader .. ":" .. u.port
  end
  return hostHeader
end

local function replaceHost(url, newHost)
  local pathIndex = url:find('[^/]/[^/]')

  if not pathIndex then
    if newHost:find('[^/]/[^/]') == nil and newHost:sub(#newHost) ~= "/" then
      return newHost .. "/"
    end

    return newHost
  end

  if newHost:sub(#newHost) == "/" then
    newHost = newHost:sub(1, -2)
  end

  local path = url:sub(pathIndex + 1)
  return newHost .. path
end

local function appendPath(currentPath, newHost)
  local newHostPathIndex = newHost:find('[^/]/[^/]')

  if currentPath == "/" then
    currentPath = ""
  end

  if newHostPathIndex then
    return newHost:sub(newHostPathIndex+1) .. currentPath
  end

  return currentPath
end

local function switchUpstream(newHost)
  local hostHeader = buildHostHeader(newHost)
  ngx.req.set_header("host", hostHeader)
  log.debug('Host header set: host'..hostHeader)
  if ngx.ctx.upstream_url then
    ngx.var.upstream_host = hostHeader
    ngx.ctx.upstream_url = replaceHost(ngx.ctx.upstream_url, newHost)
    log.debug('Upstream switch: ngx.var.upstream_host '..ngx.var.upstream_host..' ngx.ctx.upstream_url '..ngx.ctx.upstream_url)
    return
  end

  local newPath = appendPath(ngx.var.upstream_uri, newHost)
  ngx.var.upstream_uri = newPath
  ngx.ctx.api.upstream_url = replaceHost(ngx.ctx.api.upstream_url, newHost)
  log.debug('Upstream switch: ngx.ctx.api.upstream_url '..ngx.ctx.api.upstream_url..' ngx.ctx.api.upstream_url '..ngx.ctx.api.upstream_url)
end

I started with the code from github nvmlabs/kong-dynamic-upstream (it won’t let me post the full link for some reason) and modified/updated it for use with Kong 13, then discovered that sometimes I got the wrong results. I’ve now got it down to a reproducible set (I think) and will be submitting a bug report.

1 Like

Probably not a bug in Kong, but a change I missed in the changelog where you can’t change the URL in the access method anymore you have to use the rewrite method. Working on changes locally and if/when I find a working version will post it here.

1 Like

Looking forward to seeing what code you cook up! We are trying to come up with a snippit today/early next week as well.

Actually looking here: https://getkong.org/docs/0.13.x/plugin-development/custom-logic/

Seems access to me would be more in line based on descriptions here:

Any Kong developers care to shed some light on how we could change the upstream url on the fly with a simple reassignment example sudo code 1-2 liner (we will then take that and get it working w regex) :smiley: ? And the proper phase to do so in?

Actually we have a code-base that works fairly close to what we want:

local url = require "net.url"

local _M = {}

local function replaceHost(url, newHost)
  local pathIndex = url:find('[^/]/[^/]')

  if not pathIndex then
    if newHost:find('[^/]/[^/]') == nil and newHost:sub(#newHost) ~= "/" then
      return newHost .. "/"
    end

    return newHost
  end

  if newHost:sub(#newHost) == "/" then
    newHost = newHost:sub(1, -2)
  end

  local path = url:sub(pathIndex + 1)
  return newHost .. path
end

function _M.execute(conf)
  local host = ""
  for i, key in ipairs(conf.host_fields) do
    ngx.log(ngx.ERR, "Found Key " .. key)
    if string.find(key, "%$") then
      key = key:gsub('[%p%c%s]', '')
      ngx.log(ngx.ERR, "Key after stripping chars " .. key)
      if string.find(key,"version") then
        key = ngx.ctx.router_matches.uri_captures[key]
        key = key:gsub("%D+", "")
        ngx.log(ngx.ERR, "Found version: " .. key)
      else
        key = ngx.ctx.router_matches.uri_captures[key]
      end
      ngx.log(ngx.ERR, "Final value " .. key)
    end
    host = host .. key
  end
  ngx.req.set_header("host", host)
  ngx.var.upstream_host = host
  ngx.ctx.balancer_address.host = host
end

return _M

And then we create routes like:

  routes | 06887b10-c689-46f0-a91e-fc72b4075012 | 2018-04-20 16:17:25.000000+0000 |  null |                   null |             ['/api/benefits/(?<resource>claims\\S+)/(?<version>\\S+)'] |    

  routes | 0710585f-aef3-437a-93c1-fe2fe99a235d | 2018-04-19 02:02:39.000000+0000 |  null | {'GET', 'POST', 'PUT'} | ['/api/benefits/(?<resource>registration\\S+)/(?<version>\\S+)'] |    

And our plugin has values like

{
  "created_at": 1525479447491,
  "config": {
    "host_fields": [
      "$resource",
      "$version",
      "m.com"
    ]
  },
  "id": "efce0487-c4fc-42e4-a261-5a7dedf42b84",
  "enabled": true,
  "route_id": " 06887b10-c689-46f0-a91e-fc72b4075012 ",
  "name": "path-based-routing-plugin"
}

Then the regex gets paired in to produce a domain essentially like

$resource … $version … m.com , this seems to work fine in the access page and not be riddled with DNS errors.

So something like claims25m.com would be produced from /api/benefits/claims/v2.5/

Not sure if Kong devs consider this best practice but seems to get the job done, still iffy on Kong’s regex precedence and making sure the right route indeed gets called when the URL is hit. Hoping it works!

Came up with very similar code. One thing I noticed was that you can’t switch between IP’s and DNS based naming in the balancer_address.host or things go sideways. Will just stick with internal DNS names. Stripping out all the cruft looks like this (not we change the path, host and port so I have all 3 in there)

local upstreamHostInfo = url.parse(upstreamHost)
local upstreamHostType = hostname_type(upstreamHostInfo.host)

ngx.var.upstream_uri = newPath

ngx.ctx.balancer_address.host = upstreamHostInfo.host
ngx.ctx.balancer_address.type = upstreamHostType
ngx.ctx.balancer_address.port = upstreamHostInfo.port or getPortFromScheme(upstreamHostInfo.scheme)

Hi there,

FWIW, the Lua Plugin SDK we are currently writing will provide a public and stable API that provides these functionalities (request/response manipulation, dynamic balancer/port settings, and other nice-to-have as a plugin author (context, logging…). We are planning on its release for 0.14, most likely later this month.

1 Like

Cool to know the plugin SDK will solve some of the complexities of working with Kong plugins(which still will need robust documentation for everyone to harness it as Kong intends).

Alas we are going to have to change the logic of this plugin we were working on to be dumber I think… Looping back on this issue:

We realized that even if you have the regex pattern match working, we need it to be in the actual routing URL still and its currently not. @Ross_Sbriscia can talk more on this point as he has been most involved with the logic. Essentially now we are regressing to using a String split on “/” and then taking second to last and getting that version value, then taking 3rd to last and getting that as our resource and so forth without using regex to rebuild the hostname we want… And the regex was so cool too :/, now its just gonna be super rigid.