Skip to content

Commit 3ef893d

Browse files
Restructure doc
1 parent 900d455 commit 3ef893d

File tree

1 file changed

+195
-69
lines changed

1 file changed

+195
-69
lines changed

articles/azure-functions/functions-custom-handlers.md

Lines changed: 195 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,36 @@ ms.topic: article
1111

1212
Every Functions app is executed by a language-specific handler. While Azure Functions supports many [language handlers](./supported-languages.md) by default, there are cases where you may want additional control over the app execution environment. Custom handlers give you this additional control.
1313

14-
Custom handlers are lightweight HTTP servers that receive events from the Functions host. Any language that supports HTTP primitives can implement a custom handler.
14+
Custom handlers are lightweight web servers that receive events from the Functions host. Any language that supports HTTP primitives can implement a custom handler.
1515

1616
Custom handlers are best suited for situations where you want to:
1717

1818
- Implement a Functions app in a language beyond the officially supported languages
19-
- Implement a Functions app in a language version not supported by default
19+
- Implement a Functions app in a language version or runtime not supported by default
2020
- Have granular control over the app execution environment
2121

22-
With custom handlers, all [triggers and input and output bindings](./functions-triggers-bindings.md) are supported.
22+
With custom handlers, all [triggers and input and output bindings](./functions-triggers-bindings.md) are supported via [extension bundles](./functions-bindings-register.md).
23+
24+
## Overview
25+
26+
The following diagram depicts the relationship between the Functions host and a web server implemented as a custom handler.
27+
28+
![Azure Functions custom handler overview](./media/functions-custom-handlers/azure-functions-custom-handlers-overview.png)
29+
30+
- Events trigger a request sent to the Functions host. The event carries either a raw HTTP payload (for HTTP-triggered functions), or a payload that holds the input binding data for the function.
31+
- The Functions host then proxies the request to the web server by issuing a [request payload](#request-payload).
32+
- The web server executes the individual function, and returns a [response payload](#response-payload) to the Functions host.
33+
- The Functions host proxies the response as an output binding payload to the target.
34+
35+
An Azure Functions app implemented as a custom handler must configure the *host.json* and *function.json* files according a few conventions.
2336

2437
## Application structure
2538

26-
To implement a custom handler, you need the following files:
39+
To implement a custom handler, you need the following aspects in your application:
2740

2841
- A *host.json* file at the root of your app
42+
- A *function.json* file for each function (inside a folder that matches the function name)
2943
- A command, script, or executable which runs a web server
30-
- A *function.json* file for each function
3144

3245
The following diagram shows how these files look on the file system for a function named "order".
3346

@@ -38,67 +51,129 @@ The following diagram shows how these files look on the file system for a functi
3851
| host.json
3952
```
4053

41-
### App configuration
54+
### Configuration
55+
56+
The application is configured via the *host.json* file. This file tells the Functions host where to send requests by pointing to a web server capable of processing HTTP events.
57+
58+
A custom handler is defined by configuring the *host.json* file with details on how to run the web server via the `httpWorker` section.
59+
60+
```json
61+
{
62+
"version": "2.0",
63+
"httpWorker": {
64+
"description": {
65+
"defaultExecutablePath": "server.exe"
66+
}
67+
}
68+
}
69+
```
70+
The `httpWorker` section points to a target as defined by the `defaultExecutablePath`. The execution target may either be a command, executable or file where the web server is implemented.
71+
72+
For scripted apps, `defaultExecutablePath` points to the script language's runtime and `defaultWorkerPath` points to the script file location. The following example shows how a JavaScript app in Node.js is configured as a custom handler.
73+
74+
```json
75+
{
76+
"version": "2.0",
77+
"httpWorker": {
78+
"description": {
79+
"defaultExecutablePath": "node",
80+
"defaultWorkerPath": "server.js"
81+
}
82+
}
83+
}
84+
```
85+
86+
You can also pass arguments using the `arguments` array:
87+
88+
```json
89+
{
90+
"version": "2.0",
91+
"httpWorker": {
92+
"description": {
93+
"defaultExecutablePath": "node",
94+
"defaultWorkerPath": "server.js",
95+
"arguments": [ "--argument1", "--argument2" ]
96+
}
97+
}
98+
}
99+
```
100+
101+
Arguments are necessary for many debugging setups. See the [Debugging](#debugging) section for more detail.
102+
103+
> [!NOTE]
104+
> The *host.json* file must be at the same level in the directory structure as the running web server. Some languages and toolchains may not place the this file at the application root by default.
42105
43-
The application is configured via the *host.json* file. This file tells the Functions host where to send requests by pointing to an executable that implements a server capable of processing HTTP events.
106+
#### Bindings support
44107

45-
Ensure the *host.json* file is at the same level in the directory structure as the running web server. Some languages and toolchains may not place the this file at the application root by default.
108+
Standard triggers along with input and output bindings are available by referencing [extension bundles](./functions-bindings-register.md) in your *host.json* file.
46109

47110
### Function metadata
48111

49-
By convention, each function must have a folder named to match the function name, containing a *function.json* file.
112+
When used with a custom handler, the *function.json* contents are no different from how you would define a function under any other context. The only requirement is that *function.json* files must be in a folder named to match the function name.
113+
114+
### Request payload
115+
116+
todo
50117

51118
### Response payload
52119

53120
By convention, function responses are formatted as key/value pairs. Supported keys include:
54121

55-
| Key | Data type | Remarks |
56-
| ------------- | -------------- | ------------------------------------------------------------ |
57-
| `Outputs` | key/value pair | Holds response values as defined by the `bindings` array the *function.json* file.<br /><br />For instance, if a function is configured with a blob storage output binding named "blob", then `Outputs` contains a key named `blob`, which is set to the blob's value. |
58-
| `Logs` | array | Messages appear in the Functions invocation logs.<br /><br />When running in Azure, logged messaged appear in Application Insights. |
59-
| `ReturnValue` | string | Used to provide a response when an output is configured as `$return` in the *function.json* file. |
122+
| Payload key | Data type | Remarks |
123+
| ------------- | --------- | ------------------------------------------------------------ |
124+
| `Outputs` | JSON | Holds response values as defined by the `bindings` array the *function.json* file.<br /><br />For instance, if a function is configured with a blob storage output binding named "blob", then `Outputs` contains a key named `blob`, which is set to the blob's value. |
125+
| `Logs` | array | Messages appear in the Functions invocation logs.<br /><br />When running in Azure, logged messaged appear in Application Insights. |
126+
| `ReturnValue` | string | Used to provide a response when an output is configured as `$return` in the *function.json* file. |
60127

61128
See the [example for a sample payload](#server-implementation).
62129

63-
### Considerations
130+
## Examples
64131

65-
- Your app must respond to requests within 60 seconds or the host considers the request a failure and attempts a retry.
132+
Custom handlers can be implemented in any language that supports HTTP events. While Azure Functions [fully supports JavaScript and Node.js](./functions-reference-node.md), the following examples show how to implement a custom handler using JavaScript in Node.js for the purposes of instruction.
66133

67-
## Create a custom handler
134+
> [!TIP]
135+
> While being a guide for learning how to implement a custom handler in other languages, the Node.js-based examples shown here may also be useful if you wanted to run a Functions app in a non-supported version of Node.js.
68136
69-
Custom handlers can be implemented in any language that supports HTTP events. While Azure Functions [fully supports JavaScript and Node.js](./functions-reference-node.md), the following example shows how to implement a custom handler using JavaScript in Node.js for the purposes of instruction.
137+
## HTTP-only function
70138

71-
The scenario implemented in this example features a function named `order` that accepts a `POST` with a payload representing a product order.
139+
The following example demonstrates how to configure a simple HTTP-triggered function with no additional bindings or outputs. The scenario implemented in this example features a function named `http` that accepts a `GET` or `POST` .
140+
141+
The following snippet represents how a a request to the function is composed.
72142

73143
```http
74-
POST http://127.0.0.1:7071/api/order HTTP/1.1
144+
POST http://127.0.0.1:7071/api/hello HTTP/1.1
75145
content-type: application/json
76146
77147
{
78-
"id": 1005,
79-
"quantity": 2,
80-
"color": "black"
148+
"message": "Hello World!"
81149
}
82150
```
83151

84-
### Application configuration
152+
### Implementation
85153

86-
A custom handler is defined by configuring the *host.json* file with details on how to start your HTTP server. The `httpWorker` section points to an execution target as defined by the `defaultExecutablePath`. The execution target may either be an executable or the combination of an executable and a target file.
87-
88-
For an app written in a compiled language, `defaultExecutablePath` points to an executable file.
154+
In a folder named *http*, the *function.json* file configures the HTTP-triggered function.
89155

90156
```json
91157
{
92-
"version": "2.0",
93-
"httpWorker": {
94-
"description": {
95-
"defaultExecutablePath": "server.exe"
96-
}
158+
"bindings": [
159+
{
160+
"type": "httpTrigger",
161+
"direction": "in",
162+
"name": "req",
163+
"methods": ["get", "post"]
164+
},
165+
{
166+
"type": "http",
167+
"direction": "out",
168+
"name": "res"
97169
}
170+
]
98171
}
99172
```
100173

101-
For scripted apps, `defaultExecutablePath` points to the script language's executable and `defaultWorkerPath` points to the script file location. The following example shows how to a JavaScript app in Node.js is configured as a custom handler.
174+
The function is configured to accept both `GET` and `POST` requests and the result value is provided via an argument named `res`.
175+
176+
At the root of the app, the *host.json* file is configured to run Node.js and point the `server.js` file.
102177

103178
```json
104179
{
@@ -112,28 +187,59 @@ For scripted apps, `defaultExecutablePath` points to the script language's execu
112187
}
113188
```
114189

115-
You can also pass arguments using the `arguments` array:
190+
The file *server.js* file implements a web server and HTTP function.
191+
192+
```javascript
193+
const express = require("express");
194+
const app = express();
195+
196+
app.use(express.json());
197+
198+
const PORT = process.env.FUNCTIONS_HTTPWORKER_PORT;
199+
200+
const server = app.listen(PORT, "localhost", () => {
201+
console.log(`Your port is ${PORT}`);
202+
const { address: host, port } = server.address();
203+
console.log(`Example app listening at http://${host}:${port}`);
204+
});
205+
206+
app.get("/hello", (req, res) => {
207+
res.json("Hello World!");
208+
});
209+
210+
app.post("/hello", (req, res) => {
211+
res.json({ value: req.body });
212+
});
213+
```
214+
215+
In this example, Express is used to create a web server to handle HTTP events and is set to listen for requests via the `FUNCTIONS_HTTPWORKER_PORT`.
216+
217+
The function is defined at the path of `/hello` . `GET` requests are handled by returning a simple JSON object, and `POST` requests have access to the request body via `req.body`.
218+
219+
The route for the order function here is `/hello` and not `/api/hello` because the Functions host is proxying the request to the custom handler.
220+
221+
>[!NOTE]
222+
>The `FUNCTIONS_HTTPWORKER_PORT` is not the public facing port used to call the function. This port is used by the Functions host to call the custom handler.
223+
224+
## Function with bindings
225+
226+
The scenario implemented in this example features a function named `order` that accepts a `POST` with a payload representing a product order.
227+
228+
```http
229+
POST http://127.0.0.1:7071/api/order HTTP/1.1
230+
content-type: application/json
116231
117-
```json
118232
{
119-
"version": "2.0",
120-
"httpWorker": {
121-
"description": {
122-
"defaultExecutablePath": "node",
123-
"defaultWorkerPath": "server.js",
124-
"arguments": [ "--inspect" ] // allows you to set breakpoints for debugging
125-
}
126-
}
233+
"id": 1005,
234+
"quantity": 2,
235+
"color": "black"
127236
}
128237
```
129238

130-
Arguments are necessary for many debugging setups. See the [Debugging](#debugging) section for more detail.
131239

132-
Standard triggers and input and output bindings are available by referencing [extension bundles](./functions-bindings-register.md) in your *host.json* file.
240+
### Implementation
133241

134-
### Function definition
135-
136-
The following *function.json* file is no different from how you would define a function under any other context. Since the function name is *order*, this file is saved in a folder named "order".
242+
In a folder named *order*, the *function.json* file configures the HTTP-triggered function.
137243

138244
```json
139245
{
@@ -164,12 +270,21 @@ The following *function.json* file is no different from how you would define a f
164270

165271
This function is defined as an [HTTP triggered function](./functions-bindings-http-webhook-trigger.md) that returns an [HTTP response](./functions-bindings-http-webhook-output.md) and outputs a [Queue storage](./functions-bindings-storage-queue-output.md) message.
166272

167-
### Server implementation
273+
At the root of the app, the *host.json* file is configured to run Node.js and point the `server.js` file.
168274

169-
A web server is used to listen for requests coming through the `FUNCTIONS_HTTPWORKER_PORT`.
275+
```json
276+
{
277+
"version": "2.0",
278+
"httpWorker": {
279+
"description": {
280+
"defaultExecutablePath": "node",
281+
"defaultWorkerPath": "server.js"
282+
}
283+
}
284+
}
285+
```
170286

171-
>[!NOTE]
172-
>The `FUNCTIONS_HTTPWORKER_PORT` is not the public facing port used to call the function. This port is used by the Functions host to call the custom handler.
287+
The file *server.js* file implements a web server and HTTP function.
173288

174289
```javascript
175290
const express = require("express");
@@ -179,6 +294,12 @@ app.use(express.json());
179294

180295
const PORT = process.env.FUNCTIONS_HTTPWORKER_PORT;
181296

297+
const server = app.listen(PORT, "localhost", () => {
298+
console.log(`Your port is ${PORT}`);
299+
const { address: host, port } = server.address();
300+
console.log(`Example app listening at http://${host}:${port}`);
301+
});
302+
182303
app.post("/order", (req, res) => {
183304
const message = req.body.Data.req.Body;
184305
const response = {
@@ -193,33 +314,26 @@ app.post("/order", (req, res) => {
193314
};
194315
res.json(response);
195316
});
196-
197-
const server = app.listen(PORT, "localhost", () => {
198-
console.log(`Your port is ${PORT}`);
199-
const { address: host, port } = server.address();
200-
console.log(`Example app listening at http://${host}:${port}`);
201-
});
202-
203317
```
204318

205-
Express.js is used to create a web server to handle HTTP events and is set to listen for requests via the `FUNCTIONS_HTTPWORKER_PORT`.
319+
In this example, Express is used to create a web server to handle HTTP events and is set to listen for requests via the `FUNCTIONS_HTTPWORKER_PORT`.
206320

207-
The order function is defined at the path of `/order` and extracts the request payload to save as a queue message.
321+
The function is defined at the path of `/order` . The route for the order function here is `/order` and not `/api/order` because the Functions host is proxying the request to the custom handler.
208322

209-
The route for the order function here is `/order` and not `/api/order` because the function host is proxying the request to the custom handler. Once the Functions host receives the request, the proxied request sent to the handler at the handler's application root.
323+
As `POST` requests are sent to this function, data is exposed through a few points:
210324

211-
The message contents are available from the request via `req.body.Data.req.Body`.
325+
- The request body is available via `req.body`
326+
- The data posted to the function is available via `req.body.Data.req.Body`
212327

213-
The function's response is formatted into a key/value pair where the `Outputs` member holds another key/value pair where the keys match the outputs as defined in the *function.json* file.
328+
The function's response is formatted into a key/value pair where the `Outputs` member holds a JSON value where the keys match the outputs as defined in the *function.json* file.
214329

215330
By setting `message` equal to the message that came in from the request, and `res` to the expected HTTP response, this function outputs a message to Queue Storage and returns an HTTP response.
216331

217332
## Debugging
218333

219-
To debug your Functions custom handler app, the `--inspect` flag needs to be included as an argument in the *host.json* file.
334+
To debug your Functions custom handler app you need to add arguments appropriate for the language and runtime to enable debugging.
220335

221-
> [!NOTE]
222-
> The debugging configuration is part of your *host.json* file, which means that you may need to remove the `--inspect` argument before deploying to production.
336+
For instance, to debug a Node.js application, the `--inspect` flag is passed as an argument in the *host.json* file.
223337

224338
```json
225339
{
@@ -228,24 +342,28 @@ To debug your Functions custom handler app, the `--inspect` flag needs to be inc
228342
"description": {
229343
"defaultExecutablePath": "node",
230344
"defaultWorkerPath": "server.js",
231-
"arguments": [ "--inspect" ] // allows you to set breakpoints for debugging
345+
"arguments": [ "--inspect" ]
232346
}
233347
}
234348
}
235349
```
236350

351+
> [!NOTE]
352+
> The debugging configuration is part of your *host.json* file, which means that you may need to remove the some arguments before deploying to production.
353+
237354
With this configuration, you can start the Function's host process using the following command:
238355

239356
```bash
240357
func host start
241358
```
242-
243359
Once the process is started, you can attach a debugger and hit breakpoints.
244360

245361
### Visual Studio Code
246362

247363
The following example is a sample configuration that demonstrates how you can set up your *launch.json* file to connect your app to the Visual Studio Code debugger.
248364

365+
This example is for Node.js, so you may have to alter this example for other languages or runtimes.
366+
249367
```json
250368
{
251369
"version": "0.2.0",
@@ -264,3 +382,11 @@ The following example is a sample configuration that demonstrates how you can se
264382
## Deploying
265383

266384
A custom handler can be deployed to any Azure Functions hosting option. If your handler requires custom dependencies (such as a language runtime), you may need to use a [custom container](./functions-create-function-linux-custom-image.md).
385+
386+
## Restrictions
387+
388+
Custom handlers are not supported in Linux consumption plans.
389+
390+
## Samples
391+
392+
Refer to the custom handler samples GitHub repo for examples of how to implement functions in a variety of different languages.

0 commit comments

Comments
 (0)