HTTP requests
TypeScript
HTTP requests can be made with the standard fetch function.
For example:
const response = await fetch(`http://localhost:${port}/todos`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "foo",
body: "bar",
userId: 1,
}),
});
const data = await response.json();
console.log("Body:", data);Rust
HTTP requests can be made using the wstd crate. Make sure to have the crate added to your Cargo.toml:
[dependencies]
wstd = { version = "=0.6.5", features = ["default", "json"] }use serde::Serialize;
use wstd::http::{Body, Client, Request};
#[derive(Serialize)]
struct ExampleRequest {
title: String,
body: String,
user_id: u16,
}
let request_body = ExampleRequest {
title: "foo".to_string(),
body: "bar".to_string(),
user_id: 1,
};
let request = Request::post(&format!("http://localhost:{port}/todos"))
.header("Content-Type", "application/json")
.body(Body::from_json(&request_body).expect("Failed to serialize request"))
.expect("Failed to build request");
let mut response = Client::new()
.send(request)
.await
.expect("Request failed");
let data: serde_json::Value = response
.body_mut()
.json()
.await
.expect("Failed to parse response");
println!("Body: {:?}", data);It’s recommended to use wstd, but you can also use golem-wasi-http for a more reqwest-like client.
Make sure to have the crate added to your Cargo.toml:
[dependencies]
golem-wasi-http = { version = "0.2.0", features = ["async", "json"] }use golem_wasi_http::{Client, Response};
use serde::Serialize;
#[derive(Serialize)]
struct ExampleRequest {
title: String,
body: String,
user_id: u16,
}
let request_body = ExampleRequest {
title: "foo".to_string(),
body: "bar".to_string(),
user_id: 1,
};
let client = Client::builder().build().expect("Failed to build client");
let response: Response = client
.post(&format!("http://localhost:{port}/todos"))
.header("X-Test", "Golem")
.json(&request_body)
.basic_auth("some", Some("body"))
.send()
.await
.expect("Request failed");
let data: serde_json::Value = response.json().await.expect("Failed to parse response");
println!("Body: {:?}", data);Generating clients from OpenAPI specs
For typed HTTP clients generated from an OpenAPI spec, use progenitor-wasi-http. It is a fork of Oxide Computer’s Progenitor that replaces reqwest with wstd::http, so the generated client works on top of wasi:http (and therefore inside Golem workers).
There are three ways to use it: via a procedural macro, a build.rs script, or by generating a standalone crate with the cargo-progenitor CLI.
Macro-based generation
Add the following dependencies to your Cargo.toml:
[dependencies]
http = "1.4"
progenitor = { git = "https://github.com/golemcloud/progenitor-wasi-http" }
serde = { version = "1.0", features = ["derive"] }
wstd = "=0.6.5"Then expand the client directly into your source file:
use progenitor::generate_api;
generate_api!("path/to/openapi.json");
async fn example() {
let client = Client::new("https://api.example.com");
// generated methods are now available on `client`
}The macro supports additional options such as interface = Builder for a builder-style API, extra derives, and pre/post request hooks (useful for injecting authentication headers):
use progenitor::generate_api;
use wstd::http;
generate_api!(
spec = "openapi/my-api.json",
interface = Builder,
pre_hook_async = crate::add_auth_headers,
);
async fn add_auth_headers<B: http::Body>(
req: &mut http::Request<B>,
) -> Result<(), http::error::InvalidHeaderValue> {
req.headers_mut().insert(
::http::header::AUTHORIZATION,
::http::header::HeaderValue::from_str("Bearer my-token")?,
);
Ok(())
}build.rs-based generation
If you prefer to inspect the generated code on disk, run the generator from a build script. Add the runtime client to [dependencies] and the generator to [build-dependencies]:
[dependencies]
http = "1.4"
progenitor-client = { git = "https://github.com/golemcloud/progenitor-wasi-http" }
serde = { version = "1.0", features = ["derive"] }
wstd = "=0.6.5"
[build-dependencies]
prettyplease = "0.2"
progenitor = { git = "https://github.com/golemcloud/progenitor-wasi-http", default-features = false }
serde_json = "1.0"
syn = "2.0"A minimal build.rs:
use std::{env, fs, fs::File, path::Path};
fn main() {
let src = "openapi/my-api.json";
println!("cargo:rerun-if-changed={}", src);
let file = File::open(src).unwrap();
let spec = serde_json::from_reader(file).unwrap();
let mut generator = progenitor::Generator::default();
let tokens = generator.generate_tokens(&spec).unwrap();
let ast = syn::parse2(tokens).unwrap();
let content = prettyplease::unparse(&ast);
let mut out_file = Path::new(&env::var("OUT_DIR").unwrap()).to_path_buf();
out_file.push("codegen.rs");
fs::write(out_file, content).unwrap();
}Include the generated module from your code:
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
async fn example() {
let client = Client::new("https://api.example.com");
// generated methods are now available on `client`
}Standalone crate generation
To generate a self-contained client crate that can be committed and reused independently, use the cargo-progenitor CLI:
cargo install cargo-progenitor
cargo progenitor -i openapi/my-api.json -o ./my-api-client -n my-api-client -v 0.1.0See the progenitor-wasi-http README and the example-build and example-macro directories for complete worked examples, including pagination and customizing the generated builder API.
Scala
HTTP requests can be made with the standard fetch function through Scala.js interop.
For example:
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.scalajs.js
import scala.scalajs.js.Thenable.Implicits._
for {
response <- js.Dynamic.global
.fetch(
s"http://localhost:$port/todos",
js.Dynamic.literal(
method = "POST",
headers = js.Dynamic.literal(
"Content-Type" -> "application/json"
),
body = js.JSON.stringify(
js.Dynamic.literal(
title = "foo",
body = "bar",
userId = 1
)
)
)
)
.asInstanceOf[js.Promise[js.Dynamic]]
.toFuture
data <- response.json().asInstanceOf[js.Promise[js.Dynamic]].toFuture
} yield println(s"Body: ${js.JSON.stringify(data)}")MoonBit
HTTP requests can be made using generated wasi:http bindings. Assuming your generated wasi:http/types bindings are imported as @http, your wasi:http/outgoingHandler bindings are imported as @outgoingHandler, and moonbitlang/core/json is imported as @json:
struct ExampleRequest {
title : String
body : String
userId : Int
} derive(ToJson)
fn string_to_bytes(value : String) -> FixedArray[Byte] {
let chars = value.to_array()
let bytes = FixedArray::make(chars.length(), b'\x00')
for i in 0..<chars.length() {
bytes[i] = chars[i].to_int().to_byte()
}
bytes
}
fn bytes_to_string(bytes : FixedArray[Byte]) -> String {
let chars : Array[Char] = []
for i in 0..<bytes.length() {
chars.push(Int::unsafe_to_char(bytes[i].to_int()))
}
String::from_array(chars)
}
let request_body = ExampleRequest::{
title: "foo",
body: "bar",
userId: 1,
}
let headers = @http.Fields::from_list(
[("Content-Type", string_to_bytes("application/json"))],
).unwrap()
let request = @http.OutgoingRequest::outgoing_request(headers)
let _ = request.set_method(@http.Post)
let _ = request.set_scheme(Some(@http.Http))
let _ = request.set_authority(Some("localhost:\{port}"))
let _ = request.set_path_with_query(Some("/todos"))
let body = request.body().unwrap()
let output_stream = body.write().unwrap()
output_stream.blocking_write_and_flush(
string_to_bytes(request_body.to_json().stringify()),
).unwrap()
output_stream.drop()
@http.OutgoingBody::finish(body, None).unwrap()
let future_response = @outgoingHandler.handle(request, None).unwrap()
let pollable = future_response.subscribe()
pollable.block()
let response = future_response.get().unwrap().unwrap().unwrap()
let incoming_body = response.consume().unwrap()
let stream = incoming_body.stream().unwrap()
let bytes = stream.blocking_read(1048576UL).unwrap()
stream.drop()
ignore(@http.IncomingBody::finish(incoming_body))
let data = @json.parse(bytes_to_string(bytes)) catch {
_ => panic()
}
println("Body: \{data.stringify()}")For WebSocket connections, see the WebSocket client page. The HTTP APIs described above do not support WebSocket upgrades.