-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from wacker-dev/client
Merge wasi-http-client into waki
- Loading branch information
Showing
22 changed files
with
902 additions
and
141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,25 @@ | ||
# waki | ||
|
||
An HTTP library for building Web apps with WASI API. | ||
HTTP client and server library for WASI. | ||
|
||
Send a request: | ||
|
||
```rust | ||
let resp = Client::new() | ||
.post("https://httpbin.org/post") | ||
.connect_timeout(Duration::from_secs(5)) | ||
.send()?; | ||
|
||
println!("status code: {}", resp.status_code()); | ||
``` | ||
|
||
Writing an HTTP component: | ||
|
||
```rust | ||
use waki::{handler, Request, Response}; | ||
use waki::{handler, ErrorCode, Request, Response}; | ||
|
||
#[handler] | ||
fn hello(req: Request) -> Response { | ||
Response::new().body(b"Hello, WASI!") | ||
fn hello(req: Request) -> Result<Response, ErrorCode> { | ||
Response::builder().body(b"Hello, WASI!").build() | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[package] | ||
name = "test-programs" | ||
version = "0.0.0" | ||
edition = "2021" | ||
publish = false | ||
|
||
[dependencies] | ||
waki = { path = "../waki", features = ["json"] } | ||
serde = { workspace = true, features = ["derive"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[package] | ||
name = "test-programs-artifacts" | ||
version = "0.0.0" | ||
edition = "2021" | ||
publish = false | ||
|
||
[build-dependencies] | ||
anyhow.workspace = true | ||
cargo_metadata = "0.18.1" | ||
wit-component = "0.208.1" | ||
heck = "0.5.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
use anyhow::Result; | ||
use cargo_metadata::MetadataCommand; | ||
use heck::*; | ||
use std::path::{Path, PathBuf}; | ||
use std::process::Command; | ||
use std::{env, fs}; | ||
use wit_component::ComponentEncoder; | ||
|
||
fn main() -> Result<()> { | ||
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
|
||
println!("cargo::rerun-if-changed=../src"); | ||
|
||
let status = Command::new("cargo") | ||
.arg("build") | ||
.arg("--package=test-programs") | ||
.arg("--target=wasm32-wasi") | ||
.env("CARGO_TARGET_DIR", &out_dir) | ||
.env("CARGO_PROFILE_DEV_DEBUG", "1") | ||
.status()?; | ||
assert!(status.success()); | ||
|
||
let meta = MetadataCommand::new().exec()?; | ||
let targets = meta | ||
.packages | ||
.iter() | ||
.find(|p| p.name == "test-programs") | ||
.unwrap() | ||
.targets | ||
.iter() | ||
.filter(|t| t.kind == ["bin"]) | ||
.map(|t| &t.name) | ||
.collect::<Vec<_>>(); | ||
|
||
let mut generated_code = String::new(); | ||
|
||
for target in targets { | ||
let camel = target.to_shouty_snake_case(); | ||
let wasm = out_dir | ||
.join("wasm32-wasi") | ||
.join("debug") | ||
.join(format!("{target}.wasm")); | ||
|
||
let path = compile_component(&wasm)?; | ||
generated_code += &format!("pub const {camel}_COMPONENT: &str = {path:?};\n"); | ||
} | ||
|
||
fs::write(out_dir.join("gen.rs"), generated_code)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
// Compile a component, return the path of the binary | ||
fn compile_component(wasm: &Path) -> Result<PathBuf> { | ||
let module = fs::read(wasm)?; | ||
let component = ComponentEncoder::default() | ||
.module(module.as_slice())? | ||
.validate(true) | ||
.adapter( | ||
"wasi_snapshot_preview1", | ||
include_bytes!("wasi_snapshot_preview1.command.wasm"), | ||
)? | ||
.encode()?; | ||
let out_dir = wasm.parent().unwrap(); | ||
let stem = wasm.file_stem().unwrap().to_str().unwrap(); | ||
let component_path = out_dir.join(format!("{stem}.component.wasm")); | ||
fs::write(&component_path, component)?; | ||
Ok(component_path) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include!(concat!(env!("OUT_DIR"), "/gen.rs")); |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
use waki::Client; | ||
|
||
fn main() { | ||
let resp = Client::new() | ||
.get("https://httpbin.org/range/20") | ||
.query(&[("duration", "5"), ("chunk_size", "10")]) | ||
.send() | ||
.unwrap(); | ||
assert_eq!(resp.status_code(), 200); | ||
|
||
while let Some(chunk) = resp.chunk(1024).unwrap() { | ||
assert_eq!(String::from_utf8(chunk).unwrap().len(), 10); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use serde::Deserialize; | ||
use std::collections::HashMap; | ||
use waki::Client; | ||
|
||
#[derive(Deserialize)] | ||
struct Data { | ||
args: HashMap<String, String>, | ||
} | ||
|
||
fn main() { | ||
let resp = Client::new() | ||
.get("https://httpbin.org/get?a=b") | ||
.headers([("Content-Type", "application/json"), ("Accept", "*/*")]) | ||
.send() | ||
.unwrap(); | ||
assert_eq!(resp.status_code(), 200); | ||
|
||
let data = resp.json::<Data>().unwrap(); | ||
assert_eq!(data.args.get("a").unwrap(), "b"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use serde::Deserialize; | ||
use std::collections::HashMap; | ||
use std::time::Duration; | ||
use waki::Client; | ||
|
||
#[derive(Deserialize)] | ||
struct Data { | ||
form: HashMap<String, String>, | ||
} | ||
|
||
fn main() { | ||
let resp = Client::new() | ||
.post("https://httpbin.org/post") | ||
.form(&[("a", "b"), ("c", "")]) | ||
.connect_timeout(Duration::from_secs(5)) | ||
.send() | ||
.unwrap(); | ||
assert_eq!(resp.status_code(), 200); | ||
|
||
let data = resp.json::<Data>().unwrap(); | ||
assert_eq!(data.form.get("a").unwrap(), "b"); | ||
assert_eq!(data.form.get("c").unwrap(), ""); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
use serde::Deserialize; | ||
use std::collections::HashMap; | ||
use std::time::Duration; | ||
use waki::Client; | ||
|
||
#[derive(Deserialize)] | ||
struct Data { | ||
json: HashMap<String, String>, | ||
} | ||
|
||
fn main() { | ||
let resp = Client::new() | ||
.post("https://httpbin.org/post") | ||
.json(&HashMap::from([("data", "hello")])) | ||
.connect_timeout(Duration::from_secs(5)) | ||
.send() | ||
.unwrap(); | ||
assert_eq!(resp.status_code(), 200); | ||
|
||
let data = resp.json::<Data>().unwrap(); | ||
assert_eq!(data.json.get("data").unwrap(), "hello"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
use serde::Deserialize; | ||
use std::collections::HashMap; | ||
use std::time::Duration; | ||
use waki::Client; | ||
|
||
#[derive(Deserialize)] | ||
struct Data { | ||
form: HashMap<String, String>, | ||
files: HashMap<String, String>, | ||
} | ||
|
||
fn main() { | ||
let resp = Client::new() | ||
.post("https://httpbin.org/post") | ||
.header("Content-Type", "multipart/form-data; boundary=boundary") | ||
.body( | ||
"--boundary | ||
Content-Disposition: form-data; name=field1 | ||
value1 | ||
--boundary | ||
Content-Disposition: form-data; name=field2; filename=file.txt | ||
Content-Type: text/plain | ||
hello | ||
--boundary--" | ||
.as_bytes(), | ||
) | ||
.connect_timeout(Duration::from_secs(5)) | ||
.send() | ||
.unwrap(); | ||
assert_eq!(resp.status_code(), 200); | ||
|
||
let data = resp.json::<Data>().unwrap(); | ||
assert_eq!(data.form.get("field1").unwrap(), "value1"); | ||
assert_eq!(data.files.get("field2").unwrap(), "hello"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
use crate::bindings::wasi::{ | ||
http::types::{IncomingBody, InputStream}, | ||
io::streams::StreamError, | ||
}; | ||
|
||
use anyhow::{anyhow, Result}; | ||
|
||
pub struct IncomingBodyStream { | ||
// input-stream resource is a child: it must be dropped before the parent incoming-body is dropped | ||
input_stream: InputStream, | ||
_incoming_body: IncomingBody, | ||
} | ||
|
||
impl From<IncomingBody> for IncomingBodyStream { | ||
fn from(body: IncomingBody) -> Self { | ||
Self { | ||
// The stream() method can only be called once | ||
input_stream: body.stream().unwrap(), | ||
_incoming_body: body, | ||
} | ||
} | ||
} | ||
|
||
impl InputStream { | ||
pub fn chunk(&self, len: u64) -> Result<Option<Vec<u8>>> { | ||
match self.blocking_read(len) { | ||
Ok(c) => Ok(Some(c)), | ||
Err(StreamError::Closed) => Ok(None), | ||
Err(e) => Err(anyhow!("input_stream read failed: {e:?}"))?, | ||
} | ||
} | ||
} | ||
|
||
pub enum Body { | ||
Bytes(Vec<u8>), | ||
Stream(IncomingBodyStream), | ||
} | ||
|
||
impl Body { | ||
pub fn chunk(&self, len: u64) -> Result<Option<Vec<u8>>> { | ||
match &self { | ||
Body::Bytes(_) => Ok(None), | ||
Body::Stream(s) => s.input_stream.chunk(len), | ||
} | ||
} | ||
|
||
pub fn bytes(self) -> Result<Vec<u8>> { | ||
match self { | ||
Body::Bytes(data) => Ok(data), | ||
Body::Stream(s) => { | ||
let mut body = Vec::new(); | ||
while let Some(mut chunk) = s.input_stream.chunk(1024 * 1024)? { | ||
body.append(&mut chunk); | ||
} | ||
Ok(body) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.