irox_networking/
http.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2023 IROX Contributors
3
4use std::fmt::{Display, Formatter};
5use std::io::{Read, Write};
6use std::num::ParseIntError;
7use std::str::FromStr;
8
9use crate::error::Error;
10pub use client::*;
11pub use h2::*;
12pub use headers::*;
13use irox_enums::EnumIterItem;
14pub use request::*;
15pub use response::*;
16
17mod client;
18mod h2;
19mod headers;
20mod request;
21mod response;
22
23///
24/// Basic enumerated type to pick the HTTP protocol & port
25#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, EnumIterItem)]
26#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
27pub enum HttpProtocol {
28    HTTP,
29
30    #[default]
31    HTTPS,
32}
33
34impl HttpProtocol {
35    #[must_use]
36    pub const fn port(&self) -> u16 {
37        match self {
38            HttpProtocol::HTTP => 80,
39            HttpProtocol::HTTPS => 443,
40        }
41    }
42
43    #[must_use]
44    pub const fn name(&self) -> &'static str {
45        match self {
46            HttpProtocol::HTTP => "http",
47            HttpProtocol::HTTPS => "https",
48        }
49    }
50}
51
52impl FromStr for HttpProtocol {
53    type Err = Error;
54
55    fn from_str(s: &str) -> Result<Self, Self::Err> {
56        for item in HttpProtocol::iter_items() {
57            if item.name().to_lowercase() == s {
58                return Ok(item);
59            }
60        }
61        Error::unknown_scheme_err(format!("Unknown scheme: {s}"))
62    }
63}
64
65/// Methods to be used in HTTP requests
66#[derive(Debug, Clone, Eq, PartialEq)]
67pub enum HttpMethod {
68    /// The `CONNECT` method establishes a tunnel to the server identified by the target resource.
69    Connect,
70    /// The `DELETE` method deletes the specified resource
71    Delete,
72    /// The `GET` method requests a representation of the specified resource
73    Get,
74    /// The `HEAD` method asks for a response identical to a `GET` request, but without the
75    /// response body
76    Head,
77    /// The `OPTIONS` method describes the communication options for the target resource
78    Options,
79    /// The `PATCH` method applies partial modifications to a resource
80    Patch,
81    /// The `POST` method submits an entity to the specified resource, often causing a change in
82    /// state or side effects on the server
83    Post,
84    /// The `PUT` method replaces all current representations of the target resource with the
85    /// request payload
86    Put,
87    /// The `TRACE` method performs a message loop-back test along the path to the target resource
88    Trace,
89
90    /// Other/Unknown method
91    Other(String),
92}
93
94impl Display for HttpMethod {
95    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
96        write!(
97            f,
98            "{}",
99            match self {
100                HttpMethod::Connect => "CONNECT",
101                HttpMethod::Delete => "DELETE",
102                HttpMethod::Get => "GET",
103                HttpMethod::Head => "HEAD",
104                HttpMethod::Options => "OPTIONS",
105                HttpMethod::Patch => "PATCH",
106                HttpMethod::Post => "POST",
107                HttpMethod::Put => "PUT",
108                HttpMethod::Trace => "TRACE",
109                HttpMethod::Other(o) => o,
110            }
111        )
112    }
113}
114
115/// The general grouping of HTTP Status Codes
116#[derive(Debug, Copy, Clone, Eq, PartialEq)]
117pub enum HttpCodeType {
118    /// 1xx series, Informational
119    Info,
120    /// 2xx series, Successful
121    Success,
122    /// 3xx series, Redirects
123    Redirect,
124    /// 4xx series, Client Errors
125    ClientError,
126    /// 5xx series, Server Errors
127    ServerError,
128    /// Others that aren't above
129    UnknownOther,
130}
131
132#[allow(non_camel_case_types)]
133#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
134#[non_exhaustive]
135pub enum HttpCodes {
136    /// This interim response indicates that the client should continue the request or ignore the
137    /// response if the request is already finished.
138    Info_100_Continue,
139    /// This code is sent in response to an Upgrade request header from the client and indicates the
140    /// protocol the server is switching to.
141    Info_101_SwitchingProtocols,
142    /// This code indicates that the server has received and is processing the request, but no
143    /// response is available yet.
144    Info_102_Processing,
145    /// This status code is primarily intended to be used with the Link header, letting the user
146    /// agent start preloading resources while the server prepares a response or preconnect to an
147    /// origin from which the page will need resources.
148    Info_103_EarlyHints,
149
150    /// The request succeeded
151    Success_200_Ok,
152    /// The request succeeded, and a new resource was created as a result.
153    Success_201_Created,
154    /// The request has been received but not yet acted upon
155    Success_202_Accepted,
156    /// This response code means the returned metadata is not exactly the same as is available from
157    /// the origin server, but is collected from a local or a third-party copy.
158    Success_203_NotAuthoritative,
159    /// There is no content to send for this request, but the headers may be useful
160    Success_204_NoContent,
161    /// Tells the user agent to reset the document which sent this request.
162    Success_205_ResetContent,
163    /// This response code is used when the Range header is sent from the client to request only
164    /// part of a resource.
165    Success_206_PartialContent,
166    /// Conveys information about multiple resources, for situations where multiple status codes
167    /// might be appropriate.
168    Success_207_MultiStatus,
169    /// Used inside a `<dav:propstat>` response element to avoid repeatedly enumerating the internal
170    /// members of multiple bindings to the same collection.
171    Success_208_AlreadyReported,
172    /// The server has fulfilled a GET request for the resource, and the response is a
173    /// representation of the result of one or more instance-manipulations applied to the current
174    /// instance.
175    Success_226_IMUsed,
176
177    /// The request has more than one possible response
178    Redirect_300_MultipleChoices,
179    /// The URL of the requested resource has been changed permanently
180    Redirect_301_MovedPermanently,
181    /// This response code means that the URI of requested resource has been changed temporarily
182    Redirect_302_Found,
183    /// The server sent this response to direct the client to get the requested resource at another
184    /// URI with a GET request.
185    Redirect_303_SeeOther,
186    /// This is used for caching purposes. It tells the client that the response has not been
187    /// modified
188    Redirect_304_NotModified,
189    /// Defined in a previous version of the HTTP specification to indicate that a requested
190    /// response must be accessed by a proxy
191    Redirect_305_UseProxy,
192    /// This response code is no longer used; it is just reserved
193    Redirect_306_Unused,
194    /// The server sends this response to direct the client to get the requested resource at another
195    /// URI with the same method that was used in the prior request
196    Redirect_307_Temporary,
197    /// This means that the resource is now permanently located at another URI, specified by the
198    /// Location: HTTP Response header.
199    Redirect_308_PermanentRedirect,
200
201    /// The server cannot or will not process the request due to something that is perceived to be
202    /// a client error
203    ClientError_400_BadRequest,
204    /// Although the HTTP standard specifies "unauthorized", semantically this response means
205    /// "unauthenticated".
206    ClientError_401_Unauthorized,
207    /// This response code is reserved for future use. The initial aim for creating this code was
208    /// using it for digital payment systems, however this status code is used very rarely and no
209    /// standard convention exists.
210    ClientError_402_PaymentRequired,
211    /// The client does not have access rights to the content
212    ClientError_403_Forbidden,
213    /// The server cannot find the requested resource
214    ClientError_404_NotFound,
215    /// The request method is known by the server but is not supported by the target resource
216    ClientError_405_MethodNotAllowed,
217    /// This response is sent when the web server, after performing server-driven content
218    /// negotiation, doesn't find any content that conforms to the criteria given by the user agent
219    ClientError_406_NotAcceptable,
220    /// This is similar to 401 Unauthorized but authentication is needed to be done by a proxy.
221    ClientError_407_ProxyAuthenticationRequired,
222    /// This response is sent on an idle connection by some servers, even without any previous
223    /// request by the client. It means that the server would like to shut down this unused
224    /// connection.
225    ClientError_408_RequestTimeout,
226    /// This response is sent when a request conflicts with the current state of the server.
227    ClientError_409_Conflict,
228    /// This response is sent when the requested content has been permanently deleted from server,
229    /// with no forwarding address
230    ClientError_410_Gone,
231    /// Server rejected the request because the Content-Length header field is not defined and the
232    /// server requires it.
233    ClientError_411_LengthRequired,
234    /// The client has indicated preconditions in its headers which the server does not meet.
235    ClientError_412_PreconditionFailed,
236    /// Request entity is larger than limits defined by server
237    ClientError_413_PayloadTooLarge,
238    /// The URI requested by the client is longer than the server is willing to interpret.
239    ClientError_414_URITooLong,
240    /// The media format of the requested data is not supported by the server, so the server is
241    /// rejecting the request.
242    ClientError_415_UnsupportedMediaType,
243    /// The range specified by the Range header field in the request cannot be fulfilled
244    ClientError_416_RangeNotSatisfiable,
245    /// This response code means the expectation indicated by the Expect request header field cannot
246    /// be met by the server.
247    ClientError_417_ExpectationFailed,
248    /// The server refuses the attempt to brew coffee with a teapot.
249    ClientError_418_ImATeapot,
250    /// The request was directed at a server that is not able to produce a response
251    ClientError_421_MisdirectedRequest,
252    /// The request was well-formed but was unable to be followed due to semantic errors.
253    ClientError_422_UnprocessableContent,
254    /// The resource that is being accessed is locked.
255    ClientError_423_Locked,
256    /// The request failed due to failure of a previous request.
257    ClientError_424_FailedDependency,
258    /// Indicates that the server is unwilling to risk processing a request that might be replayed.
259    ClientError_425_TooEarly,
260    /// The server refuses to perform the request using the current protocol but might be willing to
261    /// do so after the client upgrades to a different protocol
262    ClientError_426_UpgradeRequired,
263    /// The origin server requires the request to be conditional. This response is intended to
264    /// prevent the 'lost update' problem, where a client GETs a resource's state, modifies it and
265    /// PUTs it back to the server, when meanwhile a third party has modified the state on the
266    /// server, leading to a conflict.
267    ClientError_428_PreconditionRequired,
268    /// The user has sent too many requests in a given amount of time ("rate limiting").
269    ClientError_429_TooManyRequests,
270    /// The server is unwilling to process the request because its header fields are too large
271    ClientError_431_RequestHeaderFieldsTooLarge,
272    /// The user agent requested a resource that cannot legally be provided, such as a web page
273    /// censored by a government.
274    ClientError_451_UnavailableForLegalReasons,
275
276    /// The server has encountered a situation it does not know how to handle
277    ServerError_500_InternalServerError,
278    /// The request method is not supported by the server and cannot be handled. The only methods
279    /// that servers are required to support (and therefore that must not return this code) are
280    /// `GET` and `HEAD`
281    ServerError_501_NotImplemented,
282    /// This error response means that the server, while working as a gateway to get a response
283    /// needed to handle the request, got an invalid response.
284    ServerError_502_BadGateway,
285    /// The server is not ready to handle the request. Common causes are a server that is down for
286    /// maintenance or that is overloaded
287    ServerError_503_ServiceUnavailable,
288    /// This error response is given when the server is acting as a gateway and cannot get a
289    /// response in time
290    ServerError_504_GatewayTimeout,
291    /// The HTTP version used in the request is not supported by the server.
292    ServerError_505_HTTPVersionNotSupported,
293    /// The server has an internal configuration error: the chosen variant resource is configured to
294    /// engage in transparent content negotiation itself, and is therefore not a proper end point in
295    /// the negotiation process
296    ServerError_506_VariantAlsoNegotiates,
297    /// The method could not be performed on the resource because the server is unable to store the
298    /// representation needed to successfully complete the request
299    ServerError_507_InsufficientStorage,
300    /// The server detected an infinite loop while processing the request
301    ServerError_508_LoopDetected,
302    /// Further extensions to the request are required for the server to fulfill it
303    ServerError_510_NotExtended,
304    /// Indicates that the client needs to authenticate to gain network access
305    ServerError_511_NetworkAuthenticationRequired,
306
307    /// Unknown/other code
308    UnknownOther(u16),
309}
310
311impl HttpCodes {
312    /// Returns the numeric HTTP status code
313    #[must_use]
314    pub fn code(&self) -> u16 {
315        match self {
316            HttpCodes::Info_100_Continue => 100,
317            HttpCodes::Info_101_SwitchingProtocols => 101,
318            HttpCodes::Info_102_Processing => 102,
319            HttpCodes::Info_103_EarlyHints => 103,
320
321            HttpCodes::Success_200_Ok => 200,
322            HttpCodes::Success_201_Created => 201,
323            HttpCodes::Success_202_Accepted => 202,
324            HttpCodes::Success_203_NotAuthoritative => 203,
325            HttpCodes::Success_204_NoContent => 204,
326            HttpCodes::Success_205_ResetContent => 205,
327            HttpCodes::Success_206_PartialContent => 206,
328            HttpCodes::Success_207_MultiStatus => 207,
329            HttpCodes::Success_208_AlreadyReported => 208,
330            HttpCodes::Success_226_IMUsed => 226,
331
332            HttpCodes::Redirect_300_MultipleChoices => 300,
333            HttpCodes::Redirect_301_MovedPermanently => 301,
334            HttpCodes::Redirect_302_Found => 302,
335            HttpCodes::Redirect_303_SeeOther => 303,
336            HttpCodes::Redirect_304_NotModified => 304,
337            HttpCodes::Redirect_305_UseProxy => 305,
338            HttpCodes::Redirect_306_Unused => 306,
339            HttpCodes::Redirect_307_Temporary => 307,
340            HttpCodes::Redirect_308_PermanentRedirect => 308,
341
342            HttpCodes::ClientError_400_BadRequest => 400,
343            HttpCodes::ClientError_401_Unauthorized => 401,
344            HttpCodes::ClientError_402_PaymentRequired => 402,
345            HttpCodes::ClientError_403_Forbidden => 403,
346            HttpCodes::ClientError_404_NotFound => 404,
347            HttpCodes::ClientError_405_MethodNotAllowed => 405,
348            HttpCodes::ClientError_406_NotAcceptable => 406,
349            HttpCodes::ClientError_407_ProxyAuthenticationRequired => 407,
350            HttpCodes::ClientError_408_RequestTimeout => 408,
351            HttpCodes::ClientError_409_Conflict => 409,
352            HttpCodes::ClientError_410_Gone => 410,
353            HttpCodes::ClientError_411_LengthRequired => 411,
354            HttpCodes::ClientError_412_PreconditionFailed => 412,
355            HttpCodes::ClientError_413_PayloadTooLarge => 413,
356            HttpCodes::ClientError_414_URITooLong => 414,
357            HttpCodes::ClientError_415_UnsupportedMediaType => 415,
358            HttpCodes::ClientError_416_RangeNotSatisfiable => 416,
359            HttpCodes::ClientError_417_ExpectationFailed => 417,
360            HttpCodes::ClientError_418_ImATeapot => 418,
361            HttpCodes::ClientError_421_MisdirectedRequest => 421,
362            HttpCodes::ClientError_422_UnprocessableContent => 422,
363            HttpCodes::ClientError_423_Locked => 423,
364            HttpCodes::ClientError_424_FailedDependency => 424,
365            HttpCodes::ClientError_425_TooEarly => 425,
366            HttpCodes::ClientError_426_UpgradeRequired => 426,
367            HttpCodes::ClientError_428_PreconditionRequired => 427,
368            HttpCodes::ClientError_429_TooManyRequests => 429,
369            HttpCodes::ClientError_431_RequestHeaderFieldsTooLarge => 431,
370            HttpCodes::ClientError_451_UnavailableForLegalReasons => 451,
371
372            HttpCodes::ServerError_500_InternalServerError => 500,
373            HttpCodes::ServerError_501_NotImplemented => 501,
374            HttpCodes::ServerError_502_BadGateway => 502,
375            HttpCodes::ServerError_503_ServiceUnavailable => 503,
376            HttpCodes::ServerError_504_GatewayTimeout => 504,
377            HttpCodes::ServerError_505_HTTPVersionNotSupported => 505,
378            HttpCodes::ServerError_506_VariantAlsoNegotiates => 506,
379            HttpCodes::ServerError_507_InsufficientStorage => 507,
380            HttpCodes::ServerError_508_LoopDetected => 508,
381            HttpCodes::ServerError_510_NotExtended => 510,
382            HttpCodes::ServerError_511_NetworkAuthenticationRequired => 511,
383
384            HttpCodes::UnknownOther(v) => *v,
385        }
386    }
387
388    /// Returns the HTTP Code Classification/Type
389    pub fn code_type(&self) -> HttpCodeType {
390        match self.code() {
391            100..=199 => HttpCodeType::Info,
392            200..=299 => HttpCodeType::Success,
393            300..=399 => HttpCodeType::Redirect,
394            400..=499 => HttpCodeType::ClientError,
395            500..=599 => HttpCodeType::ServerError,
396            _v => HttpCodeType::UnknownOther,
397        }
398    }
399}
400
401impl FromStr for HttpCodes {
402    type Err = ParseIntError;
403
404    fn from_str(value: &str) -> Result<Self, Self::Err> {
405        let val = u16::from_str(value)?;
406        Ok(match val {
407            100 => HttpCodes::Info_100_Continue,
408            101 => HttpCodes::Info_101_SwitchingProtocols,
409            102 => HttpCodes::Info_102_Processing,
410            103 => HttpCodes::Info_103_EarlyHints,
411
412            200 => HttpCodes::Success_200_Ok,
413            201 => HttpCodes::Success_201_Created,
414            202 => HttpCodes::Success_202_Accepted,
415            203 => HttpCodes::Success_203_NotAuthoritative,
416            204 => HttpCodes::Success_204_NoContent,
417            205 => HttpCodes::Success_205_ResetContent,
418            206 => HttpCodes::Success_206_PartialContent,
419            207 => HttpCodes::Success_207_MultiStatus,
420            208 => HttpCodes::Success_208_AlreadyReported,
421            226 => HttpCodes::Success_226_IMUsed,
422
423            300 => HttpCodes::Redirect_300_MultipleChoices,
424            301 => HttpCodes::Redirect_301_MovedPermanently,
425            302 => HttpCodes::Redirect_302_Found,
426            303 => HttpCodes::Redirect_303_SeeOther,
427            304 => HttpCodes::Redirect_304_NotModified,
428            305 => HttpCodes::Redirect_305_UseProxy,
429            306 => HttpCodes::Redirect_306_Unused,
430            307 => HttpCodes::Redirect_307_Temporary,
431            308 => HttpCodes::Redirect_308_PermanentRedirect,
432
433            400 => HttpCodes::ClientError_400_BadRequest,
434            401 => HttpCodes::ClientError_401_Unauthorized,
435            402 => HttpCodes::ClientError_402_PaymentRequired,
436            403 => HttpCodes::ClientError_403_Forbidden,
437            404 => HttpCodes::ClientError_404_NotFound,
438            405 => HttpCodes::ClientError_405_MethodNotAllowed,
439            406 => HttpCodes::ClientError_406_NotAcceptable,
440            407 => HttpCodes::ClientError_407_ProxyAuthenticationRequired,
441            408 => HttpCodes::ClientError_408_RequestTimeout,
442            409 => HttpCodes::ClientError_409_Conflict,
443            410 => HttpCodes::ClientError_410_Gone,
444            411 => HttpCodes::ClientError_411_LengthRequired,
445            412 => HttpCodes::ClientError_412_PreconditionFailed,
446            413 => HttpCodes::ClientError_413_PayloadTooLarge,
447            414 => HttpCodes::ClientError_414_URITooLong,
448            415 => HttpCodes::ClientError_415_UnsupportedMediaType,
449            416 => HttpCodes::ClientError_416_RangeNotSatisfiable,
450            417 => HttpCodes::ClientError_417_ExpectationFailed,
451            418 => HttpCodes::ClientError_418_ImATeapot,
452            421 => HttpCodes::ClientError_421_MisdirectedRequest,
453            422 => HttpCodes::ClientError_422_UnprocessableContent,
454            423 => HttpCodes::ClientError_423_Locked,
455            424 => HttpCodes::ClientError_424_FailedDependency,
456            425 => HttpCodes::ClientError_425_TooEarly,
457            426 => HttpCodes::ClientError_426_UpgradeRequired,
458            427 => HttpCodes::ClientError_428_PreconditionRequired,
459            429 => HttpCodes::ClientError_429_TooManyRequests,
460            431 => HttpCodes::ClientError_431_RequestHeaderFieldsTooLarge,
461            451 => HttpCodes::ClientError_451_UnavailableForLegalReasons,
462
463            500 => HttpCodes::ServerError_500_InternalServerError,
464            501 => HttpCodes::ServerError_501_NotImplemented,
465            502 => HttpCodes::ServerError_502_BadGateway,
466            503 => HttpCodes::ServerError_503_ServiceUnavailable,
467            504 => HttpCodes::ServerError_504_GatewayTimeout,
468            505 => HttpCodes::ServerError_505_HTTPVersionNotSupported,
469            506 => HttpCodes::ServerError_506_VariantAlsoNegotiates,
470            507 => HttpCodes::ServerError_507_InsufficientStorage,
471            508 => HttpCodes::ServerError_508_LoopDetected,
472            510 => HttpCodes::ServerError_510_NotExtended,
473            511 => HttpCodes::ServerError_511_NetworkAuthenticationRequired,
474            e => HttpCodes::UnknownOther(e),
475        })
476    }
477}
478
479#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
480pub enum HttpVersion {
481    Http1_0,
482
483    #[default]
484    Http1_1,
485    Http2,
486    Http3,
487}
488
489impl Display for HttpVersion {
490    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
491        write!(
492            f,
493            "{}",
494            match self {
495                HttpVersion::Http1_0 => "HTTP/1.0",
496                HttpVersion::Http1_1 => "HTTP/1.1",
497                HttpVersion::Http2 => "h2",
498                HttpVersion::Http3 => "h3",
499            }
500        )
501    }
502}
503
504pub enum HttpBody {
505    Empty,
506    Read(Box<dyn Read>),
507    String(String),
508    Bytes(Vec<u8>),
509}
510
511impl HttpBody {
512    pub fn write_to<T: Write>(self, out: &mut T) -> Result<(), std::io::Error> {
513        match self {
514            HttpBody::Empty => {}
515            HttpBody::Read(mut read) => {
516                std::io::copy(read.as_mut(), out)?;
517            }
518            HttpBody::String(s) => {
519                out.write_all(s.as_bytes())?;
520            }
521            HttpBody::Bytes(b) => {
522                out.write_all(&b)?;
523            }
524        }
525        Ok(())
526    }
527}