use actix_web::{http::header::HeaderMap, web::Bytes, *};
use futures::{StreamExt, executor};
use http::StatusCode;
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Default)]
pub struct ResponseParts {
pub headers: HeaderMap,
pub status: Option<StatusCode>,
}
#[derive(Debug, Clone, Default)]
pub struct ResponseOptions(pub Arc<RwLock<ResponseParts>>);
impl ResponseOptions {
pub async fn overwrite(&self, parts: ResponseParts) {
let mut writable = self.0.write().await;
*writable = parts
}
}
pub fn handle_server_fns() -> Route {
web::post().to(
|req: HttpRequest, params: web::Path<String>, body: web::Bytes| async move {
{
let path = params.into_inner();
let accept_header = req
.headers()
.get("Accept")
.and_then(|value| value.to_str().ok());
if let Some(server_fn) = server_fn_by_path(path.as_str()) {
let body: &[u8] = &body;
let runtime = create_runtime();
let (cx, disposer) = raw_scope_and_disposer(runtime);
let res_options = ResponseOptions::default();
provide_context(cx, req.clone());
provide_context(cx, res_options.clone());
match server_fn(cx, body).await {
Ok(serialized) => {
let res_options = use_context::<ResponseOptions>(cx).unwrap();
disposer.dispose();
runtime.dispose();
let mut res: HttpResponseBuilder;
let mut res_parts = res_options.0.write().await;
if accept_header == Some("application/json")
|| accept_header == Some("application/x-www-form-urlencoded")
|| accept_header == Some("application/cbor")
{
res = HttpResponse::Ok();
if let Some(status) = res_parts.status {
res.status(status);
}
}
else {
let referer = req
.headers()
.get("Referer")
.and_then(|value| value.to_str().ok())
.unwrap_or("/");
res = HttpResponse::SeeOther();
res.insert_header(("Location", referer))
.content_type("application/json");
};
let _count = res_parts
.headers
.drain()
.map(|(k, v)| {
if let Some(k) = k {
res.insert_header((k, v));
}
})
.count();
match serialized {
Payload::Binary(data) => {
res.content_type("application/cbor");
res.body(Bytes::from(data))
}
Payload::Url(data) => {
res.content_type("application/x-www-form-urlencoded");
res.body(data)
}
Payload::Json(data) => {
res.content_type("application/json");
res.body(data)
}
}
}
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
} else {
HttpResponse::BadRequest()
.body(format!("Could not find a server function at that route."))
}
}
},
)
}
pub fn render_app_to_stream(
options: RenderOptions,
app_fn: impl Fn(leptos::Scope) -> Element + Clone + 'static,
) -> Route {
web::get().to(move |req: HttpRequest| {
let options = options.clone();
let app_fn = app_fn.clone();
let res_options = ResponseOptions::default();
let res_options_default = res_options.clone();
async move {
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 = {
let app_fn = app_fn.clone();
move |cx| {
let integration = ServerIntegration { path: path.clone() };
provide_context(cx, RouterIntegrationContext::new(integration));
provide_context(cx, MetaContext::new());
println!("Setting Default options");
provide_context(cx, res_options_default.clone());
provide_context(cx, req.clone());
(app_fn)(cx)
}
};
let pkg_path = &options.pkg_path;
let socket_ip = &options.socket_address.ip().to_string();
let reload_port = options.reload_port;
let leptos_autoreload = match options.environment {
RustEnv::DEV => format!(
r#"
<script crossorigin="">(function () {{
var ws = new WebSocket('ws://{socket_ip}:{reload_port}/autoreload');
ws.onmessage = (ev) => {{
console.log(`Reload message: `);
if (ev.data === 'reload') window.location.reload();
}};
ws.onclose = () => console.warn('Autoreload stopped. Manual reload necessary.');
}})()
</script>
"#
),
RustEnv::PROD => "".to_string(),
};
let head = format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="modulepreload" href="{pkg_path}.js">
<link rel="preload" href="{pkg_path}_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
<script type="module">import init, {{ hydrate }} from '{pkg_path}.js'; init('{pkg_path}_bg.wasm').then(hydrate);</script>
{leptos_autoreload}
"#
);
let tail = "</body></html>";
let mut stream = Box::pin(futures::stream::once(async move { head.clone() })
.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(web::Bytes::from(html)) as Result<web::Bytes>));
let first_chunk = stream.next().await;
let second_chunk = stream.next().await;
let third_chunk = stream.next().await;
let res_options = res_options.0.read().await;
println!("Reading Options");
println!("Response Options: {:#?}", res_options);
let (status, mut headers) = (res_options.status.clone(), res_options.headers.clone());
let status = status.unwrap_or_default();
let complete_stream =
futures::stream::iter([first_chunk.unwrap(), second_chunk.unwrap(), third_chunk.unwrap()])
.chain(stream);
let mut res = HttpResponse::Ok().content_type("text/html").streaming(
complete_stream
);
for (key, value) in headers.drain(){
if let Some(key) = key{
res.headers_mut().append(key, value);
}
};
let res_status = res.status_mut();
*res_status = status;
res
}
})
}