Customising path patterns for your Apiman Gateway

· apiman, gateway, manager, plugin, extensibility
Avatar for Marc Savy
Co-founder & maintainer of Apiman. Founded Black Parrot Labs to support enterprise Apiman users.
/ Black Parrot Labs /

One common request we hear is how to create custom URL patterns for the Apiman Gateway.

For example, this means allowing changing the (Public API) default:

http://gatewayhost:port/{organizationId}/{apiId}/{version}/

To a custom alternative. As a simple example we’re going to hard-code an organisation in. We’ll assume that we’ve established a convention to always publish our APIs to a particular org. That will change the pattern to:

http://gatewayhost:port/{apiId}/{version}/
If you’re using the Vert.x Gateway you should use Apiman 1.4.3.Final or later as a bug prevented plugins from loading from static config.

Create a plugin skeleton

Check out our documentation for creating Apiman Plugins for more in-depth information.

One of Apiman’s handiest features is its plugin subsystem. This allows you to fully modularise custom code and distribute it through familiar channels like Maven repositories. It’s much more convenient than trying to play around with customising the class-path and messing around to make sure files are in the right place.

First, check out this plugin skeleton:

$ git clone https://github.com/apiman/apiman-plugins.git
$ cd custom-path-parser-demo/

Have a look at CustomPathParserImpl.java. It implements IApiRequestPathParser and takes the incoming path and headers; it returns an ApiRequestPathInfo that instructs Apiman what the orgId, apiId, apiVersion, and resource are, allowing Apiman to map the incoming request correctly.

public class CustomPathParserImpl implements IApiRequestPathParser {
    @Override
    public ApiRequestPathInfo parseEndpoint(String path, HeaderMap headers) {
        // Your custom path logic
    }
}

For reference the default implementation is io.apiman.gateway.engine.impl.DefaultRequestPathParser.

Creating our custom version

In our demonstration CustomPathParserImpl we will use a very simple parser that doesn’t inspect any headers. I recommend you check the default implementation to find utilities that handles alternative ways of capturing versioning info, such as accept headers, version headers, etc.

First we’ll add a constructor with a string map. This will allow us to pass in configuration information from our static config file (apiman.properties or conf.json).

final String defaultOrgName;

public CustomPathParserImpl(Map<String, String> config) {
   // Pass in the defaultOrgName from static config or just use "DefaultOrg".
   this.defaultOrgName = config.getOrDefault("defaultOrgName", "DefaultOrg");
}

public ApiRequestPathInfo parseEndpoint(String path, HeaderMap headers) {
   String[] split = StringUtils.split(path, "/", 3);

   if (split == null || split.length < 2) {
      throw new IllegalArgumentException("Invalid path format, expected /apiId/apiVersion/<resource path>");
   }

   ApiRequestPathInfo parsed = new ApiRequestPathInfo();
   // Let's set the org name manually as our configured `defaultOrgName`
   parsed.orgId = defaultOrgName;
   parsed.apiId = split[0];
   parsed.apiVersion = split[1];
   if (split.length > 2) {
      parsed.resource = "/" + split[2];
   } else {
      parsed.resource = "/";
   }
   return parsed;
}

So now, if we have a path like this:

/FooApi/1.0/some/resource

This will become:

orgId

DefaultOrg

apiId

FooApi

apiVersion

1.0

resource

/some/resource

Which would be the equivalent of the default style of:

/DefaultOrg/FooApi/1.0/some/resource

Wiring it up

Change the versions as applicable.

Run mvn clean install at the demo project’s root. Then open your Apiman configuration file (either apiman.properties or conf.json).

For the Vert.x Gateway:

"request-parser": {
    "class": "plugin:io.apiman.plugins:custom-path-parser-plugin-demo:1.4.3.Final:war/io.apiman.plugins.demo.custompathparser.CustomPathParserImpl",
    "config": {
        "defaultOrgName": "Apiman" // Whatever you like
    }
}

For the Servlet Gateway:

apiman-gateway.request-parser=plugin:io.apiman.plugins:custom-path-parser-plugin-demo:1.4.3.Final:war/io.apiman.plugins.demo.custompathparser.CustomPathParserImpl
apiman-gateway.request-parser.defaultOrgName=Apiman // Whatever you like
$ curl 'http://localhost:8082/TheApi/2'
{
  "method" : "GET",
  "resource" : "/services/echo/foobar/",
  "uri" : "/services/echo/foobar/",
  "headers" : {
    "Accept" : "*/*",
    "Host" : "localhost:8080",
    "transfer-encoding" : "chunked",
    "User-Agent" : "curl/7.54.0"
  },
  "bodyLength" : null,
  "bodySha1" : null,
  "counter" : 2
}
Double check that your plugin is structured correctly, and you have all the names right in your plugin URL. Otherwise, you will get errors complaining that your classes are not found.

Conclusion

We created a simple plugin to provide custom URL mappings to the Apiman Gateway and configured our gateways to use our code.

A future improvement extending IApiRequestPathParser will allow for the Apiman Gateway to report URLs back to the Apiman Manager (or whomever publishes) in a more granular fashion (rather than just using parseEndpoint).