use std::{
collections::HashMap,
sync::{Arc, RwLock},
task::{Context, Poll},
};
use futures::future::BoxFuture;
use http::{Method, StatusCode};
use crate::server::{
BoxService, BoxServiceFuture, IntoBoxService, MessageStreamError, Request, Response, Service,
UnresolvedRequest, box_service,
};
#[tracing::instrument(skip_all)]
pub async fn default_fallback(_request: &mut Request, response: &mut Response) {
tracing::debug!("call default fallback service (404 Not Found)");
_ = response.set_status(StatusCode::NOT_FOUND)
}
#[derive(Debug, Clone)]
struct Fallback(Arc<RwLock<BoxService>>);
impl Fallback {
pub fn new(service: BoxService) -> Self {
Self(Arc::new(RwLock::new(service)))
}
pub fn set(&mut self, service: BoxService) {
*self.0.write().expect("lock is not poisoned") = service;
}
}
impl Service for Fallback {
type Future<'s> = BoxServiceFuture<'s>;
fn serve<'s>(&self, request: &'s mut Request, response: &'s mut Response) -> Self::Future<'s> {
tracing::debug!("call fallback service");
self.0
.read()
.expect("lock is not poisoned")
.serve(request, response)
}
}
#[derive(Debug, Clone)]
struct RouterInner {
router: matchit::Router<BoxService>,
fallback: Fallback,
}
impl Default for RouterInner {
fn default() -> Self {
Self {
router: Default::default(),
fallback: Fallback::new(box_service(default_fallback)),
}
}
}
impl RouterInner {
fn route(&mut self, path: &str, service: impl IntoBoxService) {
self.router
.insert(path, service.into_box_service())
.expect("failed to register route");
}
pub fn on(&mut self, method: Method, path: &str, service: impl IntoBoxService) {
match self.router.at_mut(path) {
Ok(exist_service) => {
if let Some(router) = exist_service
.value
.downcast_mut::<MethodRouter<BoxService>>()
{
router.set(method, service.into_box_service());
} else {
let fallback = exist_service.value.clone();
let mut router = MethodRouter::new(fallback);
router.set(method, service.into_box_service());
*exist_service.value = router.into_box_service();
}
}
Err(..) => {
let mut router = MethodRouter::new(self.fallback.clone().into_box_service());
router.set(method, service.into_box_service());
self.route(path, router)
}
}
}
}
impl Service for RouterInner {
type Future<'s> = BoxServiceFuture<'s>;
fn serve<'s>(
&self,
request: &'s mut Request,
response: &'s mut Response,
) -> BoxServiceFuture<'s> {
let Some(path_and_query) = request.path() else {
tracing::debug!("missing path in request URI, call fallback service");
return self.fallback.serve(request, response);
};
let path = path_and_query.path();
let Ok(endpoint) = self.router.at(path) else {
tracing::debug!(path, "path route: not found, call fallback service");
return self.fallback.serve(request, response);
};
tracing::debug!(path, "path route found, call matched service");
endpoint.value.serve(request, response)
}
}
#[derive(Debug, Default, Clone)]
pub struct Router {
inner: Arc<RouterInner>,
}
impl Router {
pub fn new() -> Self {
Self::default()
}
fn inner_ref(&self) -> &RouterInner {
&self.inner
}
fn inner_mut(&mut self) -> &mut RouterInner {
Arc::make_mut(&mut self.inner)
}
pub fn route(mut self, path: &str, service: impl IntoBoxService) -> Self {
self.inner_mut().route(path, service.into_box_service());
self
}
pub fn on(mut self, method: Method, path: &str, service: impl IntoBoxService) -> Self {
self.inner_mut()
.on(method, path, service.into_box_service());
self
}
pub fn fallback(mut self, service: impl IntoBoxService) -> Self {
self.inner_mut().fallback.set(service.into_box_service());
self
}
pub fn options(self, path: &str, service: impl IntoBoxService) -> Self {
self.on(Method::OPTIONS, path, service)
}
pub fn get(self, path: &str, service: impl IntoBoxService) -> Self {
self.on(Method::GET, path, service)
}
pub fn post(self, path: &str, service: impl IntoBoxService) -> Self {
self.on(Method::POST, path, service)
}
pub fn put(self, path: &str, service: impl IntoBoxService) -> Self {
self.on(Method::PUT, path, service)
}
pub fn delete(self, path: &str, service: impl IntoBoxService) -> Self {
self.on(Method::DELETE, path, service)
}
pub fn head(self, path: &str, service: impl IntoBoxService) -> Self {
self.on(Method::HEAD, path, service)
}
pub fn trace(self, path: &str, service: impl IntoBoxService) -> Self {
self.on(Method::TRACE, path, service)
}
pub fn connect(self, path: &str, service: impl IntoBoxService) -> Self {
self.on(Method::CONNECT, path, service)
}
pub fn patch(self, path: &str, service: impl IntoBoxService) -> Self {
self.on(Method::PATCH, path, service)
}
pub fn serve<'s>(
&self,
request: &'s mut Request,
response: &'s mut Response,
) -> BoxServiceFuture<'s> {
self.inner_ref().serve(request, response)
}
#[tracing::instrument(skip(self, req), fields(method = tracing::field::Empty, uri = tracing::field::Empty))]
pub async fn handle(&self, req: UnresolvedRequest) -> Result<(), MessageStreamError> {
let (mut request, mut response) = req.resolve().await?;
tracing::Span::current()
.record("method", request.method().as_str())
.record("uri", request.uri().to_string());
self.serve(&mut request, &mut response).await;
if let Some(drop_future) = response.drop() {
drop_future.await;
}
Ok(())
}
}
impl Service for Router {
type Future<'s> = BoxServiceFuture<'s>;
fn serve<'s>(&self, request: &'s mut Request, response: &'s mut Response) -> Self::Future<'s> {
Router::serve(self, request, response)
}
}
impl tower_service::Service<UnresolvedRequest> for Router {
type Response = ();
type Error = MessageStreamError;
type Future = BoxFuture<'static, Result<(), MessageStreamError>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
_ = cx;
Poll::Ready(Ok(()))
}
fn call(&mut self, req: UnresolvedRequest) -> Self::Future {
let router = self.clone();
Box::pin(async move { router.handle(req).await })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MethodRouter<S> {
options: Option<S>,
get: Option<S>,
post: Option<S>,
put: Option<S>,
delete: Option<S>,
head: Option<S>,
trace: Option<S>,
connect: Option<S>,
patch: Option<S>,
extensions: HashMap<Method, S>,
fallback: S,
}
impl<S> MethodRouter<S> {
pub fn new(fallback: S) -> Self {
Self {
options: None,
get: None,
post: None,
put: None,
delete: None,
head: None,
trace: None,
connect: None,
patch: None,
extensions: HashMap::new(),
fallback,
}
}
pub fn service(&self, method: Method) -> Option<&S> {
match method {
Method::OPTIONS => self.options.as_ref(),
Method::GET => self.get.as_ref(),
Method::POST => self.post.as_ref(),
Method::PUT => self.put.as_ref(),
Method::DELETE => self.delete.as_ref(),
Method::HEAD => self.head.as_ref(),
Method::TRACE => self.trace.as_ref(),
Method::CONNECT => self.connect.as_ref(),
Method::PATCH => self.patch.as_ref(),
_ => self.extensions.get(&method),
}
}
pub fn service_mut(&mut self, method: Method) -> Option<&mut S> {
match method {
Method::OPTIONS => self.options.as_mut(),
Method::GET => self.get.as_mut(),
Method::POST => self.post.as_mut(),
Method::PUT => self.put.as_mut(),
Method::DELETE => self.delete.as_mut(),
Method::HEAD => self.head.as_mut(),
Method::TRACE => self.trace.as_mut(),
Method::CONNECT => self.connect.as_mut(),
Method::PATCH => self.patch.as_mut(),
_ => self.extensions.get_mut(&method),
}
}
pub fn set(&mut self, method: Method, service: S) {
match method {
Method::OPTIONS => self.options = Some(service),
Method::GET => self.get = Some(service),
Method::POST => self.post = Some(service),
Method::PUT => self.put = Some(service),
Method::DELETE => self.delete = Some(service),
Method::HEAD => self.head = Some(service),
Method::TRACE => self.trace = Some(service),
Method::CONNECT => self.connect = Some(service),
Method::PATCH => self.patch = Some(service),
_ => _ = self.extensions.insert(method, service),
}
}
pub fn set_fallback(&mut self, service: S) {
self.fallback = service;
}
}
impl<S> Service for MethodRouter<S>
where
S: Clone + for<'s> Service<Future<'s>: Send> + Send + 'static,
{
type Future<'s> = BoxServiceFuture<'s>;
fn serve<'s>(
&self,
request: &'s mut super::Request,
response: &'s mut super::Response,
) -> Self::Future<'s> {
let method = request.method();
let service = match method {
Method::OPTIONS => self.options.as_ref().unwrap_or(&self.fallback),
Method::GET => self.get.as_ref().unwrap_or(&self.fallback),
Method::POST => self.post.as_ref().unwrap_or(&self.fallback),
Method::PUT => self.put.as_ref().unwrap_or(&self.fallback),
Method::DELETE => self.delete.as_ref().unwrap_or(&self.fallback),
Method::HEAD => self.head.as_ref().unwrap_or(&self.fallback),
Method::TRACE => self.trace.as_ref().unwrap_or(&self.fallback),
Method::CONNECT => self.connect.as_ref().unwrap_or(&self.fallback),
Method::PATCH => self.patch.as_ref().unwrap_or(&self.fallback),
_ => self.extensions.get(&method).unwrap_or(&self.fallback),
}
.clone();
Box::pin(async move { service.serve(request, response).await })
}
}
#[cfg(test)]
mod tests {
use http::Method;
use super::MethodRouter;
fn make_router() -> MethodRouter<&'static str> {
let mut router = MethodRouter::new("fallback");
router.set(Method::GET, "get_handler");
router.set(Method::POST, "post_handler");
router.set(Method::PUT, "put_handler");
router.set(Method::DELETE, "delete_handler");
router
}
#[test]
fn method_router_service_lookup() {
let router = make_router();
assert_eq!(router.service(Method::GET), Some(&"get_handler"));
assert_eq!(router.service(Method::POST), Some(&"post_handler"));
assert_eq!(router.service(Method::PUT), Some(&"put_handler"));
assert_eq!(router.service(Method::DELETE), Some(&"delete_handler"));
}
#[test]
fn method_router_unset_returns_none() {
let router = make_router();
assert_eq!(router.service(Method::PATCH), None);
assert_eq!(router.service(Method::HEAD), None);
assert_eq!(router.service(Method::OPTIONS), None);
assert_eq!(router.service(Method::TRACE), None);
assert_eq!(router.service(Method::CONNECT), None);
}
#[test]
fn method_router_fallback() {
let router = make_router();
assert_eq!(router.fallback, "fallback");
}
#[test]
fn method_router_set_all_standard_methods() {
let mut router = MethodRouter::new("fb");
router.set(Method::OPTIONS, "opt");
router.set(Method::GET, "get");
router.set(Method::POST, "post");
router.set(Method::PUT, "put");
router.set(Method::DELETE, "del");
router.set(Method::HEAD, "head");
router.set(Method::TRACE, "trace");
router.set(Method::CONNECT, "connect");
router.set(Method::PATCH, "patch");
assert_eq!(router.service(Method::OPTIONS), Some(&"opt"));
assert_eq!(router.service(Method::GET), Some(&"get"));
assert_eq!(router.service(Method::POST), Some(&"post"));
assert_eq!(router.service(Method::PUT), Some(&"put"));
assert_eq!(router.service(Method::DELETE), Some(&"del"));
assert_eq!(router.service(Method::HEAD), Some(&"head"));
assert_eq!(router.service(Method::TRACE), Some(&"trace"));
assert_eq!(router.service(Method::CONNECT), Some(&"connect"));
assert_eq!(router.service(Method::PATCH), Some(&"patch"));
}
#[test]
fn method_router_service_mut() {
let mut router = make_router();
if let Some(handler) = router.service_mut(Method::GET) {
*handler = "updated_get";
}
assert_eq!(router.service(Method::GET), Some(&"updated_get"));
}
#[test]
fn method_router_set_fallback() {
let mut router = make_router();
router.set_fallback("new_fallback");
assert_eq!(router.fallback, "new_fallback");
}
#[test]
fn method_router_overwrite() {
let mut router = make_router();
router.set(Method::GET, "overwritten");
assert_eq!(router.service(Method::GET), Some(&"overwritten"));
}
#[test]
fn router_builder_chain() {
use super::Router;
async fn dummy(_req: &mut super::Request, _resp: &mut super::Response) {}
let _router = Router::new()
.route("/exact", dummy)
.get("/api/users", dummy)
.post("/api/users", dummy)
.fallback(dummy);
}
#[test]
fn matchit_path_matching() {
let mut router = matchit::Router::new();
router.insert("/", "root").unwrap();
router.insert("/users", "users").unwrap();
router.insert("/users/{id}", "user_by_id").unwrap();
router.insert("/files/{*path}", "files_catch_all").unwrap();
assert_eq!(*router.at("/").unwrap().value, "root");
assert_eq!(*router.at("/users").unwrap().value, "users");
let m = router.at("/users/42").unwrap();
assert_eq!(*m.value, "user_by_id");
assert_eq!(m.params.get("id"), Some("42"));
let m = router.at("/files/docs/readme.md").unwrap();
assert_eq!(*m.value, "files_catch_all");
assert_eq!(m.params.get("path"), Some("docs/readme.md"));
assert!(router.at("/nonexistent").is_err());
assert!(router.at("").is_err());
}
}