use crate::{ alarm, analytics_engine, assets, auto_response, cache, container, counter, d1, durable, fetch, form, js_snippets, kv, put_raw, queue, r2, rate_limit, request, secret_store, service, socket, sql_counter, sql_iterator, user, ws, SomeSharedData, GLOBAL_STATE, }; #[cfg(feature = "http")] use std::convert::TryInto; use std::sync::atomic::Ordering; use worker::{console_log, Env, Fetch, Request, Response, ResponseBuilder, Result}; #[cfg(not(feature = "http"))] use worker::{RouteContext, Router}; #[cfg(feature = "http")] use axum::{ routing::{delete, get, head, options, patch, post, put}, Extension, }; // Transform the argument into the correct form for the router. // For axum::Router: // - "*arg" -> "{*arg}" // - "arg" -> "{arg}" #[cfg(feature = "http")] macro_rules! transform_arg ( ($arg_name:literal) => { format!("{{{}}}", $arg_name) } ); // For worker::Router using matchit=0.7. (Note that in matchit=0.8.6 // (), parameter matching // is much closer to axum::Router so we should be able to remove all of this // logic if we update.) // - "*arg" -> "*arg" // - "arg" -> ":arg" #[cfg(not(feature = "http"))] macro_rules! transform_arg ( ($arg_name:literal) => { if $arg_name.starts_with('*') { format!("{}", $arg_name) } else { format!(":{}", $arg_name) } }; ); // Like `format!`, but apply `transform_arg!` to each argument first. macro_rules! format_route ( ($path_format:literal $(, $arg_name:literal)*) => { &format!($path_format, $( transform_arg!($arg_name) ),*) }; ); // Rewrites a handler with legacy http types to use axum extractors / response type. // Returns an async handler unless the macro is called with a second argument 'sync'. #[cfg(feature = "http")] macro_rules! handler ( ($name:path) => { |Extension(env): Extension, Extension(data): Extension, req: axum::extract::Request| async { let resp = $name(req.try_into().expect("convert request"), env, data).await.expect("handler result"); Into::>::into(resp) } }; ($name:path, sync) => { |Extension(env): Extension, Extension(data): Extension, req: axum::extract::Request| async { let resp = $name(req.try_into().expect("convert request"), env, data).expect("handler result"); Into::>::into(resp) } }; ); #[cfg(not(feature = "http"))] macro_rules! handler ( ($name:path, sync) => { |req: Request, ctx: RouteContext| { $name(req, ctx.env, ctx.data) } }; ($name:path) => { |req: Request, ctx: RouteContext| async { $name(req, ctx.env, ctx.data).await } }; ); // Add a route to the router. (Ideally this would be a postfix macro // https://github.com/rust-lang/rfcs/pull/2442.) #[cfg(feature = "http")] macro_rules! add_route ( ($obj:ident, $method:ident, sync, $route:expr, $name:path) => { let $obj = $obj.route($route, $method(handler!($name, sync))); }; ($obj:ident, $method:ident, $route:expr, $name:path) => { let $obj = $obj.route($route, $method(handler!($name))); }; ); // Use `paste::item` to create an identifier '_async' since that's // the format for worker::Router's async methods. #[cfg(not(feature = "http"))] macro_rules! add_route ( ($obj:ident, $method:ident, $route:expr, $name:path) => { paste::item! { let $obj = $obj.[<$method _ async>]($route, handler!($name)); } }; ($obj:ident, $method:ident, sync, $route:expr, $name:path) => { let $obj = $obj.$method($route, handler!($name, sync)); }; ); macro_rules! add_routes ( ($obj:ident) => { add_route!($obj, get, sync, "/request", request::handle_a_request); add_route!($obj, get, "/analytics-engine", analytics_engine::handle_analytics_event); add_route!($obj, get, "/async-request", request::handle_async_request); add_route!($obj, get, format_route!("/asset/{}", "name"), assets::handle_asset); add_route!($obj, get, "/websocket", ws::handle_websocket); add_route!($obj, get, "/got-close-event", handle_close_event); add_route!($obj, get, "/ws-client",ws::handle_websocket_client); add_route!($obj, get, "/test-data", request::handle_test_data); add_route!($obj, post, format_route!("/xor/{}", "num"), request::handle_xor); add_route!($obj, post, "/headers", request::handle_headers); add_route!($obj, post, "/formdata-name", form::handle_formdata_name); add_route!($obj, post, "/is-secret", form::handle_is_secret); add_route!($obj, post, "/formdata-file-size", form::handle_formdata_file_size); add_route!($obj, get, format_route!("/formdata-file-size/{}", "hash"), form::handle_formdata_file_size_hash); add_route!($obj, post, "/post-file-size",request::handle_post_file_size); add_route!($obj, get, format_route!("/user/{}/test", "id"), user::handle_user_id_test); add_route!($obj, get, format_route!("/user/{}", "id"), user::handle_user_id); add_route!($obj, post, format_route!("/account/{}/zones", "id"), user::handle_post_account_id_zones); add_route!($obj, get, format_route!("/account/{}/zones","id"), user::handle_get_account_id_zones); add_route!($obj, post, "/async-text-echo", request::handle_async_text_echo); add_route!($obj, get, "/fetch",fetch::handle_fetch); add_route!($obj, get, "/fetch_json",fetch::handle_fetch_json); add_route!($obj, get, format_route!("/proxy_request/{}", "*url") ,fetch::handle_proxy_request); add_route!($obj, get, "/durable/alarm", alarm::handle_alarm); add_route!($obj, get, format_route!("/durable/{}", "id"), counter::handle_id); add_route!($obj, get, "/durable/put-raw", put_raw::handle_put_raw); add_route!($obj, get, "/durable/websocket", counter::handle_websocket); add_route!($obj, get, "/secret", request::handle_secret); add_route!($obj, get, "/var", request::handle_var); add_route!($obj, get, "/object-var", request::handle_object_var); add_route!($obj, post, format_route!("/kv/{}/{}", "key", "value"), kv::handle_post_key_value); add_route!($obj, get, "/bytes",request::handle_bytes); add_route!($obj, post, "/api-data",request::handle_api_data); add_route!($obj, post, "/nonsense-repeat", request::handle_nonsense_repeat); add_route!($obj, get, format_route!("/status/{}", "code"), request::handle_status); add_route!($obj, put, sync, "/",respond); add_route!($obj, patch, sync, "/", respond); add_route!($obj, delete, sync, "/",respond); add_route!($obj, head, sync, "/", respond); add_route!($obj, put, "/async", respond_async); add_route!($obj, patch, "/async", respond_async); add_route!($obj, delete, "/async", respond_async); add_route!($obj, head, "/async", respond_async); add_route!($obj, options, format_route!("/{}", "*catchall"), handle_options_catchall); add_route!($obj, get, "/request-init-fetch", fetch::handle_request_init_fetch); add_route!($obj, get, "/request-init-fetch-post", fetch::handle_request_init_fetch_post); add_route!($obj, get, "/cancelled-fetch", fetch::handle_cancelled_fetch); add_route!($obj, get, "/fetch-timeout", fetch::handle_fetch_timeout); add_route!($obj, get, "/redirect-default", request::handle_redirect_default); add_route!($obj, get, "/redirect-307", request::handle_redirect_307); add_route!($obj, get, "/now", request::handle_now); add_route!($obj, get, "/cloned", request::handle_cloned); add_route!($obj, get, "/cloned-stream", request::handle_cloned_stream); add_route!($obj, get, "/cloned-fetch", fetch::handle_cloned_fetch); add_route!($obj, get, "/cloned-response", fetch::handle_cloned_response_attributes); add_route!($obj, get, format_route!("/wait/{}", "delay"), request::handle_wait_delay); add_route!($obj, get, "/custom-response-body", request::handle_custom_response_body); add_route!($obj, get, "/init-called", handle_init_called); add_route!($obj, get, "/cache-example", cache::handle_cache_example); add_route!($obj, get, format_route!("/cache-api/get/{}", "key"), cache::handle_cache_api_get); add_route!($obj, put, format_route!("/cache-api/put/{}", "key"), cache::handle_cache_api_put); add_route!($obj, post, format_route!("/cache-api/delete/{}", "key"), cache::handle_cache_api_delete); add_route!($obj, get, "/cache-stream", cache::handle_cache_stream); add_route!($obj, get, "/remote-by-request", service::handle_remote_by_request); add_route!($obj, get, "/remote-by-path", service::handle_remote_by_path); add_route!($obj, post, format_route!("/queue/send/{}", "id"), queue::handle_queue_send); add_route!($obj, post, "/queue/send_batch", queue::handle_batch_send); add_route!($obj, get, "/queue",queue::handle_queue); add_route!($obj, get, "/d1/prepared", d1::prepared_statement); add_route!($obj, get, "/d1/batch", d1::batch); add_route!($obj, get, "/d1/dump", d1::dump); add_route!($obj, post, "/d1/exec", d1::exec); add_route!($obj, get, "/d1/error", d1::error); add_route!($obj, get, "/d1/jsvalue_null_is_null", d1::jsvalue_null_is_null); add_route!($obj, get, "/d1/serialize_optional_none", d1::serialize_optional_none); add_route!($obj, get, "/d1/serialize_optional_some", d1::serialize_optional_some); add_route!($obj, get, "/d1/deserialize_optional_none", d1::deserialize_optional_none); add_route!($obj, get, "/d1/insert_and_retrieve_optional_none", d1::insert_and_retrieve_optional_none); add_route!($obj, get, "/d1/insert_and_retrieve_optional_some", d1::insert_and_retrieve_optional_some); add_route!($obj, get, "/d1/retrieve_optional_none", d1::retrieve_optional_none); add_route!($obj, get, "/d1/retrieve_optional_some", d1::retrieve_optional_some); add_route!($obj, get, "/d1/retrive_first_none", d1::retrive_first_none); add_route!($obj, get, "/kv/get", kv::get); add_route!($obj, get, "/kv/get-not-found", kv::get_not_found); add_route!($obj, get, "/kv/list-keys", kv::list_keys); add_route!($obj, get, "/kv/put-simple", kv::put_simple); add_route!($obj, get, "/kv/put-metadata", kv::put_metadata); add_route!($obj, get, "/kv/put-metadata-struct", kv::put_metadata_struct); add_route!($obj, get, "/kv/put-expiration", kv::put_expiration); add_route!($obj, get, "/r2/list-empty", r2::list_empty); add_route!($obj, get, "/r2/list", r2::list); add_route!($obj, get,"/r2/get-empty", r2::get_empty); add_route!($obj, get, "/r2/get", r2::get); add_route!($obj, put, "/r2/put", r2::put); add_route!($obj, put, "/r2/put-properties", r2::put_properties); add_route!($obj, put, "/r2/put-multipart", r2::put_multipart); add_route!($obj, delete, "/r2/delete", r2::delete); add_route!($obj, get, "/socket/failed", socket::handle_socket_failed); add_route!($obj, get, "/socket/read", socket::handle_socket_read); add_route!($obj, get, "/durable/auto-response", auto_response::handle_auto_response); add_route!($obj, get, "/durable/hello", durable::handle_hello); add_route!($obj, get, "/durable/hello-unique", durable::handle_hello_unique); add_route!($obj, get, "/durable/storage", durable::handle_storage); add_route!($obj, get, "/durable/handle-basic-test", durable::handle_basic_test); add_route!($obj, get, "/durable/get-by-name", durable::handle_get_by_name); add_route!($obj, get, "/durable/get-by-name-with-location-hint", durable::handle_get_by_name_with_location_hint); add_route!($obj, get, "/js_snippets/now", js_snippets::performance_now); add_route!($obj, get, "/js_snippets/log", js_snippets::console_log); add_route!($obj, get, format_route!("/sql-counter/{}", "*path"), sql_counter::handle_sql_counter); add_route!($obj, get, format_route!("/sql-iterator/{}", "*path"), sql_iterator::handle_sql_iterator); add_route!($obj, get, "/get-from-secret-store", secret_store::get_from_secret_store); add_route!($obj, get, "/get-from-secret-store-missing", secret_store::get_from_secret_store_missing); add_route!($obj, get, sync, "/test-panic", handle_test_panic); add_route!($obj, get, sync, "/test-abort", handle_test_abort); add_route!($obj, get, sync, "/test-oom", handle_test_oom); add_route!($obj, get, sync, "/test-js-error", js_snippets::throw_js_error); add_route!($obj, post, "/container/echo", container::handle_container); add_route!($obj, get, "/container/ws", container::handle_container); add_route!($obj, get, "/rate-limit/check", rate_limit::handle_rate_limit_check); add_route!($obj, get, format_route!("/rate-limit/key/{}", "key"), rate_limit::handle_rate_limit_with_key); add_route!($obj, get, "/rate-limit/bulk-test", rate_limit::handle_rate_limit_bulk_test); add_route!($obj, get, "/rate-limit/reset", rate_limit::handle_rate_limit_reset); }); #[cfg(feature = "http")] pub fn make_router(data: SomeSharedData, env: Env) -> axum::Router { let router = axum::Router::new(); add_routes!(router); router .fallback(get(handler!(catchall))) .layer(Extension(env)) .layer(Extension(data)) } #[cfg(not(feature = "http"))] pub fn make_router<'a>(data: SomeSharedData) -> Router<'a, SomeSharedData> { let router = Router::with_data(data); add_routes!(router); router.or_else_any_method_async("/*catchall", handler!(catchall)) } #[allow(clippy::needless_pass_by_value)] fn respond(req: Request, _env: Env, _data: SomeSharedData) -> Result { ResponseBuilder::new() .with_header("x-testing", "123")? .ok(format!("Ok: {}", String::from(req.method()))) } async fn respond_async(req: Request, _env: Env, _data: SomeSharedData) -> Result { ResponseBuilder::new() .with_header("x-testing", "123")? .ok(format!("Ok (async): {}", String::from(req.method()))) } #[worker::send] async fn handle_close_event(_req: Request, env: Env, _data: SomeSharedData) -> Result { let some_namespace_kv = env.kv("SOME_NAMESPACE")?; let got_close_event = some_namespace_kv .get("got-close-event") .text() .await? .unwrap_or_else(|| "false".into()); // Let the integration tests have some way of knowing if we successfully received the closed event. Response::ok(got_close_event) } #[worker::send] async fn catchall(req: Request, _env: Env, _data: SomeSharedData) -> Result { let uri = req.url()?; let path = uri.path(); console_log!("[or_else_any_method_async] caught: {}", path); let (builder, body) = Fetch::Url("https://github.com/404".parse().unwrap()) .send() .await? .into_parts(); Ok(builder.with_status(404).body(body)) } async fn handle_options_catchall( req: Request, _env: Env, _data: SomeSharedData, ) -> Result { let uri = req.url()?; let path = uri.path(); Response::ok(path) } async fn handle_init_called(_req: Request, _env: Env, _data: SomeSharedData) -> Result { let init_called = GLOBAL_STATE.load(Ordering::SeqCst); Response::ok(init_called.to_string()) } fn handle_test_panic(_req: Request, _env: Env, _data: SomeSharedData) -> Result { panic!("Intentional panic for testing context abort functionality"); } fn handle_test_abort(_req: Request, _env: Env, _data: SomeSharedData) -> Result { // Explicitly call abort() to verify raw aborts are also recoverable #[cfg(target_arch = "wasm32")] core::arch::wasm32::unreachable(); #[cfg(not(target_arch = "wasm32"))] std::process::abort(); } fn handle_test_oom(_req: Request, _env: Env, _data: SomeSharedData) -> Result { // Attempt to allocate excessive memory to trigger OOM let mut vecs = Vec::new(); loop { vecs.push(vec![0u8; 1024 * 1024 * 1024]); // 1GB chunks } }