diff --git a/examples/hackernews-axum/Cargo.toml b/examples/hackernews-axum/Cargo.toml index 7e294319c..173e5e46b 100644 --- a/examples/hackernews-axum/Cargo.toml +++ b/examples/hackernews-axum/Cargo.toml @@ -27,13 +27,14 @@ axum = { version = "0.5.17", optional=true } tower = { version = "0.4.13", optional=true } tower-http = { version = "0.3.4", features = ["fs"], optional = true } tokio = { version = "1.0", features = ["full"], optional = true } +http = {version = "0.2.8", optional = true} [features] default = ["csr"] csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] -ssr = ["dep:axum", "dep:tower", "dep:tower-http", "dep:tokio", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr"] +ssr = ["dep:axum", "dep:tower", "dep:tower-http", "dep:tokio", "dep:http", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr"] [package.metadata.cargo-all-features] -denylist = ["axum", "tower", "tower-http", "tokio"] +denylist = ["axum", "tower", "tower-http", "tokio", "http"] skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]] diff --git a/examples/hackernews-axum/src/handlers.rs b/examples/hackernews-axum/src/handlers.rs index 2caa1bfe5..0e0f013ab 100644 --- a/examples/hackernews-axum/src/handlers.rs +++ b/examples/hackernews-axum/src/handlers.rs @@ -1,42 +1,89 @@ use cfg_if::cfg_if; cfg_if! { - if #[cfg(feature = "ssr")] { -use axum::{ - body::{boxed, Body, BoxBody}, - http::{Request, Response, StatusCode, Uri}, -}; -use tower::ServiceExt; -use tower_http::services::ServeDir; +if #[cfg(feature = "ssr")] { + use axum::{ + body::{boxed, Body, Bytes, BoxBody, StreamBody}, + http::{Request, Response, StatusCode, Uri}, + }; + use tower::ServiceExt; + use tower_http::services::ServeDir; + use std::io; + use futures::{Stream, StreamExt}; + use leptos::*; + use leptos_router::*; + use leptos_meta::*; + use crate::*; -pub async fn file_handler(uri: Uri) -> Result, (StatusCode, String)> { - let res = get_static_file(uri.clone()).await?; - println!("{:?}", res); + pub async fn file_handler(uri: Uri) -> Result, (StatusCode, String)> { + let res = get_static_file(uri.clone()).await?; + println!("{:?}", res); - if res.status() == StatusCode::NOT_FOUND { - // try with `.html` - // TODO: handle if the Uri has query parameters - match format!("{}.html", uri).parse() { - Ok(uri_html) => get_static_file(uri_html).await, - Err(_) => Err((StatusCode::INTERNAL_SERVER_ERROR, "Invalid URI".to_string())), + if res.status() == StatusCode::NOT_FOUND { + // try with `.html` + // TODO: handle if the Uri has query parameters + match format!("{}.html", uri).parse() { + Ok(uri_html) => get_static_file(uri_html).await, + Err(_) => Err((StatusCode::INTERNAL_SERVER_ERROR, "Invalid URI".to_string())), + } + } else { + Ok(res) } - } else { - Ok(res) } -} -async fn get_static_file(uri: Uri) -> Result, (StatusCode, String)> { - let req = Request::builder().uri(uri).body(Body::empty()).unwrap(); + async fn get_static_file(uri: Uri) -> Result, (StatusCode, String)> { + let req = Request::builder().uri(uri).body(Body::empty()).unwrap(); - // `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot` - // When run normally, the root should be the crate root - match ServeDir::new("./pkg").oneshot(req).await { - Ok(res) => Ok(res.map(boxed)), - Err(err) => Err(( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Something went wrong: {}", err), - )), + // `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot` + // When run normally, the root should be the crate root + match ServeDir::new("./pkg").oneshot(req).await { + Ok(res) => Ok(res.map(boxed)), + Err(err) => Err(( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Something went wrong: {}", err), + )), + } + } + + // match every path — our router will handle actual dispatch, except for the static css files + async fn render_app(request: Request) -> StreamBody>> { + + // Need to get the path and query string of the Request + let path = req.path(); + let query = req.query_string(); + + let path = if query.is_empty() { + "http://leptos".to_string() + path + } else { + "http://leptos".to_string() + path + "?" + query + }; + + let app = move |cx| { + let integration = ServerIntegration { path: path.clone() }; + provide_context(cx, RouterIntegrationContext::new(integration)); + + view! { cx, } + }; + + let head = r#" + + + + + "#; + let tail = ""; + + let stream = futures::stream::once(async { head.to_string() }) + .chain(render_to_stream(move |cx| { + let app = app(cx); + let head = use_context::(cx) + .map(|meta| meta.dehydrate()) + .unwrap_or_default(); + format!("{head}{app}") + })) + .chain(futures::stream::once(async { tail.to_string() })) + .map(|html| Ok(Bytes::from(html)) as io::Result); + StreamBody::new(stream) + } } -} } -} diff --git a/examples/hackernews-axum/src/main.rs b/examples/hackernews-axum/src/main.rs index fa96a506a..1debd7377 100644 --- a/examples/hackernews-axum/src/main.rs +++ b/examples/hackernews-axum/src/main.rs @@ -3,131 +3,92 @@ use leptos::*; // boilerplate to run in different modes cfg_if! { - if #[cfg(feature = "ssr")] { - // use actix_files::{Files, NamedFile}; - // use actix_web::*; - use axum::{ - routing::{get}, - Router, - body::Bytes, - response::{IntoResponse, Response}, - }; - use std::net::SocketAddr; - use futures::StreamExt; - use leptos_meta::*; - use leptos_router::*; +if #[cfg(feature = "ssr")] { + // use actix_files::{Files, NamedFile}; + // use actix_web::*; + use axum::{ + routing::{get}, + Router, + body::Bytes, + response::{IntoResponse, Response}, + }; + use std::net::SocketAddr; + use futures::StreamExt; + use leptos_meta::*; + use leptos_router::*; + use leptos_hackernews_axum::*; + use crate::handlers::file_handler; + use std::io; + + // #[get("/static/style.css")] + // async fn css() -> impl Responder { + // NamedFile::open_async("./style.css").await + // } + + #[tokio::main] + async fn main() -> std::io::Result<()> { + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + + log::debug!("serving at {addr}"); + + simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging"); + + // uncomment these lines (and .bind_openssl() below) to enable HTTPS, which is sometimes + // necessary for proper HTTP/2 streaming + + // load TLS keys + // to create a self-signed temporary cert for testing: + // `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'` + // let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + // builder + // .set_private_key_file("key.pem", SslFiletype::PEM) + // .unwrap(); + // builder.set_certificate_chain_file("cert.pem").unwrap(); + + // HttpServer::new(|| { + // App::new() + // .service(css) + // .service( + // web::scope("/pkg") + // .service(Files::new("", "./dist")) + // .wrap(middleware::Compress::default()), + // ) + // .service(render_app) + // }) + // .bind(("127.0.0.1", 8080))? + // // replace .bind with .bind_openssl to use HTTPS + // //.bind_openssl(&format!("{}:{}", host, port), builder)? + // .run() + // .await + + // build our application with a route + let app = Router::new() + // `GET /` goes to `root` + .nest("/pkg", get(file_handler)) + .fallback(fallback.into_service()); + + // run our app with hyper + // `axum::Server` is a re-export of `hyper::Server` + tracing::debug!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); + + } +} + + // client-only stuff for Trunk + else { use leptos_hackernews_axum::*; - use crate::handlers::file_handler; - use std::io; - - #[get("/static/style.css")] - async fn css() -> impl Responder { - NamedFile::open_async("./style.css").await - } - - // match every path — our router will handle actual dispatch - #[get("{tail:.*}")] - async fn render_app(req: Request) -> StreamBody> { - let path = req.path(); - - let query = req.query_string(); - let path = if query.is_empty() { - "http://leptos".to_string() + path - } else { - "http://leptos".to_string() + path + "?" + query - }; - - let app = move |cx| { - let integration = ServerIntegration { path: path.clone() }; - provide_context(cx, RouterIntegrationContext::new(integration)); + pub fn main() { + console_error_panic_hook::set_once(); + _ = console_log::init_with_level(log::Level::Debug); + console_error_panic_hook::set_once(); + mount_to_body(|cx| { view! { cx, } - }; - - let head = r#" - - - - - "#; - let tail = ""; - - let stream = futures::stream::once(async { head.to_string() }) - .chain(render_to_stream(move |cx| { - let app = app(cx); - let head = use_context::(cx) - .map(|meta| meta.dehydrate()) - .unwrap_or_default(); - format!("{head}{app}") - })) - .chain(futures::stream::once(async { tail.to_string() })) - .map(|html| Ok(Bytes::from(html)) as Result); - StreamBody::new(stream) - } - - #[tokio::main] - async fn main() -> std::io::Result<()> { - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); - - log::debug!("serving at {addr}"); - - simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging"); - - // uncomment these lines (and .bind_openssl() below) to enable HTTPS, which is sometimes - // necessary for proper HTTP/2 streaming - - // load TLS keys - // to create a self-signed temporary cert for testing: - // `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'` - // let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - // builder - // .set_private_key_file("key.pem", SslFiletype::PEM) - // .unwrap(); - // builder.set_certificate_chain_file("cert.pem").unwrap(); - - // HttpServer::new(|| { - // App::new() - // .service(css) - // .service( - // web::scope("/pkg") - // .service(Files::new("", "./dist")) - // .wrap(middleware::Compress::default()), - // ) - // .service(render_app) - // }) - // .bind(("127.0.0.1", 8080))? - // // replace .bind with .bind_openssl to use HTTPS - // //.bind_openssl(&format!("{}:{}", host, port), builder)? - // .run() - // .await - - // build our application with a route - let app = Router::new() - // `GET /` goes to `root` - .nest("/pkg", get(file_handler)); - - // run our app with hyper - // `axum::Server` is a re-export of `hyper::Server` - tracing::debug!("listening on {}", addr); - axum::Server::bind(&addr) - .serve(app.into_make_service()) - .await - .unwrap(); - - } - - // client-only stuff for Trunk - else { - use leptos_hackernews::*; - - pub fn main() { - console_error_panic_hook::set_once(); - _ = console_log::init_with_level(log::Level::Debug); - console_error_panic_hook::set_once(); - mount_to_body(|cx| { - view! { cx, } - }); - } + }); } } }