Use Azure Functions as a Proxy

26/03/2017

Azure function recently added support for using it as a proxy for other APIs. The intention behind azure function proxies are that you can create a function app and use it as a proxy to hide several other function apps. But it can be used for many other interesting things.

Today I used it in order to get some more readable webhook urls, for an azure automation webhook. Also I was able to create a function proxy that could be invoked on GET, while the azure automation webhook only can be invoked by a POST request (which is not too easy to do in a browser).

My proxies.json looked like this
[js]
{
"proxies": {
"start": {
"matchCondition": {
"route": "/api/start"
},
"backendUri": {
"uri": "https://s9events.azure-automation.net/webhooks?token=<removed>",
"method": "POST"
}
},
"stop": {
"matchCondition": {
"route": "/api/stop"
},
"backendUri": {
"uri": "https://s9events.azure-automation.net/webhooks?token=<removed>",
"method": "POST"
}
}
}
}
[/js]

How did I figure out this what is possible? Well, some nice guys from the Azure function developer team shared the following proxies.json sample filewith me, that lists a lot of interesting things that you can do that is not covered in any documentation.

[js]
{
"proxies": {
"ListAll": {
"desc": [
"This is the index of examples."
],
"matchCondition": {
"methods": [
"GET"
],
"route": "/list"
},
"responseOverrides": {
"response.status": "200 OK",
"response.header.Content-Type": "application/json",
"response.body": {
"px1": {
"url": "https://%WEBSITE_HOSTNAME%/ip",
"title": "px1 – Simplest rewrite"
},
"px2": {
"url": "https://%WEBSITE_HOSTNAME%/test2/{id}",
"title": "px2 – Transform header, segment and querystring"
},
"px3": {
"url": "https://%WEBSITE_HOSTNAME%/test3/{code}",
"title": "px3 – Conditional output based on Response status"
},
"px4a": {
"url": "https://%WEBSITE_HOSTNAME%/verbs/posts/{id}",
"title": "px4a – Example for other Verbs"
},
"px4b": {
"url": "https://%WEBSITE_HOSTNAME%/verbs/posts",
"title": "px4b – Example for other Verbs"
},
"px5": {
"url": "https://%WEBSITE_HOSTNAME%/status/{code}/{desc}",
"title": "px5 – Mock API for Status"
},
"px6": {
"url": "https://%WEBSITE_HOSTNAME%/json/{id}",
"title": "px6 – Example of Json output"
},
"px7": {
"url": "https://%WEBSITE_HOSTNAME%/xml/{id}",
"title": "px7 – Example of XML output"
},
"px8": {
"url": "https://%WEBSITE_HOSTNAME%/method/{method}",
"title": "px8 – Change Http Methods – IN ROUTING 0.0.4 (Not in Prod)"
},
"px9": {
"url": "https://%WEBSITE_HOSTNAME%/cert",
"title": "px9 – Client Certificate Based Auth – IN ROUTING 0.0.4 (Not in Prod)"
},
"px10": {
"url": "https://%WEBSITE_HOSTNAME%/debug",
"title": "px10 – Trace Logging and Debugging – IN ROUTING 0.0.4 (Not in Prod)"
}
}
}
},
"px1 – Simplest rewrite": {
"matchCondition": {
"methods": [
"GET"
],
"route": "/ip"
},
"backendUri": "https://httpbin.org/ip"
},
"px2 – Transform header, segment and querystring": {
"desc": [
"Example show how you can",
"1. Read headers (request.header.<<header name>>), segements({xyz}) and querystring (request.querystring.<<param name>>) from input request ",
"2. Set the backend headers (backend.request.header.<<header name>>, querystring (backend.request.querystring.<<param name>>)",
"3. Set the response headers (repsonse.header.<<header name>>), status (response.status)",
"4. You can use AppSettings in your proxy definitons by enclosing theam with %% For e.g.%APP_SETTING_NAME%",
"Sample input: https://<<yourhostname>>/test2/1?operation=posts&version=1.2"
],
"matchCondition": {
"methods": [
"GET"
],
"route": "/test2/{uid}"
},
"backendUri": "https://jsonplaceholder.typicode.com/users/{uid}/{request.querystring.operation}",
"requestOverrides": {
"backend.request.querystring.newkey": "request.header.me",
"backend.request.querystring.userId": "{uid}",
"backend.request.querystring.token": "%MY_CUSTOM_TOKEN%",
"backend.request.header.appname": "Appname is %CUSTOM_SETTING%",
"backend.request.header.myheaderv": "request.querystring.version"
},
"responseOverrides": {
"response.header.myversion": "backend.response.header.version",
"response.header.anotherHeader": "Some other value is %CUSTOM_SETTING%",
"response.status": "200 FINE"
}
},
"px3 – Conditional output based on Response status": {
"desc": [
"Example to handle backend response code and transform response accordingly",
"Note that you can also set the response body to either json or xml (shown in px6 and px7)"
],
"matchCondition": {
"methods": [
"GET"
],
"route": "/test3/{code}"
},
"backendUri": "https://httpbin.org/status/{code}",
"responseOverrides": {
"backend.response.statusCode": {
"302": {
"response.header.Location": "http://myerrorpage"
},
"501": {
"response.header.myversion": "backend.response.header.version",
"response.status": "404 Not found"
},
"2XX": {
"response.header.mystatus": "Success"
},
"4xx": {
"response.status": "{backend.response.statusCode} My Custom Message",
"response.body": "The backend gave a status code = {backend.response.statusCode}."
}
}
}
},
"px4a – Example for other Verbs": {
"desc": [
"The following verbs are supported GET, POST, HEAD, PUT, DELETE, OPTIONS, PATCH, CONNECT, TRACE",
"You can also use ANY to represent all the abover verbs"
],
"matchCondition": {
"methods": [
"PUT",
"PATCH",
"DELETE",
"GET"
],
"route": "/verbs/posts/{id}"
},
"backendUri": "https://jsonplaceholder.typicode.com/posts/{id}"
},
"px4b – Example for other Verbs": {
"desc": [
"The following verbs are supported GET, POST, HEAD, PUT, DELETE, OPTIONS, PATCH, CONNECT, TRACE",
"You can also use ANY to represent all the abover verbs"
],
"matchCondition": {
"methods": [
"POST"
],
"route": "/verbs/posts"
},
"backendUri": "https://jsonplaceholder.typicode.com/posts"
},
"px5 – Mock API for Status": {
"desc": [
"You can skip providing the backendUri to create mock apis.",
"Here is a sample api which returns the requests http status"
],
"matchCondition": {
"methods": [
"GET"
],
"route": "status/{code}/{desc}"
},
"responseOverrides": {
"response.status": "{code} {desc}"
}
},
"px6 – Example of Json output ": {
"desc": [
"You can set the response body to a json object.",
"You can use this in conjunction with conditional overrrides to provie response based on status codes",
"The is can be useful for creating mock apis for development and testing"
],
"matchCondition": {
"methods": [
"GET"
],
"route": "json/{id}"
},
"responseOverrides": {
"response.status": "200 OK",
"response.header.Content-Type": "application/json",
"response.body": {
"User": {
"Id": "{id}",
"Name": "User Name {id}"
}
}
}
},
"px7 – Example of XML output": {
"desc": [
"You can set the response body to a xml string.",
"You can use this in conjunction with conditional overrrides to provie response based on status codes",
"The is can be useful for creating mock apis for development and testing"
],
"matchCondition": {
"methods": [
"GET"
],
"route": "xml/{id}"
},
"responseOverrides": {
"response.status": "200 OK",
"response.header.Content-Type": "application/xml",
"response.body": "<xml><User><Id>{id}</Id><Name>User Name {id}</Name></User></xml>"
}
},
"px8 – Change Http Methods": {
"desc": [
"The backendUri can either be set as a uri as shown in the previious examples",
"or as an json object. This allows you to change the http method to the backend api",
"Example show how you can",
"This example supports GET, POST, PATCH, PUT, DELETE"
],
"matchCondition": {
"methods": [
"GET"
],
"route": "/method/{method}"
},
"backendUri": {
"uri": "https://httpbin.org/{method}",
"method": "{method}"
}
},
"px9 – Client Certificate Based Auth": {
"desc": [
"Example to showcase authentication with the backend APIs using client certificate",
"You would need to add an App setting MY_CLIENT_CERT_THUMBPRINT=<<thumbprint>>"
],
"matchCondition": {
"methods": [
"GET"
],
"route": "/cert"
},
"backendUri": {
"uri": "https://omisampleapiapp.azurewebsites.net/api/ToDoList?owner=*",
"method": "GET",
"clientCertificate": "%MY_CLIENT_CERT_THUMBPRINT%"
}
},
"px10 – Trace Logging and Debugging": {
"desc": [
"Example to enable trace logging. Equivalent to setting trace header for all requests of the route",
"You can set it to false to disable trace logging explicitly. So the trace header is ignored even if its passed"
],
"matchCondition": {
"methods": [
"GET"
],
"route": "/debug"
},
"backendUri": "https://httpbin.org/get",
"debug": "true"
},
"default": {
"desc": [
"Default Route to ensure existing Function apps and portal works. Do not remove"
],
"matchCondition": {
"methods": [
"ANY"
],
"route": "/{*rest}"
},
"backendUri": "https://%WEBSITE_HOSTNAME%/{rest}"
}
}
}
[/js]