Dynamic Http Proxy with Azure Functions

Eugen Podaru
November 14, 2020

In the following article, I am going to show you how to build a dynamic HTTP proxy using Azure Functions and very little code. If you feel like reading code rather than reading an article about code, go straight to GitHub and have it your way.

You might be asking yourself, what is a dynamic HTTP proxy and why would I need one? You could think of it as a service registry that also allows you to proxy the http calls to the services it registers, so a dynamic HTTP proxy, DHP or DHTTPP for short.

One possible use case for such a service is between your frontends and your backends. Regardless of how fragmented or dynamic your backend landscape is, the DHP makes it possible for you to define a single, predictable API surface for the frontends to consume. The frontends also become simpler by only needing to know about one endpoint. You could accomplish the same using a solution like API Management of course, but while you need to reconfigure API Management for each modified or added backend, the DHP reconfigures itself.

If you used Azure Functions before, you might have stumbled upon Azure Functions Proxies. They are somewhat like API Management and can be used to achieve the same goal: a single API surface for all your services. But just like API Management, they are not dynamic; you would have to add any additional route and potentially redeploy the functions app. I will not use them for the DHP.

The idea is simple, every discoverable service registers itself with the DHP. After registering, the service will be accessible through the proxy.

Let us start with the service registration. When registering itself, a service needs to provide a name, a version, and the host where it can be found. I guess a name and a host would have sufficed, but it is nice to have API versioning out-of-the-box and it adds little in terms of extra code to the final solution. In the following code listing you can see how the service registration function looks like:

        
      
View raw file on Github

The function is as simple as it gets. The DHP accepts service registrations at /api/register and the payload for the registrations needs to be a ServiceEntry delivered in the body of the post method. Once it gets the ServiceEntry, it simply saves it to a table in an Azure Table Storage. I will leave service deregistration (delete) and service queries (get) up to you, since they are not mandatory for the DHP.

You might have noticed that before saving to the table, we set the RegisteredAt property of the ServiceEntry to the current datetime in UTC. This can be useful in multiple ways. If your services register only once, then this will only tell you when that happened, of course. However, if your services register every few minutes, then this tells you which services are up and which are down. Nice and simple!

In the following code listing you can see the ServiceEntry class:

        
      
View raw file on Github

As you can see, I am using the service name and version as the partition and row keys, which feels very natural, doesn’t it? Without the service version, we would have had to make up one of the keys, which would have been a waste.

This is all for service registration, let us see how the proxy functions look like. It is only one function and you can see it in the following code listing:

        
      
View raw file on Github

It is a simple HTTP-triggered function that accepts all HTTP verbs. Using Azure Functions binding expressions, it maps the partition and row keys of the table input binding to the name and version variables extracted from the route. This way, the runtime does the job of retrieving the matched ServiceEntry for us. Next, it builds the target endpoint using the service host and the rest of the path extracted from the route, adjusts the HttpRequestMessage and sends it using the injected IProxyService. You can see the implementation of the IProxyService in the next code listing:

        
      
View raw file on Github

I bet you thought this will be complicated. Here we are just wrapping the good old HttpClient to make the call to our proxied service. The only thing worth mentioning here is the use of HttpCompletionOption.ResponseHeadersRead which means the operation should complete as soon as a response is available and headers are read. Since we are proxying the response, there is no need to wait until all the content is read.

As promised: little code, yet quite capable!

That's all Folks