Skip to content

Hostcall API reference ​

Signature-level reference for every proxy-wasm hostcall the gateway's WASM host registers: parameters, returns, statuses, selector values, and error codes. Every entry below was extracted from the host source (crates/dwara-core/src/wasm/).

This page is deliberately dry. For workflow-oriented prose (the scaffold/build/load quickstart, recipes, lifecycle, troubleshooting) see Proxy-Wasm plugins and the Plugin SDK. The full generated Rust API documentation for the workspace crates is published at https://shristilabs.github.io/dwara/api/dwara_core/ (built by CI from main; public items only).

Upstream references:

Hostcalls ​

The host registers every import under the module name env. Where a hostcall has a Rust SDK spelling, the table names it; parameters and returns are given in SDK terms (strings and byte vectors, not ABI pointers). Status values below (Supported / Partial / Stub) match the hostcall support matrix -- that page carries the prose; this one carries the signatures.

Status codes the host can return ​

Every hostcall returns a proxy-wasm status integer. The values dwara can actually answer with:

StatusValueReturned by
Ok0Success (including "not found, zero values written" reads).
generic error1Invalid pointer/size arguments, unknown buffer or map selector, corrupt map serialization, allocation failure in plugin memory. Indicates a bug or ABI mismatch, not a branchable condition.
BadArgument2proxy_http_call only: unparsable or non-http(s) URI, invalid dispatch header map, or a request-head validation failure (see Callouts). The SDK surfaces it as Err(Status::BadArgument) from dispatch_http_call.
CasMismatch8proxy_set_shared_data only: CAS mismatch or a set that would exceed the shared-data caps. The SDK surfaces it as Err(Status::CasMismatch) from set_shared_data.
InternalFailure10Every stubbed hostcall. See Stubs for which SDK wrappers trap versus return Err on this status.

The Rust SDK maps only specific statuses to Err per wrapper; every other non-Ok status panics the module (which the host reports as a plugin failure). The three statuses above are the ones dwara emits deliberately.

Header operations ​

Selector: the MapType integer (bt). Valid selectors: request headers 0, request trailers 1, response headers 2, response trailers 3, callout response headers 6, callout response trailers 7. Trailer maps read as empty and their writes are accepted and discarded (trailers are not plumbed through the pipeline).

HostcallSDK surfaceParameters -> returnStatus
proxy_get_header_map_pairsget_http_request_headers() / get_http_response_headers() / get_http_call_response_headers() -> Vec<(String, String)>bt -> the whole mapSupported
proxy_set_header_map_pairsset_http_request_headers(Vec<(String, String)>) / set_http_response_headers(...)bt, the whole map -> unitPartial
proxy_get_header_map_valueget_http_request_header(name: &str) / get_http_response_header(name: &str) -> Option<String>bt, key: String -> value or emptySupported
proxy_add_header_map_valueadd_http_request_header(name, value) / add_http_response_header(name, value)bt, key, value -> unitSupported
proxy_replace_header_map_value(SDK: set_map-level; no typed wrapper)bt, key, value -> unitSupported
proxy_remove_header_map_valueremove_http_request_header(name) / remove_http_response_header(name)bt, key -> unitSupported

Caveats:

  • get/add/replace/remove lookups are case-insensitive; add appends (duplicate names possible); replace replaces the first case-insensitive match or appends; remove removes every case-insensitive match. A missing key on get reads as empty (the SDK maps that to None).
  • proxy_set_header_map_pairs replaces the whole map and cannot carry non-UTF-8 values (untouched headers keep theirs). The map wire format is the spec/SDK serialization: LE u32 entry count, LE u32 (key_len, value_len) table, then NUL-terminated strings.
  • The callout response map (selector 6) is readable only inside on_http_call_response; it carries :status first, then the ordinary headers with hop-by-hop headers stripped. Writes to it are discarded.

Buffers and bodies ​

Selector: the BufferType integer (bt). Valid selectors: request body 0, response body 1, callout response body 4, VM configuration 6, plugin configuration 7.

