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 aserde-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 aGraphQLErrordirectly 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()
}