Skip to main content

ntex/http/
error.rs

1//! HTTP-related errors.
2use std::{error, fmt, io, io::Write, string::FromUtf8Error};
3
4use ntex_h2::{self as h2};
5use ntex_http::{StatusCode, header};
6
7// Re-export for convenience.
8pub use crate::channel::Canceled;
9pub use ntex_http::error::Error as HttpError;
10
11use crate::error::Failure;
12use crate::http::{body::Body, response::Response};
13use crate::util::{BytesMut, Either, clone_io_error};
14
15/// An error that can be converted into an HTTP [`Response`].
16pub trait ResponseError: error::Error {
17    /// Creates an HTTP response for this error.
18    ///
19    /// The default response has status `500 Internal Server Error`, a
20    /// `text/plain; charset=utf-8` content type, and the error's
21    /// [`Display`](fmt::Display) representation as its body. Implementations
22    /// should override this method when that text is not suitable for clients.
23    fn error_response(&self) -> Response {
24        let mut resp = Response::new(StatusCode::INTERNAL_SERVER_ERROR);
25        let mut buf = BytesMut::new();
26        let _ = write!(&mut buf, "{self}");
27        resp.headers_mut().insert(
28            header::CONTENT_TYPE,
29            header::HeaderValue::from_static("text/plain; charset=utf-8"),
30        );
31        resp.set_body(Body::from(buf))
32    }
33}
34
35impl<T: ResponseError> ResponseError for &T {
36    fn error_response(&self) -> Response {
37        (*self).error_response()
38    }
39}
40
41impl<T: ResponseError> From<T> for Response {
42    fn from(err: T) -> Response {
43        let resp = err.error_response();
44        if resp.head().status == StatusCode::INTERNAL_SERVER_ERROR {
45            log::error!("Internal Server Error: {err:?}");
46        } else {
47            log::debug!("Error in response: {err:?}");
48        }
49        resp
50    }
51}
52
53/// Return `InternalServerError` for `HttpError`,
54/// Response generation can return `HttpError`, so it is internal error
55impl ResponseError for HttpError {}
56
57/// Return `InternalServerError` for `io::Error`
58impl ResponseError for io::Error {}
59
60/// `InternalServerError` for `JsonError`
61impl ResponseError for serde_json::error::Error {}
62
63/// Errors that can occur while encoding HTTP streams.
64#[derive(thiserror::Error, Clone, Debug)]
65pub enum EncodeError {
66    /// An invalid `HttpVersion`, such as `HTP/1.1`
67    #[error("Unsupported HTTP version specified, {0:?}")]
68    UnsupportedVersion(super::Version),
69
70    /// The encoded stream ended before the message was complete.
71    #[error("Unexpected end of bytes stream")]
72    UnexpectedEof,
73
74    /// A streaming request body has neither chunked coding nor `Content-Length`.
75    #[error("Request body length is unknown")]
76    UnknownLength,
77}
78
79/// Errors that can occur while parsing HTTP streams.
80#[derive(thiserror::Error, Copy, Clone, Debug, PartialEq, Eq, Hash)]
81pub enum DecodeError {
82    /// An invalid `Method`, such as `GE.T`.
83    #[error("Invalid Method specified")]
84    Method,
85    /// An invalid `Uri`, such as `exam ple.domain`.
86    #[error("Uri error")]
87    Uri,
88    /// An invalid `HttpVersion`, such as `HTP/1.1`
89    #[error("Invalid HTTP version specified")]
90    Version,
91    /// An invalid `Header`.
92    #[error("Invalid Header provided")]
93    Header,
94    /// The message contains more headers than the configured limit.
95    #[error("Maximum number of headers are received")]
96    MaxHeaders,
97    /// A message head is too large to be reasonable.
98    #[error("Message head is too large")]
99    TooLarge(usize),
100    /// A request or status line exceeds the configured limit.
101    #[error("Request or status line is too long")]
102    StartLineTooLong(usize),
103    /// A request applies a transfer coding other than `chunked`.
104    #[error("Unsupported transfer coding")]
105    UnsupportedTransferCoding,
106    /// A message reached EOF, but is not complete.
107    #[error("Message is incomplete")]
108    Incomplete,
109    /// An invalid `Status`, such as `1337 ELITE`.
110    #[error("Invalid Status provided")]
111    Status,
112    /// An `InvalidInput` occurred while trying to parse incoming stream.
113    #[error("`InvalidInput` occurred while trying to parse incoming stream: {0}")]
114    InvalidInput(&'static str),
115    /// Parsing a field as string failed
116    #[error("UTF8 error")]
117    Utf8,
118}
119
120impl From<urly::InvalidUrl> for DecodeError {
121    fn from(_: urly::InvalidUrl) -> DecodeError {
122        DecodeError::Uri
123    }
124}
125
126impl From<FromUtf8Error> for DecodeError {
127    fn from(_: FromUtf8Error) -> DecodeError {
128        DecodeError::Utf8
129    }
130}
131
132impl From<httparse::Error> for DecodeError {
133    fn from(err: httparse::Error) -> DecodeError {
134        match err {
135            httparse::Error::HeaderName
136            | httparse::Error::HeaderValue
137            | httparse::Error::NewLine
138            | httparse::Error::Token => DecodeError::Header,
139            httparse::Error::Status => DecodeError::Status,
140            httparse::Error::TooManyHeaders => DecodeError::TooLarge(0),
141            httparse::Error::Version => DecodeError::Version,
142        }
143    }
144}
145
146impl From<ntex_httparse::Error> for DecodeError {
147    fn from(err: ntex_httparse::Error) -> DecodeError {
148        match err {
149            ntex_httparse::Error::HeaderName
150            | ntex_httparse::Error::HeaderValue
151            | ntex_httparse::Error::NewLine
152            | ntex_httparse::Error::Token => DecodeError::Header,
153            ntex_httparse::Error::Status => DecodeError::Status,
154            ntex_httparse::Error::TooManyHeaders => DecodeError::TooLarge(0),
155            ntex_httparse::Error::Version => DecodeError::Version,
156        }
157    }
158}
159
160#[derive(thiserror::Error, Debug)]
161/// Errors that can occur while processing a payload.
162pub enum PayloadError {
163    /// A payload reached EOF, but is not complete.
164    #[error("A payload reached EOF, but is not complete. With error: {0:?}")]
165    Incomplete(#[source] Option<io::Error>),
166    /// Content encoding stream corruption
167    #[error("Cannot decode content-encoding.")]
168    EncodingCorrupted,
169    /// A payload reached size limit.
170    #[error("A payload reached size limit.")]
171    Overflow,
172    /// A payload length is unknown.
173    #[error("A payload length is unknown.")]
174    UnknownLength,
175    /// Http2 payload error
176    #[error("")]
177    Http2Payload(
178        #[from]
179        #[source]
180        h2::StreamError,
181    ),
182    /// Decode error
183    #[error("Decode error")]
184    Decode(
185        #[from]
186        #[source]
187        DecodeError,
188    ),
189    /// Io error
190    #[error("{0}")]
191    Io(
192        #[from]
193        #[source]
194        io::Error,
195    ),
196}
197
198impl Clone for PayloadError {
199    fn clone(&self) -> PayloadError {
200        match self {
201            PayloadError::Incomplete(err) => {
202                PayloadError::Incomplete(err.as_ref().map(clone_io_error))
203            }
204            PayloadError::EncodingCorrupted => PayloadError::EncodingCorrupted,
205            PayloadError::Overflow => PayloadError::Overflow,
206            PayloadError::UnknownLength => PayloadError::UnknownLength,
207            PayloadError::Http2Payload(err) => PayloadError::Http2Payload(*err),
208            PayloadError::Decode(err) => PayloadError::Decode(*err),
209            PayloadError::Io(err) => PayloadError::Io(clone_io_error(err)),
210        }
211    }
212}
213
214impl From<Either<PayloadError, io::Error>> for PayloadError {
215    fn from(err: Either<PayloadError, io::Error>) -> Self {
216        match err {
217            Either::Left(err) => err,
218            Either::Right(err) => PayloadError::Io(err),
219        }
220    }
221}
222
223#[derive(thiserror::Error, Clone, Debug)]
224/// Errors that can occur while dispatching HTTP requests.
225pub enum DispatchError {
226    /// Service error
227    #[error("Service error {0}")]
228    Service(Failure),
229
230    /// Control service error
231    #[error("Control service error")]
232    Control(Failure),
233}
234
235#[derive(thiserror::Error, Clone, Debug)]
236/// Errors that can occur while dispatching HTTP/2 requests.
237pub enum H2Error {
238    /// Too many consecutive empty non-final `DATA` frames
239    #[error("Too many consecutive empty DATA frames")]
240    EmptyDataFrames,
241}
242
243impl crate::error::ErrorDiagnostic for H2Error {
244    fn signature(&self) -> &'static str {
245        "ntex-http-h2error"
246    }
247}
248
249/// Errors that can occur while parsing a `Content-Type` header.
250#[derive(thiserror::Error, Copy, Clone, PartialEq, Eq, Hash, Debug)]
251pub enum ContentTypeError {
252    /// Cannot parse content type
253    #[error("Cannot parse content type")]
254    ParseError,
255    /// Unknown content encoding
256    #[error("Unknown content encoding")]
257    UnknownEncoding,
258    /// Unexpected Content-Type
259    #[error("Unexpected Content-Type")]
260    Unexpected,
261    /// Content-Type is expected
262    #[error("Content-Type is expected")]
263    Expected,
264}
265
266/// An error produced by a blocking operation.
267#[derive(thiserror::Error, Debug)]
268pub enum BlockingError<E: fmt::Debug> {
269    /// The blocking operation returned an error.
270    #[error("{0:?}")]
271    Error(#[source] E),
272    /// The thread pool stopped before completing the operation.
273    #[error("Thread pool is gone")]
274    Canceled,
275}
276
277impl From<crate::rt::JoinError> for PayloadError {
278    fn from(_: crate::rt::JoinError) -> Self {
279        PayloadError::Io(io::Error::new(
280            io::ErrorKind::Interrupted,
281            "Operation is canceled",
282        ))
283    }
284}
285
286impl From<crate::rt::BlockingError> for PayloadError {
287    fn from(_: crate::rt::BlockingError) -> Self {
288        PayloadError::Io(io::Error::new(
289            io::ErrorKind::Interrupted,
290            "Operation is canceled",
291        ))
292    }
293}
294
295impl From<BlockingError<io::Error>> for PayloadError {
296    fn from(err: BlockingError<io::Error>) -> Self {
297        match err {
298            BlockingError::Error(e) => PayloadError::Io(e),
299            BlockingError::Canceled => PayloadError::Io(io::Error::new(
300                io::ErrorKind::Interrupted,
301                "Operation is canceled",
302            )),
303        }
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use ntex_http::Error as HttpError;
311
312    #[test]
313    fn test_into_response() {
314        let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into();
315        let resp: Response = err.error_response();
316        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
317    }
318
319    #[test]
320    fn test_error_http_response() {
321        let orig = io::Error::other("other");
322        let resp: Response = orig.into();
323        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
324    }
325
326    #[test]
327    fn test_payload_error() {
328        let err: PayloadError = io::Error::other("DecodeError").into();
329        assert!(format!("{err}").contains("DecodeError"), "{err}");
330
331        let err: PayloadError = BlockingError::Canceled.into();
332        assert!(format!("{err}").contains("Operation is canceled"), "{err}");
333
334        let err: PayloadError = BlockingError::Error(io::Error::other("DecodeError")).into();
335        assert!(format!("{err}").contains("DecodeError"), "{err}");
336
337        let err = PayloadError::Incomplete(None);
338        assert_eq!(
339            format!("{err}"),
340            "A payload reached EOF, but is not complete. With error: None"
341        );
342    }
343
344    macro_rules! from {
345        ($from:expr => $error:pat) => {
346            match DecodeError::from($from) {
347                e @ $error => {
348                    assert!(format!("{e}").len() >= 5);
349                }
350                e => unreachable!("{e:?}"),
351            }
352        };
353    }
354
355    #[test]
356    fn test_from() {
357        from!(httparse::Error::HeaderName => DecodeError::Header);
358        from!(httparse::Error::HeaderName => DecodeError::Header);
359        from!(httparse::Error::HeaderValue => DecodeError::Header);
360        from!(httparse::Error::NewLine => DecodeError::Header);
361        from!(httparse::Error::Status => DecodeError::Status);
362        from!(httparse::Error::Token => DecodeError::Header);
363        from!(httparse::Error::TooManyHeaders => DecodeError::TooLarge(0));
364        from!(httparse::Error::Version => DecodeError::Version);
365        from!(ntex_httparse::Error::HeaderName => DecodeError::Header);
366        from!(ntex_httparse::Error::HeaderName => DecodeError::Header);
367        from!(ntex_httparse::Error::HeaderValue => DecodeError::Header);
368        from!(ntex_httparse::Error::NewLine => DecodeError::Header);
369        from!(ntex_httparse::Error::Status => DecodeError::Status);
370        from!(ntex_httparse::Error::Token => DecodeError::Header);
371        from!(ntex_httparse::Error::TooManyHeaders => DecodeError::TooLarge(0));
372        from!(ntex_httparse::Error::Version => DecodeError::Version);
373    }
374
375    #[test]
376    fn test_decode_error_from() {
377        let err = "".parse::<urly::Url>().unwrap_err();
378        assert!(matches!(DecodeError::from(err), DecodeError::Uri));
379        let err = String::from_utf8(vec![0xff]).unwrap_err();
380        assert!(matches!(DecodeError::from(err), DecodeError::Utf8));
381    }
382
383    #[test]
384    fn test_payload_error_clone() {
385        let errs = [
386            PayloadError::Incomplete(None),
387            PayloadError::Incomplete(Some(io::Error::other("inc"))),
388            PayloadError::EncodingCorrupted,
389            PayloadError::Overflow,
390            PayloadError::UnknownLength,
391            PayloadError::Http2Payload(ntex_h2::StreamError::Closed),
392            PayloadError::Decode(DecodeError::Method),
393            PayloadError::Io(io::Error::other("io")),
394        ];
395        for err in &errs {
396            assert_eq!(err.to_string(), err.clone().to_string());
397        }
398    }
399
400    #[test]
401    fn test_payload_error_conversions() {
402        let err = PayloadError::from(Either::<PayloadError, io::Error>::Left(
403            PayloadError::Overflow,
404        ));
405        assert!(matches!(err, PayloadError::Overflow));
406        let err = PayloadError::from(Either::<PayloadError, io::Error>::Right(io::Error::other(
407            "right",
408        )));
409        assert!(matches!(err, PayloadError::Io(ref e) if e.to_string() == "right"));
410
411        for err in [
412            PayloadError::from(crate::rt::JoinError),
413            PayloadError::from(crate::rt::BlockingError),
414        ] {
415            assert!(
416                matches!(err, PayloadError::Io(ref e) if e.kind() == io::ErrorKind::Interrupted)
417            );
418        }
419    }
420
421    #[test]
422    fn test_h2_error() {
423        use crate::error::ErrorDiagnostic;
424
425        let err = H2Error::EmptyDataFrames;
426        assert_eq!(err.signature(), "ntex-http-h2error");
427        assert_eq!(err.to_string(), "Too many consecutive empty DATA frames");
428    }
429}