Some more WIP improvements for the Axum example
This commit is contained in:
parent
3885816699
commit
19f89633ff
|
@ -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"]]
|
||||
|
|
|
@ -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<Response<BoxBody>, (StatusCode, String)> {
|
||||
let res = get_static_file(uri.clone()).await?;
|
||||
println!("{:?}", res);
|
||||
pub async fn file_handler(uri: Uri) -> Result<Response<BoxBody>, (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<Response<BoxBody>, (StatusCode, String)> {
|
||||
let req = Request::builder().uri(uri).body(Body::empty()).unwrap();
|
||||
async fn get_static_file(uri: Uri) -> Result<Response<BoxBody>, (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<Body>) -> StreamBody<impl Stream<Item = io::Result<Bytes>>> {
|
||||
|
||||
// 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, <App/> }
|
||||
};
|
||||
|
||||
let head = r#"<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<script type="module">import init, { main } from '/pkg/leptos_hackernews_axum.js'; init().then(main);</script>"#;
|
||||
let tail = "</body></html>";
|
||||
|
||||
let stream = futures::stream::once(async { head.to_string() })
|
||||
.chain(render_to_stream(move |cx| {
|
||||
let app = app(cx);
|
||||
let head = use_context::<MetaContext>(cx)
|
||||
.map(|meta| meta.dehydrate())
|
||||
.unwrap_or_default();
|
||||
format!("{head}</head><body>{app}")
|
||||
}))
|
||||
.chain(futures::stream::once(async { tail.to_string() }))
|
||||
.map(|html| Ok(Bytes::from(html)) as io::Result<Bytes>);
|
||||
StreamBody::new(stream)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<impl Stream<Item = io::Result<Bytes>> {
|
||||
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, <App/> }
|
||||
};
|
||||
|
||||
let head = r#"<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<script type="module">import init, { main } from '/pkg/leptos_hackernews_axum.js'; init().then(main);</script>"#;
|
||||
let tail = "</body></html>";
|
||||
|
||||
let stream = futures::stream::once(async { head.to_string() })
|
||||
.chain(render_to_stream(move |cx| {
|
||||
let app = app(cx);
|
||||
let head = use_context::<MetaContext>(cx)
|
||||
.map(|meta| meta.dehydrate())
|
||||
.unwrap_or_default();
|
||||
format!("{head}</head><body>{app}")
|
||||
}))
|
||||
.chain(futures::stream::once(async { tail.to_string() }))
|
||||
.map(|html| Ok(Bytes::from(html)) as Result<Bytes>);
|
||||
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, <App/> }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue