feat: allow setting http response header in fullstack

This commit is contained in:
Jonathan Kelley committed 2025-10-15 01:20:16 -07:00
1 parent 0aa7351473
commit 20fa29ac6e
3 files changed
+38 -5

No files matched your search

+20 -1
View File
@@ -2,8 +2,8 @@ use crate::{HttpError, ServerFnError};
use axum_core::{extract::FromRequest, response::IntoResponse}; use axum_core::{extract::FromRequest, response::IntoResponse};
use dioxus_core::{try_consume_context, CapturedError}; use dioxus_core::{try_consume_context, CapturedError};
use dioxus_signals::{ReadableExt, Signal, WritableExt}; use dioxus_signals::{ReadableExt, Signal, WritableExt};
use http::request::Parts;
use http::StatusCode; use http::StatusCode;
use http::{request::Parts, HeaderMap};
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
/// The context provided by dioxus fullstack for server-side rendering. /// The context provided by dioxus fullstack for server-side rendering.
@@ -13,6 +13,7 @@ use std::{cell::RefCell, rc::Rc};
pub struct FullstackContext { pub struct FullstackContext {
current_status: Signal<StreamingStatus>, current_status: Signal<StreamingStatus>,
request_headers: Rc<RefCell<http::request::Parts>>, request_headers: Rc<RefCell<http::request::Parts>>,
response_headers: Rc<RefCell<Option<HeaderMap>>>,
route_http_status: Signal<HttpError>, route_http_status: Signal<HttpError>,
} }
@@ -34,6 +35,7 @@ impl FullstackContext {
status: http::StatusCode::OK, status: http::StatusCode::OK,
message: None, message: None,
}), }),
response_headers: Rc::new(RefCell::new(Some(HeaderMap::new()))),
} }
} }
@@ -99,6 +101,23 @@ impl FullstackContext {
self.route_http_status.set(status); self.route_http_status.set(status);
} }
/// Add a header to the response. This will be sent to the client when the response is committed.
pub fn add_response_header(
&self,
key: impl Into<http::header::HeaderName>,
value: impl Into<http::header::HeaderValue>,
) {
if let Some(headers) = self.response_headers.borrow_mut().as_mut() {
headers.insert(key.into(), value.into());
}
}
/// Take the response headers out of the context. This will leave the context without any headers,
/// so it should only be called once when the response is being committed.
pub fn take_response_headers(&self) -> Option<HeaderMap> {
self.response_headers.borrow_mut().take()
}
/// Set the current HTTP status for the route. This will be used when committing the response /// Set the current HTTP status for the route. This will be used when committing the response
/// to the client. /// to the client.
pub fn commit_http_status(status: StatusCode, message: Option<String>) { pub fn commit_http_status(status: StatusCode, message: Option<String>) {
+9 -1
View File
@@ -304,15 +304,23 @@ impl RenderHandleState {
.await; .await;
match response { match response {
Ok((status, freshness, rx)) => { Ok((status, headers, freshness, rx)) => {
let mut response = Response::builder() let mut response = Response::builder()
.status(status.status) .status(status.status)
.header(CONTENT_TYPE, "text/html; charset=utf-8") .header(CONTENT_TYPE, "text/html; charset=utf-8")
.body(Body::from_stream(rx)) .body(Body::from_stream(rx))
.unwrap(); .unwrap();
// Write our freshness header
freshness.write(response.headers_mut()); freshness.write(response.headers_mut());
// write the other headers set by the user
for (key, value) in headers.into_iter() {
if let Some(key) = key {
response.headers_mut().insert(key, value);
}
}
response response
} }
+9 -3
View File
@@ -17,7 +17,7 @@ use dioxus_router::ParseRouteError;
use dioxus_ssr::Renderer; use dioxus_ssr::Renderer;
use futures_channel::mpsc::Sender; use futures_channel::mpsc::Sender;
use futures_util::{Stream, StreamExt}; use futures_util::{Stream, StreamExt};
use http::{request::Parts, StatusCode}; use http::{request::Parts, HeaderMap, StatusCode};
use std::{ use std::{
collections::HashMap, collections::HashMap,
fmt::Write, fmt::Write,
@@ -103,6 +103,7 @@ impl SsrRendererPool {
) -> Result< ) -> Result<
( (
HttpError, HttpError,
HeaderMap,
RenderFreshness, RenderFreshness,
impl Stream<Item = Result<String, IncrementalRendererError>>, impl Stream<Item = Result<String, IncrementalRendererError>>,
), ),
@@ -154,6 +155,7 @@ impl SsrRendererPool {
status: StatusCode::OK, status: StatusCode::OK,
message: None, message: None,
}, },
HeaderMap::new(),
freshness, freshness,
ReceiverWithDrop { ReceiverWithDrop {
receiver: rx, receiver: rx,
@@ -292,9 +294,12 @@ impl SsrRendererPool {
// Check the FullstackContext in case the user set the statuscode manually or via a layout. // Check the FullstackContext in case the user set the statuscode manually or via a layout.
let http_status = streaming_context.current_http_status(); let http_status = streaming_context.current_http_status();
let headers = streaming_context
.take_response_headers()
.unwrap_or_default();
// Now that we handled any errors from rendering, we can send the initial ok result // Now that we handled any errors from rendering, we can send the initial ok result
_ = initial_result_tx.send(Ok(http_status)); _ = initial_result_tx.send(Ok((http_status, headers)));
// Wait long enough to assemble the `<head>` of the document before starting to stream // Wait long enough to assemble the `<head>` of the document before starting to stream
let mut pre_body = String::new(); let mut pre_body = String::new();
@@ -418,12 +423,13 @@ impl SsrRendererPool {
let join_handle = Self::spawn_platform(create_render_future); let join_handle = Self::spawn_platform(create_render_future);
// Wait for the initial result which determines the status code // Wait for the initial result which determines the status code
let status = initial_result_rx let (status, headers) = initial_result_rx
.await .await
.map_err(|err| SSRError::Incremental(IncrementalRendererError::Other(err.into())))??; .map_err(|err| SSRError::Incremental(IncrementalRendererError::Other(err.into())))??;
Ok(( Ok((
status, status,
headers,
RenderFreshness::now(None), RenderFreshness::now(None),
ReceiverWithDrop { ReceiverWithDrop {
receiver: rx, receiver: rx,