Cranking out some docs

Not finished yet we've got so many docs to write.  It's doc writing time!
This commit is contained in:
Emi Tatsuo 2020-12-05 14:09:16 -05:00
parent 3ef7b2751e
commit f98b94235a
Signed by: Emi
GPG Key ID: 68FAB2E2E6DFC98B
7 changed files with 132 additions and 8 deletions

View File

@ -30,9 +30,15 @@ use crate::{Document, types::{Body, Response, Request}};
/// Each implementation has bespoke docs that describe how the type is used, and what
/// response is produced.
pub enum Handler {
/// A handler that responds to a request by delegating to an [`Fn`]
FnHandler(HandlerInner),
/// A handler that always serves an identical response, for any and all request
StaticHandler(Response),
#[cfg(feature = "serve_dir")]
/// A handler that serves a directory, including a directory listing
FilesHandler(PathBuf),
}

View File

@ -1,3 +1,4 @@
#![warn(missing_docs)]
//! Kochab is an ergonomic and intuitive library for quickly building highly functional
//! and advanced Gemini applications on either SCGI or raw Gemini.
//!
@ -228,7 +229,7 @@ use tokio_rustls::TlsAcceptor;
#[cfg(feature = "gemini_srv")]
use rustls::Session;
pub mod types;
mod types;
pub mod util;
pub mod routing;
pub mod handling;
@ -247,7 +248,10 @@ pub use cert::CertGenMode;
pub use uriparse as uri;
pub use types::*;
/// The maximun length of a Request URI
pub const REQUEST_URI_MAX_LEN: usize = 1024;
/// The default port for the gemini protocol
pub const GEMINI_PORT: u16 = 1965;
use handling::Handler;

View File

@ -206,6 +206,11 @@ impl<T> Default for RoutingNode<T> {
}
#[derive(Debug, Clone, Copy)]
/// An error returned when attempting to register a route that already exists
///
/// Routes will not be overridden if this error is returned. Routes are never overwritten
///
/// See [`RoutingNode::add_route_by_path()`]
pub struct ConflictingRouteError();
impl std::error::Error for ConflictingRouteError { }

View File

@ -8,8 +8,19 @@ use std::borrow::Borrow;
use crate::types::Document;
/// The body of a response
///
/// The content of a successful response to be sent back to the user. This can be either
/// some bytes which will be sent directly to the user, or a reader which will be read at
/// some point before sending to the user.
pub enum Body {
/// In-memory bytes that may be sent back to the user
Bytes(Vec<u8>),
/// A reader which will be streamed to the user
///
/// If a reader blocks for too long, it MAY be killed before finishing, which results
/// in the user receiving a malformed response or timing out.
Reader(Box<dyn AsyncRead + Send + Sync + Unpin>),
}

View File

