#Request/Response Hooks
Hooks allow running code as part of the Request/Response pipeline. Hooks are typically added using a plugin as shown in this document, but they can also be added globally using Runtime Extensions.
All hooks can be either synchronous or asynchronous. To make your hook
asynchronous simply add the async
keyword on the function.
#Hook: OnResponseSending
The OnResponseSending
hook on ZuploContext
fires just before the response is
sent to the client. The Response
can be modified by returning a new Response
from this hook. This hook is useful for creating an inbound policy that also
needs to run some logic after the response is returned from the handler.
The example below shows a simple tracing policy that adds a trace header to the request and ensures the same header is returned with the response.
import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; export async function tracingPlugin( request: ZuploRequest, context: ZuploContext, policyName: string, ) { // Get the trace header let traceparent = request.headers.get("traceparent"); // If not set, add the header to the request if (!traceparent) { traceparent = crypto.randomUUID(); const headers = new Headers(request.headers); headers.set("traceparent", traceparent); return new ZuploRequest(request, { headers }); } context.addResponseSendingHook((response, latestRequest, context) => { // If the response doesn't have the trace header that matches, set it if (response.headers.get("traceparent") !== traceparent) { const headers = new Headers(response.headers); headers.set("traceparent", traceparent); return new Response(response.body, { headers, }); } return response; }); return request; }ts
#Hook: OnResponseSendingFinal
The OnResponseSendingFinal
hook on ZuploContext
fires immediately before the
response is sent to the client. The Response
in this hook is immutable. This
hook is useful for custom tasks like caching, logging or analytics.
This simple example shows logging the time from the start of this policy until just before the response is sent.
import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; export async function pluginWithHook( request: ZuploRequest, context: ZuploContext, policyName: string, ) { const start = Date.now(); async (response, latestRequest, context) => { const end = Date.now(); const delta = end - start; context.log.debug(`request took ${delta}ms`); }); return request; }ts
Note that the call can block the response, so if you wish to run an activity asynchronously
and not block the response you should not await inside the hook and use context.waitUntil
to ensure your async activity is not terminated prematurely - this is shown in the example
below. This example shows details of the request and final response being sent to an imaginary
logging service.
import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; export async function pluginWithHook( request: ZuploRequest, context: ZuploContext, policyName: string, ) { // Note we must clone the request here, before it is used by the handler const requestClone = request.clone(); context.addResponseSendingFinalHook( async (response, latestRequest, context) => { // Note, we must clone the response to read the body // as the original will be used the by response pipeline // itself const responseClone = response.clone(); const asyncInnerFunction = async () => { // Note - we must clone the response const requestBody = await requestClone.text(); const responseBody = await responseClone.text(); await fetch("https://example.com", { method: "GET", body: JSON.stringify({ requestBody, responseBody }); }); } const promise = asyncInnerFunction(); // Don't block the response while waiting for the fetch context.waitUntil(promise); }); return request; }ts