I've been working at the European Commission for the last 4 years as a Software Architect, working for a unit responsible for developing reusable components, and advocating open source software. In this context, we organized already a couple of Hackathons and Bug bounties open to all the open source community.
In the team we worked already a couple of times with Apache Camel, and i personally like the elegance and performance compared with other integration frameworks.
Recently i was challenged to find alternatives to the existing API Gateway infrastructure, and immediately started to search for products implemented on top of Apache Camel.
Not being able to find any solution with all we need to offer, i decided to work on a small POC with the following features:
SO, WHY APACHE CAMEL?
As part of the POC, i've decided already to combine other technologies:
The POC is available, and currently being tested, already working with Apache Camel 3.0.0. Let's then go for some technical details...
routeDefinition .streamCaching() .setHeader(...) //core headers .process(authenticationProcessor) .choice() .when(...) //check execution of the authentication processor .process(pathProcessor) //evaluates the path .toF(toEndpoint) //proxy to the deployed backend .removeHeader(...) //remove some core headers .process(metricsProcessor) //process metrics //api was not authenticated (ex.: expired token) .otherwise() .setHeader(...) //core error headers .toF(apiGatewayErrorEndpoint) //proxy to default error endpoint .removeHeader(...) //remove some core headers .process(metricsProcessor) //process metrics .end() .setId(routeID);
The toEndpoint contains the default configuration:
throwExceptionOnFailure=false //we will catch the exceptions connectTimeout=... bridgeEndpoint=true copyHeaders=true connectionClose=true
Since we want to be able to catch Network, IO exceptions we also do this on the route definition:
routeDefinition .onException(exceptionClass) .continued(continued) .setHeader(...) //exception headers .toF(apiGatewayErrorEndpoint) //proxy to default error endpoint .removeHeader(...) //remove some core headers .end();
All information about a running API can be found in the shared cache. This allows the component that manages API's to know if an API must be suspended, or temporarily blocked (due to failed attempts and/or number of calls exceeding the defined threshold.) Information about a running API, includes:
Main components Managing enabled Routes:
{ "_id" : "XXX-XXX-XXX-XXX", "endpoint" : "remote.domain.com:8080", "endpointType" : "HTTPS", "name" : "Friendly API Name", "secured" : true, "context" : "context-name", "swagger" : true, "swaggerEndpoint" : "https://remote.domain.com:8080/v2/api-docs", "blockIfInError" : true, "maxAllowedFailedCalls" : 10, //after 10 failed calls, the route will be removed "unblockAfter" : true, "unblockAfterMinutes" : 2, //after 2 minutes of being removed, the route is added //100 calls per minute, above this, the route is suspended. "throttlingPolicy" : { "maxCallsAllowed" : "100", "periodForMaxCalls" : "60000", "applyPerPath" : true } }
With the following configuration your service will be available at:
https://localhost:8380/gateway/context-name/
The following configuration will be applied:
You can define your own paths, in case you dont have a Swagger Endpoint (Swagger 2/Open API), so if swagger: false, then CAPI will look for a list of PATH like the below example:
{ "_id" : "XXX-XXX-XXX-XXX", "endpoint" : "remote.domain.com:8080", "endpointType" : "HTTPS", "name" : "Friendly API Name", //this time, the API will be available for everyone "secured" : false, "context" : "context-name", "blockIfInError" : false, //no swagger definition present, you need to define the available paths. "swagger" : false, "paths" : [ { "verb" : "GET", "path" : "/services/path" }, { "verb" : "POST", "path" : "/services/path" } ] }
This will change after the integration with Keycloak. Example of client (with the password: web-client-secret)
{ "_id" : "XXX-XXX-XXX-XXX", "clientId" : "web-publisher", "resourceIds" : [], "secretRequired" : true, "clientSecret" : "$2a$10$oQBqS4ZOEiIGVNiZnB0nMuFw/n/Od57IG/uy4nFuOJxLtHE/Z5jDC", "scoped" : false, "scope" : [ "read-foo" ], "authorizedGrantTypes" : [ "refresh_token", "client_credentials", ], "registeredRedirectUri" : [], "authorities" : [ { "role" : "ROLE_USER", "_class" : "org.springframework.security.core.authority.SimpleGrantedAuthority" }, { "role" : "ROLE_PUBLISHER", "_class" : "org.springframework.security.core.authority.SimpleGrantedAuthority" } // All the API's you subscribe will be an authority ], "accessTokenValiditySeconds" : 60, "refreshTokenValiditySeconds" : 14400, "autoApprove" : false }
If you wish to enable security for your API (api.secured = true), then you will need to subscribe your API with a client. Your API ID will be added as an authority in the authorities list of your client.
"authorities" : [ { "role" : "ROLE_USER", "_class" : "org.springframework.security.core.authority.SimpleGrantedAuthority" }, { "role" : "ROLE_PUBLISHER", "_class" : "org.springframework.security.core.authority.SimpleGrantedAuthority" }, { "role" : "YOUR API ID", "_class" : "org.springframework.security.core.authority.SimpleGrantedAuthority" } ]
$ sudo docker-compose up -d
curl -X POST https://localhost:8080/oauth/token -H 'Authorization: Basic d2ViLXB1Ymxpc2hlcjp3ZWItY2xpZW50LXNlY3JldA==' -H 'Content-Type: multipart/form-data;' -F grant_type=client_credentials -F 'response_type=access_token'
curl -X POST "http://localhost:8080/route/simple-rest" -H "accept: application/json" -H "Content-Type: application/json" -d "<your-api>" (see Example of an API definition)
Docker compose will create instances of Grafana, Prometheus and Zipkin, but if you wish to use already existing instances you just need to change this environment variables:
Results for 20k calls 1000 concurrency level: Server Hostname: localhost Server Port: 8380 SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256 Server Temp Key: ECDH P-256 256 bits TLS Server Name: localhost Document Path: /gateway/myctx/internal/12345 Document Length: 33 bytes Concurrency Level: 1000 Time taken for tests: 65.563 seconds Complete requests: 20000 Failed requests: 0 Total transferred: 6560000 bytes HTML transferred: 660000 bytes Requests per second: 305.05 [#/sec] (mean) Time per request: 3278.129 [ms] (mean) Time per request: 3.278 [ms] (mean, across all concurrent requests) Transfer rate: 97.71 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 7 2431 1388.0 2260 18381 Processing: 5 798 883.7 684 13862 Waiting: 3 796 883.3 681 13862 Total: 58 3229 1683.6 3091 18639
It would be amazing to have feedback from the Apache Camel Community. If you have any question, feel free to contact me. The repo is located at: https://github.com/rodrigoserracoelho/capi-gateway