Skip to content

Azure Functions Proxies

2018-01-29

Proxies are a nice addition to Azure Functions that give you a subset of features that an application gateway could provide for your function instances. It has request matching capabilities that let you inspect a specific route and send that request to a specific back-end. You can setup matches based on combinations of uri and HTTP method.

Silvrback blog image sb_float_center

Let's explore this with a default function. This is created from the functions portal and it the default for the HTTP trigger.

using System.Net;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");

    // parse query parameter
    string name = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
        .Value;

    // Get request body
    dynamic data = await req.Content.ReadAsAsync<object>();

    // Set name to query string or body data
    name = name ?? data?.name;

    return name == null
        ? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
        : req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}

So we can access this anonymously

Silvrback blog image sb_float_center

You can set this directly in configuration as well by updating the bindings in function.json

{
  "bindings": [
    {
      "authLevel": "anonymous",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in"
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    }
  ],
  "disabled": false
}

Taking the generated url from the function portal

»  Invoke-RestMethod https://yourbackend.azurewebsites.net/api/HttpTriggerCSharp1
Invoke-RestMethod : "Please pass a name on the query string or in the request body"

and you see that the HTTP is not happy with your request as a name is required in the query string or POST body.

»  Invoke-RestMethod https://yourbackend.azurewebsites.net/api/HttpTriggerCSharp1?name="Colter Wall"
Hello Colter Wall

Now we have the falling-out-of-the-chair 'Hello World' Azure Function working.

Personally I'd like to see a File > New > Function code sample to work by default

Now let's add a proxy to see if we can make the simple HTTP trigger execute correctly by a client invocation with no parameters.

From the Function portal it would be created like the following

Silvrback blog image sb_float_center

the more interesting json view of this look like this. This time we're in proxies.json

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "add-name-to-query": {
            "matchCondition": {
                "route": "/api/HttpTriggerCSharp1"
            },
            "backendUri": "https://yourbackend.azurewebsites.net/api/HttpTriggerCSharp1",
            "requestOverrides": {
                "backend.request.querystring.name": "Colter Wall"
            }
        }
    }
}

What we've configured here is a named proxy configuration called add-name-to-query that is looking for a route match on /api/HttpTriggerCSharp1 in which the request will be ferried over to the uri https://yourbackend.azurewebsites.net/api/HttpTriggerCSharp1. As it, this is completely pointless however we are enhancing the request by adding a query string parameter called name with the value of Colter Wall.

Now we can make that original request again

»  Invoke-RestMethod https://yourbackend.azurewebsites.net/api/HttpTriggerCSharp1
Hello Colter Wall
and it works!

How are we going to make some routing adjustments by having the route match at the root of our functions site

Adding static query string values

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "add-name-to-query": {
            "matchCondition": {
                "route": "/"
            },
            "backendUri": "https://yourbackend.azurewebsites.net/api/HttpTriggerCSharp1",
            "requestOverrides": {
                "backend.request.querystring.name": "Colter Wall"
            }
        }
    }
}

which will let us make a call such as

»  Invoke-RestMethod https://yourbackend.azurewebsites.net/
Hello Colter Wall

Function Proxies Templates variables

Thing are getting cleaner but despite how great Colter Wall is I didn't ask for him. Let's see if we can apply a route template into the mix.

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "add-name-to-query": {
            "matchCondition": {
                "route": "/{name}"
            },
            "backendUri": "https://yourbackend.azurewebsites.net/api/HttpTriggerCSharp1",
            "requestOverrides": {
                "backend.request.querystring.name": "{name}"
            }
        }
    }
}
»  Invoke-RestMethod https://yourbackend.azurewebsites.net/queens-of-the-stone-age
Hello queens-of-the-stone-age

Function Proxies to route to other sites

You don't need to necessarily have that backend pointed to your function. If you are not a rebot you can head over to one of Runscope's utility sites called https://requestbin.com/ and create a throw away endpoint that will show you what your http requests look like to the server.

Updating the backendUri property to a requestbin inspector

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "add-name-to-query": {
            "matchCondition": {
                "route": "/{name}"
            },
            "backendUri": "https://requestbin.com/19x58nh1?inspect",
            "requestOverrides": {
                "backend.request.querystring.name": "{name}"
            }
        }
    }
}

and calling the same request will just get you a 200 OK response but on the requestbin page you can see the headers and query string values

Silvrback blog image sb_float_center

RequestBin is handy for light weight request sharing. Httpbin is awesome in a different way in that you can have some control over the response. Again update the backendUri with a new value of https://httpbin.org/headers

Template values for the backenduri

Templates parameter can also be used in the backendUri value

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "add-name-to-query": {
            "matchCondition": {
                "route": "/{name}"
            },
            "backendUri": "https://httpbin.org/{name}"
        }
    }
}

