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
).