See what makes Kong the fastest, most-adopted API gateway
Check out the latest Kong feature releases and updates
Single platform for SaaS end-to-end connectivity
Enterprise service mesh based on Kuma and Envoy
Collaborative API design platform
How to Scale High-Performance APIs and Microservices
Call for speakers & sponsors, Kong API Summit 2023!
5 MIN READ
Recently, I came across a request from one of our community users looking for a Kong plugin that could look up address details based on a few location search keywords. There are many libraries that support geocoding. I decided to pick up a Python Library as they are simple to use — and the Kong Gateway supports plugins written in Python since version 2.3.
To show the simplicity of using the Kong Python PDK, I’ve picked the GeoPy library, which will allow us to perform GeoCoding within our Kong Plugin.
When making an API request to Kong, the caller should be able to pass location search keywords such as “Trafalgar Square” in the request header. The plugin will use the GeoPy library to lookup the address for the search terms, determine the latitude and longitude for the address, and add them as additional headers to the request before passing it to the upstream.
The Python plugin support in Kong Gateway works by running a Python plugin server as a separate process on the same host as Kong Gateway and passing messages back and forth using Unix Sockets. The same mechanism is used for other external plugins in Kong, such as JS and Go.
In order to make things easier, I’ve put together a Docker-based environment for you to use. All you require is a machine with Docker and Docker Compose installed. Since it might take a minute or two to download the images and build our environment, I recommend running it now in the background as you keep reading:
$ git clone --recurse-submodules https://github.com/Kong/kong-plugin-py-geocode $ cd kong-plugin-py-geocode $ docker compose build
The Docker Compose file sets up the following:
The Kong Gateway is configured to start a Python plugin server and load all the plugins in the kong-py-plugin directory. Currently, it contains a single plugin file: py-geocode.py. We start our plugin by importing the required Python libraries:
#!/usr/bin/env python3 import os import kong_pdk.pdk.kong as kong from geopy.geocoders import Nominatim
These Python libraries should be installed using pip in our Dockerfile used to build our Kong image.
Like all well-written plugins, the py-geocode plugin exposes all its parameters via configuration 😎
Schema = ( {"location_search_header": {"type": "string"}}, {"location_address_header": {"type": "string"}}, {"location_lat_header": {"type": "string"}}, {"location_long_header": {"type": "string"}}, )
We also specify the version and priority of the plugin. We start by implementing the plugin object and the phases where we want to implement our custom logic.
There are five phases available for HTTP requests in the life-cycle of a Kong Gateway request:
Since we want to add headers to the request before sending it to the upstream, we do it in the access phase:
class Plugin(object): def __init__(self, config): self.config = config def access(self, kong: kong.kong): geolocator = Nominatim(user_agent="kong")
We’ll also be setting default values for these various headers if they’re not set in plugin configuration. After all, we want our plugin to be resilient 💪
Here’s a snippet:
# set defaults for header values if 'location_search_header' in self.config: location_search_header = self.config['location_search_header']
Finally, we perform our custom logic that:
We wrap all the logic in a try-except block for proper exception handling. This ensures that our plugin will not crash and burn when it hits an error 👊
try: location_search = kong.request.get_header(location_search_header) location = geolocator.geocode(location_search) kong.service.request.set_header(location_address_header, location.address) kong.service.request.set_header(location_lat_header, location.latitude) kong.service.request.set_header(location_long_header, location.longitude) except Exception as ex: kong.log.error(ex)
The environment we’re running uses Kong’s declarative configuration capability. This means our Kong configuration is defined as a YAML file. Open up config/kong.yml, and you should see a service defined that proxies to httpbin.org/anything, and exposes a single route on the path “/any” that has our geocoding plugin applied:
services: - name: httpbin-svc url: http://httpbin.org/anything routes: - name: example-route paths: - /any plugins: - name: py-geocode
The Kong Gateway only allows you to use plugins that are on an allowlist for security purposes. Open up docker-compose.yml and you can see that our plugin “py-geocode” has been enabled:
KONG_PLUGINS=bundled,py-geocode
Finally, we enable the python plugin server using environment variables used to start the gateway container.
At this point the API gateway is ready to run our Python plugin, so let’s go ahead and start it:
$ docker compose up
Once the containers are up, the Kong Gateway’s proxy port will be available on port 8000 on our local machine.
Lets try a simple location search, specifying the location search keywords in the HTTP header “x-location-search” which is the default header that our plugin will look at. (We’ve not specified any plugin configuration in our declarative configuration.)
curl localhost:8000/any -H 'x-location-search:"Trafalgar Square"' { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.68.0", "X-Amzn-Trace-Id": "Root=1-6246cc5b-52a77b5d39a34a9f7d836589", "X-Forwarded-Host": "localhost", "X-Forwarded-Path": "/any", "X-Forwarded-Prefix": "/any", "X-Location-Address": "Trafalgar Square, St. James's, Covent Garden, City of Westminster, London, Greater London, England, WC2, United Kingdom", "X-Location-Lat": "51.508037", "X-Location-Long": "-0.12804941070725", "X-Location-Search": "\"Trafalgar Square\"" }, "json": null, "method": "GET", "origin": "192.168.48.1, 116.14.136.90", "url": "http://localhost/anything" }
As you can see, our custom plugin has looked up the location address, latitude, and longitude and added them as upstream headers which are reflected by the httpbin response.
Suppose, we want the plugin to lookup the search keywords in the header “x-search” — and we want the location address to be added to a header “x-location-addr”. We can set these in the plugin configuration, because remember: we wrote our plugin to be intelligent. Head over to the declarative configuration in the dbless_config directory and edit kong.yaml to specify these in the plugin configuration:
services: - name: httpbin-svc url: http://httpbin.org/anything routes: - name: example-route paths: - /any plugins: - name: py-geocode config: location_search_header: x-search location_address_header: x-location-addr
If we restart our API gateway by pressing Ctrl+C then running docker-compose up again, we should now be able to make a request to localhost:8000, specifying the search location in the ‘x-search’ header:
curl localhost:8000/any -H 'x-search:"Trevi Fountain"' 1.639s { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.68.0", "X-Amzn-Trace-Id": "Root=1-6246cd17-0ec71c21628936c5388d9386", "X-Forwarded-Host": "localhost", "X-Forwarded-Path": "/any", "X-Forwarded-Prefix": "/any", "X-Location-Addr": "Fontana di Trevi, Piazza di Trevi, Trevi, Municipio Roma I, Roma, Roma Capitale, Lazio, 00187, Italia", "X-Location-Lat": "41.9009778", "X-Location-Long": "12.48328484234", "X-Search": "\"Trevi Fountain\"" }, "json": null, "method": "GET", "origin": "192.168.48.1, 116.14.136.90", "url": "http://localhost/anything" }
In a few lines of Python code, we have a working Kong Gateway plugin, complete with configuration options and exception handling!
What we’ve built together is a trivial plugin, but using the environment provided and what you’ve learned about Kong’s configuration, you can go ahead and build plugins to your heart’s content making full use of the wide range of libraries provided by the Python ecosystem.
If you’re looking for more plugin examples, take a look at the examples directory in the kong-python-pdk.
Questions? Post them on Kong Nation. And stay in touch by joining the Kong Community.
Share Post
Learn how to make your API strategy a competitive advantage.