Calling a couple of the documented httpbin resources

»  Invoke-RestMethod https://yourbackend.azurewebsites.net/headers | ConvertTo-Json
{
    "headers":  {
                    "Connection":  "close",
                    "Disguised-Host":  "yourbackend.azurewebsites.net",
                    "Host":  "httpbin.org",
                    "Max-Forwards":  "10",
                    "User-Agent":  "Mozilla/5.0,(Windows NT; Windows NT 10.0; en-US),WindowsPowerShell/5.1.16299.98",
                    "Was-Default-Hostname":  "yourbackend.azurewebsites.net",
                    "X-Arr-Log-Id":  "dc4e8792-5823-444f-a76f-8efa20eee6f0",
                    "X-Arr-Ssl":  "2048|256|C=US, S=Washington, L=Redmond, O=Microsoft Corporation, OU=Microsoft IT, CN=Microsoft IT TLS CA 4|CN=*.azurewebsites.net",
                    "X-Original-Url":  "/queens-of-the-stone-age",
                    "X-Site-Deployment-Id":  "yourbackend",
                    "X-Waws-Unencoded-Url":  "/queens-of-the-stone-age"
                }
}
»  Invoke-RestMethod https://yourbackend.azurewebsites.net/robots.txt
User-agent: *
Disallow: /deny

Multiple Uri Segments

{*restOfPath} can be used for sub-resources where you have multiple segments in your Uri

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "add-name-to-query": {
            "matchCondition": {
                "route": "/{*restOfPath}"
            },
            "backendUri": "https://httpbin.org/{restOfPath}"
        }
    }
}

And this time we use PowerShell's Invoke-WebRequest so we can extract the links from the response

»  (Invoke-WebRequest https://yourbackend.azurewebsites.net/links/3/3).Links


innerHTML : 0
innerText : 0
outerHTML : <A href="/links/3/0">0</A>
outerText : 0
tagName   : A
href      : /links/3/0

innerHTML : 1
innerText : 1
outerHTML : <A href="/links/3/1">1</A>
outerText : 1
tagName   : A
href      : /links/3/1

innerHTML : 2
innerText : 2
outerHTML : <A href="/links/3/2">2</A>
outerText : 2
tagName   : A
href      : /links/3/2

I'm using this feature in Dotnetzero so that I can take a source off of dotnetzero.com/dotnetcli and pass some query string parameters to that the function will return a different command to the user.

A snippet of this looks like the following and the little 'm' magic is where I do a route match of the /dotnetcli to passed a couple of PowerShell pipeline commands instead of the full parent script. This will just run the wizard portion of the dotnetzero where the dotnet command are wrapped for user input.

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "appcli": {
            "matchCondition": {
                "route": "/dotnetcli"
            },
            "backendUri": "https://yourbackend.azurewebsites.net/api/fullapp",
            "requestOverrides": {
                "backend.request.method": "get",
                "backend.request.querystring.clicmd": "Get-DotNetProjects | New-DotNetSolution"
            }
        }
    }
}

Adding values to HTTP request headers

Like because were we added a static query string value we can also work with the request headers

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "add-name-to-query": {
            "matchCondition": {
                "route": "/{*restOfPath}"
            },
            "backendUri": "https://httpbin.org/{restOfPath}",
            "requestOverrides": {
                "backend.request.headers.x-experimental": "8675309"
            }
        }
    }
}
»  irm https://yourbackend.azurewebsites.net/headers | ConvertTo-Json
{
    "headers":  {
                    "Connection":  "close",
                    "Disguised-Host":  "yourbackend.azurewebsites.net",
                    "Host":  "httpbin.org",
                    "Max-Forwards":  "10",
                    "User-Agent":  "Mozilla/5.0,(Windows NT; Windows NT 10.0; en-US),WindowsPowerShell/5.1.16299.98",
                    "Was-Default-Hostname":  "yourbackend.azurewebsites.net",
                    "X-Arr-Log-Id":  "f76a5b15-22fa-4f6b-9486-02d2a3b0da35",
                    "X-Arr-Ssl":  "2048|256|C=US, S=Washington, L=Redmond, O=Microsoft Corporation, OU=Microsoft IT, CN=Microsoft IT TLS CA 4|CN=*.azurewebsites.net",
                    "X-Experimental":  "8675309",
                    "X-Original-Url":  "/headers",
                    "X-Site-Deployment-Id":  "yourbackend",
                    "X-Waws-Unencoded-Url":  "/headers"
                }
}

I think that Azure Functions proxies are going to be useful in a different areas. For scenarios where you might not need to use Azure's various gateway solutions or maybe not yet able to justify using API Gateway I think that proxies can provide soutions for a number of scenarios.

Next up we will talk about the Response overrides and where that can help with API consumer development and prototyping.