Testing and Mixing with Rust Code
The app is a function from request to reply.
In Process
wisp::prepare::<App>().await?; // runs `init`, once
let mut req = wisp::Request::new("GET", "/posts?page=2");
req.header("accept", "text/html");
let reply = wisp::handle::<App>(req).await; // reply.status, .headers (Vec<(name, value)>), .body (wisp::Body)
Same parser, limits, hooks and CSRF check as the server.
Testing an App
wisp::test::client needs no port and keeps cookies like a browser:
// tests/app.rs
wisp::app!();
#[test]
fn counter() {
let mut app = wisp::test::client::<App>();
assert!(app.get("/").text().contains("Clicked 0 times"));
app.post_form("/?/increment", &[]);
assert!(app.get("/").text().contains("Clicked 1 times"));
assert!(app.cookie("user").is_none());
}
| Method | What it does |
|---|---|
get(path) | GET |
post_form(target, &[(name, value)]) | form POST |
send(req) | any request |
next_chunk(&mut reply) | next chunk of a stream |
cookie(name) | a stored cookie |
More in AGENTS.md and api.md. Nothing upgrades in process, so Response::websocket is 501: test WebSockets against the running server (tests/app/tests/http.rs uses a TcpStream).
WebSockets
A +server.rs upgrades with Response::websocket; the connection closes when the handler returns.
// src/routes/ws/+server.rs
fn get() -> Response {
Response::websocket(|ws| async move {
while let Some(msg) = ws.recv().await {
ws.send(msg).await?; // a String is text, a Vec<u8> binary
}
Ok(())
})
}
ws.recv()is the nextwisp::Message(Text/Binary;msg.text(),msg.bytes()), orNoneonce the client closed or went, or the server stops. Pings are answered, fragments joined.ws.send(msg)fails once closed: stop.recvandsendcan overlap (tokio::select!, orwisp::spawna sender withws.clone()).- While
recvwaits, a client quiet for 30 s is pinged and for 60 s closed (1001).WISP_WS_IDLEsets the 60 (seconds;0never). A send-only handler isn't timed (a client that stops reading failssend). - A message is at most the route's
BODY_LIMIT(1 MB default), else close 1009. beforeruns first (cookies, hooks).- A page on another site is 403 as for a cross-site form (
Originmust name the host, orORIGINwhen set). A non-upgrade request gets 426. - Only the built-in server upgrades;
tower, edge targets and the test client answer 501.
Browser side: new WebSocket(${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws) with onmessage, onopen, onclose.
The tower Feature
Wisp becomes a tower::Service; the default build keeps its two deps.
wisp = { git = "https://wisp.ar0.eu", features = ["tower"] }
let wisp = wisp::tower::service::<App>().await?; // Service<http::Request<B>>
axum answers its own routes, Wisp the rest (examples/axum):
wisp::app!();
#[tokio::main]
async fn main() -> std::io::Result<()> {
let listener = tokio::net::TcpListener::bind(wisp::address()).await?;
let wisp = wisp::tower::service::<App>().await?;
let app = axum::Router::new()
.route(
"/api/hello",
axum::routing::get(|| async { "hello from axum" }),
)
.fallback_service(wisp);
axum::serve(listener, app).await
}
Middleware: tower::ServiceBuilder::new().layer(tower_http::compression::CompressionLayer::new()).service(wisp::tower::service::<App>().await?).
hyper:
let svc = hyper_util::service::TowerToHyperService::new(wisp::tower::service::<App>().await?);
hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new())
.serve_connection(hyper_util::rt::TokioIo::new(stream), svc)
.await?;
Lambda with a main of your own (--target lambda needs no code; put WISP_SECRET in the function's environment):
wisp::app!();
#[tokio::main]
async fn main() -> Result<(), lambda_http::Error> {
lambda_http::run(wisp::tower::service::<App>().await?).await
}
Axum Inside Wisp
Send paths to an axum Router from before (or a catch-all +server.rs). before may return Result<Option<Response>> (Some answers instead of the route). async fn before takes every route off the no-wait fast path.
// src/hooks.rs
async fn before(cx: &mut Cx) -> Result<Option<Response>> {
if cx.path().starts_with("/api") {
let mut api = api_router(); // an axum::Router
return Ok(Some(wisp::tower::call(&mut api, cx).await?));
}
Ok(None)
}
Is This Page Useful?