1mod pool;
2mod runtime;
3
4use pool::PoolHandle;
5
6use std::collections::HashMap;
7use std::path::PathBuf;
8use std::sync::Arc;
9use std::{fmt, io};
10
11use serde::{Deserialize, Serialize};
12
13use chuchi::header::{Mime, StatusCode};
14use chuchi::{ChuchiShared, Request, Resource, Response};
15
16use aho_corasick::AhoCorasick;
17
18#[derive(Debug)]
19#[non_exhaustive]
20pub enum Error {
21 Panicked,
22 Io(io::Error),
23 Other(String),
24}
25
26impl fmt::Display for Error {
27 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28 fmt::Debug::fmt(self, f)
29 }
30}
31
32impl std::error::Error for Error {}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36struct SsrRequest {
37 pub address: String,
39 pub method: String,
41 pub uri: String,
42 pub headers: HashMap<String, String>,
43 pub body: String,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
52struct SsrResponse {
53 pub status: u16,
55 pub fields: HashMap<String, String>,
58}
59
60#[derive(Clone, Resource)]
61pub struct JsServer {
62 pool: PoolHandle,
63 index: Arc<String>, }
65
66impl JsServer {
67 pub fn new<T: Serialize>(
74 base_dir: impl Into<PathBuf>,
75 index_html: impl Into<String>,
76 opts: T,
77 max_threads: usize,
78 ) -> Self {
79 let pool = PoolHandle::new(
80 base_dir.into(),
81 max_threads,
82 serde_json::to_value(opts).unwrap(),
83 );
84
85 Self {
87 pool,
88 index: Arc::new(index_html.into()),
89 }
90 }
91
92 pub async fn route_internally(&self, shared: ChuchiShared) {
97 self.pool.send_pit(shared).await;
98 }
99
100 pub async fn request(&self, req: &mut Request) -> Result<Response, Error> {
101 let body = req.take_body().into_string().await.map_err(Error::Io)?;
102
103 let header = &req.header;
104 let method = header.method.to_string().to_uppercase();
105
106 let headers = header.values.clone().into_inner();
107 let headers: HashMap<_, _> = headers
108 .iter()
109 .filter_map(|(key, val)| {
110 val.to_str().ok().map(|s| (key.to_string(), s.to_string()))
111 })
112 .collect();
113
114 let uri = if let Some(query) = header.uri.query() {
115 format!("{}?{}", header.uri.path(), query)
116 } else {
117 header.uri.path().to_string()
118 };
119
120 let ssr_request = SsrRequest {
121 address: header.address.to_string(),
122 method,
123 uri,
124 headers,
125 body,
126 };
127
128 let resp = self
129 .pool
130 .send_req(ssr_request)
131 .await
132 .ok_or(Error::Panicked)?;
133
134 let ac = AhoCorasick::new(
135 resp.fields.keys().map(|k| format!("<!--ssr-{k}-->")),
136 )
137 .expect("aho corasick limit exceeded");
138
139 let values: Vec<_> = resp.fields.values().collect();
140
141 let index = ac.replace_all(&self.index, &values);
142
143 let resp = Response::builder()
144 .status_code(
145 StatusCode::from_u16(resp.status)
146 .map_err(|e| Error::Other(e.to_string()))?,
147 )
148 .content_type(Mime::HTML)
149 .body(index)
150 .build();
151
152 Ok(resp)
153 }
154}