HostcallSDK surfaceParameters -> returnStatus
proxy_get_buffer_bytesget_http_request_body(offset, max_size) / get_http_response_body(...) / get_http_call_response_body(...) / get_plugin_configuration() / get_vm_configuration() -> Option<Vec<u8>>bt, offset: usize, max_size: usize -> chunkSupported
proxy_get_buffer_statusget_http_request_body_size() / get_http_response_body_size() -> Option<usize>bt -> lengthSupported
proxy_set_buffer_bytesset_http_request_body(offset, size, data) / set_http_response_body(offset, size, data)bt, offset, data -> unitPartial

Caveats:

  • proxy_set_buffer_bytes writes the request and response bodies only; the write replaces the buffer from offset to the end (spec-range in-place edits are not supported). Configuration buffers are read-only.
  • A body phase only runs when the body is buffered (route limits.max_body_bytes, default 1 MiB); over-cap answers 500 plugin_body_too_large. Streaming and content-encoded responses skip response_body.
  • The configuration buffers are how on_configure receives the plugin config string (bytes) and on_vm_start receives the VM buffer (empty today).

Local response ​

HostcallSDK surfaceParameters -> returnStatus
proxy_send_local_responsesend_http_response(status: u16, headers: Vec<(String, String)>, body: Option<Vec<u8>>)status_code: i32, status details (ignored), body, headers (spec map serialization), gRPC status (ignored) -> unitSupported
proxy_send_http_responsenone (dwara's original spelling)status: i32, headers (legacy big-endian map layout), body, trailers (ignored) -> unitSupported

Caveats:

  • Both spellings store the same local response and end the stream: the proxy returns it immediately, the upstream is never dialed.
  • The spec spelling parses the SDK map serialization (an empty map is the 4-byte LE count 0); the legacy spelling parses dwara's big-endian (u32 key_len, key, u32 val_len, val) layout. Modules built with the Rust SDK must use send_http_response (the SDK imports the spec name).
  • A non-empty body without a content-type defaults to application/json.

Logging and time ​

HostcallSDK surfaceParameters -> returnStatus
proxy_loglog(level: LogLevel, message: &str)level: i32 (0 Trace .. 5 Critical), message: String -> unitPartial
proxy_get_log_level(ABI-level)-> level: i32Partial
proxy_get_current_time_nanosecondsget_current_time()-> nanoseconds since Unix epoch (u64)Supported

Caveats:

  • proxy_log lines (and WASI fd_write bytes, including panic output) land in a per-instance buffer capped at 64 KiB: past the cap the head of the overflowing line plus a truncation marker is kept and further lines are dropped. The buffer is not surfaced to the gateway log today; no level filtering.
  • proxy_get_log_level always reports Info.
  • dwara's original spelling proxy_get_current_time shares the same implementation.

Shared state ​

HostcallSDK surfaceParameters -> returnStatus
proxy_get_shared_dataget_shared_data(key: &str) -> Option<(Vec<u8>, Option<u32>)>key: String -> value bytes + CASPartial
proxy_set_shared_dataset_shared_data(key: &str, value: Option<&[u8]>, cas: Option<u32>) -> Result<(), Status>key, value: Vec<u8>, cas: u32 -> statusPartial

Caveats:

  • The store is VM-scoped: one map per compiled plugin module, shared by every per-request instance of that plugin; not shared across different plugins; a plugin definition change starts a fresh map on reload.
  • Caps per module: 1024 entries and 1 MiB of key + value bytes. An over-cap set fails with CasMismatch (the SDK's only clean set_shared_data error); entries are never evicted.
  • cas of 0 means write unconditionally; a positive cas that does not match the current version fails with CasMismatch. The CAS check, cap check, and write are one critical section (racing instances cannot interleave).

Metrics ​

HostcallSDK surfaceParameters -> returnStatus
proxy_define_metricdefine_metric(MetricType, name: &str) -> u32metric_type: i32 (0 Counter, 1 Gauge, 2 Histogram), name: String -> metric idPartial
proxy_record_metricrecord_metric(metric_id: u32, value: f64)name bytes, value: f64 -> unitPartial
proxy_increment_metricincrement_metric(metric_id: u32, increment: f64)name bytes, increment: f64 -> unitPartial
proxy_get_metricget_metric(metric_id: u32) -> f64name bytes -> f64Partial

Caveats:

  • The per-instance metric map is keyed by the name bytes the ABI call carries; the metric-id return pointer of proxy_define_metric is not populated, so SDK wrappers that pass the numeric id back find no metric (the call stays Ok).
  • Metrics are per-instance (reset every request) and are not exported to /metrics. Emit gateway-visible signals from plugin config or headers instead.

Callouts ​

HostcallSDK surfaceParameters -> returnStatus
proxy_http_calldispatch_http_call(uri: &str, headers: Vec<(String, String)>, body: Option<&[u8]>, trailers: Vec<(String, String)>, timeout: Duration) -> Result<u32, Status>upstream (absolute URI string), dispatch headers (spec map serialization), body, trailers (ignored), timeout_ms: i32 -> token: u32Supported

Returns Ok + token, BadArgument (unparsable or non-http(s) URI, invalid dispatch map, or request-head validation failure), or InternalFailure (token write failure). The response arrives via the proxy_on_http_call_response(token_id, num_headers, body_size, num_trailers) export; read it with the callout map/buffer selectors (6 and 4). Dispatching a callout pauses the phase regardless of the returned action.

The full contract -- pause/resume, pseudo-headers, guardrails (timeout clamp 1 ms .. 5 s, 8 callout rounds per phase, 4 MiB response cap, refused redirects, SSRF filter), and failure semantics -- is documented once in Plugin SDK: HTTP callouts.

Properties, contexts, streams ​

HostcallSDK surfaceParameters -> returnStatus
proxy_get_propertyget_property(path: Vec<&str>) -> Option<Vec<u8>>path parts -> valueStub
proxy_set_propertyset_property(path, value)path parts, value -> unitStub
proxy_set_effective_contextset_effective_context(id: u32)context_id: i32 -> unitPartial
proxy_continue_streamresume_stream()bt: i32 -> unitPartial
proxy_close_streamclose_stream()bt: i32 -> unitPartial
proxy_donedone()-> unitSupported
proxy_get_statusget_grpc_status() -> Option<i32>-> code + messagePartial

Caveats:

  • proxy_get_property returns an empty value for every path; no properties resolve. proxy_set_property is a no-op reporting success.
  • proxy_set_effective_context records the ID; cross-context callbacks are not re-dispatched.
  • proxy_continue_stream is a no-op reporting success; proxy_close_stream ends the stream (same as returning Action::EndStream).
  • proxy_get_status always answers Ok with code 0 and no message (no gRPC callout is ever in flight); the SDK reads that as (0, None). It must answer Ok because the SDK wrapper panics on any non-Ok status.

Stubs ​

Registered with their spec signatures so SDK modules instantiate, but unsupported. All return InternalFailure (10):

proxy_grpc_call, proxy_grpc_stream, proxy_grpc_send, proxy_grpc_cancel, proxy_grpc_close, proxy_register_shared_queue, proxy_resolve_shared_queue, proxy_dequeue_shared_queue, proxy_enqueue_shared_queue, proxy_set_tick_period_milliseconds, proxy_call_foreign_function.

Of the SDK wrappers over these, only dispatch_grpc_call and open_grpc_stream map InternalFailure to a clean Err; the rest panic, which traps the module and fails the referencing route closed (500 plugin_failed). There is no tick loop (on_tick never fires) and no shared-queue mechanism.

WASI subset (wasm32-wasip1) ​

Modules built for wasm32-wasip1 import a small wasi_snapshot_preview1 set; the host stubs exactly these:

ImportBehavior
environ_sizes_get / environ_getEmpty environment.
fd_writeRouted into the per-instance log buffer under the 64 KiB cap.
proc_exit(code)Traps the instance (reported like any plugin failure).
random_getNon-cryptographic (splitmix64); not for key material.
clock_time_getWall-clock nanoseconds.
sched_yieldNo-op.

Any other WASI import fails instantiation, and referencing routes fail closed.

Where to go next ​

  • Proxy-Wasm plugins - configuration and operations: phases, limits, failure semantics.
  • Plugin SDK - the building guide: scaffold, build, load, iterate.
  • Extension API - the index of every extension surface's reference page.