--- title: "Requests and responses" description: "How a function handler receives an event and returns a response, and how CORS, bodies, init(), and errors work in the HTTP runtimes." content_type: "concept" product: "functions" platform: "MyAccount" url: "/myaccount/functions/concepts/requests-and-responses/" --- # Requests and responses In the Python and Node.js HTTP runtimes, a function handler receives each HTTP request as an event and returns a response. The FastAPI, Flask, and Express runtimes use their own app and routes instead. See [FastAPI, Flask, and Express runtimes](#fastapi-flask-and-express-runtimes). ## The handler for each runtime In a Python HTTP runtime, define `handler` in func.py. In a Node.js HTTP runtime, export `handler` from index.mjs. ```python import json async def handler(event, context): return { "statusCode": 200, "body": json.dumps({"message": "Hello World"}) } ``` ```js export const handler = async (event, context) => { return { statusCode: 200, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: "Hello World" }), }; }; ``` The Python handler can be an `async def` or a plain `def`. The platform calls either kind. Use `async def` for work that waits on I/O, such as database calls, HTTP requests, and external APIs. Use a plain `def` for lightweight processing and CPU-bound logic. Go and C# have their own fixed handler. In Go, handle.go is in package main and defines this function: ```go func Handle(ctx context.Context, request APIGatewayProxyRequest) (APIGatewayProxyResponse, error) ``` In C#, Handler.cs defines a public class named Handler with this method: ```csharp public async Task> HandleAsync(Dictionary awsEvent, Dictionary context) ``` In PHP, index.php runs for every request. It reads the request from PHP's $_SERVER array and writes the response with PHP's header and echo. The PHP sample puts that work in a function named handleRequest and calls it. ## The event the handler receives In the Python and Node.js HTTP runtimes, every request becomes an event passed to the handler. The event holds the fields below. | Field | What it holds | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | httpMethod | The HTTP method, such as GET or POST. | | path | The URL path, for example `/users/42`. | | headers | The request headers as key-value pairs, with lowercase names. | | multiValueHeaders | The request headers, each value as a list, with lowercase names. | | queryStringParameters | The query parameters. For a parameter given more than once, the last value. Null when there is no query string. | | multiValueQueryStringParameters | The query parameters, each value as a list, repeated or not. Null when there is no query string. | | body | The request body, as a UTF-8 string for text types or a base64 string for the others. Null when there is no body. | | isBase64Encoded | True when the body is base64. False for text. | | rawBody | The raw bytes of the request body, for parsing binary content. | | requestContext | An object with stage, set to local, and identity, holding sourceIp and userAgent. | Reading the event in Python: ```python import json async def handler(event, context): body = json.loads(event["body"]) if event["body"] else {} page = (event["queryStringParameters"] or {}).get("page") method = event["httpMethod"] return {"statusCode": 200} ``` Reading the event in Node.js: ```js export const handler = async (event) => { const body = event.body ? JSON.parse(event.body) : {}; const page = event.queryStringParameters?.page ?? "1"; const method = event.httpMethod; const path = event.path; const ip = event.requestContext.identity.sourceIp; return { statusCode: 200 }; }; ``` ## The handler's context The handler's context holds functionName, set to function, and functionVersion, set to $LATEST. To set your own stage, function name, or version, add STAGE, FUNCTION_NAME, or FUNCTION_VERSION as environment variables. The event's stage and the context then show your values. ## The handler's response A handler returns a response object. Only the status code is required. To redirect, return a 302 with a Location header. To return JSON, put it in the body and set the matching Content-Type. | Field | What it does | | ----------------- | ---------------------------------------------------- | | statusCode | Required, an integer from 100 to 599. | | headers | Sets single-value headers, for example Content-Type. | | multiValueHeaders | Sets headers that repeat, for example Set-Cookie. | | body | The payload. A string, an object, or null. | | isBase64Encoded | Set true to send binary content. | To report an error to the caller, return a non-2xx statusCode with a body that describes it. Some common responses in Python, with a handler that returns one of them: ```python import json json_response = { "statusCode": 200, "headers": {"Content-Type": "application/json"}, "body": json.dumps({"success": True}) } plain_text = { "statusCode": 200, "headers": {"Content-Type": "text/plain"}, "body": "Hello" } redirect = { "statusCode": 302, "headers": {"Location": "https://example.com"} } cookies = { "statusCode": 200, "multiValueHeaders": {"Set-Cookie": ["session=abc; HttpOnly"]}, "body": json.dumps({"ok": True}) } no_content = {"statusCode": 204, "body": None} def handler(event, context): return json_response ``` The same responses in Node.js: ```js const jsonResponse = { statusCode: 200, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ success: true }), }; const plainText = { statusCode: 200, headers: { "Content-Type": "text/plain" }, body: "Hello", }; const redirect = { statusCode: 302, headers: { Location: "https://example.com" }, }; const cookies = { statusCode: 200, multiValueHeaders: { "Set-Cookie": ["session=abc; HttpOnly"] }, body: JSON.stringify({ ok: true }), }; const noContent = { statusCode: 204, body: null }; export const handler = async (event) => jsonResponse; ``` To send a file, set the Content-Type and Content-Disposition headers, put the base64 bytes in the body, and set isBase64Encoded to true: ```python import base64 def handler(event, context): data = b"\x89PNG\r\n\x1a\n" # a PNG's first bytes, in place of real file data return { "statusCode": 200, "headers": { "Content-Type": "image/png", "Content-Disposition": 'inline; filename="photo.png"' }, "body": base64.b64encode(data).decode(), "isBase64Encoded": True } ``` ```js export const handler = async (event) => { const data = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); // a PNG's first bytes return { statusCode: 200, headers: { "Content-Type": "image/png", "Content-Disposition": 'inline; filename="photo.png"', }, body: data.toString("base64"), isBase64Encoded: true, }; }; ``` ## Change CORS and body limits with the init function Define an init function to change CORS and body handling. It runs when the function starts. Without one, the defaults apply. ```python def init(): return { "cors": { "origins": ["*"], "credentials": False }, "body": { "json": True, "urlencoded": False, "content_types": [], "max_bytes": 10 * 1024 * 1024 } } ``` ```js export const init = () => ({ cors: { origins: ["*"], credentials: false, }, body: { json: true, urlencoded: false, contentTypes: [], maxBytes: 10 * 1024 * 1024, }, }); ``` The init function must be synchronous and return a plain dictionary or object. An asynchronous init function, a return value of another type, or an empty or non-string origins list stops the function from starting. The runtime logs then show an error line that says why, such as "init() must be synchronous". See [View logs and monitoring](/myaccount/functions/how-to/view-logs-and-monitoring/). The defaults when the init function is not defined: | Setting | Default | Meaning | | ------------------ | ------- | ----------------------------------------- | | cors.origins | `["*"]` | Any origin can call the function. | | cors.credentials | False | CORS does not allow credentials. | | body.json | True | JSON requests are accepted. | | body.urlencoded | False | Form submissions are rejected with 415. | | body.content_types | `[]` | No additional content types are accepted. | | body.max_bytes | 10 MB | Larger bodies get 413. | By default a function answers cross-origin requests from any origin. CORS applies only to calls from a browser. Calls from a server, such as curl or another backend, reach the function whatever the origins setting. In Node.js, two settings are spelled differently: contentTypes for content_types, and maxBytes for max_bytes. The setting that reaches each goal: | Goal | Setting | | ---------------------------- | ------------------------------------------------------------------------------------------ | | Use the defaults | Do not define the init function. | | Allow only some origins | Set cors.origins to a list of your domains. | | Allow cookies or credentials | Set cors.credentials to True, and list specific domains in cors.origins. | | Accept form posts | Set body.urlencoded to True. | | Accept file uploads | Add a multipart/form-data match to body.content_types, and raise body.max_bytes if needed. | | Accept XML | Add text/xml and application/xml to body.content_types. | | Stop accepting JSON | Set body.json to False. | | Accept plain text | Add text/plain to body.content_types. | | Accept images | Add an image/ match to body.content_types. | | Accept PDF | Add application/pdf to body.content_types. | | Accept other binary data | Add application/octet-stream to body.content_types. | | Set a strict body limit | Set body.max_bytes below the 10 MB default. | Each entry in body.content_types is a string or a pattern. A string matches any content type that starts with it, in any case. A pattern is a compiled re pattern in Python, or a RegExp in Node.js. Credentials need specific origins. A wildcard origin with credentials stops the function from starting. The body size limit is 10 MB by default, and you can raise it up to 1000 MB. A value above that cap stops the function from starting. A body over the limit you set gets 413. ### Init examples in Python File upload as form data, with the limit raised to 50 MB: ```python import re def init(): return { "body": { "content_types": [re.compile(r"^multipart/form-data")], "max_bytes": 50 * 1024 * 1024 } } ``` XML: ```python def init(): return { "body": { "content_types": ["text/xml", "application/xml"] } } ``` JSON stays accepted. A `text/xml` body arrives as text. An `application/xml` body arrives base64-encoded, with its bytes in rawBody. To accept only XML, also set "json": False in body. Images: ```python import re def init(): return {"body": {"content_types": [re.compile(r"^image/")]}} ``` PDF: ```python def init(): return {"body": {"content_types": ["application/pdf"]}} ``` ### Init examples in Node.js You can read the allowed origins from an environment variable. Set the variable in **Function Configuration**, and use it in the init function: ```js export const init = () => ({ cors: { origins: (process.env.CORS_ORIGINS || "*").split(","), credentials: false, }, }); ``` Set CORS_ORIGINS to a comma-separated list of domains, such as `https://app.example.com,https://staging.example.com`. To read a form post, accept form submissions and parse the body: ```js export const init = () => ({ body: { json: true, urlencoded: true }, }); export const handler = async (event) => { const form = Object.fromEntries(new URLSearchParams(event.body)); return { statusCode: 200, body: JSON.stringify({ email: form.email }) }; }; ``` To read a file upload, accept multipart, raise the limit to 50 MB, and parse the raw body. This example uses the busboy library. Add `busboy` to the dependencies in your `package.json`: ```js import busboy from "busboy"; import { Readable } from "stream"; export const init = () => ({ body: { contentTypes: [/^multipart\/form-data/], maxBytes: 50 * 1024 * 1024, }, }); export const handler = async (event) => { const files = []; await new Promise((resolve, reject) => { const parser = busboy({ headers: event.headers }); parser.on("file", (name, file, info) => { files.push(info.filename); file.resume(); }); parser.on("close", resolve); parser.on("error", reject); Readable.from([event.rawBody]).pipe(parser); }); return { statusCode: 200, body: JSON.stringify({ files }) }; }; ``` To read an image, accept image types and use the raw bytes: ```js export const init = () => ({ body: { contentTypes: [/^image\//] }, }); export const handler = async (event) => { // event.body holds the image as base64, and event.rawBody holds its bytes. const bytes = event.rawBody; return { statusCode: 200, body: JSON.stringify({ size: bytes.length }) }; }; ``` ## Binary bodies JSON, form-urlencoded, and `text/*` bodies arrive in `body` as a UTF-8 string, and so does a body with no content type. Every other content type the init function allows arrives base64-encoded in `body`. The raw bytes are in rawBody. Use rawBody for multipart and file parsing. | Content type | How it arrives in `body` | isBase64Encoded | | --------------------------------------------------- | ------------------------ | --------------- | | application/json | UTF-8 string | false | | application/x-www-form-urlencoded | UTF-8 string | false | | text/plain, text/xml, and other text/\* types | UTF-8 string | false | | No content type | UTF-8 string | false | | application/xml | base64 string | true | | multipart/form-data | base64 string | true | | image/\*, application/pdf, application/octet-stream | base64 string | true | A request with no body, such as a GET, has `body` set to null. ## FastAPI, Flask, and Express runtimes In the FastAPI, Flask, and Express runtimes, you write an ordinary app with its own routes. The app takes the place of the handler, the event, and the init function. In these runtimes, your app decides what a request can carry, including its size and content type. The platform's templates for these runtimes allow cross-origin calls from any origin. For FastAPI and Flask, func.py must create the app under the name `app`. The platform starts it on port 8080. For Express, index.js must start the app on the port the platform gives it, with this line: ```js app.listen(process.env.PORT); ``` The template marks that line as one not to change. Keep it as it is. ## Errors and status codes If a handler throws, the platform returns a generic error and logs the handler error. An out-of-memory error returns 503. Any other handler error returns 500. A handler that returns the wrong shape also gets 500. The caller never sees the handler's error text. A body larger than the configured limit returns 413 before the handler runs. A content type that is not allowed returns 415 before the handler runs. Handle errors in the handler with a try/except, or try/catch: ```python import json async def handler(event, context): try: body = json.loads(event["body"]) if event["body"] else {} if not body.get("email"): return { "statusCode": 400, "headers": {"Content-Type": "application/json"}, "body": json.dumps({"error": "email required"}) } return {"statusCode": 200, "body": json.dumps({"ok": True})} except Exception as err: print(err) return { "statusCode": 500, "headers": {"Content-Type": "application/json"}, "body": json.dumps({"error": "Internal Server Error"}) } ``` ```js export const handler = async (event) => { try { const body = event.body ? JSON.parse(event.body) : {}; if (!body.email) { return { statusCode: 400, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: "email required" }), }; } return { statusCode: 200, body: JSON.stringify({ ok: true }) }; } catch (err) { console.error(err); return { statusCode: 500, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: "Internal Server Error" }), }; } }; ``` ## What to read next See [Call a function](/myaccount/functions/how-to/call-a-function/) to call a function, and [View logs and monitoring](/myaccount/functions/how-to/view-logs-and-monitoring/) to read what a handler logged.