syndicate-rs/syndicate-server/src/services/http_router.rs

165 lines
6.8 KiB
Rust

use preserves_schema::Codec;
use std::sync::Arc;
use syndicate::actor::*;
use syndicate::enclose;
use syndicate::error::Error;
use syndicate::preserves::rec;
use syndicate::preserves::value::Map;
use syndicate::preserves::value::NestedValue;
use syndicate::preserves::value::Set;
use syndicate::schemas::http;
use crate::language::language;
use crate::lifecycle;
use crate::schemas::internal_services::HttpRouter;
use syndicate_macros::during;
pub fn on_demand(t: &mut Activation, ds: Arc<Cap>) {
t.spawn(Some(AnyValue::symbol("http_router_listener")), move |t| {
Ok(during!(t, ds, language(), <run-service $spec: HttpRouter::<AnyValue>>, |t: &mut Activation| {
t.spawn_link(Some(rec![AnyValue::symbol("http_router"), language().unparse(&spec)]),
enclose!((ds) |t| run(t, ds, spec)));
Ok(())
}))
});
}
type MethodTable = Map<http::MethodPattern, Set<Arc<Cap>>>;
type RoutingTable = Map<http::HostPattern, Map<http::PathPattern, MethodTable>>;
fn run(t: &mut Activation, ds: Arc<Cap>, spec: HttpRouter) -> ActorResult {
ds.assert(t, language(), &lifecycle::started(&spec));
ds.assert(t, language(), &lifecycle::ready(&spec));
let httpd = spec.httpd;
during!(t, httpd, language(), <http-bind _ $port _ _ _>, |t: &mut Activation| {
let routes: Arc<Field<RoutingTable>> = t.named_field("routes", Map::new());
let port1 = port.clone();
enclose!((httpd, routes) during!(t, httpd, language(), <http-listener #(&port1)>, enclose!((routes) |t: &mut Activation| {
during!(t, httpd, language(), <http-bind $host #(&port) $method $path $handler>, |t: &mut Activation| {
let host = language().parse::<http::HostPattern>(&host)?;
let path = language().parse::<http::PathPattern>(&path)?;
let method = language().parse::<http::MethodPattern>(&method)?;
let handler = handler.value().to_embedded()?;
t.get_mut(&routes)
.entry(host.clone()).or_default()
.entry(path.clone()).or_default()
.entry(method.clone()).or_default()
.insert(handler.clone());
t.on_stop(enclose!((routes, handler, method, path, host) move |t| {
let host_map = t.get_mut(&routes);
let path_map = host_map.entry(host.clone()).or_default();
let method_map = path_map.entry(path.clone()).or_default();
let handler_set = method_map.entry(method.clone()).or_default();
handler_set.remove(&handler);
if handler_set.is_empty() {
method_map.remove(&method);
}
if method_map.is_empty() {
path_map.remove(&path);
}
if path_map.is_empty() {
host_map.remove(&host);
}
Ok(())
}));
Ok(())
});
Ok(())
})));
during!(t, httpd, language(), <request $req $res>, |t: &mut Activation| {
let req = match language().parse::<http::HttpRequest>(&req) { Ok(v) => v, Err(_) => return Ok(()) };
let res = match res.value().to_embedded() { Ok(v) => v, Err(_) => return Ok(()) };
let methods = match try_hostname(t, &routes, http::HostPattern::Host(req.host.clone()), &req.path)? {
Some(methods) => methods,
None => match try_hostname(t, &routes, http::HostPattern::Any, &req.path)? {
Some(methods) => methods,
None => {
res.message(t, language(), &http::HttpResponse::Status {
code: 404.into(), message: "Not found".into() });
res.message(t, language(), &http::HttpResponse::Done {
chunk: Box::new(http::Chunk::Bytes(vec![])) });
return Ok(())
}
}
};
let handlers = match methods.get(&http::MethodPattern::Specific(req.method.clone())) {
Some(handlers) => handlers,
None => match methods.get(&http::MethodPattern::Any) {
Some(handlers) => handlers,
None => {
let allowed = methods.keys().map(|k| match k {
http::MethodPattern::Specific(m) => m.to_uppercase(),
http::MethodPattern::Any => unreachable!(),
}).collect::<Vec<String>>().join(", ");
res.message(t, language(), &http::HttpResponse::Status {
code: 405.into(), message: "Method Not Allowed".into() });
res.message(t, language(), &http::HttpResponse::Header {
name: "allow".into(), value: allowed });
res.message(t, language(), &http::HttpResponse::Done {
chunk: Box::new(http::Chunk::Bytes(vec![])) });
return Ok(())
}
}
};
if handlers.len() > 1 {
tracing::warn!(?req, "Too many handlers available");
}
let handler = handlers.first().expect("Nonempty handler set").clone();
handler.assert(t, language(), &http::HttpContext { req, res: res.clone() });
Ok(())
});
Ok(())
});
Ok(())
}
fn path_pattern_matches(path_pat: &http::PathPattern, path: &Vec<String>) -> bool {
let mut path_iter = path.iter();
for pat_elem in path_pat.0.iter() {
match pat_elem {
http::PathPatternElement::Label(v) => match path_iter.next() {
Some(path_elem) => {
if v != path_elem {
return false;
}
}
None => return false,
},
http::PathPatternElement::Wildcard => match path_iter.next() {
Some(_) => (),
None => return false,
},
http::PathPatternElement::Rest => return true,
}
}
true
}
fn try_hostname<'turn, 'routes>(
t: &'routes mut Activation<'turn>,
routes: &'routes Arc<Field<RoutingTable>>,
host_pat: http::HostPattern,
path: &Vec<String>,
) -> Result<Option<&'routes MethodTable>, Error> {
match t.get(routes).get(&host_pat) {
None => Ok(None),
Some(path_table) => {
for (path_pat, method_table) in path_table.iter() {
if path_pattern_matches(path_pat, path) {
return Ok(Some(method_table));
}
}
Ok(None)
}
}
}