base_http_protocol/
lib.rs1#![doc = include_str!("../readme.md")]
5#![forbid(unsafe_code)]
6
7use base_endpoint::stream::{ReadableStream, WritableStream};
8use base_http1::{RequestReader, RequestWriter, ResponseReader, ResponseWriter};
9use std::fmt;
10
11#[derive(Clone, Copy, Debug, Default)]
13pub struct HttpProtocols {
14 pub require_alpn: bool,
17}
18impl HttpProtocols {
19 pub fn alpn_protocols(&self) -> Vec<Vec<u8>> {
21 vec![b"http/1.1".to_vec()]
22 }
23 pub fn plaintext(&self) -> HttpProtocol {
25 HttpProtocol::Http1
26 }
27 pub fn negotiated(&self, selected: Option<&[u8]>) -> Result<HttpProtocol, NegotiationError> {
29 match selected {
30 Some(b"http/1.1") => Ok(HttpProtocol::Http1),
31 None if !self.require_alpn => Ok(HttpProtocol::Http1),
32 None => Err(NegotiationError::Missing),
33 Some(name) => Err(NegotiationError::Unsupported(name.to_vec())),
34 }
35 }
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum HttpProtocol {
41 Http1,
43}
44impl HttpProtocol {
45 pub fn client<R: ReadableStream, W: WritableStream>(
47 self,
48 reader: R,
49 writer: W,
50 options: base_http1::Options,
51 ) -> Result<(ResponseReader<R>, RequestWriter<W>), base_http1::Error> {
52 match self {
53 Self::Http1 => Ok((
54 ResponseReader::new(reader, options)?,
55 RequestWriter::new(writer),
56 )),
57 }
58 }
59 pub fn server<R: ReadableStream, W: WritableStream>(
61 self,
62 reader: R,
63 writer: W,
64 options: base_http1::Options,
65 ) -> Result<(RequestReader<R>, ResponseWriter<W>), base_http1::Error> {
66 match self {
67 Self::Http1 => Ok((
68 RequestReader::new(reader, options)?,
69 ResponseWriter::new(writer),
70 )),
71 }
72 }
73}
74
75#[derive(Clone, Debug, PartialEq, Eq)]
77pub enum NegotiationError {
78 Missing,
80 Unsupported(Vec<u8>),
82}
83impl fmt::Display for NegotiationError {
84 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 Self::Missing => formatter.write_str("HTTP application protocol was not negotiated"),
87 Self::Unsupported(name) => {
88 let name = name.escape_ascii();
89 write!(
90 formatter,
91 "unsupported HTTP application protocol: \"{name}\""
92 )
93 }
94 }
95 }
96}
97impl std::error::Error for NegotiationError {}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn unsupported_identifiers_never_fall_back_to_http1() {
105 for required in [false, true] {
106 let protocols = HttpProtocols {
107 require_alpn: required,
108 };
109 for name in [b"h2".as_slice(), b"h3", b"http/1.1\0", b"\xff"] {
110 assert_eq!(
111 protocols.negotiated(Some(name)),
112 Err(NegotiationError::Unsupported(name.to_vec()))
113 );
114 }
115 assert_eq!(
116 protocols.negotiated(Some(b"http/1.1")),
117 Ok(HttpProtocol::Http1)
118 );
119 assert_eq!(protocols.plaintext(), HttpProtocol::Http1);
120 }
121 }
122
123 #[test]
124 fn http1_is_advertised_and_absent_negotiation_follows_policy() {
125 let optional = HttpProtocols::default();
126 assert_eq!(optional.alpn_protocols(), vec![b"http/1.1".to_vec()]);
127 assert_eq!(optional.negotiated(None), Ok(HttpProtocol::Http1));
128 let required = HttpProtocols { require_alpn: true };
129 assert_eq!(required.negotiated(None), Err(NegotiationError::Missing));
130 }
131
132 #[test]
133 fn negotiation_errors_describe_the_selection() {
134 assert_eq!(
135 NegotiationError::Missing.to_string(),
136 "HTTP application protocol was not negotiated"
137 );
138 assert_eq!(
139 NegotiationError::Unsupported(b"h2".to_vec()).to_string(),
140 "unsupported HTTP application protocol: \"h2\""
141 );
142 assert_eq!(
143 NegotiationError::Unsupported(b"\xff\"".to_vec()).to_string(),
144 r#"unsupported HTTP application protocol: "\xff\"""#
145 );
146 }
147}