I’m busy playing with ApiAxle at the moment, as a way of exposing our internally facing API endpoints to customers and partners.
The documentation is really good, but the part that had me puzzled for a while was connecting the endpoint you want exposed externally, with the internal endpoint of your API. This is quite probably down to me not thinking it through properly, but in case someone else is struggling with the same issue, here it is.
First, get it all up and running by following the ApiAxle documentation for installing node, redis and the 3 ApiAxle programs.
There are 4 parts that you need to connect together:
- The ApiAxle proxy tool running and configured to handle the incoming requests passed to it by nginx
- A local hostname mapping for each of the API endpoints you want to expose
- nginx to listen for the incoming external requests, potentially rewrite them and pass them on the the ApiAxle proxy
- The destination endpoint API
For the purpose of this write up I’m going to use a vagrant hosted ApiAxle installation to proxy API requests through to Reddit. Specifically, the endpoint for http://www.reddit.com/r/nottheonion/top that you get by hitting http://www.reddit.com/r/nottheonion/top.json. We’ll set up our local machine to listen on http://localhost/api/reddit/nottheonion/top.json, rewrite the request and pass it through to Reddit.
If you’re using Vagrant, make sure that you are proxying requests to your local machine through to port 80 on the vagrant box:
config.vm.network :forwarded_port, guest: 80, host: 8000
Then, install nginx (to handle the incoming web requests) and supervisor (to manage the apiaxle-proxy instances)
sudo apt-get install nginx supervisor
Once that is done, change to /etc/supervisor/conf.d and create a new file called apiaxle-proxy.conf:
[program:apiaxle-proxy]
process_name = apiaxle-proxy-%(process_num)s
command = apiaxle-proxy -f 1 -p %(process_num)s
numprocs = 4
numprocs_start = 3000
redirect_stderr=true
stdout_logfile=/var/log/apiaxle-proxy-%(process_num)s-stdout.log
stderr_logfile=/var/log/apiaxle-proxy-%(process_num)s-stderr.log
This will kick off 4 copies of the apiaxle-proxy application, listening on ports 3000, 3001, 3002 and 3003
vagrant@precise64:~$ sudo service supervisor start
Starting supervisor: supervisord.
which can be verified by looking at the process list:
r/conf.d$ ps ax | grep apiaxle
8247 ? Sl 0:17 node /usr/bin/apiaxle-proxy -f 1 -p 3001
8252 ? Sl 0:17 node /usr/bin/apiaxle-proxy -f 1 -p 3000
8253 ? Sl 0:16 node /usr/bin/apiaxle-proxy -f 1 -p 3003
8256 ? Sl 0:16 node /usr/bin/apiaxle-proxy -f 1 -p 3002
8260 ? Sl 0:30 /usr/bin/nodejs /usr/bin/apiaxle-proxy -f 1 -p 3001
8261 ? Sl 0:29 /usr/bin/nodejs /usr/bin/apiaxle-proxy -f 1 -p 3002
8262 ? Sl 0:30 /usr/bin/nodejs /usr/bin/apiaxle-proxy -f 1 -p 3003
8266 ? Sl 0:30 /usr/bin/nodejs /usr/bin/apiaxle-proxy -f 1 -p 3000
Next we need to set up a local hostname for the API. ApiAxle uses subdomains to segment different proxies API’s, so edit /etc/hosts and add:
127.0.0.1 reddit.api.localhost
Now, if we hit http://localhost:3000 we get:
{“meta”:{“version”:1,”status_code”:404},”results”:{“error”:{“type”:”ApiUnknown”,”message”:”No api specified (via subdomain)”}}}
and if we hit http://reddit.api.localhost:3000 we get:
{“meta”:{“version”:1,”status_code”:404},”results”:{“error”:{“type”:”ApiUnknown”,”message”:”’reddit’ is not known to us.”}}}
So, ApiAxle is taking the subdomain prefix before .api.localhost and using that to select an API to proxy. The error message that we are getting is because that proxy hasn’t yet been set up. To do that we need to configure the ApiAxle proxy tool. Open up the REPL command apiaxle:
axle> api “reddit” create endPoint=”www.reddit.com"
Then create a key for the API authentication and link it to the new API you just created:
axle> key “9876" create
axle> api reddit linkkey “9876"
Now, if you were to visit http://reddit.api.localhost:3000?api_key=9876 ApiAxle will simply forward that request on to http://www.reddit.com — which at this point isn’t terribly helpful (for our purposes)
The last piece of the puzzle is to set up nginx to listen for the external incoming requests, rewrite the URL and pass it on the ApiAxle.
Open /etc/nginx/sites-available/default and add the following:
upstream apiaxle {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
This defines the upstream cluster of ApiAxle proxy instances. Then, under the server, the specific location that you want to listen on for proxying this API:
location /api/reddit/ {
rewrite /api/reddit/(.*) /r/$1 break;
proxy_pass http://apiaxle;
proxy_set_header Host “reddit.api.localhost”;
}
We’re listening on /api/reddit/ and then rewriting the URL to /r/ — anything after /api/reddit — the (.*) is put on to the end of /r/ (the $1).
This is useful since then you can map your own API structure (for managing multiple API endpoints) to whatever the remote API endpoint looks like.
Restart nginx to make the changes take effect:
sudo service nginx restart
To test the nginx upstream config, try connect to:
curl http://localhost/api/reddit/nottheonion/top.json?api_key=9876
- nginx on port 80
- match the /api/reddit/ Location block
- rewrite /api/reddit/nottheonion/top.json to /r/nottheonion/top.json
- Add the reddit.api.localhost Header
- Pass the request on to the upstream server (ApiAxle)
If it’s all working correctly you should see the JSON returned from reddit.com
Since vagrant is listening on port 8000 on my local machine proxying requests through to port 80 (nginx) on the vagrant box, I should be able to hit http://localhost:8000/api/reddit/nottheonion/top.json?api_key=9876 on my local machine and get the response from Reddit:
{
"kind": "Listing",
"data": {
"modhash": "",
"children": [
{
"kind": "t3",
"data": {
"domain": "washingtonpost.com",
"banned_by": null,
"media_embed": {},
"subreddit": "nottheonion",
"selftext_html": null,
"selftext": "",
"likes": null,
"secure_media": null,
"link_flair_text": null,
"id": "2a1g6d",
"gilded": 0,
"secure_media_embed": {},
"clicked": false,
"stickied": false,
"author": "CarbonNitrogen",
"media": null,
"score": 1256,
"approved_by": null,
"over_18": false,
"hidden": false,
Email me when PMB Digital publishes stories
