Merge branch 'routes' into rate-limiting
This commit is contained in:
commit
5ae6f578e3
14
CHANGELOG.md
14
CHANGELOG.md
|
|
@ -7,15 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- `document` API for creating Gemini documents
|
- `document` API for creating Gemini documents
|
||||||
- preliminary timeout API, incl a special case for complex MIMEs by [@Alch-Emi](https://github.com/Alch-Emi)
|
- preliminary timeout API, incl a special case for complex MIMEs by [@Alch-Emi]
|
||||||
- `Response::success_with_body` by [@Alch-Emi](https://github.com/Alch-Emi)
|
- `Response::success_with_body` by [@Alch-Emi]
|
||||||
- `redirect_temporary_lossy` for `Response` and `ResponseHeader`
|
- `redirect_temporary_lossy` for `Response` and `ResponseHeader`
|
||||||
- `bad_request_lossy` for `Response` and `ResponseHeader`
|
- `bad_request_lossy` for `Response` and `ResponseHeader`
|
||||||
- support for a lot more mime-types in `guess_mime_from_path`, backed by the `mime_guess` crate
|
- support for a lot more mime-types in `guess_mime_from_path`, backed by the `mime_guess` crate
|
||||||
- customizable TLS cert & key paths by [@Alch-Emi](https://github.com/Alch-Emi)
|
- customizable TLS cert & key paths by [@Alch-Emi]
|
||||||
- `server_dir` default feature for serve_dir utils [@Alch-Emi](https://github.com/Alch-Emi)
|
- `server_dir` default feature for serve_dir utils [@Alch-Emi]
|
||||||
|
- Docments can be converted into responses with std::convert::Into [@Alch-Emi]
|
||||||
### Improved
|
### Improved
|
||||||
- build time and size by [@Alch-Emi](https://github.com/Alch-Emi)
|
- build time and size by [@Alch-Emi](https://github.com/Alch-Emi)
|
||||||
|
- build time and size by [@Alch-Emi]
|
||||||
### Changed
|
### Changed
|
||||||
- Added route API [@Alch-Emi](https://github.com/Alch-Emi)
|
- Added route API [@Alch-Emi](https://github.com/Alch-Emi)
|
||||||
|
|
||||||
|
|
@ -36,4 +38,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [0.2.0] - 2020-11-14
|
## [0.2.0] - 2020-11-14
|
||||||
### Added
|
### Added
|
||||||
- Access to client certificates by [@Alch-Emi](https://github.com/Alch-Emi)
|
- Access to client certificates by [@Alch-Emi]
|
||||||
|
|
||||||
|
[@Alch-Emi]: https://github.com/Alch-Emi
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,7 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
fn handle_request(_request: Request) -> BoxFuture<'static, Result<Response>> {
|
fn handle_request(_request: Request) -> BoxFuture<'static, Result<Response>> {
|
||||||
async move {
|
async move {
|
||||||
let mut document = Document::new();
|
let response = Document::new()
|
||||||
|
|
||||||
document
|
|
||||||
.add_preformatted(include_str!("northstar_logo.txt"))
|
.add_preformatted(include_str!("northstar_logo.txt"))
|
||||||
.add_blank_line()
|
.add_blank_line()
|
||||||
.add_link("https://docs.rs/northstar", "Documentation")
|
.add_link("https://docs.rs/northstar", "Documentation")
|
||||||
|
|
@ -44,9 +42,9 @@ fn handle_request(_request: Request) -> BoxFuture<'static, Result<Response>> {
|
||||||
.add_preformatted_with_alt("sh", concat!(
|
.add_preformatted_with_alt("sh", concat!(
|
||||||
"mkdir cert && cd cert\n",
|
"mkdir cert && cd cert\n",
|
||||||
"openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365",
|
"openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365",
|
||||||
));
|
))
|
||||||
|
.into();
|
||||||
Ok(Response::document(document))
|
Ok(response)
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
src/lib.rs
21
src/lib.rs
|
|
@ -18,6 +18,7 @@ use tokio::{
|
||||||
};
|
};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use rustls::ClientCertVerifier;
|
use rustls::ClientCertVerifier;
|
||||||
|
use rustls::internal::msgs::handshake::DigitallySignedStruct;
|
||||||
use tokio_rustls::{rustls, TlsAcceptor};
|
use tokio_rustls::{rustls, TlsAcceptor};
|
||||||
use rustls::*;
|
use rustls::*;
|
||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
|
|
@ -510,6 +511,8 @@ impl ClientCertVerifier for AllowAnonOrSelfsignedClient {
|
||||||
Some(false)
|
Some(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the below methods are a hack until webpki doesn't break with certain certs
|
||||||
|
|
||||||
fn verify_client_cert(
|
fn verify_client_cert(
|
||||||
&self,
|
&self,
|
||||||
_: &[Certificate],
|
_: &[Certificate],
|
||||||
|
|
@ -517,6 +520,24 @@ impl ClientCertVerifier for AllowAnonOrSelfsignedClient {
|
||||||
) -> Result<ClientCertVerified, TLSError> {
|
) -> Result<ClientCertVerified, TLSError> {
|
||||||
Ok(ClientCertVerified::assertion())
|
Ok(ClientCertVerified::assertion())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify_tls12_signature(
|
||||||
|
&self,
|
||||||
|
_message: &[u8],
|
||||||
|
_cert: &Certificate,
|
||||||
|
_dss: &DigitallySignedStruct,
|
||||||
|
) -> Result<HandshakeSignatureValid, TLSError> {
|
||||||
|
Ok(HandshakeSignatureValid::assertion())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_tls13_signature(
|
||||||
|
&self,
|
||||||
|
_message: &[u8],
|
||||||
|
_cert: &Certificate,
|
||||||
|
_dss: &DigitallySignedStruct,
|
||||||
|
) -> Result<HandshakeSignatureValid, TLSError> {
|
||||||
|
Ok(HandshakeSignatureValid::assertion())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,31 @@ use crate::types::Request;
|
||||||
///
|
///
|
||||||
/// Routing is only performed on normalized paths, so "/endpoint" and "/endpoint/" are
|
/// Routing is only performed on normalized paths, so "/endpoint" and "/endpoint/" are
|
||||||
/// considered to be the same route.
|
/// considered to be the same route.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use northstar::routing::RoutingNode;
|
||||||
|
/// let mut routes = RoutingNode::<&'static str>::default();
|
||||||
|
/// routes.add_route("/", "base");
|
||||||
|
/// routes.add_route("/trans/rights/", "short route");
|
||||||
|
/// routes.add_route("/trans/rights/r/human", "long route");
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// routes.match_path(&["any", "other", "request"]),
|
||||||
|
/// Some((vec![&"any", &"other", &"request"], &"base"))
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(
|
||||||
|
/// routes.match_path(&["trans", "rights"]),
|
||||||
|
/// Some((vec![], &"short route"))
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(
|
||||||
|
/// routes.match_path(&["trans", "rights", "now"]),
|
||||||
|
/// Some((vec![&"now"], &"short route"))
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(
|
||||||
|
/// routes.match_path(&["trans", "rights", "r", "human", "rights"]),
|
||||||
|
/// Some((vec![&"rights"], &"long route"))
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
pub struct RoutingNode<T>(Option<T>, HashMap<String, Self>);
|
pub struct RoutingNode<T>(Option<T>, HashMap<String, Self>);
|
||||||
|
|
||||||
impl<T> RoutingNode<T> {
|
impl<T> RoutingNode<T> {
|
||||||
|
|
@ -33,7 +58,7 @@ impl<T> RoutingNode<T> {
|
||||||
/// should be represented as `&["dir", "image.png"]`.
|
/// should be represented as `&["dir", "image.png"]`.
|
||||||
///
|
///
|
||||||
/// If a match is found, it is returned, along with the segments of the path trailing
|
/// If a match is found, it is returned, along with the segments of the path trailing
|
||||||
/// the subpath matcing the route. For example, a route `/foo` recieving a request to
|
/// the subpath matching the route. For example, a route `/foo` receiving a request to
|
||||||
/// `/foo/bar` would produce `vec!["bar"]`
|
/// `/foo/bar` would produce `vec!["bar"]`
|
||||||
///
|
///
|
||||||
/// See [`RoutingNode`] for details on how routes are matched.
|
/// See [`RoutingNode`] for details on how routes are matched.
|
||||||
|
|
@ -137,6 +162,41 @@ impl<T> RoutingNode<T> {
|
||||||
to_shrink.extend(shrink.values_mut().map(|n| &mut n.1));
|
to_shrink.extend(shrink.values_mut().map(|n| &mut n.1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterate over the items in this map
|
||||||
|
///
|
||||||
|
/// This includes not just the direct children of this node, but also all children of
|
||||||
|
/// those children. No guarantees are made as to the order values are visited in.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// # use std::collections::HashSet;
|
||||||
|
/// # use northstar::routing::RoutingNode;
|
||||||
|
/// let mut map = RoutingNode::<usize>::default();
|
||||||
|
/// map.add_route("/", 0);
|
||||||
|
/// map.add_route("/hello/world", 1312);
|
||||||
|
/// map.add_route("/example", 621);
|
||||||
|
///
|
||||||
|
/// let values: HashSet<&usize> = map.iter().collect();
|
||||||
|
/// assert!(values.contains(&0));
|
||||||
|
/// assert!(values.contains(&1312));
|
||||||
|
/// assert!(values.contains(&621));
|
||||||
|
/// assert!(!values.contains(&1));
|
||||||
|
/// ```
|
||||||
|
pub fn iter(&self) -> Iter<'_, T> {
|
||||||
|
Iter {
|
||||||
|
unexplored: vec![self],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> IntoIterator for &'a RoutingNode<T> {
|
||||||
|
type Item = &'a T;
|
||||||
|
type IntoIter = Iter<'a, T>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Iter<'a, T> {
|
||||||
|
self.iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for RoutingNode<T> {
|
impl<T> Default for RoutingNode<T> {
|
||||||
|
|
@ -155,3 +215,25 @@ impl std::fmt::Display for ConflictingRouteError {
|
||||||
write!(f, "Attempted to create a route with the same matcher as an existing route")
|
write!(f, "Attempted to create a route with the same matcher as an existing route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
/// An iterator over the values in a [`RoutingNode`] map
|
||||||
|
pub struct Iter<'a, T> {
|
||||||
|
unexplored: Vec<&'a RoutingNode<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for Iter<'a, T> {
|
||||||
|
type Item = &'a T;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
while let Some(node) = self.unexplored.pop() {
|
||||||
|
self.unexplored.extend(node.1.values());
|
||||||
|
if node.0.is_some() {
|
||||||
|
return node.0.as_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::iter::FusedIterator for Iter<'_, T> { }
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ use tokio::io::AsyncRead;
|
||||||
#[cfg(feature="serve_dir")]
|
#[cfg(feature="serve_dir")]
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
|
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
use crate::types::Document;
|
use crate::types::Document;
|
||||||
|
|
||||||
pub enum Body {
|
pub enum Body {
|
||||||
|
|
@ -9,9 +11,9 @@ pub enum Body {
|
||||||
Reader(Box<dyn AsyncRead + Send + Sync + Unpin>),
|
Reader(Box<dyn AsyncRead + Send + Sync + Unpin>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Document> for Body {
|
impl<D: Borrow<Document>> From<D> for Body {
|
||||||
fn from(document: Document) -> Self {
|
fn from(document: D) -> Self {
|
||||||
Self::from(document.to_string())
|
Self::from(document.borrow().to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ impl Request {
|
||||||
///
|
///
|
||||||
/// If the trailing segments have not been set, this method will panic, but this
|
/// If the trailing segments have not been set, this method will panic, but this
|
||||||
/// should only be possible if you are constructing the Request yourself. Requests
|
/// should only be possible if you are constructing the Request yourself. Requests
|
||||||
/// to handlers registered through [`add_route`](northstar::Builder::add_route()) will
|
/// to handlers registered through [`add_route`](crate::Builder::add_route()) will
|
||||||
/// always have trailing segments set.
|
/// always have trailing segments set.
|
||||||
pub fn trailing_segments(&self) -> &Vec<String> {
|
pub fn trailing_segments(&self) -> &Vec<String> {
|
||||||
self.trailing_segments.as_ref().unwrap()
|
self.trailing_segments.as_ref().unwrap()
|
||||||
|
|
@ -64,7 +64,7 @@ impl Request {
|
||||||
/// All of the segments in this path, percent decoded
|
/// All of the segments in this path, percent decoded
|
||||||
///
|
///
|
||||||
/// For example, for a request to `/api/v1/endpoint`, this would return `["api", "v1",
|
/// For example, for a request to `/api/v1/endpoint`, this would return `["api", "v1",
|
||||||
/// "endpoint"]`, no matter what route the handler that recieved this request was
|
/// "endpoint"]`, no matter what route the handler that received this request was
|
||||||
/// bound to. This is not to be confused with
|
/// bound to. This is not to be confused with
|
||||||
/// [`trailing_segments()`](Self::trailing_segments), which contains only the segments
|
/// [`trailing_segments()`](Self::trailing_segments), which contains only the segments
|
||||||
/// following the bound route.
|
/// following the bound route.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
use uriparse::URIReference;
|
use uriparse::URIReference;
|
||||||
|
|
@ -19,7 +20,7 @@ impl Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn document(document: Document) -> Self {
|
pub fn document(document: impl Borrow<Document>) -> Self {
|
||||||
Self::success_with_body(&GEMINI_MIME, document)
|
Self::success_with_body(&GEMINI_MIME, document)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,3 +95,9 @@ impl Response {
|
||||||
self.body.take()
|
self.body.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<D: Borrow<Document>> From<D> for Response {
|
||||||
|
fn from(doc: D) -> Self {
|
||||||
|
Self::document(doc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue