API Gateway

The API gateway is a server that acts as an API front-end, receives API requests, enforces throttling and security policies, passes requests to the back-end service and then passes the response back to the requester. It can provide some or all of the following security and management features:

  • API management
  • Portal features (allowing users to discover and use your APIs)
  • Security
    • Authentication & authorization
    • Threat protection
  • Protocol transformation, routing & orchestration
  • Analytics & monitoring (who is using your APIs, when and how)
  • Contract & SLA management

The idea is to implement our API gateway using Walhall to discover new APIs and OpenAPI (old Swagger) to map/route them and provide a single documentation. To achieve that services need to generate an OpenAPI schema, so the API Gateway can fetch it and based on that route incoming requests to the right endpoint and service.

Generate schemas

To provide the possibility to route requests and unify documentation of different APIs, services need to generate swagger files describing their endpoints. Services have to provide the endpoint /docs/swagger.json, so the API Gateway can fetch the API schema in the JSON format.

In the Django Rest Framework version 3.7, there is built in support for automatic OpenAPI 2.0 schema generation, however, this generation is based on the coreapi standard, which for the moment is inferior to OpenAPI standard in both features and tooling support. Because of that we decided to use drf-yasg instead. drf-yasg generates Swagger/OpenAPI 2.0 specifications from a Django Rest Framework API and it also provides many different features that are needed, like: - Full support for nested Serializers and Schemas - Model definitions compatible with codegen tools - JSON and YAML format for spec - Support for Django REST Framework API versioning with URLPathVersioning and NamespaceVersioning; other DRF or custom versioning schemes are not currently supported

To learn more about all these features, how to configure the lib and add it to a Django project review the documentation.

Gather schemas and route

The API Gateway has different responsibilities/features and some of them are: - fetch OpenAPI schemas from registered/used services in an application; - route incoming requests to the right module.

The API Gateway uses pyswagger, a Python package, to fetch schemas and perform the traffic directing functions. pyswagger is a Python client for Swagger enabled REST API, it loads Swagger resource file from an URL and provides a REST interface. It’s independent of Django Framework and provides many different features that we need, for example: - Support Swagger 1.2, 2.0 on python 2.6, 2.7, 3.3, 3.5, 3.6 - Typesafe, input/output are converted to python types according to Data Type described in Swagger. Limitations like minimum/maximum or enum are also checked - Built-in client implementation based on various HTTP clients in python - requests - tornado.httpclient.AsyncHTTPClient - flask.testing.FlaskClient - webapp2

The current implementation loads Swagger resource file into an object, from the following endpoint of services https://<service_url>/docs/swagger.json, inits a swagger client and resolves paths based on the model, service, and id used in requests to perform operations in the right module. The API Gateway expects that all requests follow the pattern /<service>/<model>/<pk>/, where only pk is optional. If we want to fetch a list of products from the service inventory, this is an example of the path: /inventory/products/.

Check the lib documentation to know more about it.

pyswagger-workflow (image from pyswagger GitHub repo)

Unify Documentation

Unified documentation is one of the most important parts of the API Gateway because it provides endpoint documentation in one place, shows how requests should be made and how responses are going to be created. In summary the API Gateway is able to create a swagger REST API from several other swagger REST APIs in a micro-service environment.

The API Gateway is built with Django Rest Framework, so creating a documentation for it shouldn’t be harder than generating schemas for services, therefore it uses the same library (drf-yasg) that services use to generate Swagger/OpenAPI 2.0 specifications. The API Gateway has a swagger aggregator that merge swagger definition of services into one, and a custom schema generator of the drf-yasg’s one that combines merged swagger definitions with BiFrost’s schema, and generates only one OpenAPI specification.

In addition because the swagger deffinition file is not platform specific, a defeintion can be generated by a micro-service or Humanitec LogicModule using any backend technology that can provide a standard REST API.

A Swagger aggregator is provided by the API Gateway and it’s responsible to gather schemas from REST APIs, combine the schemas, update names of specifications like definitions and paths, and regenerate the operation id of endpoints. The aggregator expects a configuration to be given with some information about the new swagger schema and this config can have: - info (with a title, description, version)(required) - host (optional) - base path (optional) - list of APIs with their URLs (required) - type of media that it’s allowed to consume and produce (optional)

This is an example of a configuration:

config_aggregator = {
	'info': {
		'title': 'API Gateway',
		'description': '',
		'version': '1.0'
	},
	'host': 'http://walhall.com',
	'basePath': '/',
	'apis': {
		'inventory': 'http://inventory.humanitec.com',
		'documents': 'http://documents.humanitec.com'
	},
	'produces': ['application/json'],
	'consumes': ['application/json']
}

The Swagger aggregator and custom OpenAPI schema generator work together but the aggregator doen’t depend on the schema generator. To know more about generators in drf-yasg, check its documentation.

Make request

When we need to fetch data from BiFrost or any services deployed in the system, we’ll send HTTP requests to the API Gateway and it defines some rules that need to be applied. These are the rules that we need to follow:

  1. All the requests need to have the Authorization header (it works only with JWT now)
  2. The URL should follow this structure http://<base_url>/<service_name>/<model_name>/<id>/
    • Base URL is the API Gateway URL
    • Service name is the name of the service that we are trying to fetch data
    • Model name is the name of the model that we want to retrieve
    • ID is the identifier of the model object if we are trying to fetch one (optional)

TODO: We need to support OAuth2 (Bearer Token); We need to implement a more flexible URL pattern (regex).

Reserved Names

There are some URL paths in Bifrost that shouldn’t be caught by API Gateway, it means that they are important for the Bifrost functionality, so these name should be reserved. We cannot have services with these names and have to exclude them from the gateway regular expression. This the current list of reserved names:

  • admin
  • oauth
  • health_check
  • docs
  • complete
  • disconnect
  • static
  • graphql
  • workflow
  • core
  • logicmodule
  • user
  • group
  • milestone
  • organization

Further info

Swagger in Django Rest Framework

The OpenAPI codec/compatibility layer provided by Django Rest Framework, based on the coreapi, has a few major problems:

  • there is no support for documenting response schemas and status codes
  • nested schemas do not work properly
  • does not handle more complex fields such as FileField, ChoiceField, …
  • does not support API versioning

In short this makes the generated schema unusable for code generation, and bad at best for documentation.