Azure Functions custom handlers let you run code written in any language, even if it’s not directly supported by the Azure Functions runtime.
Let’s see a simple Python HTTP triggered function using a custom handler.
First, we need a host.json file to tell the Functions runtime to use custom handlers and where to find our executable.
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "3.3.0"
},
"customHandler": {
"description": {
"defaultExecutablePath": "handler",
"workingDirectory": ""
}
}
}
The defaultExecutablePath points to our compiled handler. In this case, we’ll name our executable handler. The workingDirectory can be left empty if the executable is in the root of the function app.
Next, we need our actual function code. This will be a Python script, let’s call it main.py, which will be executed by our custom handler.
import json
import sys
import os
def main():
while True:
try:
line = sys.stdin.readline().strip()
if not line:
break
request = json.loads(line)
# Extract necessary information from the request
method = request.get('method')
url = request.get('url')
headers = request.get('headers')
query = request.get('query')
body = request.get('body')
# Simulate processing the request
response_body = {
"message": f"Received {method} request for {url}",
"headers": headers,
"query_params": query,
"body_content": body
}
response = {
"status": 200,
"body": json.dumps(response_body)
}
print(json.dumps(response))
except Exception as e:
error_response = {
"status": 500,
"body": json.dumps({"error": str(e)})
}
print(json.dumps(error_response))
if __name__ == "__main__":
main()
This Python script reads JSON objects from standard input, which represent incoming requests from the Azure Functions host. It then processes these requests (in this simple case, just echoing back information) and writes a JSON response object to standard output. The Azure Functions host will then take this output and return it as an HTTP response.
Now, we need the custom handler executable itself. This is a small executable that will manage the lifecycle of our main.py script. For Python, the Azure Functions Core Tools provide a way to create this. If you’re using another language, you’d compile your code into an executable that conforms to the custom handler protocol.
Let’s assume we have a function.json for our HTTP trigger:
{
"scriptFile": "main.py",
"entryPoint": "main",
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
],
"route": "hello"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
For our custom handler setup, we’ll create a file named handler (which will be our executable) and make it executable.
On Linux/macOS:
echo '#!/bin/bash' > handler
echo 'python main.py' >> handler
chmod +x handler
On Windows:
You can create a simple batch file:
@echo off
python main.py
Save this as handler.cmd or handler.bat and ensure handler in host.json is set to handler.cmd or handler.bat.
The custom handler protocol is surprisingly simple. The Functions host communicates with your executable by sending JSON objects representing the request to its standard input. Your executable reads these JSON objects, processes them, and writes a JSON object representing the response to its standard output. The host then interprets this output as the HTTP response. The request object from the host contains method, url, headers, query, and body. The response object you write back needs status and body.
When you deploy this function app, the host.json, main.py, function.json, and the handler executable (or handler.cmd) must all be present in the root directory.
When a request comes in to your hello route, the Azure Functions host will:
- Read the
host.jsonand see thatcustomHandleris configured. - Execute the
handlerexecutable. - The
handlerexecutable (ourpython main.pyscript) starts. - The host sends the incoming HTTP request as a JSON object to the
handler’s stdin. main.pyreads this JSON, processes it, and writes a JSON response to its stdout.- The host reads the JSON response from
handler’s stdout and converts it into an HTTP response to the client.
The key insight is that your custom handler executable acts as an intermediary, translating the host’s generic request format into whatever your application language needs, and then translating your application’s output back into the host’s expected response format. This allows you to leverage existing codebases or preferred languages without needing direct runtime support.
The next thing you’ll likely encounter is handling different types of triggers beyond HTTP, like queue or blob triggers, which also follow the same input/output JSON contract.