@ -19,6 +19,20 @@ use ring::digest;
use crate::user_management::{UserManager, User};
#[derive(Clone)]
/// A request from a Gemini client to the app
///
/// When originally sent out by a client, a request is literally just a URL, and honestly,
/// if you want to use it as just a URL, that'll work fine!
///
/// That said, kochab and any proxies the request might hit add a little bit more
/// information that you can use, like
/// * [What TLS certificate (if any) did the client use](Self::certificate)
/// * [What part of the path is relevant (ie, everything after the route)](Self::trailing_segments)
/// * [Is the user registered with the user database?](Self::user)
///
/// The only way to get your hands on one of these bad boys is when you register an [`Fn`]
/// based handler to a [`Server`](crate::Server), and a user makes a request to the
/// endpoint.
pub struct Request {
uri: URIReference<'static>,
input: Option<String>,
@ -33,7 +47,34 @@ pub struct Request {
}
impl Request {
pub fn new(
/// Construct a new request
///
/// When in `gemini_srv` mode, this is done using a URL. If you do construct a
/// request this way, by default it will not have a certificate attached, so make
/// sure you add in a certificate with [`Request::set_cert()`].
///
/// By contrast, in `scgi_srv` mode, the certificate fingerprint is grabbed out of the
/// request parameters, so you don't need to do anything. The headers passed should
/// be the header sent by the SCGI client.
///
/// When in SCGI mode, the following headers are expected:
///
/// * `PATH_INFO`: The part of the path following the route the app is bound to
/// * `QUERY_STRING`: The part of the request following ?, url encoded. Will produce
/// an error if it contains invalid UTF-8. No error if missing
/// * `TLS_CLIENT_HASH`: Optional. The base64 or hex encoded SHA256 sum of the DER
/// certificate of the requester.
/// * `SCRIPT_PATH` or `SCRIPT_NAME`: The base path the app is mounted on
///
/// # Errors
///
/// Produces an error if:
/// * The SCGI server didn't include the mandatory `PATH_INFO` header
/// * The provided URI reference is invalid, including if the SCGI server sent an
/// invalid `PATH_INFO`
/// * The `TLS_CLIENT_HASH` sent by the SCGI server isn't sha256, or is encoded with
/// something other than base64 or hexadecimal
pub (crate) fn new(
#[cfg(feature = "gemini_srv")]
mut uri: URIReference<'static>,
#[cfg(feature = "scgi_srv")]
@ -112,6 +153,19 @@ impl Request {
})
}
/// The URI reference requested by the user
///
/// Although they are not exactly the same thing, it is generally preferred to use the
/// [`Request::trailing_segments()`] method if possible.
///
/// Returns the URIReference requested by the user. **If running in SCGI mode, this
/// will contain only the parts of the URIReference that were relevant to the app.**
/// This means you will get `/path`, not `/app/path`.
///
/// When running in `scgi_srv` mode, this is guaranteed to be a relative reference.
/// When running in `gemini_srv` mode, clients are obliged by the spec to send a full
/// URI, but if a client fails to respect this, kochab will still accept and pass on
/// the relative reference.
pub const fn uri(&self) -> &URIReference {
&self.uri
}
@ -123,11 +177,6 @@ impl Request {
/// received to `/api/v1/endpoint`, then this value would be `["v1", "endpoint"]`.
/// This should not be confused with [`path_segments()`](Self::path_segments()), which
/// contains *all* of the segments, not just those trailing the route.
///
/// 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
/// to handlers registered through [`add_route()`](crate::Server::add_route()) will
/// always have trailing segments set.
pub fn trailing_segments(&self) -> &Vec<String> {
self.trailing_segments.as_ref().unwrap()
}
@ -167,6 +216,10 @@ impl Request {
/// the request otherwise. Bear in mind that **not all SCGI clients send the same
/// headers**, and these are *never* available when operating in `gemini_srv` mode.
///
/// By using this method, you are almost certainly reducing the number of proxy
/// servers your app supports, and you are strongly encouraged to find a different
/// method.
///
/// Some examples of headers mollybrown sets are:
/// - `REMOTE_ADDR` (The user's IP address and port)
/// - `TLS_CLIENT_SUBJECT_CN` (The CommonName on the user's certificate, when present)
@ -187,7 +240,8 @@ impl Request {
});
}
pub fn set_trailing(&mut self, segments: Vec<String>) {
/// Sets the segments returned by [`Request::trailing_segments()`]
pub (crate) fn set_trailing(&mut self, segments: Vec<String>) {
self.trailing_segments = Some(segments);
}

View File

@ -2,9 +2,48 @@ use std::borrow::Borrow;
use crate::types::{Body, Document};
/// A response to a client's [`Request`]
///
/// Requests in Gemini are pretty simple. They consist of three parts:
///
/// * A two status code, similar to the status codes in HTML. You don't need to know
/// anything about these, since this part of the response will be filled in for you
/// depending on the associated function you use to create the Response
/// * A meta, a <1024 byte string whose meaning depends on the status
/// * A body, but only for successful requests
///
/// Responses will be identical in both `scgi_srv` mode and `gemini_srv` mode.
///
/// [`Request`]: crate::Request
pub struct Response {
/// The status code of the request. A value between 10 and 62
///
/// Each block of 10 status codes (e.g. 10-19) has a specific meaning or category,
/// defined in depth in the gemini documentation. Generally:
///
/// * 1X is input
/// * 20 is success
/// * 3X is redirect
/// * >= 40 is an error
pub status: u8,
/// The meta associated with this request
///
/// Because the meaning of the meta field depends on the status, please consult the
/// status code before interpreting this value. The function signature of the method
/// used to create the response should also provide more detail about what the field
/// is. In general, the meaning of the meta for a status code is
///
/// * If the status code is 20, the meta is the mime type of the body
/// * If the status code is 3X, the meta is a URL to redirect to
/// * If the status code is 44, the meta is a time in seconds until ratelimiting ends
/// * If the status code is anything els, the meta is a message or prompt for the user
pub meta: String,
/// The body of this request
///
/// This never needs to be present, and **cannot** be present if the status code !=
/// 20.
pub body: Option<Body>,
}

View File

@ -1,3 +1,8 @@
//! Utilities for serving a file or directory
//!
//! ⚠️ Docs still under construction & API not yet stable ⚠️
#![allow(missing_docs)]
#[cfg(feature="serve_dir")]
use std::path::{Path, PathBuf};
use anyhow::*;