Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wasi-http/src/lib.rs
3069 views
1
//! # Wasmtime's WASI HTTP Implementation
2
//!
3
//! This crate is Wasmtime's host implementation of the `wasi:http` package as
4
//! part of WASIp2. This crate's implementation is primarily built on top of
5
//! [`hyper`] and [`tokio`].
6
//!
7
//! # WASI HTTP Interfaces
8
//!
9
//! This crate contains implementations of the following interfaces:
10
//!
11
//! * [`wasi:http/incoming-handler`]
12
//! * [`wasi:http/outgoing-handler`]
13
//! * [`wasi:http/types`]
14
//!
15
//! The crate also contains an implementation of the [`wasi:http/proxy`] world.
16
//!
17
//! [`wasi:http/proxy`]: crate::bindings::Proxy
18
//! [`wasi:http/outgoing-handler`]: crate::bindings::http::outgoing_handler::Host
19
//! [`wasi:http/types`]: crate::bindings::http::types::Host
20
//! [`wasi:http/incoming-handler`]: crate::bindings::exports::wasi::http::incoming_handler::Guest
21
//!
22
//! This crate is very similar to [`wasmtime_wasi`] in the it uses the
23
//! `bindgen!` macro in Wasmtime to generate bindings to interfaces. Bindings
24
//! are located in the [`bindings`] module.
25
//!
26
//! # The `WasiHttpView` trait
27
//!
28
//! All `bindgen!`-generated `Host` traits are implemented in terms of a
29
//! [`WasiHttpView`] trait which provides basic access to [`WasiHttpCtx`],
30
//! configuration for WASI HTTP, and a [`wasmtime_wasi::ResourceTable`], the
31
//! state for all host-defined component model resources.
32
//!
33
//! The [`WasiHttpView`] trait additionally offers a few other configuration
34
//! methods such as [`WasiHttpView::send_request`] to customize how outgoing
35
//! HTTP requests are handled.
36
//!
37
//! # Async and Sync
38
//!
39
//! There are both asynchronous and synchronous bindings in this crate. For
40
//! example [`add_to_linker_async`] is for asynchronous embedders and
41
//! [`add_to_linker_sync`] is for synchronous embedders. Note that under the
42
//! hood both versions are implemented with `async` on top of [`tokio`].
43
//!
44
//! # Examples
45
//!
46
//! Usage of this crate is done through a few steps to get everything hooked up:
47
//!
48
//! 1. First implement [`WasiHttpView`] for your type which is the `T` in
49
//! [`wasmtime::Store<T>`].
50
//! 2. Add WASI HTTP interfaces to a [`wasmtime::component::Linker<T>`]. There
51
//! are a few options of how to do this:
52
//! * Use [`add_to_linker_async`] to bundle all interfaces in
53
//! `wasi:http/proxy` together
54
//! * Use [`add_only_http_to_linker_async`] to add only HTTP interfaces but
55
//! no others. This is useful when working with
56
//! [`wasmtime_wasi::p2::add_to_linker_async`] for example.
57
//! * Add individual interfaces such as with the
58
//! [`bindings::http::outgoing_handler::add_to_linker`] function.
59
//! 3. Use [`ProxyPre`](bindings::ProxyPre) to pre-instantiate a component
60
//! before serving requests.
61
//! 4. When serving requests use
62
//! [`ProxyPre::instantiate_async`](bindings::ProxyPre::instantiate_async)
63
//! to create instances and handle HTTP requests.
64
//!
65
//! A standalone example of doing all this looks like:
66
//!
67
//! ```no_run
68
//! use wasmtime::bail;
69
//! use hyper::server::conn::http1;
70
//! use std::sync::Arc;
71
//! use tokio::net::TcpListener;
72
//! use wasmtime::component::{Component, Linker, ResourceTable};
73
//! use wasmtime::{Engine, Result, Store};
74
//! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
75
//! use wasmtime_wasi_http::bindings::ProxyPre;
76
//! use wasmtime_wasi_http::bindings::http::types::Scheme;
77
//! use wasmtime_wasi_http::body::HyperOutgoingBody;
78
//! use wasmtime_wasi_http::io::TokioIo;
79
//! use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
80
//!
81
//! #[tokio::main]
82
//! async fn main() -> Result<()> {
83
//! let component = std::env::args().nth(1).unwrap();
84
//!
85
//! // Prepare the `Engine` for Wasmtime
86
//! let engine = Engine::default();
87
//!
88
//! // Compile the component on the command line to machine code
89
//! let component = Component::from_file(&engine, &component)?;
90
//!
91
//! // Prepare the `ProxyPre` which is a pre-instantiated version of the
92
//! // component that we have. This will make per-request instantiation
93
//! // much quicker.
94
//! let mut linker = Linker::new(&engine);
95
//! wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
96
//! wasmtime_wasi_http::add_only_http_to_linker_async(&mut linker)?;
97
//! let pre = ProxyPre::new(linker.instantiate_pre(&component)?)?;
98
//!
99
//! // Prepare our server state and start listening for connections.
100
//! let server = Arc::new(MyServer { pre });
101
//! let listener = TcpListener::bind("127.0.0.1:8000").await?;
102
//! println!("Listening on {}", listener.local_addr()?);
103
//!
104
//! loop {
105
//! // Accept a TCP connection and serve all of its requests in a separate
106
//! // tokio task. Note that for now this only works with HTTP/1.1.
107
//! let (client, addr) = listener.accept().await?;
108
//! println!("serving new client from {addr}");
109
//!
110
//! let server = server.clone();
111
//! tokio::task::spawn(async move {
112
//! if let Err(e) = http1::Builder::new()
113
//! .keep_alive(true)
114
//! .serve_connection(
115
//! TokioIo::new(client),
116
//! hyper::service::service_fn(move |req| {
117
//! let server = server.clone();
118
//! async move { server.handle_request(req).await }
119
//! }),
120
//! )
121
//! .await
122
//! {
123
//! eprintln!("error serving client[{addr}]: {e:?}");
124
//! }
125
//! });
126
//! }
127
//! }
128
//!
129
//! struct MyServer {
130
//! pre: ProxyPre<MyClientState>,
131
//! }
132
//!
133
//! impl MyServer {
134
//! async fn handle_request(
135
//! &self,
136
//! req: hyper::Request<hyper::body::Incoming>,
137
//! ) -> Result<hyper::Response<HyperOutgoingBody>> {
138
//! // Create per-http-request state within a `Store` and prepare the
139
//! // initial resources passed to the `handle` function.
140
//! let mut store = Store::new(
141
//! self.pre.engine(),
142
//! MyClientState {
143
//! table: ResourceTable::new(),
144
//! wasi: WasiCtx::builder().inherit_stdio().build(),
145
//! http: WasiHttpCtx::new(),
146
//! },
147
//! );
148
//! let (sender, receiver) = tokio::sync::oneshot::channel();
149
//! let req = store.data_mut().new_incoming_request(Scheme::Http, req)?;
150
//! let out = store.data_mut().new_response_outparam(sender)?;
151
//! let pre = self.pre.clone();
152
//!
153
//! // Run the http request itself in a separate task so the task can
154
//! // optionally continue to execute beyond after the initial
155
//! // headers/response code are sent.
156
//! let task = tokio::task::spawn(async move {
157
//! let proxy = pre.instantiate_async(&mut store).await?;
158
//!
159
//! if let Err(e) = proxy
160
//! .wasi_http_incoming_handler()
161
//! .call_handle(store, req, out)
162
//! .await
163
//! {
164
//! return Err(e);
165
//! }
166
//!
167
//! Ok(())
168
//! });
169
//!
170
//! match receiver.await {
171
//! // If the client calls `response-outparam::set` then one of these
172
//! // methods will be called.
173
//! Ok(Ok(resp)) => Ok(resp),
174
//! Ok(Err(e)) => Err(e.into()),
175
//!
176
//! // Otherwise the `sender` will get dropped along with the `Store`
177
//! // meaning that the oneshot will get disconnected and here we can
178
//! // inspect the `task` result to see what happened
179
//! Err(_) => {
180
//! let e = match task.await {
181
//! Ok(Ok(())) => {
182
//! bail!("guest never invoked `response-outparam::set` method")
183
//! }
184
//! Ok(Err(e)) => e,
185
//! Err(e) => e.into(),
186
//! };
187
//! return Err(e.context("guest never invoked `response-outparam::set` method"));
188
//! }
189
//! }
190
//! }
191
//! }
192
//!
193
//! struct MyClientState {
194
//! wasi: WasiCtx,
195
//! http: WasiHttpCtx,
196
//! table: ResourceTable,
197
//! }
198
//!
199
//! impl WasiView for MyClientState {
200
//! fn ctx(&mut self) -> WasiCtxView<'_> {
201
//! WasiCtxView { ctx: &mut self.wasi, table: &mut self.table }
202
//! }
203
//! }
204
//!
205
//! impl WasiHttpView for MyClientState {
206
//! fn ctx(&mut self) -> &mut WasiHttpCtx {
207
//! &mut self.http
208
//! }
209
//!
210
//! fn table(&mut self) -> &mut ResourceTable {
211
//! &mut self.table
212
//! }
213
//! }
214
//! ```
215
216
#![deny(missing_docs)]
217
#![doc(test(attr(deny(warnings))))]
218
#![doc(test(attr(allow(dead_code, unused_variables, unused_mut))))]
219
#![cfg_attr(docsrs, feature(doc_cfg))]
220
221
mod error;
222
mod http_impl;
223
mod types_impl;
224
225
pub mod body;
226
#[cfg(feature = "component-model-async")]
227
pub mod handler;
228
pub mod io;
229
pub mod types;
230
231
pub mod bindings;
232
233
#[cfg(feature = "p3")]
234
pub mod p3;
235
236
pub use crate::error::{
237
HttpError, HttpResult, http_request_error, hyper_request_error, hyper_response_error,
238
};
239
#[doc(inline)]
240
pub use crate::types::{
241
DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE, WasiHttpCtx,
242
WasiHttpImpl, WasiHttpView,
243
};
244
use http::header::CONTENT_LENGTH;
245
use wasmtime::component::{HasData, Linker};
246
247
/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
248
///
249
/// This function will add the `async` variant of all interfaces into the
250
/// `Linker` provided. For embeddings with async support disabled see
251
/// [`add_to_linker_sync`] instead.
252
///
253
/// # Example
254
///
255
/// ```
256
/// use wasmtime::{Engine, Result};
257
/// use wasmtime::component::{ResourceTable, Linker};
258
/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
259
/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
260
///
261
/// fn main() -> Result<()> {
262
/// let engine = Engine::default();
263
///
264
/// let mut linker = Linker::<MyState>::new(&engine);
265
/// wasmtime_wasi_http::add_to_linker_async(&mut linker)?;
266
/// // ... add any further functionality to `linker` if desired ...
267
///
268
/// Ok(())
269
/// }
270
///
271
/// struct MyState {
272
/// ctx: WasiCtx,
273
/// http_ctx: WasiHttpCtx,
274
/// table: ResourceTable,
275
/// }
276
///
277
/// impl WasiHttpView for MyState {
278
/// fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
279
/// fn table(&mut self) -> &mut ResourceTable { &mut self.table }
280
/// }
281
///
282
/// impl WasiView for MyState {
283
/// fn ctx(&mut self) -> WasiCtxView<'_> {
284
/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
285
/// }
286
/// }
287
/// ```
288
pub fn add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> wasmtime::Result<()>
289
where
290
T: WasiHttpView + wasmtime_wasi::WasiView + 'static,
291
{
292
wasmtime_wasi::p2::add_to_linker_proxy_interfaces_async(l)?;
293
add_only_http_to_linker_async(l)
294
}
295
296
/// A slimmed down version of [`add_to_linker_async`] which only adds
297
/// `wasi:http` interfaces to the linker.
298
///
299
/// This is useful when using [`wasmtime_wasi::p2::add_to_linker_async`] for
300
/// example to avoid re-adding the same interfaces twice.
301
pub fn add_only_http_to_linker_async<T>(
302
l: &mut wasmtime::component::Linker<T>,
303
) -> wasmtime::Result<()>
304
where
305
T: WasiHttpView + 'static,
306
{
307
let options = crate::bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
308
crate::bindings::http::outgoing_handler::add_to_linker::<_, WasiHttp<T>>(l, |x| {
309
WasiHttpImpl(x)
310
})?;
311
crate::bindings::http::types::add_to_linker::<_, WasiHttp<T>>(l, &options.into(), |x| {
312
WasiHttpImpl(x)
313
})?;
314
315
Ok(())
316
}
317
318
struct WasiHttp<T>(T);
319
320
impl<T: 'static> HasData for WasiHttp<T> {
321
type Data<'a> = WasiHttpImpl<&'a mut T>;
322
}
323
324
/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
325
///
326
/// This function will add the `sync` variant of all interfaces into the
327
/// `Linker` provided. For embeddings with async support see
328
/// [`add_to_linker_async`] instead.
329
///
330
/// # Example
331
///
332
/// ```
333
/// use wasmtime::{Engine, Result, Config};
334
/// use wasmtime::component::{ResourceTable, Linker};
335
/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
336
/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
337
///
338
/// fn main() -> Result<()> {
339
/// let config = Config::default();
340
/// let engine = Engine::new(&config)?;
341
///
342
/// let mut linker = Linker::<MyState>::new(&engine);
343
/// wasmtime_wasi_http::add_to_linker_sync(&mut linker)?;
344
/// // ... add any further functionality to `linker` if desired ...
345
///
346
/// Ok(())
347
/// }
348
///
349
/// struct MyState {
350
/// ctx: WasiCtx,
351
/// http_ctx: WasiHttpCtx,
352
/// table: ResourceTable,
353
/// }
354
/// impl WasiHttpView for MyState {
355
/// fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
356
/// fn table(&mut self) -> &mut ResourceTable { &mut self.table }
357
/// }
358
/// impl WasiView for MyState {
359
/// fn ctx(&mut self) -> WasiCtxView<'_> {
360
/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
361
/// }
362
/// }
363
/// ```
364
pub fn add_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
365
where
366
T: WasiHttpView + wasmtime_wasi::WasiView + 'static,
367
{
368
wasmtime_wasi::p2::add_to_linker_proxy_interfaces_sync(l)?;
369
add_only_http_to_linker_sync(l)
370
}
371
372
/// A slimmed down version of [`add_to_linker_sync`] which only adds
373
/// `wasi:http` interfaces to the linker.
374
///
375
/// This is useful when using [`wasmtime_wasi::p2::add_to_linker_sync`] for
376
/// example to avoid re-adding the same interfaces twice.
377
pub fn add_only_http_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
378
where
379
T: WasiHttpView + 'static,
380
{
381
let options = crate::bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
382
crate::bindings::sync::http::outgoing_handler::add_to_linker::<_, WasiHttp<T>>(l, |x| {
383
WasiHttpImpl(x)
384
})?;
385
crate::bindings::sync::http::types::add_to_linker::<_, WasiHttp<T>>(l, &options.into(), |x| {
386
WasiHttpImpl(x)
387
})?;
388
389
Ok(())
390
}
391
392
/// Extract the `Content-Length` header value from a [`http::HeaderMap`], returning `None` if it's not
393
/// present. This function will return `Err` if it's not possible to parse the `Content-Length`
394
/// header.
395
fn get_content_length(headers: &http::HeaderMap) -> wasmtime::Result<Option<u64>> {
396
let Some(v) = headers.get(CONTENT_LENGTH) else {
397
return Ok(None);
398
};
399
let v = v.to_str()?;
400
let v = v.parse()?;
401
Ok(Some(v))
402
}
403
404