├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker-api-gateway.png ├── package.json └── src └── service.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | *.iml 3 | /.idea 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:5 2 | 3 | ADD . /home/service 4 | 5 | RUN cd /home/service && npm install 6 | 7 | CMD cd /home/service && node src/service.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Andrey Chausenko 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-api-gateway-example 2 | 3 | This project is a simple example of API Gateway pattern for [microservices](http://microservices.io/patterns/apigateway.html) implemented in Node.js. 4 | 5 | It's **not production-ready** service and its sole purpose is to demonstrate usage of **node-docker-monitor** and **http-proxy** npm modules. 6 | 7 | Service reacts on Docker events and as Docker containers go up and down, it creates or removes HTTP routing rules for them. 8 | To indicate that a container is to be handled by the gateway it must have label **api_route** defining URL prefix for that container 9 | plus it must EXPOSE port that containerised service listens on. 10 | 11 | ![Routing HTTP requests with API Gateway](docker-api-gateway.png) 12 | 13 | To be able to monitor local Docker instance, we need to make UNIX socket `/var/run/docker.sock` available to the container. 14 | 15 | ``` 16 | docker run -d --name api-gateway -v /var/run/docker.sock:/var/run/docker.sock -p 80:8080 beh01der/docker-api-gateway-example 17 | ``` 18 | 19 | An upstream service can be started with command similar to (depending on your service implementation) 20 | 21 | ``` 22 | docker run -d --name service1 -e SERVICE_NAME=service1 -l=api_route='/service1' --expose 3000 beh01der/web-service-dockerized-example 23 | ``` 24 | 25 | When upstream service containers are being discovered we should see output like 26 | 27 | ``` 28 | $ docker logs api-gateway 29 | Registered new api route: {"apiRoute":"/service1","upstreamUrl":"http://172.17.0.2:3000"} 30 | Registered new api route: {"apiRoute":"/service2","upstreamUrl":"http://172.17.0.3:3000"} 31 | ``` 32 | 33 | Now, we can test it 34 | 35 | ``` 36 | $ curl 127.0.0.1/service1 37 | Hello World! 38 | I am service 1! 39 | ``` 40 | -------------------------------------------------------------------------------- /docker-api-gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Beh01der/docker-api-gateway-example/9df0ed85a511d94014a729a2caa327ed6be7beda/docker-api-gateway.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker-api-gateway-example", 3 | "version": "1.0.0", 4 | "description": "This project is a simple example of API Gateway pattern for microservices implemented in Node.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Beh01der/docker-api-gateway-example.git" 12 | }, 13 | "keywords": [], 14 | "author": "Andrey Chausenko (https://memz.co)", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/Beh01der/docker-api-gateway-example/issues" 18 | }, 19 | "homepage": "https://github.com/Beh01der/docker-api-gateway-example#readme", 20 | "dependencies": { 21 | "http-proxy": "1.13.2", 22 | "node-docker-monitor": "1.0.4", 23 | "parseurl": "1.3.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/service.js: -------------------------------------------------------------------------------- 1 | var monitor = require('node-docker-monitor'); 2 | var http = require('http'); 3 | var httpProxy = require('http-proxy'); 4 | var parseurl = require('parseurl'); 5 | 6 | // process input via env vars 7 | var dockerOpts = { socketPath: process.env.DOCKER_SOCKET }; 8 | if (!dockerOpts.socketPath) { 9 | dockerOpts.host = process.env.DOCKER_HOST; 10 | dockerOpts.port = process.env.DOCKER_PORT; 11 | if (!dockerOpts.host) { 12 | dockerOpts.socketPath = '/var/run/docker.sock'; 13 | } 14 | } 15 | var httpPort = process.env.HTTP_HOST || 8080; 16 | 17 | // available routes collection 18 | var routes = { 19 | // '303c56be38b748576be1598eb9d6a746fb2792c5c9c6d83608ed8f2b5501b063' : { 20 | // apiRoute: '/service1', 21 | // upstreamUrl: 'http://127.0.0.1:8887' 22 | // } 23 | }; 24 | 25 | console.log('Connecting to Docker: %j', dockerOpts); 26 | 27 | monitor({ 28 | onContainerUp: function (containerInfo, docker) { 29 | if (containerInfo.Labels && containerInfo.Labels.api_route) { 30 | // register a new route if container has "api_route" label defined 31 | var container = docker.getContainer(containerInfo.Id); 32 | // get running container details 33 | container.inspect(function (err, containerDetails) { 34 | if (err) { 35 | console.log('Error getting container details for: %j', containerInfo, err); 36 | } else { 37 | try { 38 | // prepare and register a new route 39 | var route = { 40 | apiRoute: containerInfo.Labels.api_route, 41 | upstreamUrl: getUpstreamUrl(containerDetails) 42 | }; 43 | 44 | routes[containerInfo.Id] = route; 45 | console.log('Registered new api route: %j', route); 46 | } catch (e) { 47 | console.log('Error creating new api route for: %j', containerDetails, e); 48 | } 49 | } 50 | }); 51 | } 52 | }, 53 | 54 | onContainerDown: function (container) { 55 | if (container.Labels && container.Labels.api_route) { 56 | // remove existing route when container goes down 57 | var route = routes[container.Id]; 58 | if (route) { 59 | delete routes[container.Id]; 60 | console.log('Removed api route: %j', route); 61 | } 62 | } 63 | } 64 | }, dockerOpts); 65 | 66 | // create and start http server 67 | var server = http.createServer(function (req, res) { 68 | for (id in routes) { 69 | if (routes.hasOwnProperty(id) && handleRoute(routes[id], req, res)) { 70 | return; 71 | } 72 | } 73 | 74 | returnError(req, res); 75 | }); 76 | 77 | console.log('API gateway is listening on port: %d', httpPort); 78 | server.listen(httpPort); 79 | 80 | // create proxy 81 | var proxy = httpProxy.createProxyServer(); 82 | proxy.on('error', function (err, req, res) { 83 | returnError(req, res); 84 | }); 85 | 86 | // proxy HTTP request / response to / from destination upstream service if route matches 87 | function handleRoute(route, req, res) { 88 | var url = req.url; 89 | var parsedUrl = parseurl(req); 90 | 91 | if (parsedUrl.path.indexOf(route.apiRoute) === 0) { 92 | req.url = url.replace(route.apiRoute, ''); 93 | proxy.web(req, res, { target: route.upstreamUrl }); 94 | return true; 95 | } 96 | } 97 | 98 | // generate upstream url from containerDetails 99 | function getUpstreamUrl(containerDetails) { 100 | var ports = containerDetails.NetworkSettings.Ports; 101 | for (id in ports) { 102 | if (ports.hasOwnProperty(id)) { 103 | return 'http://' + containerDetails.NetworkSettings.IPAddress + ':' + id.split('/')[0]; 104 | } 105 | } 106 | } 107 | 108 | // send 502 response to the client in case of an error 109 | function returnError(req, res) { 110 | res.writeHead(502, {'Content-Type': 'text/plain'}); 111 | res.write('Bad Gateway for: ' + req.url); 112 | res.end(); 113 | } 114 | --------------------------------------------------------------------------------