#![doc = include_str!("../readme.md")]
#![forbid(unsafe_code)]
use base_endpoint::stream::{ReadableStream, WritableStream};
use base_http1::{RequestReader, RequestWriter, ResponseReader, ResponseWriter};
use std::fmt;
#[derive(Clone, Copy, Debug, Default)]
pub struct HttpProtocols {
pub require_alpn: bool,
}
impl HttpProtocols {
pub fn alpn_protocols(&self) -> Vec<Vec<u8>> {
vec![b"http/1.1".to_vec()]
}
pub fn plaintext(&self) -> HttpProtocol {
HttpProtocol::Http1
}
pub fn negotiated(&self, selected: Option<&[u8]>) -> Result<HttpProtocol, NegotiationError> {
match selected {
Some(b"http/1.1") => Ok(HttpProtocol::Http1),
None if !self.require_alpn => Ok(HttpProtocol::Http1),
None => Err(NegotiationError::Missing),
Some(name) => Err(NegotiationError::Unsupported(name.to_vec())),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HttpProtocol {
Http1,
}
impl HttpProtocol {
pub fn client<R: ReadableStream, W: WritableStream>(
self,
reader: R,
writer: W,
options: base_http1::Options,
) -> Result<(ResponseReader<R>, RequestWriter<W>), base_http1::Error> {
match self {
Self::Http1 => Ok((
ResponseReader::new(reader, options)?,
RequestWriter::new(writer),
)),
}
}
pub fn server<R: ReadableStream, W: WritableStream>(
self,
reader: R,
writer: W,
options: base_http1::Options,
) -> Result<(RequestReader<R>, ResponseWriter<W>), base_http1::Error> {
match self {
Self::Http1 => Ok((
RequestReader::new(reader, options)?,
ResponseWriter::new(writer),
)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum NegotiationError {
Missing,
Unsupported(Vec<u8>),
}
impl fmt::Display for NegotiationError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Missing => formatter.write_str("HTTP application protocol was not negotiated"),
Self::Unsupported(name) => {
let name = name.escape_ascii();
write!(
formatter,
"unsupported HTTP application protocol: \"{name}\""
)
}
}
}
}
impl std::error::Error for NegotiationError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unsupported_identifiers_never_fall_back_to_http1() {
for required in [false, true] {
let protocols = HttpProtocols {
require_alpn: required,
};
for name in [b"h2".as_slice(), b"h3", b"http/1.1\0", b"\xff"] {
assert_eq!(
protocols.negotiated(Some(name)),
Err(NegotiationError::Unsupported(name.to_vec()))
);
}
assert_eq!(
protocols.negotiated(Some(b"http/1.1")),
Ok(HttpProtocol::Http1)
);
assert_eq!(protocols.plaintext(), HttpProtocol::Http1);
}
}
#[test]
fn http1_is_advertised_and_absent_negotiation_follows_policy() {
let optional = HttpProtocols::default();
assert_eq!(optional.alpn_protocols(), vec![b"http/1.1".to_vec()]);
assert_eq!(optional.negotiated(None), Ok(HttpProtocol::Http1));
let required = HttpProtocols { require_alpn: true };
assert_eq!(required.negotiated(None), Err(NegotiationError::Missing));
}
#[test]
fn negotiation_errors_describe_the_selection() {
assert_eq!(
NegotiationError::Missing.to_string(),
"HTTP application protocol was not negotiated"
);
assert_eq!(
NegotiationError::Unsupported(b"h2".to_vec()).to_string(),
"unsupported HTTP application protocol: \"h2\""
);
assert_eq!(
NegotiationError::Unsupported(b"\xff\"".to_vec()).to_string(),
r#"unsupported HTTP application protocol: "\xff\"""#
);
}
}