ttpkit_http/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! HTTP 1.x implementation.
4
5#[cfg(feature = "server")]
6#[macro_use]
7extern crate log;
8
9#[cfg(feature = "openssl")]
10mod tls;
11
12pub mod request;
13pub mod response;
14
15#[cfg(feature = "client")]
16#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
17pub mod client;
18
19#[cfg(feature = "server")]
20#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
21pub mod server;
22
23#[cfg(feature = "ws")]
24#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
25pub mod ws;
26
27#[cfg(any(feature = "client", feature = "server", feature = "ws"))]
28#[cfg_attr(
29    docsrs,
30    doc(cfg(any(feature = "client", feature = "server", feature = "ws")))
31)]
32/// Connection abstractions.
33pub mod connection {
34    pub use ttpkit_utils::io::{
35        Connection, ConnectionBuilder, ConnectionReader, ConnectionWriter, UpgradeFuture,
36        UpgradeRequest, Upgraded,
37    };
38}
39
40use std::{
41    fmt::{self, Display, Formatter},
42    io,
43    str::FromStr,
44};
45
46use bytes::Bytes;
47use ttpkit::Error as BaseError;
48
49#[cfg(feature = "server")]
50use self::server::OutgoingResponse;
51
52pub use ttpkit::{
53    self,
54    body::{self, Body},
55    error::CodecError,
56    header,
57};
58
59#[cfg(any(feature = "client", feature = "server"))]
60#[cfg_attr(docsrs, doc(cfg(any(feature = "client", feature = "server"))))]
61pub use ttpkit_url as url;
62
63pub use self::{
64    request::{Request, RequestHeader},
65    response::{Response, ResponseHeader, Status},
66};
67
68/// Inner error.
69#[derive(Debug)]
70enum InnerError {
71    Error(BaseError),
72
73    #[cfg(feature = "server")]
74    ErrorWithResponse(Box<dyn ErrorToResponse + Send + Sync>),
75}
76
77/// Error type.
78#[derive(Debug)]
79pub struct Error {
80    inner: InnerError,
81}
82
83impl Error {
84    /// Create a new error with a given message.
85    pub fn from_msg<T>(msg: T) -> Self
86    where
87        T: Into<String>,
88    {
89        Self {
90            inner: InnerError::Error(BaseError::from_msg(msg)),
91        }
92    }
93
94    /// Create a new error with a given message.
95    #[inline]
96    pub const fn from_static_msg(msg: &'static str) -> Self {
97        Self {
98            inner: InnerError::Error(BaseError::from_static_msg(msg)),
99        }
100    }
101
102    /// Create a new error from a given custom error.
103    pub fn from_other<T>(err: T) -> Self
104    where
105        T: Into<Box<dyn std::error::Error + Send + Sync>>,
106    {
107        Self {
108            inner: InnerError::Error(BaseError::from_cause(err)),
109        }
110    }
111
112    /// Create a new error from a given custom error.
113    #[cfg(feature = "server")]
114    #[cfg_attr(docsrs, doc(cfg(feature = "server")))]
115    pub fn from_other_with_response<T>(err: T) -> Self
116    where
117        T: ErrorToResponse + Send + Sync + 'static,
118    {
119        Self {
120            inner: InnerError::ErrorWithResponse(Box::new(err)),
121        }
122    }
123
124    /// Get error response (if supported).
125    #[cfg(feature = "server")]
126    #[cfg_attr(docsrs, doc(cfg(feature = "server")))]
127    pub fn to_response(&self) -> Option<OutgoingResponse> {
128        if let InnerError::ErrorWithResponse(err) = &self.inner {
129            Some(err.to_response())
130        } else {
131            None
132        }
133    }
134}
135
136impl Display for Error {
137    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
138        match &self.inner {
139            InnerError::Error(err) => Display::fmt(err, f),
140
141            #[cfg(feature = "server")]
142            InnerError::ErrorWithResponse(err) => Display::fmt(err, f),
143        }
144    }
145}
146
147impl std::error::Error for Error {
148    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
149        match &self.inner {
150            InnerError::Error(err) => err.source(),
151
152            #[cfg(feature = "server")]
153            InnerError::ErrorWithResponse(err) => err.source(),
154        }
155    }
156}
157
158impl From<io::Error> for Error {
159    #[inline]
160    fn from(err: io::Error) -> Self {
161        Self {
162            inner: InnerError::Error(BaseError::from_static_msg_and_cause("IO", err)),
163        }
164    }
165}
166
167impl From<ttpkit::Error> for Error {
168    #[inline]
169    fn from(err: ttpkit::Error) -> Self {
170        Self {
171            inner: InnerError::Error(err),
172        }
173    }
174}
175
176impl From<Error> for ttpkit::Error {
177    fn from(err: Error) -> Self {
178        match err.inner {
179            InnerError::Error(err) => err,
180
181            #[cfg(feature = "server")]
182            InnerError::ErrorWithResponse(_) => ttpkit::Error::from_cause(err),
183        }
184    }
185}
186
187/// Trait for errors that can generate an error response.
188#[cfg(feature = "server")]
189#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
190pub trait ErrorToResponse: std::error::Error {
191    /// Create a custom HTTP error response.
192    fn to_response(&self) -> OutgoingResponse;
193}
194
195/// A type placeholder for HTTP protocol.
196#[derive(Debug, Copy, Clone, Eq, PartialEq)]
197struct Protocol;
198
199impl AsRef<[u8]> for Protocol {
200    #[inline]
201    fn as_ref(&self) -> &[u8] {
202        b"HTTP"
203    }
204}
205
206impl TryFrom<Bytes> for Protocol {
207    type Error = Error;
208
209    fn try_from(value: Bytes) -> Result<Self, Self::Error> {
210        match value.as_ref() {
211            b"HTTP" => Ok(Self),
212            _ => Err(Error::from_static_msg("invalid protocol string")),
213        }
214    }
215}
216
217/// HTTP version.
218#[derive(Debug, Copy, Clone, Eq, PartialEq)]
219pub enum Version {
220    Version10,
221    Version11,
222}
223
224impl AsRef<[u8]> for Version {
225    #[inline]
226    fn as_ref(&self) -> &[u8] {
227        match *self {
228            Self::Version10 => b"1.0",
229            Self::Version11 => b"1.1",
230        }
231    }
232}
233
234impl AsRef<str> for Version {
235    #[inline]
236    fn as_ref(&self) -> &str {
237        match *self {
238            Self::Version10 => "1.0",
239            Self::Version11 => "1.1",
240        }
241    }
242}
243
244impl Display for Version {
245    #[inline]
246    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
247        f.write_str(self.as_ref())
248    }
249}
250
251impl TryFrom<Bytes> for Version {
252    type Error = Error;
253
254    fn try_from(value: Bytes) -> Result<Self, Self::Error> {
255        let v = match value.as_ref() {
256            b"1.0" => Self::Version10,
257            b"1.1" => Self::Version11,
258            _ => return Err(Error::from_static_msg("unsupported HTTP protocol version")),
259        };
260
261        Ok(v)
262    }
263}
264
265/// HTTP method.
266#[derive(Debug, Copy, Clone, Eq, PartialEq)]
267pub enum Method {
268    Options,
269    Head,
270    Get,
271    Post,
272    Put,
273    Delete,
274    Trace,
275    Connect,
276}
277
278impl AsRef<[u8]> for Method {
279    fn as_ref(&self) -> &[u8] {
280        match *self {
281            Self::Options => b"OPTIONS",
282            Self::Head => b"HEAD",
283            Self::Get => b"GET",
284            Self::Post => b"POST",
285            Self::Put => b"PUT",
286            Self::Delete => b"DELETE",
287            Self::Trace => b"TRACE",
288            Self::Connect => b"CONNECT",
289        }
290    }
291}
292
293impl AsRef<str> for Method {
294    #[inline]
295    fn as_ref(&self) -> &str {
296        match *self {
297            Self::Options => "OPTIONS",
298            Self::Head => "HEAD",
299            Self::Get => "GET",
300            Self::Post => "POST",
301            Self::Put => "PUT",
302            Self::Delete => "DELETE",
303            Self::Trace => "TRACE",
304            Self::Connect => "CONNECT",
305        }
306    }
307}
308
309impl Display for Method {
310    #[inline]
311    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
312        f.write_str(self.as_ref())
313    }
314}
315
316impl TryFrom<Bytes> for Method {
317    type Error = Error;
318
319    fn try_from(value: Bytes) -> Result<Self, Self::Error> {
320        let m = match value.as_ref() {
321            b"OPTIONS" => Self::Options,
322            b"HEAD" => Self::Head,
323            b"GET" => Self::Get,
324            b"POST" => Self::Post,
325            b"PUT" => Self::Put,
326            b"DELETE" => Self::Delete,
327            b"TRACE" => Self::Trace,
328            b"CONNECT" => Self::Connect,
329            _ => return Err(Error::from_static_msg("unsupported HTTP method")),
330        };
331
332        Ok(m)
333    }
334}
335
336/// Valid URL schemes.
337#[allow(clippy::upper_case_acronyms)]
338#[derive(Debug, Copy, Clone, Eq, PartialEq)]
339pub enum Scheme {
340    HTTP,
341    HTTPS,
342}
343
344impl Scheme {
345    /// Get default port for this URL scheme.
346    #[inline]
347    pub const fn default_port(self) -> u16 {
348        match self {
349            Scheme::HTTP => 80,
350            Scheme::HTTPS => 443,
351        }
352    }
353}
354
355impl AsRef<str> for Scheme {
356    #[inline]
357    fn as_ref(&self) -> &str {
358        match *self {
359            Self::HTTP => "http",
360            Self::HTTPS => "https",
361        }
362    }
363}
364
365impl Display for Scheme {
366    #[inline]
367    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
368        f.write_str(self.as_ref())
369    }
370}
371
372impl FromStr for Scheme {
373    type Err = Error;
374
375    fn from_str(method: &str) -> Result<Self, Self::Err> {
376        let scheme = match &method.to_lowercase() as &str {
377            "http" => Scheme::HTTP,
378            "https" => Scheme::HTTPS,
379            _ => return Err(Error::from_static_msg("invalid HTTP URL scheme")),
380        };
381
382        Ok(scheme)
383    }
384}