redirects, middleware, Transport, set/capture error status (#4751)

* wip: redirect

* redirect example

* fix file upload not in a form

* wip

* deny missing cfgs

* wip: add router fallback but it's not working

* fix suspense bug after rebuilds that queued work

* more cleanup

* add test for error codes

* add helpful comment

* bring back middleware annotation

* fix cfg

* add server feature

* fix doctest

* just disable liveview test for now

* fix liveview

* dont send init

* cache more playwright!

* add more pkgs to cache
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-10-10 21:43:57 -07:00
1 parent 8a81475ba8
commit 9243ef4128
35 files changed
+935 -800

No files matched your search

+94 -24
View File
@@ -1,42 +1,39 @@
use crate::ServerFnError;
use crate::{HttpError, ServerFnError};
use axum_core::{extract::FromRequest, response::IntoResponse};
use dioxus_core::try_consume_context;
use dioxus_core::{try_consume_context, CapturedError};
use dioxus_signals::{ReadableExt, Signal, WritableExt};
use http::request::Parts;
use http::StatusCode;
use std::{cell::RefCell, rc::Rc};
/// The status of the streaming response
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum StreamingStatus {
/// The initial chunk is still being rendered. The http response parts can still be modified at this point.
RenderingInitialChunk,
/// The initial chunk has been committed and the response is now streaming. The http response parts
/// have already been sent to the client and can no longer be modified.
InitialChunkCommitted,
}
/// The context dioxus fullstack provides for the status of streaming responses on the server
/// The context provided by dioxus fullstack for server-side rendering.
///
/// This context will only be set on the server during a streaming response.
#[derive(Clone, Debug)]
pub struct StreamingContext {
pub struct FullstackContext {
current_status: Signal<StreamingStatus>,
request_headers: Rc<RefCell<http::request::Parts>>,
route_http_status: Signal<HttpError>,
}
impl PartialEq for StreamingContext {
impl PartialEq for FullstackContext {
fn eq(&self, other: &Self) -> bool {
self.current_status == other.current_status
&& Rc::ptr_eq(&self.request_headers, &other.request_headers)
}
}
impl StreamingContext {
impl FullstackContext {
/// Create a new streaming context. You should not need to call this directly. Dioxus fullstack will
/// provide this context for you.
pub fn new(parts: Parts) -> Self {
Self {
current_status: Signal::new(StreamingStatus::RenderingInitialChunk),
request_headers: Rc::new(RefCell::new(parts)),
route_http_status: Signal::new(HttpError {
status: http::StatusCode::OK,
message: None,
}),
}
}
@@ -52,7 +49,7 @@ impl StreamingContext {
/// Get the current status of the streaming response. This method is reactive and will cause
/// the current reactive context to rerun when the status changes.
pub fn current_status(&self) -> StreamingStatus {
pub fn streaming_state(&self) -> StreamingStatus {
*self.current_status.read()
}
@@ -65,7 +62,7 @@ impl StreamingContext {
/// since it's assumed that rendering the app is done under a `GET` request.
pub async fn extract<T: FromRequest<(), M>, M>() -> Result<T, ServerFnError> {
let this = Self::current()
.ok_or_else(|| ServerFnError::new("No StreamingContext found".to_string()))?;
.ok_or_else(|| ServerFnError::new("No FullstackContext found".to_string()))?;
let parts = this.request_headers.borrow_mut().clone();
let request =
@@ -79,18 +76,66 @@ impl StreamingContext {
}
}
/// Get the current `StreamingContext` if it exists. This will return `None` if called on the client
/// Get the current `FullstackContext` if it exists. This will return `None` if called on the client
/// or outside of a streaming response on the server.
pub fn current() -> Option<Self> {
if let Some(rt) = dioxus_core::Runtime::try_current() {
let id = rt.try_current_scope_id()?;
if let Some(ctx) = rt.consume_context::<StreamingContext>(id) {
if let Some(ctx) = rt.consume_context::<FullstackContext>(id) {
return Some(ctx);
}
}
None
}
/// Get the current HTTP status for the route. This will default to 200 OK, but can be modified
/// by calling `FullstackContext::commit_error_status` with an error.
pub fn current_http_status(&self) -> HttpError {
self.route_http_status.read().clone()
}
pub fn set_current_http_status(&mut self, status: HttpError) {
self.route_http_status.set(status);
}
/// Set the current HTTP status for the route. This will be used when committing the response
/// to the client.
pub fn commit_http_status(status: StatusCode, message: Option<String>) {
if let Some(mut ctx) = Self::current() {
ctx.set_current_http_status(HttpError { status, message });
}
}
/// Commit the CapturedError as the current HTTP status for the route.
/// This will attempt to downcast the error to known types and set the appropriate
/// status code. If the error type is unknown, it will default to
/// `StatusCode::INTERNAL_SERVER_ERROR`.
pub fn commit_error_status(error: impl Into<CapturedError>) -> HttpError {
let error = error.into();
let status = status_code_from_error(&error);
let http_error = HttpError {
status,
message: Some(error.to_string()),
};
if let Some(mut ctx) = Self::current() {
ctx.set_current_http_status(http_error.clone());
}
http_error
}
}
/// The status of the streaming response
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum StreamingStatus {
/// The initial chunk is still being rendered. The http response parts can still be modified at this point.
RenderingInitialChunk,
/// The initial chunk has been committed and the response is now streaming. The http response parts
/// have already been sent to the client and can no longer be modified.
InitialChunkCommitted,
}
/// Commit the initial chunk of the response. This will be called automatically if you are using the
@@ -113,7 +158,7 @@ impl StreamingContext {
/// ```
pub fn commit_initial_chunk() {
crate::history::finalize_route();
if let Some(mut streaming) = try_consume_context::<StreamingContext>() {
if let Some(mut streaming) = try_consume_context::<FullstackContext>() {
streaming.commit_initial_chunk();
}
}
@@ -141,9 +186,34 @@ pub fn commit_initial_chunk() {
/// }
/// ```
pub fn current_status() -> StreamingStatus {
if let Some(streaming) = try_consume_context::<StreamingContext>() {
streaming.current_status()
if let Some(streaming) = try_consume_context::<FullstackContext>() {
streaming.streaming_state()
} else {
StreamingStatus::InitialChunkCommitted
}
}
/// Convert a `CapturedError` into an appropriate HTTP status code.
///
/// This will attempt to downcast the error to known types and return a corresponding status code.
/// If the error type is unknown, it will default to `StatusCode::INTERNAL_SERVER_ERROR`.
pub fn status_code_from_error(error: &CapturedError) -> StatusCode {
if let Some(err) = error.downcast_ref::<ServerFnError>() {
match err {
ServerFnError::ServerError {
code: Some(code), ..
} => return StatusCode::from_u16(*code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
_ => return StatusCode::INTERNAL_SERVER_ERROR,
}
}
if let Some(err) = error.downcast_ref::<StatusCode>() {
return *err;
}
if let Some(err) = error.downcast_ref::<HttpError>() {
return err.status;
}
StatusCode::INTERNAL_SERVER_ERROR
}