8000 Customizable fallback handler and HTML responses by RomanEmreis · Pull Request #75 · RomanEmreis/volga · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
8000

Customizable fallback handler and HTML responses #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "volga"
version = "0.5.1"
version = "0.5.2"
edition = "2021"
rust-version = "1.80.0"
authors = ["Roman Emreis <roman.emreis@outlook.com>"]
Expand Down Expand Up @@ -200,3 +200,8 @@ required-features = ["tracing"]
name = "global_error_handler"
path = "examples/global_error_handler.rs"
required-features = ["tracing", "problem-details"]

[[example]]
name = "global_404_handler"
path = "examples/global_404_handler.rs"
required-features = ["tracing"]
4 changes: 4 additions & 0 deletions benches/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ fn benchmark(c: &mut Criterion) {
app.map_get("/", || async { "Hello, World!" });
app.map_get("/err", || async { Error::new(ErrorKind::Other, "error") });
app.map_err(|err| async move { status!(500, err.to_string()) });
app.map_fallback(|| async { status!(404) });
_ = app.run().await;
});
});
Expand All @@ -68,6 +69,9 @@ fn benchmark(c: &mut Criterion) {
c.bench_function("err", |b| b.iter_custom(
|iters| rt.block_on(routing(iters, black_box("/err")))
));
c.bench_function("fallback", |b| b.iter_custom(
|iters| rt.block_on(routing(iters, black_box("/fall")))
));

#[cfg(feature = "di")]
{
Expand Down
26 changes: 26 additions & 0 deletions examples/global_404_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use volga::{App, http::{Method, Uri}, html};
use tracing_subscriber::prelude::*;

#[tokio::main]
async fn main() -> std::io::Result<()> {
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.init();

let mut app = App::new();

app.map_get("/", || async {});

// Enabling global 404 handler
app.map_fallback(|uri: Uri, method: Method| async move {
tracing::debug!("route not found {} {}", method, uri);
html!(r#"
<!doctype html>
<html>
<head>Not Found!</head>
</html>
"#)
});

app.run().await
}
39 changes: 28 additions & 11 deletions src/app/pipeline.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
use std::sync::Arc;
use hyper::{Request, body::Incoming};

use crate::{
error::{
ErrorFunc,
ErrorFunc,
FallbackFunc,
fallback::{PipelineFallbackHandler, default_fallback_handler},
handler::{PipelineErrorHandler, WeakErrorHandler, default_error_handler}
},
http::endpoints::Endpoints
http::endpoints::Endpoints,
HttpResult
};

#[cfg(feature = "middleware")]
use crate::{
middleware::{Middlewares, HttpContext, Next},
HttpResult
};
use crate::{middleware::{Middlewares, HttpContext, Next}};

pub(crate) struct PipelineBuilder {
#[cfg(feature = "middleware")]
middlewares: Middlewares,
endpoints: Endpoints,
error_handler: PipelineErrorHandler
error_handler: PipelineErrorHandler,
fallback_handler: PipelineFallbackHandler
}

pub(crate) struct Pipeline {
#[cfg(feature = "middleware")]
start: Option<Next>,
endpoints: Endpoints,
error_handler: PipelineErrorHandler
error_handler: PipelineErrorHandler,
fallback_handler: PipelineFallbackHandler
}

impl PipelineBuilder {
Expand All @@ -33,15 +37,17 @@ impl PipelineBuilder {
Self {
middlewares: Middlewares::new(),
endpoints: Endpoints::new(),
error_handler: ErrorFunc(default_error_handler).into()
error_handler: ErrorFunc(default_error_handler).into(),
fallback_handler: FallbackFunc::new(default_fallback_handler).into()
}
}

#[cfg(not(feature = "middleware"))]
pub(super) fn new() -> Self {
Self {
endpoints: Endpoints::new(),
error_handler: ErrorFunc(default_error_handler).into()
error_handler: ErrorFunc(default_error_handler).into(),
fallback_handler: FallbackFunc::new(default_fallback_handler).into()
}
}

Expand All @@ -51,6 +57,7 @@ impl PipelineBuilder {
Pipeline {
endpoints: self.endpoints,
error_handler: self.error_handler,
fallback_handler: self.fallback_handler,
start
}
}
Expand All @@ -59,7 +66,8 @@ impl PipelineBuilder {
pub(super) fn build(self) -> Pipeline {
Pipeline {
endpoints: self.endpoints,
error_handler: self.error_handler
error_handler: self.error_handler,
fallback_handler: self.fallback_handler
}
}

Expand All @@ -80,6 +88,10 @@ impl PipelineBuilder {
pub(crate) fn set_error_handler(&mut self, handler: PipelineErrorHandler) {
self.error_handler = handler;
}

pub(crate) fn set_fallback_handler(&mut self, handler: PipelineFallbackHandler) {
self.fallback_handler = handler;
}
}

impl Pipeline {
Expand All @@ -93,6 +105,11 @@ impl Pipeline {
Arc::downgrade(&self.error_handler)
}

#[inline]
pub(super) async fn fallback(&self, req: Request<Incoming>) -> HttpResult {
self.fallback_handler.call(req).await
}

#[cfg(feature = "middleware")]
pub(crate) fn has_middleware_pipeline(&self) -> bool {
self.start.is_some()
Expand Down
2 changes: 1 addition & 1 deletion src/app/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl Scope {

let pipeline = &shared.pipeline;
match pipeline.endpoints().get_endpoint(request.method(), request.uri()) {
RouteOption::RouteNotFound => status!(404),
RouteOption::RouteNotFound => pipeline.fallback(request).await,
RouteOption::MethodNotFound(allowed) => status!(405, [
(ALLOW, allowed)
]),
Expand Down
49 changes: 44 additions & 5 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
//! Error Handling tools

use super::{App, http::{StatusCode, IntoResponse}};

use std::{
convert::Infallible,
fmt,
future::Future,
io::Error as IoError,
io::{ErrorKind, Error as IoError},
error::Error as StdError
};
use std::io::ErrorKind;
pub use self::handler::{ErrorHandler, ErrorFunc};

use super::{
App,
http::{
GenericHandler,
FromRawRequest,
StatusCode,
IntoResponse
}
};

pub use self::{
handler::{ErrorHandler, ErrorFunc},
fallback::{FallbackHandler, FallbackFunc}
};

#[cfg(feature = "problem-details")]
pub use self::problem::Problem;

pub mod handler;
pub mod fallback;
#[cfg(feature = "problem-details")]
pub mod problem;

Expand Down Expand Up @@ -183,4 +195,31 @@ impl App {
.set_error_handler(ErrorFunc(handler).into());
self
}

/// Adds a special fallback handler that handles the unregistered paths
///
/// # Example
/// ```no_run
/// use volga::{App, error::Error, not_found};
///
/// # #[tokio::main]
/// # async fn main() -> std::io::Result<()> {
/// let mut app = App::new();
///
/// app.map_fallback(|| async {
/// not_found!()
/// });
/// # app.run().await
/// # }
/// ```
pub fn map_fallback<F, Args, R>(&mut self, handler: F) -> &mut Self
where
F: GenericHandler<Args, Output = R>,
Args: FromRawRequest + Send + Sync + 'static,
R: IntoResponse
{
self.pipeline
.set_fallback_handler(FallbackFunc::new(handler).into());
self
}
}
77 changes: 77 additions & 0 deletions src/error/fallback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Fallback handler

use std::{marker::PhantomData, sync::Arc};
use futures_util::future::BoxFuture;
use hyper::{Request, body::Incoming};

use crate::{
error::handler::default_error_handler,
http::{GenericHandler, FromRawRequest, IntoResponse},
HttpResult,
status
};

/// Trait for types that represents a fallback handler
pub trait FallbackHandler {
fn call(&self, req: Request<Incoming>) -> BoxFuture<HttpResult>;
}

/// Owns a closure that handles a 404
pub struct FallbackFunc<F, Args>(pub(crate) F, PhantomData<Args>);

impl<F, Args, R> FallbackFunc<F, Args>
where
F: GenericHandler<Args, Output = R>,
Args: FromRawRequest + Send + Sync + 'static,
R: IntoResponse
{
pub(crate) fn new(func: F) -> Self {
Self(func, PhantomData)
}
}

impl<F, Args, R> FallbackHandler for FallbackFunc<F, Args>
where
F: GenericHandler<Args, Output = R>,
Args: FromRawRequest + Send + Sync + 'static,
R: IntoResponse
{
#[inline]
fn call(&self, req: Request<Incoming>) -> BoxFuture<HttpResult> {
Box::pin(async move {
let args = match Args::from_request(req).await {
Ok(args) => args,
Err(err) => return default_error_handler(err).await,
};
match self.0.call(args).await.into_response() {
Ok(resp) => Ok(resp),
Err(err) => default_error_handler(err).await,
}
})
}
}

impl<F, Args, R> From<FallbackFunc<F, Args>> for PipelineFallbackHandler
where
F: GenericHandler<Args, Output = R>,
Args: FromRawRequest + Send + Sync + 'static,
R: IntoResponse
{
#[inline]
fn from(func: FallbackFunc<F, Args>) -> Self {
Arc::new(func)
}
}

/// Holds a reference to global error handler
pub(crate) type PipelineFallbackHandler = Arc<
dyn FallbackHandler
+ Send
+ Sync
>;

/// Default fallback handler that creates a 404 [`HttpResult`]
#[inline]
pub(crate) async fn default_fallback_handler() -> HttpResult {
status!(404)
}
10 changes: 9 additions & 1 deletion src/http.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
//! Base HTTP tools

// Re-exporting HTTP status codes, Response and some headers from hyper/http
pub use hyper::{Response, StatusCode};
pub use hyper::{
http::{Uri, Method, Extensions},
Response,
StatusCode,
};

pub use body::{BoxBody, UnsyncBoxBody, HttpBody};
pub use endpoints::{
args::{FromRawRequest, FromRequestRef, FromRequest, FromRequestParts},
handlers::GenericHandler
};
pub use request::HttpRequest;
pub use response::{
into_response::IntoResponse,
Expand Down
Loading
0