Customizing the Grafbase Gateway with Hooks
You can customize the Grafbase Gateway with dynamically loaded WebAssembly system interface (WASI) components. A selection of languages are available for implementing the guest components. Rust together with the Cargo component subcommand is the recommended tool set, with it being the most robust and easy to use solution for building WASI components.
WASI Preview 2 introduces features unavailable in previous versions, such as network requests and file system access. Note that not every library currently supports network access from WASI. Check the final chapter for tested libraries.
You must deploy the Grafbase Gateway together with the WASM file containing the code for the custom hooks. The first Grafbase Gateway version with support for custom hooks is 0.4.0.
The minimum configuration to enable hooks must define a valid path to the WASM component:
[hooks]
location = "path/to/custom.component.wasm"
networking = false
stdout = false
stderr = false
environment_variables = false
max_pool_size = 1000
When starting the Gateway, it will display a log if the component loaded successfully.
location
is the path to the WASM file containing the custom hooks. The path must be valid and the file must have read access.networking
enables network access with TCP and UDP sockets, name resolution and provides WASI HTTP bindings to the guest. Keep in mind the TCP and UDP sockets only work if the guest language provides support to the WASI preview 2 standard. Default value is false.stdout
enables the guest to write to the standard output stream. Default value is false.stderr
enables the guest to write to the standard error stream. Default value is false.environment_variables
copies the all the host environment variables to the guest. Default value is false.max_pool_size
sets the number of instances of the hook that can run concurrently. Default value is four times the number of CPU cores.
You can enable guest access to the filesystem by defining one or more pre-opened directories:
[[hooks.preopened_directories]]
host_path = "/path/in/host/filesystem"
guest_path = "/path/in/guest/filesystem"
read_permission = true
write_permission = true
host_path
should point to an existing directory in the host filesystem. The user executing the Grafbase Gateway binary must have access to the given directory.guest_path
sets the path which is visible in the guest. This path is virtual and can be any value. The guest must use this path when accessing the filesystem.read_permission
enables reading all files and directories from the givenhost_path
.write_permission
enables creating and modifying files and directories in the givenhost_path
.
Grafbase provides a WebAssembly Interface Type (WIT) file which defines all the interfaces for communication between the host and the guest. The file defines all the types available in the guest, together with the hook function interfaces. You can define the hook functions you want to implement in the world section by exporting the corresponding function interfaces.
There will be more hooks in the future Gateway versions. If you use hooks in your deployment, it's a good idea to read the changelog and adapt to any changes in the WIT definition and guest implementation.
The context object is a key-value store available in all hooks during the request lifetime. You can store information into the context object in the beginning of the request, and read it in every subsequent hook. The values are strings, and if needing more structured data, consider storing it as JSON string.
In the gateway request hook, the storage is mutable and provides the following methods:
get
fetches a value from the context with the given name, returning none if the value doesn't exist.set
stores a value to the context with the given name.delete
deletes a value from the context with the given name, returning it if existing.
Hooks called after the gateway request get a shared version of the context, which provides read-only access to key-value storage. Additionally the shared context object provides the following methods:
trace-id
returns the OpenTelemetry trace id value for the current request on systems enabling OpenTelemetry metrics.log-access
stores the given vector of bytes to the system access log file, if enabled.
In the gateway request hook, the request headers are available to read and modify. The interface works similarly to the context methods, but they will return an error if the given header names or values contain characters not allowed in headers.
The following methods are available:
get
fetches a header value with the given name.set
sets a header value with the given name.delete
removes a header value with the given name. The method returns the value on successful deletion.entries
lists all header key value pairs
You must provide valid strings for header names and values with only ASCII characters. Otherwise the host responds with an error.
invalid-header-name
: you provided an invalid header name.invalid-header-value
: you provided an invalid header value.
The hook can return a GraphQL error. Depending on the hook, the engine either stops processing the request or it adds the error to the request errors and continues processing other parts of the request. The error struct has the following fields:
message
: custom string message.extensions
is a list of tuples. The first part of the tuple is the name of the extension, and the second part the contents of the extension. The content can be a string, and if needing more structured data the value can be JSON encoded as string. If the value is JSON, the engine returns it in the structured form.
If code
is provided in the extensions
, it will override the default error code set by the gateway.
The engine calls the edge authorization functions with an edge definition, which holds the following data:
parent-type-name
: is the name of the type the edge is part of.field-name
: is the name of the field.
The engine calls the node authorization functions with a node definition, which holds the following data:
type-name
: is the name of the node's type.
The following hooks are available in the Grafbase Gateway. The engine will call the hook if exporting it in the world section of the WIT file and the guest implements it.
Available in Gateway version 0.4.0.
The gateway-request
interface defines a hook function which the engine calls just before authentication in the federated gateway. It gives a mutable access to the context object, and a mutable access to the request headers. By returning an Ok
the request execution will continue and by returning an error, the engine ends the execution and the given error returned to the client.
The hook has the following WIT definition:
interface gateway-request {
use types.{headers, error, context};
// The hook is called in the federated gateway just before authentication. It can be used
// to read and modify the request headers. The context object is provided in a mutable form,
// allowing storage for the subsequent hooks to read.
//
// If returning an error from the hook, the request processing is stopped and the given error
// returned to the client.
on-gateway-request: func(context: context, headers: headers) -> result<_, error>;
}
The subgraph-request
interface defines a hook function which the engine calls just before sending the HTTP request to the subgraph allowing header modifications. If an error is returned, the subgraph request won't be executed and will be considered as failed with the provided error. The hook has the following WIT definition:
interface subgraph-request {
use types.{shared-context, headers, error};
// The hook is called just before sending the HTTP request to the subgraph.
on-subgraph-request: func(context: shared-context, subgraph-name: string, method: string, url: string, headers: headers) -> result<_, error>;
}
The authorization
interface defines hook functions which are called when fetching data for a node or an edge defining an @authorized
directive.
The interface must implement the following hooks has the following WIT definition:
interface authorization {
use types.{error, shared-context, edge-definition, node-definition};
// The hook is called in the request cycle if the schema defines an authorization directive on
// an edge, providing the arguments of the edge selected in the directive, the definition of the esge
// and the metadata of the directive to the hook.
//
// The hook is run before fetching any data.
//
// The result, if an error, will stop the request execution and return an error back to the user.
// Result of the edge will be null for an error response.
authorize-edge-pre-execution: func(
context: shared-context,
definition: edge-definition,
arguments: string,
metadata: string
) -> result<_, error>;
// The hook is called in the request cycle if the schema defines an authorization directive to
// a node, providing the definition of the node and the metadata of the directive to the hook.
//
// The hook is run before fetching any data.
//
// The result, if an error, will stop the request execution and return an error back to the user.
// Result of the edge will be null for an error response.
authorize-node-pre-execution: func(
context: shared-context,
definition: node-definition,
metadata: string
) -> result<_, error>;
// Called when `@authorized` is used on a field with `fields` argument:
//
// type User {
// id: ID!
// address: Address @authorized(fields: "id")
// }
//
// The engine calls the hook after the subgraph response has arrived with the list of parent fields for
// every node containing the address field.
authorize-parent-edge-post-execution: func(
context: shared-context,
definition: edge-definition,
parents: list<string>,
metadata: string
) -> list<result<_, error>>;
// Called when `@authorized` is used on a field with `node` argument:
//
// type User {
// id: ID!
// }
//
// type Query {
// users: [User]! @authorized(node: "id")
// }
//
// The engine calls the hook after the subgraph response has arrived with the list of nodes (User here) for the
// field.
authorize-edge-node-post-execution: func(
context: shared-context,
definition: edge-definition,
nodes: list<string>,
metadata: string
) -> list<result<_, error>>;
}
Available in Gateway version 0.4.0.
Called when @authorized
is applied on a field with arguments
:
type Query {
user(id: ID): User @authorized(arguments: "id")
}
The engine calls the hook when a query accesses a field with an @authorize
directive on the edge level before executing the query.
Engine calls the hook before executing the query with the following:
context
is the request context object, which can be populated in theon-gateway-request
hook.definition
defines the edge with its parent type name and the name of the field.arguments
is JSON data in string form, with data taken from the query arguments.metadata
is static JSON data in string form taken from the directive's metadata argument.
Result of the hook can be one of the following:
- An empty response allows the request to fetch the data.
- An
error
which stops the execution of the request and returns an error back to the user. The requested data will benull
.
If the queried edge is returning an optional value, this value will be set null
and an error added to the response errors. If the edge is returning a required value, the null
gets propagated up to the first nullable edge.
Available in Gateway version 0.7.0.
Called when @authorized
is applied on a field with fields
:
type User {
id: ID!
address: Address @authorized(fields: "id")
}
The engine will call the hook after the subgraph response has arrived with:
context
is the request context object, which can be populated in theon-gateway-request
hook.definition
defines the edge with its parent type name and the name of the field.parents
is a list of parent fields, defined by thefields
directive argument, serialized in JSON.metadata
is static JSON data in string form taken from the directive's metadata argument.
Result is a list of results for each parent. If empty the field is authorized for this particular parent otherwise it's denied and an error is raised and propagated following GraphQL spec instead.
Available in Gateway version 0.7.0.
Called when @authorized
is applied on a field with node
:
type User {
id: ID!
}
type Query {
users: [User]! @authorized(node: "id")
}
The engine will call the hook after the subgraph response has arrived with:
context
is the request context object, which can be populated in theon-gateway-request
hook.definition
defines the edge with its parent type name and the name of the field.nodes
is a list of field output nodes with the selected fields defined by thenode
directive argument, serialized in JSON.metadata
is static JSON data in string form taken from the directive's metadata argument.
Result is a list of results for each node. If empty the node is authorized, otherwise it's denied and an error is raised and propagated following GraphQL spec instead.
Available in Gateway version 0.4.0.
The engine calls the hook when a query accesses a type with an @authorize
directive on the node level before executing the query.
Engine sends the following data to the hook:
context
is the request context object, which can be populated in theon-gateway-request
hook.definition
defines the node with the name of the type.metadata
is static JSON data in string form taken from the directive's metadata argument.
Result of the hook can be one of the following:
- An empty response allows the request to fetch the data.
- An
error
which stops the execution of the request and returns an error back to the user. The requested data will benull
.
If the hook returns an error, the part of the query which tries to access the given node will return null
together with the error defined in the hook. If the field is required, the null
value is propagated up to the first optional field in the query.
For the following types:
type User {
address: Address
secret: Secret
}
type Address {
street: String!
}
type Secret @authorized {
socialSecurityNumber: String!
}
A failing authorization for the Secret
node will return the following data:
{
"data": {
"user" {
"address": { "street": "123 Folsom Street" },
"secret: null,
}
},
"errors": [
{
"message": "the message from the hook error response",
"path": [ "user", "secret" ],
"extensions": { ... }
}
]
}
If the secret
field in the User
type is required:
type User {
address: Address
secret: Secret!
}
type Address {
street: String!
}
type Secret @authorized {
socialSecurityNumber: String!
}
A failing authorization for the Secret
node will propagate the null value to the first optional parent node:
{
"data": null,
"errors": [
{
"message": "the message from the hook error response",
"path": [ "user", "secret" ],
"extensions": { ... }
}
]
}
Available in Gateway version 0.12.0.
The responses
interface defines three hook functions that the engine calls after executing certain steps of its pipeline. These hooks do not get called for subscriptions, only queries and mutations. They provide access to various metrics and shared methods for logging access information. Unlike system logs, access logs allow you to customize what gets logged, when it occurs, and how it formats.
Enable access logs in the Gateway configuration:
[gateway.access_logs]
enabled = true
path = "/path/to/logs"
Read more on configuration options.
With access logs enabled, invoking the log-access
method from the shared context will append the specified bytes to a file called access.log
in the configured path.
The response hooks provide access to various points during the request's lifecycle:
interface responses {
use types.{shared-context, executed-operation, executed-subgraph-request, executed-http-request, operation};
// The hook is called after a subgraph entity has been either requested or fetched from cache.
// The output is a list of bytes, which will be available in the on-operation-response hook.
on-subgraph-response: func(
context: shared-context,
request: executed-subgraph-request,
) -> list<u8>;
// The hook is called after an operation is handled in the gateway. The output is a list of bytes,
// which will be available in the on-http-response hook.
on-operation-response: func(
context: shared-context,
request: executed-operation,
) -> list<u8>;
// The hook is called right before a response is sent to the user.
on-http-response: func(
context: shared-context,
request: executed-http-request,
);
}
In all hooks, the shared-context
resource gives access to context data stored in the on-gateway-request
hook, the log file using the log-access
method, and the current trace ID with the trace-id
method on systems that enable OpenTelemetry metrics.
The first two hooks can return a set of bytes. The return values of on-subgraph-response
hooks appear in the executed-operation
record of the on-operation-response
hook, and the outputs of on-operation-response
hooks are in the executed-http-request
record of the on-http-response
hook.
This record holds information about an executed subgraph call:
record executed-subgraph-request {
// The name of the subgraph.
subgraph-name: string,
// The request method.
method: string,
// The subgraph URL.
url: string,
// The subgraph executions
executions: list<subgraph-request-execution-kind>,
// The cache status of the subgraph call.
cache-status: cache-status,
// The time in milliseconds taken for the whole operation.
total-duration-ms: u64,
// True, if the subgraph returned any errors.
has-errors: bool,
}
If retries are enabled, executions
may contain multiple responses. If data is fetched from the cache, executions
will be empty.
The subgraph-request-execution-kind
variant holds information about the subgraph request:
variant subgraph-request-execution-kind {
// Internal server error in the gateway.
internal-server-error,
// Response prevented by subgraph request hook.
hook-error,
// HTTP request failed.
request-error,
// Request was rate-limited.
rate-limited,
// A response was received.
response(subgraph-response),
}
If a request executed successfully, subgraph-response
provides metrics on the request:
record subgraph-response {
// The milliseconds it took to connect to the host.
connection-time-ms: u64,
// The milliseconds it took for the host to respond with data.
response-time-ms: u64,
// The response status code
status-code: u16
}
The status code will be 0
for requests that fail before fetching any data.
The cache-status
variant indicates caching information:
enum cache-status {
// Cache hit
hit,
// Some data fetched from cache.
partial-hit,
// Cache miss
miss,
}
The executed-operation
record holds metrics and previous responses from subgraphs:
record executed-operation {
// The name of the operation, if present.
name: option<string>,
// The operation document in sanitized form.
document: string,
// The time taken in preparing.
prepare-duration-ms: u64,
// True, if the plan was taken from cache.
cached-plan: bool,
// Time in milliseconds spent executing the operation.
duration: u64,
// The status of the operation.
status: graphql-response-status,
// If queried any subgraphs, the outputs of on-subgraph-response hooks.
// Will be empty if no subgraphs were called.
on-subgraph-response-outputs: list<list<u8>>,
}
The on-subgraph-response-outputs
aggregates the return values from all invoked on-subgraph-response
hooks.
The GraphQL request status record contains data on success or failure:
variant graphql-response-status {
// Request was successful.
success,
// A field returned an error.
field-error(field-error),
// A request error.
request-error(request-error),
// The request was refused.
refused-request,
}
record field-error {
// The number of errors.
count: u64,
// The returned data is null.
data-is-null: bool,
}
record request-error {
// The number of errors.
count: u64,
}
The on-http-response
input parameter executed-http-request
contains details about the HTTP response:
record executed-http-request {
// The request method.
method: string,
// The request URL.
url: string,
// The response status code.
status-code: u16,
// The outputs of executed on-operation-response hooks for every operation of the request.
on-operation-response-outputs: list<list<u8>>,
}
The on-operation-response-outputs
aggregates outputs from all invoked on-operation-response
hooks.
The metrics counter grafbase.gateway.access_log.pending
increments with each log-access
call and decrements once the bytes are written to the access.log
. Monitoring this value is crucial. Each log-access
call consumes memory until data gets written, and the channel can hold a maximum of 128,000 messages. For the blocking
access log method, a full channel will block all log-access
calls, while the non-blocking
method returns errors, sending data back to the caller.
Rust implementation is so far the easiest due to Rust having the best tools to compile WASI components. This chapter goes through how a guest component implementation works.
You can compile a Wasm component with a recent Rust compiler together with the Cargo component subcommand. The cargo component
subcommand provides all the needed tooling for creating and compiling a WASI component. The minimum required version of cargo-component is 0.14.0.
> cargo component new --lib my-hooks
This will create a project structure for a WASI component library called my-hooks
with the following project structure:
my-hooks/
├── Cargo.toml
├── src
│ └── lib.rs
└── wit
└── world.wit
The Cargo.toml
file provides the dependencies and build instructions to compile the project:
[package]
name = "my-hooks"
version = "0.1.0"
edition = "2021"
license = "MIT"
[dependencies]
wit-bindgen-rt = { version = "0.26.0", features = ["bitflags"] }
[lib]
crate-type = ["cdylib"]
[profile.release]
codegen-units = 1
opt-level = "s"
debug = false
strip = true
lto = true
[package.metadata.component]
package = "component:my-hooks"
[package.metadata.component.dependencies]
First, add the WIT definition from this documentation to the wit/world.wit
file.
By building the component, the framework will generate the needed bindings:
> cargo component build
This will return a bunch of errors, which you will fix in the next chapters.
First make sure the to export the gateway-request
interface in the wit/world.wit
file, in the hooks
world:
world hooks {
export gateway-request;
}
Compile the component to generate bindings. You must edit src/lib.rs
to implement the interface:
#[allow(warnings)]
mod bindings;
// Import the types generated from the WIT file.
use bindings::{
component::grafbase::types::{Context, Error, Headers},
exports::component::grafbase::gateway_request,
};
// An empty struct which can implement an interface.
struct Component;
// Implementing the gateway-request interface for the component
impl gateway_request::Guest for Component {
fn on_gateway_request(context: Context, headers: Headers) -> Result<(), Error> {
Ok(())
}
}
// Export the component to WASI with the given bindings.
bindings::export!(Component with_types_in bindings);
The implementation is the simplest possible, returning an empty response and allowing the request to continue.
First make sure the to export the subgraph-request
interface in the wit/world.wit
file, in the hooks
world:
world hooks {
export subgraph-request;
}
Compile the component to generate bindings. You must edit src/lib.rs
to implement the interface:
#[allow(warnings)]
mod bindings;
// Import the types generated from the WIT file.
use bindings::{
component::grafbase::types::{Context, Error, Headers},
exports::component::grafbase::subgraph_request,
};
// An empty struct which can implement an interface.
struct Component;
// Implementing the gateway-request interface for the component
impl subgraph_request::Guest for Component {
fn on_subgraph_request(context: Context, subgraph_name: String, method: String, url: String, headers: Headers) -> Result<(), Error> {
Ok(())
}
}
// Export the component to WASI with the given bindings.
bindings::export!(Component with_types_in bindings);
The implementation is the simplest possible, returning an empty response and allowing the request to continue.
First make sure the to export the authorization
interface in the wit/world.wit
file, in the hooks
world:
world hooks {
export authorization;
}
Compile the component to generate bindings. You must edit src/lib.rs
to implement the interface:
#[allow(warnings)]
mod bindings;
use bindings::{
component::grafbase::types::{
Context, EdgeDefinition, Error, Headers, NodeDefinition, SharedContext,
},
exports::component::grafbase::{authorization, gateway_request},
};
struct Component;
impl authorization::Guest for Component {
fn authorize_edge_pre_execution(
context: SharedContext,
definition: EdgeDefinition,
arguments: String,
metadata: String,
) -> Result<(), Error> {
Ok(())
}
fn authorize_node_pre_execution(
context: SharedContext,
definition: NodeDefinition,
metadata: String,
) -> Result<(), Error> {
Ok(())
}
}
bindings::export!(Component with_types_in bindings);
The implementation is the simplest possible. Both hooks return an empty Ok
value to the engine, and the engine will return the requested data in all cases.
First, export the responses
interface in the wit/world.wit
file, within the hooks
world:
world hooks {
export responses;
}
Compile the component to generate bindings. Then, modify src/lib.rs
to implement the interface:
use bindings::component::grafbase::types::{
CacheStatus, ExecutedHttpRequest, ExecutedOperation, ExecutedSubgraphRequest, Operation, ResponseKind,
SharedContext,
};
use bindings::exports::component::grafbase::responses::Guest;
#[allow(warnings)]
mod bindings;
struct Component;
impl Guest for Component {
fn on_subgraph_response(context: SharedContext, request: ExecutedSubgraphRequest) -> Vec<u8> {
// Combine a set of bytes from context and subgraph request.
}
fn on_operation_response(
context: SharedContext,
operation: Operation,
request: ExecutedOperation
) -> Vec<u8> {
// Combine a set of bytes from context and operation data.
}
fn on_http_response(context: SharedContext, request: ExecutedHttpRequest) {
let data = Vec::new();
// Combine a set of bytes from context and HTTP request data.
// Calling the log_access will write the bytes to the access.log.
context.log_access(&data).unwrap();
}
}
bindings::export!(Component with_types_in bindings);
The serialization of data can vary as long as it returns bytes. As an example, if your access.log contains JSON data, define structured data as Serde structures in the hooks and serialize them into bytes using serde_json.
The log_access
method is available in all hooks via shared context. This implementation captures one row of data per request, which the method writes to the log in the on-http-response
hook.
See a full example project implementing access logs with Grafbase Gateway.
When done implementing the hooks, we must compile them in release mode. The component can define multiple hooks, and they're all deployment as a single WASI component.
> cargo component build --release
The component is available in target/wasm32-wasip1/release/my_hook.wasm
. You must deploy this file with the gateway binary, and you must configure the gateway binary to look for the file in its configuration.
The Go implementation requires more steps and tooling. The tooling isn't yet as robust as the Rust counterparts. You need the following tools for development:
- wit-bindgen version 0.26.0 or later
- wasm-tools version 1.211.1 or later
- TinyGo version 0.32.0 or later
- Go version 1.21 or later
- clang compiler and the needed system libraries
Create a new Go project
> mkdir my-hook && cd my-hook
> go mod init hooks.com
Copy the WIT definition from this documentation to the project root, name it as grafbase.wit
and generate the needed bindings:
> wit-bindgen tiny-go ./grafbase.wit --world hooks --out-dir=gen
Download the wasi_snapshot_preview1.reactor.wasm
file from the latest wasmtime release and put it to the root of the project.
You now have the project framework to implement the WASI component.
First make sure to export the gateway-request
interface in the grafbase.wit
file, in the hooks
world:
world hooks {
export gateway-request;
}
If needed, run the wit-bindgen
command as defined in the previous chapter. Create a new file hooks.go
with the implementation:
package main
import (
. "hooks.com/gen"
)
type HooksImpl struct {
}
func (i HooksImpl) OnGatewayRequest(
context ComponentGrafbaseTypesContext,
headers ComponentGrafbaseTypesHeaders,
) Result[struct{}, ComponentGrafbaseTypesError] {
return Ok[struct{}, ComponentGrafbaseTypesError](struct{}{})
}
func init() {
hooks := HooksImpl{}
SetExportsComponentGrafbaseGatewayRequest(hooks)
}
func main() {}
The hook is the minimum possible, doing nothing and letting a request to go through.
First make sure to export the authorization
in the grafbase.wit
file, in the hooks
world:
world hooks {
export authorization;
}
If needed, run the wit-bindgen
command as defined in the previous chapter. Create a new file hooks.go
with the implementation:
package main
import (
. "gateway.com/gen"
)
type AuthorizationImpl struct{}
func (i AuthorizationImpl) AuthorizeEdgePreExecution(
context ComponentGrafbaseTypesSharedContext,
definition ComponentGrafbaseTypesEdgeDefinition,
arguments string,
metadata string,
) Result[struct{}, ComponentGrafbaseTypesError] {
return Ok[struct{}, ComponentGrafbaseTypesError](struct{}{})
}
func (i AuthorizationImpl) AuthorizeNodePreExecution(
context ComponentGrafbaseTypesSharedContext,
definition ComponentGrafbaseTypesNodeDefinition,
metadata string,
) Result[struct{}, ComponentGrafbaseTypesError] {
return Ok[struct{}, ComponentGrafbaseTypesError](struct{}{})
}
func init() {
hooks := AuthorizationImpl{}
SetExportsComponentGrafbaseAuthorization(hooks)
}
func main() {}
The implementation is the simplest possible. Both hooks return an empty Ok
value to the engine, and the engine will return the requested data in all cases.
Next we build the module using TinyGo, componentize it, and adapt it for WASI 0.2:
> tinygo build -o hooks.wasm -target=wasi hooks.go
> wasm-tools component embed --world hooks ./grafbase.wit hooks.wasm -o hooks.embed.wasm
> wasm-tools component new -o hooks.component.wasm --adapt wasi_snapshot_preview1="wasi_snapshot_preview1.reactor.wasm" hooks.embed.wasm
The component is available in hooks.component.wasm
. You must deploy the file with the gateway binary, and you must configure the gateway binary to look for the file in its configuration.
So far the support for networking and HTTP from WASI components is work in progress. WASI support in common libraries might not be available. If needing to trigger HTTP requests from the hooks, please consider writing them in Rust and using a crate which works in the WASI context, such as waki.
WASI support in more popular HTTP libraries such as reqwest or Go's net/http is still work in progress. This documentation will get updated when the situation changes.