commit 9e4bf24377d9dc7a6e3430ead0ca0f2c28b26189 Author: Peter Cai Date: Tue Apr 7 14:23:45 2020 +0800 initial commit diff --git a/.cargo-ok b/.cargo-ok new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db91124 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/target +**/*.rs.bk +Cargo.lock +bin/ +pkg/ +wasm-pack.log +worker/generated/ +config.json \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..de83145 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "paprika" +version = "0.1.0" +authors = ["Peter Cai "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +cfg-if = "0.1.2" +lazy_static = "1.4" +serde = { version = "1.0", features = [ "derive" ] } +serde_json = "1.0" +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +web-sys = { version = "0.3", features = [ "Request", "Response", "ResponseInit", "Url" ] } + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.1", optional = true } + +# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size +# compared to the default allocator's ~10K. It is slower than the default +# allocator, however. +wee_alloc = { version = "0.4.2", optional = true } + +[dev-dependencies] +wasm-bindgen-test = "0.2" + +[profile.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" diff --git a/README.md b/README.md new file mode 100644 index 0000000..781feb8 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# πŸ‘·β€β™€οΈπŸ¦€πŸ•ΈοΈ `rustwasm-worker-template` + +A template for kick starting a Cloudflare worker project using +[`wasm-pack`](https://github.com/rustwasm/wasm-pack). + +This template is designed for compiling Rust libraries into WebAssembly and +publishing the resulting worker to Cloudflare's worker infrastructure. + +## πŸ”‹ Batteries Included + +* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating + between WebAssembly and JavaScript. +* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) + for logging panic messages to the developer console. +* [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized + for small code size. + +## 🚴 Usage + +### πŸ‘ Use `wrangler generate` to Clone this Template + +[Learn more about `wrangler generate` here.](https://github.com/cloudflare/wrangler) + +``` +wrangler generate wasm-worker https://github.com/cloudflare/rustwasm-worker-template.git +cd wasm-worker +``` + +### πŸ› οΈ Build with `wasm-pack build` + +``` +wasm-pack build +``` + +### πŸ”¬ Test in Headless Browsers with `wasm-pack test` + +``` +wasm-pack test --headless --firefox +``` diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ef697e6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,60 @@ +#[macro_use] +extern crate lazy_static; + +mod utils; +mod router; + +use cfg_if::cfg_if; +use utils::{Error, MyResult}; +use wasm_bindgen::prelude::*; +use web_sys::*; + +cfg_if! { + // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global + // allocator. + if #[cfg(feature = "wee_alloc")] { + extern crate wee_alloc; + #[global_allocator] + static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + } +} + +lazy_static! { + static ref ROUTER: router::Router = { + build_routes() + }; +} + +fn build_routes() -> router::Router { + let mut router = router::Router::new(&default_route); + router.add_route("/hello", &hello_world); + return router; +} + +async fn default_route(_req: Request, _url: Url) -> MyResult { + Err(Error::NotFound("This page is not available".into())) +} + +async fn hello_world(_req: Request, _url: Url) -> MyResult { + Ok(Response::new_with_opt_str_and_init( + Some("Hello, world from Rust"), + ResponseInit::new().status(200) + ).unwrap()) +} + +#[wasm_bindgen] +pub async fn handle_request_rs(req: Request) -> Response { + let url = Url::new(&req.url()).unwrap(); + let result = ROUTER.execute(req, url).await; + + match result { + Ok(resp) => resp, + Err(err) => { + let code = err.status_code(); + let reason: String = err.into(); + Response::new_with_opt_str_and_init( + Some(&reason), ResponseInit::new().status(code) + ).unwrap() + } + } +} \ No newline at end of file diff --git a/src/router.rs b/src/router.rs new file mode 100644 index 0000000..e8a685b --- /dev/null +++ b/src/router.rs @@ -0,0 +1,70 @@ +use crate::utils::MyResult; +use std::future::Future; +use std::pin::Pin; +use std::vec::Vec; +use web_sys::*; + +// We have to box everything in order to make them fit in a Vec +type RouteHandler = Box Pin>>>>; + +// Convert a async function to RouteHandler +// both boxes the function and the returned Future +macro_rules! async_fn_boxed { + ($f:ident) => { + Box::new(move |req, url| Box::pin($f(req, url))) + }; +} + +struct Route { + path: String, + handler: RouteHandler +} + +pub struct Router { + routes: Vec, + default_handler: RouteHandler +} + +impl Router { + pub fn new(default_handler: &'static F) -> Router + where F: Sync + Fn(Request, Url) -> T, + T: 'static + Future> { + Router { + routes: vec![], + default_handler: async_fn_boxed!(default_handler) + } + } + + pub fn add_route( + &mut self, + path: &str, + handler: &'static F + ) where F: Sync + Fn(Request, Url) -> T, + T: 'static + Future> + { + self.routes.push(Route { + path: path.into(), + handler: async_fn_boxed!(handler) + }); + } + + pub async fn execute(&self, req: Request, url: Url) -> MyResult { + for route in self.routes.iter() { + // Routes added earlier overrides routes added later + // e.g. if '/path/aaa' was added before '/path/', then + // calls to '/path/aaa' will not be dispatched to '/path/' + // Routes ending with '/' are considered prefixes. + if route.path.ends_with("/") { + if url.pathname().starts_with(&route.path) { + return (route.handler)(req, url).await; + } + } else { + if url.pathname() == route.path { + return (route.handler)(req, url).await; + } + } + } + + return (self.default_handler)(req, url).await; + } +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..a873de3 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,52 @@ +use cfg_if::cfg_if; +use serde::Deserialize; + +cfg_if! { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + if #[cfg(feature = "console_error_panic_hook")] { + extern crate console_error_panic_hook; + pub use self::console_error_panic_hook::set_once as set_panic_hook; + } else { + #[inline] + pub fn set_panic_hook() {} + } +} + +pub type MyResult = Result; + +pub enum Error { + NotFound(String) +} + +impl Error { + pub fn status_code(&self) -> u16 { + match self { + Error::NotFound(_) => 404 + } + } +} + +impl Into for Error { + fn into(self) -> String { + match self { + Error::NotFound(reason) => { + format!("Not Found, Reason: {}", reason) + } + } + } +} + +#[derive(Deserialize)] +pub struct Config { + // The secret value used to authenticate the Standard Notes plugin link + secret: String +} + +pub fn get_config() -> Config { + serde_json::from_str(std::include_str!("../config.json")).unwrap() +} \ No newline at end of file diff --git a/worker/metadata_wasm.json b/worker/metadata_wasm.json new file mode 100644 index 0000000..afc6d35 --- /dev/null +++ b/worker/metadata_wasm.json @@ -0,0 +1,10 @@ +{ + "body_part": "script", + "bindings": [ + { + "name": "wasm", + "type": "wasm_module", + "part": "wasmprogram" + } + ] +} diff --git a/worker/worker.js b/worker/worker.js new file mode 100644 index 0000000..03eabfc --- /dev/null +++ b/worker/worker.js @@ -0,0 +1,14 @@ +const { handle_request_rs } = wasm_bindgen; + +addEventListener('fetch', event => { + event.respondWith(handleRequest(event.request)) +}) + +/** + * Fetch and log a request + * @param {Request} request + */ +async function handleRequest(request) { + await wasm_bindgen(wasm); + return await handle_request_rs(request); +}