Skip to Content

Plugin Execution and Lifecycle

Control Flow

Short-Circuit Responses

In many of the hooks mentioned above, you can short-circuit the request processing by providing a custom response.

For example, to return an early error response in certain conditions:

return payload.end_with_response( Response::with_body( StatusCode::BAD_REQUEST, body.to_string().into(), ) );

The end_with_response method on payload provides a custom HTTP response. Several helper methods are also available to simplify creating early responses.

Helpers for early responses

To make it easier to create early responses, Hive Router provides some helper methods on the hook payloads.

  • end_with_response(response: Response) - Accepts a serde-serializable body, automatically serializes it to JSON, and sets the appropriate headers. To simplify the above example:
let body = json!({ "errors": [ { "message": "Anonymous operations are not allowed", "extensions": { "code": "ANONYMOUS_OPERATION" } } ] }); // Here we short-circuit the request processing by returning an early response return payload.end_with_response( Response::with_body( StatusCode::BAD_REQUEST, body.to_string().into(), ) );
  • end_with_graphql_error(error: GraphQLError, status: StatusCode) - Accepts a GraphQLError directly and constructs the appropriate GraphQL error response.
let graphql_error = GraphQLError::from_message_and_code("Anonymous operations are not allowed", "ANONYMOUS_OPERATION"); // Here we short-circuit the request processing by returning an early response return payload.end_with_graphql_error( graphql_error, StatusCode::BAD_REQUEST, );

Overriding Default Behavior

Rather than short-circuiting the entire request and returning an early response, you may want to override the default behavior at a specific stage.

For example, when implementing automatic persisted queries (APQ), you need to replace the query field in the GraphQLParams struct with the actual query string, resolved from the hash provided by the client.

#[async_trait::async_trait] impl RouterPlugin for APQPlugin { async fn on_graphql_params<'exec>( &'exec self, payload: OnGraphQLParamsStartHookPayload<'exec>, ) -> OnGraphQLParamsStartHookResult<'exec> { payload.on_end(|mut payload| { let persisted_query_ext = payload .graphql_params .extensions .as_ref() .and_then(|ext| ext.get("persistedQuery")) .and_then(|pq| pq.as_object()); // Get the original query string from the map using the hash from the extensions let query = get_persisted_query(persisted_query_ext); payload.graphql_params.query = Some(query); payload.proceed() }) } }

Integrate with the Router

Performance Considerations

When creating custom plugins, it’s important to understand that your plugin will be part of the request’s hot path. The hot path refers to the critical path that affects the performance of the router.

When implementing hooks in your plugin, you should be mindful of the performance implications of your code.

Avoid performing heavy computations or blocking operations in request lifecycle hooks, as they can significantly impact the latency of your GraphQL requests.

Prefer loading data in the background and caching it when possible, instead of fetching it during a GraphQL request.

Background Tasks

Hive Router provides a simple mechanism to run background tasks using the tokio runtime.

As a plugin developer, you don’t need to deal with threads or async runtimes - you can use the Hive Router core runtime to register and manage the lifecycle of your background tasks.

Background tasks can be registered during the on_plugin_init hook. Here is a simple example of a background task that runs periodically:

use hive_router::background_tasks::BackgroundTask; use tokio_util::sync::CancellationToken; use std::time::Duration; struct MyBackgroundTask; impl MyBackgroundTask { fn new() -> Self { Self {} } async fn do_something_periodically(&self) { // Do something periodically } } #[async_trait] impl BackgroundTask for MyBackgroundTask { fn id(&self) -> &str { "my_bg_task" } async fn run(&self, token: CancellationToken) { let interval = Duration::from_secs(10); let mut tokio_interval = tokio::time::interval(interval); loop { tokio::select! { _ = tokio_interval.tick() => { self.do_something_periodically().await } _ = token.cancelled() => { println!("Background task stopped"); return; } } } } } struct MyPlugin; #[async_trait] impl RouterPlugin for MyPlugin { // ... fn on_plugin_init(payload: OnPluginInitPayload<Self>) -> OnPluginInitResult<Self> { // payload.bg_tasks_manager.register_task(MyBackgroundTask::new()); payload.initialize_plugin_with_defaults() } }

Logging

Hive Router uses the tracing crate for its internal logging. Logs emitted from plugins are correlated with the request being processed.

Use the built-in macros (debug!(...), info!(...), warn!(...), and error!(...)) to emit log lines, which will appear based on the Router’s logger configuration.

use tracing::{debug}; fn on_http_request<'req>( &'req self, payload: OnHttpRequestHookPayload<'req>, ) -> OnHttpRequestHookResult<'req> { debug!("on_http_request is called now!"); payload.proceed() }

OpenTelemetry Traces

Hive Router supports OpenTelemetry at its core, and uses the tracing crate for collecting, processing, and exporting OpenTelemetry trace information.

Use the tracing API to create spans and enrich the exported trace data with custom spans:

use tracing::{debug, span, Level}; fn on_http_request<'req>( &'req self, payload: OnHttpRequestHookPayload<'req>, ) -> OnHttpRequestHookResult<'req> { // Create and enter the span; the guard finalizes it when the function returns let my_span = span!(Level::INFO, "my_plugin_on_http_request"); let _guard = my_span.enter(); payload.proceed() }
Last updated on