render.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. //! A shared pool of renderers for efficient server side rendering.
  2. use std::sync::Arc;
  3. use crate::server_context::SERVER_CONTEXT;
  4. use dioxus::prelude::VirtualDom;
  5. use dioxus_ssr::{
  6. incremental::{IncrementalRendererConfig, RenderFreshness, WrapBody},
  7. Renderer,
  8. };
  9. use serde::Serialize;
  10. use std::sync::RwLock;
  11. use tokio::task::spawn_blocking;
  12. use crate::prelude::*;
  13. use dioxus::prelude::*;
  14. enum SsrRendererPool {
  15. Renderer(RwLock<Vec<Renderer>>),
  16. Incremental(RwLock<Vec<dioxus_ssr::incremental::IncrementalRenderer>>),
  17. }
  18. impl SsrRendererPool {
  19. async fn render_to<P: Clone + Serialize + Send + Sync + 'static>(
  20. &self,
  21. cfg: &ServeConfig<P>,
  22. route: String,
  23. component: Component<P>,
  24. props: P,
  25. server_context: &DioxusServerContext,
  26. ) -> Result<(RenderFreshness, String), dioxus_ssr::incremental::IncrementalRendererError> {
  27. let wrapper = FullstackRenderer {
  28. cfg: cfg.clone(),
  29. server_context: server_context.clone(),
  30. };
  31. match self {
  32. Self::Renderer(pool) => {
  33. let server_context = Box::new(server_context.clone());
  34. let mut renderer = pool.write().unwrap().pop().unwrap_or_else(pre_renderer);
  35. let (tx, rx) = tokio::sync::oneshot::channel();
  36. spawn_blocking(move || {
  37. tokio::runtime::Runtime::new()
  38. .expect("couldn't spawn runtime")
  39. .block_on(async move {
  40. let mut vdom = VirtualDom::new_with_props(component, props);
  41. let mut to = WriteBuffer { buffer: Vec::new() };
  42. // before polling the future, we need to set the context
  43. let prev_context =
  44. SERVER_CONTEXT.with(|ctx| ctx.replace(server_context));
  45. // poll the future, which may call server_context()
  46. log::info!("Rebuilding vdom");
  47. let _ = vdom.rebuild();
  48. vdom.wait_for_suspense().await;
  49. log::info!("Suspense resolved");
  50. // after polling the future, we need to restore the context
  51. SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
  52. if let Err(err) = wrapper.render_before_body(&mut *to) {
  53. let _ = tx.send(Err(err));
  54. return;
  55. }
  56. if let Err(err) = renderer.render_to(&mut to, &vdom) {
  57. let _ = tx.send(Err(
  58. dioxus_router::prelude::IncrementalRendererError::RenderError(
  59. err,
  60. ),
  61. ));
  62. return;
  63. }
  64. if let Err(err) = wrapper.render_after_body(&mut *to) {
  65. let _ = tx.send(Err(err));
  66. return;
  67. }
  68. match String::from_utf8(to.buffer) {
  69. Ok(html) => {
  70. let _ =
  71. tx.send(Ok((renderer, RenderFreshness::now(None), html)));
  72. }
  73. Err(err) => {
  74. dioxus_ssr::incremental::IncrementalRendererError::Other(
  75. Box::new(err),
  76. );
  77. }
  78. }
  79. });
  80. });
  81. let (renderer, freshness, html) = rx.await.unwrap()?;
  82. pool.write().unwrap().push(renderer);
  83. Ok((freshness, html))
  84. }
  85. Self::Incremental(pool) => {
  86. let mut renderer =
  87. pool.write().unwrap().pop().unwrap_or_else(|| {
  88. incremental_pre_renderer(cfg.incremental.as_ref().unwrap())
  89. });
  90. let (tx, rx) = tokio::sync::oneshot::channel();
  91. let server_context = server_context.clone();
  92. spawn_blocking(move || {
  93. tokio::runtime::Runtime::new()
  94. .expect("couldn't spawn runtime")
  95. .block_on(async move {
  96. let mut to = WriteBuffer { buffer: Vec::new() };
  97. match renderer
  98. .render(
  99. route,
  100. component,
  101. props,
  102. &mut *to,
  103. |vdom| {
  104. Box::pin(async move {
  105. // before polling the future, we need to set the context
  106. let prev_context = SERVER_CONTEXT
  107. .with(|ctx| ctx.replace(Box::new(server_context)));
  108. // poll the future, which may call server_context()
  109. log::info!("Rebuilding vdom");
  110. let _ = vdom.rebuild();
  111. vdom.wait_for_suspense().await;
  112. log::info!("Suspense resolved");
  113. // after polling the future, we need to restore the context
  114. SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
  115. })
  116. },
  117. &wrapper,
  118. )
  119. .await
  120. {
  121. Ok(freshness) => {
  122. match String::from_utf8(to.buffer).map_err(|err| {
  123. dioxus_ssr::incremental::IncrementalRendererError::Other(
  124. Box::new(err),
  125. )
  126. }) {
  127. Ok(html) => {
  128. let _ = tx.send(Ok((freshness, html)));
  129. }
  130. Err(err) => {
  131. let _ = tx.send(Err(err));
  132. }
  133. }
  134. }
  135. Err(err) => {
  136. let _ = tx.send(Err(err));
  137. }
  138. }
  139. })
  140. });
  141. let (freshness, html) = rx.await.unwrap()?;
  142. Ok((freshness, html))
  143. }
  144. }
  145. }
  146. }
  147. /// State used in server side rendering. This utilizes a pool of [`dioxus_ssr::Renderer`]s to cache static templates between renders.
  148. #[derive(Clone)]
  149. pub struct SSRState {
  150. // We keep a pool of renderers to avoid re-creating them on every request. They are boxed to make them very cheap to move
  151. renderers: Arc<SsrRendererPool>,
  152. }
  153. impl SSRState {
  154. pub(crate) fn new<P: Clone>(cfg: &ServeConfig<P>) -> Self {
  155. if cfg.incremental.is_some() {
  156. return Self {
  157. renderers: Arc::new(SsrRendererPool::Incremental(RwLock::new(vec![
  158. incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
  159. incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
  160. incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
  161. incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
  162. ]))),
  163. };
  164. }
  165. Self {
  166. renderers: Arc::new(SsrRendererPool::Renderer(RwLock::new(vec![
  167. pre_renderer(),
  168. pre_renderer(),
  169. pre_renderer(),
  170. pre_renderer(),
  171. ]))),
  172. }
  173. }
  174. /// Render the application to HTML.
  175. pub fn render<'a, P: 'static + Clone + serde::Serialize + Send + Sync>(
  176. &'a self,
  177. route: String,
  178. cfg: &'a ServeConfig<P>,
  179. server_context: &'a DioxusServerContext,
  180. ) -> impl std::future::Future<
  181. Output = Result<RenderResponse, dioxus_ssr::incremental::IncrementalRendererError>,
  182. > + Send
  183. + 'a {
  184. async move {
  185. let ServeConfig { app, props, .. } = cfg;
  186. let (freshness, html) = self
  187. .renderers
  188. .render_to(cfg, route, *app, props.clone(), server_context)
  189. .await?;
  190. Ok(RenderResponse { html, freshness })
  191. }
  192. }
  193. }
  194. struct FullstackRenderer<P: Clone + Send + Sync + 'static> {
  195. cfg: ServeConfig<P>,
  196. server_context: DioxusServerContext,
  197. }
  198. impl<P: Clone + Serialize + Send + Sync + 'static> dioxus_ssr::incremental::WrapBody
  199. for FullstackRenderer<P>
  200. {
  201. fn render_before_body<R: std::io::Write>(
  202. &self,
  203. to: &mut R,
  204. ) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
  205. let ServeConfig { index, .. } = &self.cfg;
  206. to.write_all(index.pre_main.as_bytes())?;
  207. Ok(())
  208. }
  209. fn render_after_body<R: std::io::Write>(
  210. &self,
  211. to: &mut R,
  212. ) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
  213. // serialize the props
  214. crate::html_storage::serialize::encode_props_in_element(&self.cfg.props, to)?;
  215. // serialize the server state
  216. crate::html_storage::serialize::encode_in_element(
  217. &*self.server_context.html_data().map_err(|_| {
  218. dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new({
  219. #[derive(Debug)]
  220. struct HTMLDataReadError;
  221. impl std::fmt::Display for HTMLDataReadError {
  222. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  223. f.write_str(
  224. "Failed to read the server data to serialize it into the HTML",
  225. )
  226. }
  227. }
  228. impl std::error::Error for HTMLDataReadError {}
  229. HTMLDataReadError
  230. }))
  231. })?,
  232. to,
  233. )?;
  234. #[cfg(all(debug_assertions, feature = "hot-reload"))]
  235. {
  236. // In debug mode, we need to add a script to the page that will reload the page if the websocket disconnects to make full recompile hot reloads work
  237. let disconnect_js = r#"(function () {
  238. const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  239. const url = protocol + '//' + window.location.host + '/_dioxus/disconnect';
  240. const poll_interval = 1000;
  241. const reload_upon_connect = () => {
  242. console.log('Disconnected from server. Attempting to reconnect...');
  243. window.setTimeout(
  244. () => {
  245. // Try to reconnect to the websocket
  246. const ws = new WebSocket(url);
  247. ws.onopen = () => {
  248. // If we reconnect, reload the page
  249. window.location.reload();
  250. }
  251. // Otherwise, try again in a second
  252. reload_upon_connect();
  253. },
  254. poll_interval);
  255. };
  256. // on initial page load connect to the disconnect ws
  257. const ws = new WebSocket(url);
  258. // if we disconnect, start polling
  259. ws.onclose = reload_upon_connect;
  260. })()"#;
  261. to.write_all(r#"<script>"#.as_bytes())?;
  262. to.write_all(disconnect_js.as_bytes())?;
  263. to.write_all(r#"</script>"#.as_bytes())?;
  264. }
  265. let ServeConfig { index, .. } = &self.cfg;
  266. to.write_all(index.post_main.as_bytes())?;
  267. Ok(())
  268. }
  269. }
  270. /// A rendered response from the server.
  271. #[derive(Debug)]
  272. pub struct RenderResponse {
  273. pub(crate) html: String,
  274. pub(crate) freshness: RenderFreshness,
  275. }
  276. impl RenderResponse {
  277. /// Get the rendered HTML.
  278. pub fn html(&self) -> &str {
  279. &self.html
  280. }
  281. /// Get the freshness of the rendered HTML.
  282. pub fn freshness(&self) -> RenderFreshness {
  283. self.freshness
  284. }
  285. }
  286. fn pre_renderer() -> Renderer {
  287. let mut renderer = Renderer::default();
  288. renderer.pre_render = true;
  289. renderer.into()
  290. }
  291. fn incremental_pre_renderer(
  292. cfg: &IncrementalRendererConfig,
  293. ) -> dioxus_ssr::incremental::IncrementalRenderer {
  294. let mut renderer = cfg.clone().build();
  295. renderer.renderer_mut().pre_render = true;
  296. renderer
  297. }
  298. #[cfg(all(feature = "ssr", feature = "router"))]
  299. /// Pre-caches all static routes
  300. pub async fn pre_cache_static_routes_with_props<Rt>(
  301. cfg: &crate::prelude::ServeConfig<crate::router::FullstackRouterConfig<Rt>>,
  302. ) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError>
  303. where
  304. Rt: dioxus_router::prelude::Routable + Send + Sync + Serialize,
  305. <Rt as std::str::FromStr>::Err: std::fmt::Display,
  306. {
  307. let wrapper = FullstackRenderer {
  308. cfg: cfg.clone(),
  309. server_context: Default::default(),
  310. };
  311. let mut renderer = incremental_pre_renderer(
  312. cfg.incremental
  313. .as_ref()
  314. .expect("incremental renderer config must be set to pre-cache static routes"),
  315. );
  316. dioxus_router::incremental::pre_cache_static_routes::<Rt, _>(&mut renderer, &wrapper).await
  317. }
  318. struct WriteBuffer {
  319. buffer: Vec<u8>,
  320. }
  321. impl std::fmt::Write for WriteBuffer {
  322. fn write_str(&mut self, s: &str) -> std::fmt::Result {
  323. self.buffer.extend_from_slice(s.as_bytes());
  324. Ok(())
  325. }
  326. }
  327. impl std::ops::Deref for WriteBuffer {
  328. type Target = Vec<u8>;
  329. fn deref(&self) -> &Self::Target {
  330. &self.buffer
  331. }
  332. }
  333. impl std::ops::DerefMut for WriteBuffer {
  334. fn deref_mut(&mut self) -> &mut Self::Target {
  335. &mut self.buffer
  336. }
  337. }