diff --git a/docs/node/interceptors.md b/docs/node/interceptors.md index e15c5380..ea98c979 100644 --- a/docs/node/interceptors.md +++ b/docs/node/interceptors.md @@ -19,7 +19,7 @@ import { connectNodeAdapter } from "@connectrpc/connect-node"; import type { Interceptor } from "@connectrpc/connect"; const logger: Interceptor = (next) => async (req) => { - console.log(`recevied message on ${req.url}`); + console.log(`received message on ${req.url}`); return await next(req); }; @@ -39,7 +39,7 @@ To intercept responses, we simply look at the return value of `next()`: ```ts const logger: Interceptor = (next) => async (req) => { - console.log(`recevied message on ${req.url}`); + console.log(`received message on ${req.url}`); const res = await next(req); if (!res.stream) { console.log("message:", res.message); @@ -231,4 +231,4 @@ await server.listen({ }); ``` -The request passed to the `contextValues` function is different for each server plugin, please refer to the documentation for the server plugin you are using. \ No newline at end of file +The request passed to the `contextValues` function is different for each server plugin, please refer to the documentation for the server plugin you are using. diff --git a/docs/node/server-plugins.md b/docs/node/server-plugins.md index 0a430948..63112e01 100644 --- a/docs/node/server-plugins.md +++ b/docs/node/server-plugins.md @@ -25,7 +25,7 @@ http.createServer( ).listen(8080); ``` -The function accepts all common options, and the following additional +The function accepts all [common options](#common-options), and the following additional ones: - `fallback?: NodeHandlerFn`
@@ -75,7 +75,13 @@ await server.listen({ }); ``` -The plugin accepts all common options, and the following additional ones: +The plugin accepts all [common options](#common-options), and the following additional ones: +- `shutdownTimeoutMs?: number`
+ If set, the server will wait for the specified duration before aborting any + in-flight requests once [`fastify.close`](https://fastify.dev/docs/latest/Reference/Server/#close) is called. +- `shutdownError?: unknown`
+ The reason to use when shutdown occurs. Note that if this is a `ConnectError` it will + be sent to the client. - `contextValues?: (req: FastifyRequest) => ContextValues`
A function that returns a set of context values for each request. The context values are passed to the service implementation. See @@ -112,9 +118,14 @@ This file is a Next.js [catch-all API route](https://nextjs.org/docs/routing/dyn serve your Connect RPCs with the `/api` prefix. Make sure to include the `/api` prefix in the `baseUrl` option for your client transport. -The middleware accepts all common options, and the following additional +The middleware accepts all [common options](#common-options), and the following additional one: +- `prefix?: string`
+ Serve all handlers under this prefix. For example, the prefix "/something" + will serve the RPC foo.FooService/Bar under "/something/foo.FooService/Bar". + By default, this is `/api` for Next.js.
+ Note that many gRPC client implementations do not allow for prefixes. - `contextValues?: (req: NextApiRequest) => ContextValues`
A function that returns a set of context values for each request. The context values are passed to the service implementation. See @@ -150,13 +161,13 @@ app.use(expressConnectMiddleware({ http.createServer(app).listen(8080); ``` -The middleware accepts all common options, and the following additional +The middleware accepts all [common options](#common-options), and the following additional one: - `requestPathPrefix?: string`
-Serve all handlers under this prefix. For example, the prefix "/something" -will serve the RPC foo.FooService/Bar under "/something/foo.FooService/Bar". -Note that many gRPC client implementations do not allow for prefixes. + Serve all handlers under this prefix. For example, the prefix "/something" + will serve the RPC foo.FooService/Bar under "/something/foo.FooService/Bar". + Note that many gRPC client implementations do not allow for prefixes. - `contextValues?: (req: express.Request) => ContextValues`
A function that returns a set of context values for each request. The context values are passed to the service implementation. See @@ -176,10 +187,14 @@ All adapters take a set of common options: - `routes: (router: ConnectRouter) => void`
The adapter will call this function, and lets you register your services.
See [Implementing services](./implementing-services.md) for an example. +- `maxTimeoutMs?: number`
+ The maximum value for [timeouts](./timeouts) that clients may specify. + If a client requests a timeout that is greater than `maxTimeoutMs`, + the server responds with the error code `invalid_argument`. - `connect?: boolean`
Whether to enable the Connect protocol for your routes. Enabled by default. - `grpcWeb?: boolean`
- Whether to enable the gRPC protocol for your routes. Enabled by default. + Whether to enable the gRPC-web protocol for your routes. Enabled by default. - `grpc?: boolean`
Whether to enable the gRPC protocol for your routes. Enabled by default. - `interceptors?: Interceptor[]`
diff --git a/docs/node/testing.md b/docs/node/testing.md index 5bd0d5e3..404d5461 100644 --- a/docs/node/testing.md +++ b/docs/node/testing.md @@ -1,6 +1,6 @@ --- title: Testing -sidebar_position: 6 +sidebar_position: 7 --- When writing tests for your Connect for Node.js application, your approach will diff --git a/docs/node/timeouts.md b/docs/node/timeouts.md new file mode 100644 index 00000000..a059192d --- /dev/null +++ b/docs/node/timeouts.md @@ -0,0 +1,84 @@ +--- +title: Timeouts +sidebar_position: 8 +--- + +Timeouts can be used to limit the time a server may take to process a response. +In Connect-ES, timeout values are set by the client via the `timeoutMs` option +when issuing a requests. If handling the response takes longer than the timeout, +they will respond with the error code `deadline_exceeded`. In gRPC, the concept +is also known as [deadlines](https://grpc.io/docs/guides/deadlines/). + +## Using `HandlerContext` + +Servers can interact with this timeout via the [handler context](https://connectrpc.com/docs/node/implementing-services#context). +Depending on your needs, there are a few ways to approach it: + +The first way is via an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) +on the context. Using this signal, route handlers can then tell if the timeout specified +by the client was reached and abort their processes accordingly. The `AbortSignal` can be found +via the property name `signal`. + +The signal can be passed to other functions or used to gracefully stop processes when the timeout is reached. +Using `signal` works for any operation you might want to call as long as the API supports it. + +```ts +import type { HandlerContext } from "@bufbuild/connect"; + +const say = async (req: SayRequest, ctx: HandlerContext) => { + + ctx.signal.aborted; // true if timed out + ctx.signal.reason; // an error with code deadline_exceeded if timed out + + // raises an error with code deadline_exceeded if timed out + ctx.signal.throwIfAborted(); + + // the AbortSignal can be passed to other functions + await longRunning(ctx.signal); + + return new SayResponse({sentence: `You said: ${req.sentence}`}); +}; +``` + +A second way to interact with the timeout value is via the `timeoutMs()` function +on the handler context. If the current request has a timeout, this function +returns the remaining time. + +Using the `timeoutMs()` function is preferable when invoking upstream RPC calls +because it is more efficient and robust - you have a guarantee that the peer is +aware of the deadline, regardless of network issues. In gRPC, the concept is also +known as [deadline propagation](https://grpc.io/docs/guides/deadlines/#deadline-propagation). + +```ts +import type { HandlerContext } from "@bufbuild/connect"; + +const say = async (req: SayRequest, ctx: HandlerContext) => { + + // If a timeout was set on the call to this service, the timeoutMs() method + // returns the remaining time in milliseconds. + + // Passing the value to an upstream client call propagates the timeout. + await upstreamClient.someCall({}, { timeoutMs: ctx.timeoutMs() }); + + return new SayResponse({sentence: `You said: ${req.sentence}`}); +}; +``` + +In addition, to server-side support for timeouts, there is also a related option on `ConnectRouter` +that helps constraining timeout values: `maxTimeoutMs`. For an explanation of this option, +see the docs on [Server Plugins](server-plugins#common-options) + +Also note that while this page discusses timeouts in the context of a server, Connect-ES clients +honor timeout values and will raise a `ConnectError` with code `DeadlineExceeded`. Even if a connection +becomes unresponsive, the client call will still abort at the configured timeout. + +```ts +try { + // If this call takes more than 200 milliseconds, it is canceled + await client.say({sentence: "Hello"}, { timeoutMs: 200 }); +} catch (err) { + if (err instanceof ConnectError && err.code === Code.DeadlineExceeded) { + // handle the timeout error + } +} +```