-
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 #1 from wacker-dev/init
Initial version of waki
- Loading branch information
Showing
48 changed files
with
3,290 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
name: CI | ||
on: | ||
push: | ||
branches: | ||
- 'main' | ||
- 'release-**' | ||
pull_request: | ||
jobs: | ||
ci: | ||
name: Lint and test | ||
runs-on: ubuntu-latest | ||
timeout-minutes: 30 | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Install Rust | ||
uses: dtolnay/rust-toolchain@1.78.0 | ||
with: | ||
components: clippy, rustfmt | ||
- name: cargo fmt | ||
run: cargo fmt --all -- --check | ||
- name: cargo clippy | ||
run: cargo clippy --all-targets --all-features -- -D warnings |
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 @@ | ||
name: release | ||
on: | ||
push: | ||
tags: | ||
- "v*" | ||
permissions: | ||
contents: write | ||
jobs: | ||
publish: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 1 | ||
- name: Install Rust | ||
uses: dtolnay/rust-toolchain@1.78.0 | ||
- name: cargo publish | ||
run: | | ||
cargo publish -p waki-macros | ||
cargo publish -p waki | ||
env: | ||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} |
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 @@ | ||
[workspace] | ||
resolver = "2" | ||
members = [ | ||
"waki", | ||
"waki-macros", | ||
] | ||
|
||
[workspace.package] | ||
version = "0.1.0" | ||
authors = ["Xinzhao Xu"] | ||
edition = "2021" | ||
categories = ["wasm"] | ||
keywords = ["webassembly", "wasm", "wasi"] | ||
repository = "https://github.com/wacker-dev/waki" | ||
license = "Apache-2.0" | ||
description = "An HTTP library for building Web apps with WASI API" | ||
readme = "README.md" | ||
|
||
[workspace.dependencies] | ||
waki-macros = { path = "waki-macros", version = "0.1.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 |
---|---|---|
@@ -1,2 +1,12 @@ | ||
# waki | ||
|
||
An HTTP library for building Web apps with WASI API. | ||
|
||
```rust | ||
use waki::{handler, Request, Response}; | ||
|
||
#[handler] | ||
fn hello(req: Request) -> Response { | ||
Response::new().body(b"Hello, WASI!") | ||
} | ||
``` |
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,19 @@ | ||
[package] | ||
name = "waki-macros" | ||
readme.workspace = true | ||
description.workspace = true | ||
version.workspace = true | ||
authors.workspace = true | ||
edition.workspace = true | ||
categories.workspace = true | ||
keywords.workspace = true | ||
repository.workspace = true | ||
license.workspace = true | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
syn = { version = "2.0.66", features = ["full"] } | ||
quote = "1.0.36" | ||
proc-macro2 = "1.0.85" |
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 @@ | ||
use proc_macro2::TokenStream; | ||
use quote::quote; | ||
|
||
pub fn wrap_in_const(code: TokenStream) -> TokenStream { | ||
quote! { | ||
#[doc(hidden)] | ||
const _: () = { | ||
#code | ||
}; | ||
} | ||
} |
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 crate::dummy; | ||
|
||
use proc_macro2::TokenStream; | ||
use quote::quote; | ||
use syn::{ItemFn, Result}; | ||
|
||
pub fn handler(input: ItemFn) -> Result<TokenStream> { | ||
let fn_name = &input.sig.ident; | ||
|
||
Ok(dummy::wrap_in_const(quote! { | ||
#input | ||
|
||
struct Component; | ||
|
||
::waki::bindings::export!(Component with_types_in ::waki::bindings); | ||
|
||
impl ::waki::bindings::exports::wasi::http::incoming_handler::Guest for Component { | ||
fn handle(request: ::waki::bindings::wasi::http::types::IncomingRequest, response_out: ::waki::bindings::wasi::http::types::ResponseOutparam) { | ||
::waki::handle_response(response_out, #fn_name(request.into())) | ||
} | ||
} | ||
})) | ||
} |
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,12 @@ | ||
mod dummy; | ||
mod export; | ||
|
||
use proc_macro::TokenStream; | ||
use syn::{parse_macro_input, ItemFn}; | ||
|
||
#[proc_macro_attribute] | ||
pub fn handler(_: TokenStream, input: TokenStream) -> TokenStream { | ||
export::handler(parse_macro_input!(input as ItemFn)) | ||
.unwrap_or_else(syn::Error::into_compile_error) | ||
.into() | ||
} |
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 @@ | ||
[package] | ||
name = "waki" | ||
readme.workspace = true | ||
description.workspace = true | ||
version.workspace = true | ||
authors.workspace = true | ||
edition.workspace = true | ||
categories.workspace = true | ||
keywords.workspace = true | ||
repository.workspace = true | ||
license.workspace = true | ||
|
||
[dependencies] | ||
waki-macros.workspace = true | ||
|
||
anyhow = "1.0.86" | ||
wit-bindgen = "0.26.0" | ||
url = "2.5.0" | ||
|
||
[build-dependencies] | ||
wit-deps = "0.3.1" | ||
anyhow = "1.0.86" |
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,6 @@ | ||
use anyhow::Result; | ||
|
||
fn main() -> Result<()> { | ||
wit_deps::lock_sync!()?; | ||
Ok(()) | ||
} |
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,44 @@ | ||
//! # waki | ||
//! | ||
//! An HTTP library for building Web apps with WASI API. | ||
//! | ||
//! ``` | ||
//! use waki::{handler, Request, Response}; | ||
//! | ||
//! #[handler] | ||
//! fn hello(req: Request) -> Response { | ||
//! Response::new().body(b"Hello, WASI!") | ||
//! } | ||
//! ``` | ||
|
||
mod request; | ||
mod response; | ||
|
||
#[doc(hidden)] | ||
pub mod bindings { | ||
wit_bindgen::generate!({ | ||
path: "wit", | ||
world: "http", | ||
pub_export_macro: true, | ||
}); | ||
} | ||
|
||
#[doc(hidden)] | ||
pub use self::response::handle_response; | ||
pub use self::{bindings::wasi::http::types::Method, request::Request, response::Response}; | ||
|
||
/// Export the annotated function as entrypoint of the WASI HTTP component. | ||
/// | ||
/// The function needs to have one [`Request`] parameter and one [`Response`] return value. | ||
/// | ||
/// For example: | ||
/// | ||
/// ``` | ||
/// use waki::{handler, Request, Response}; | ||
/// | ||
/// #[handler] | ||
/// fn hello(req: Request) -> Response { | ||
/// Response::new().body(b"Hello, WASI!") | ||
/// } | ||
/// ``` | ||
pub use waki_macros::handler; |
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,110 @@ | ||
use crate::{ | ||
bindings::wasi::{ | ||
http::types::{IncomingBody, IncomingRequest, InputStream, Scheme}, | ||
io::streams::StreamError, | ||
}, | ||
Method, | ||
}; | ||
|
||
use anyhow::{anyhow, Result}; | ||
use std::collections::HashMap; | ||
use url::Url; | ||
|
||
pub struct Request { | ||
url: Url, | ||
method: Method, | ||
headers: HashMap<String, String>, | ||
// 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<IncomingRequest> for Request { | ||
fn from(req: IncomingRequest) -> Self { | ||
let scheme = match req.scheme().unwrap_or(Scheme::Http) { | ||
Scheme::Http => "http".into(), | ||
Scheme::Https => "https".into(), | ||
Scheme::Other(s) => s, | ||
}; | ||
let method = req.method(); | ||
let url = Url::parse(&format!( | ||
"{}://{}{}", | ||
scheme, | ||
req.authority().unwrap_or("localhost".into()), | ||
req.path_with_query().unwrap_or("/".into()) | ||
)) | ||
.unwrap(); | ||
|
||
let headers_handle = req.headers(); | ||
let headers: HashMap<String, String> = headers_handle | ||
.entries() | ||
.into_iter() | ||
.map(|(key, value)| (key, String::from_utf8_lossy(&value).to_string())) | ||
.collect(); | ||
drop(headers_handle); | ||
|
||
// The consume() method can only be called once | ||
let incoming_body = req.consume().unwrap(); | ||
drop(req); | ||
|
||
// The stream() method can only be called once | ||
let input_stream = incoming_body.stream().unwrap(); | ||
Self { | ||
url, | ||
method, | ||
headers, | ||
input_stream, | ||
_incoming_body: incoming_body, | ||
} | ||
} | ||
} | ||
|
||
impl Request { | ||
/// Get the full URL of the request. | ||
pub fn url(&self) -> Url { | ||
self.url.clone() | ||
} | ||
|
||
/// Get the HTTP method of the request. | ||
pub fn method(&self) -> Method { | ||
self.method.clone() | ||
} | ||
|
||
/// Get the path of the request. | ||
pub fn path(&self) -> String { | ||
self.url.path().to_string() | ||
} | ||
|
||
/// Get the query string of the request. | ||
pub fn query(&self) -> HashMap<String, String> { | ||
let query_pairs = self.url.query_pairs(); | ||
query_pairs.into_owned().collect() | ||
} | ||
|
||
/// Get the headers of the request. | ||
pub fn headers(&self) -> HashMap<String, String> { | ||
self.headers.clone() | ||
} | ||
|
||
/// Get a chunk of the request body. | ||
/// | ||
/// It will block until at least one byte can be read or the stream is closed. | ||
pub fn chunk(&self, len: u64) -> Result<Option<Vec<u8>>> { | ||
match self.input_stream.blocking_read(len) { | ||
Ok(c) => Ok(Some(c)), | ||
Err(StreamError::Closed) => Ok(None), | ||
Err(e) => Err(anyhow!("input_stream read failed: {e:?}"))?, | ||
} | ||
} | ||
|
||
/// Get the full request body. | ||
/// | ||
/// It will block until the stream is closed. | ||
pub fn body(self) -> Result<Vec<u8>> { | ||
let mut body = Vec::new(); | ||
while let Some(mut chunk) = self.chunk(1024 * 1024)? { | ||
body.append(&mut chunk); | ||
} | ||
Ok(body) | ||
} | ||
} |
Oops, something went wrong.