Skip to content

Commit 3ea504a

Browse files
committed
feat(http1): customizable error messages
1 parent b8affd8 commit 3ea504a

File tree

5 files changed

+148
-22
lines changed

5 files changed

+148
-22
lines changed

src/proto/h1/conn.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ use std::future::Future;
44
use std::io;
55
use std::marker::{PhantomData, Unpin};
66
use std::pin::Pin;
7+
#[cfg(feature = "server")]
8+
use std::sync::Arc;
79
use std::task::{Context, Poll};
810
#[cfg(feature = "server")]
911
use std::time::{Duration, Instant};
1012

1113
use crate::rt::{Read, Write};
14+
#[cfg(feature = "server")]
15+
use crate::server::conn::http1::Http1ErrorResponder;
1216
use bytes::{Buf, Bytes};
1317
use futures_core::ready;
1418
use http::header::{HeaderValue, CONNECTION, TE};
@@ -61,6 +65,8 @@ where
6165
#[cfg(feature = "server")]
6266
h1_header_read_timeout: None,
6367
#[cfg(feature = "server")]
68+
h1_error_responder: None,
69+
#[cfg(feature = "server")]
6470
h1_header_read_timeout_fut: None,
6571
#[cfg(feature = "server")]
6672
h1_header_read_timeout_running: false,
@@ -156,6 +162,11 @@ where
156162
self.state.date_header = false;
157163
}
158164

165+
#[cfg(feature = "server")]
166+
pub(crate) fn set_error_responder(&mut self, val: Arc<dyn Http1ErrorResponder>) {
167+
self.state.h1_error_responder = Some(val);
168+
}
169+
159170
pub(crate) fn into_inner(self) -> (I, Bytes) {
160171
self.io.into_inner()
161172
}
@@ -810,10 +821,16 @@ where
810821
if self.has_h2_prefix() {
811822
return Err(crate::Error::new_version_h2());
812823
}
813-
if let Some(msg) = T::on_error(&err) {
824+
825+
if let Some(msg) = T::on_error(
826+
&err,
827+
#[cfg(feature = "server")]
828+
&self.state.h1_error_responder,
829+
) {
814830
// Drop the cached headers so as to not trigger a debug
815831
// assert in `write_head`...
816832
self.state.cached_headers.take();
833+
debug!("writing head");
817834
self.write_head(msg, None);
818835
self.state.error = Some(err);
819836
return Ok(());
@@ -927,6 +944,8 @@ struct State {
927944
#[cfg(feature = "server")]
928945
h1_header_read_timeout: Option<Duration>,
929946
#[cfg(feature = "server")]
947+
h1_error_responder: Option<Arc<dyn Http1ErrorResponder>>,
948+
#[cfg(feature = "server")]
930949
h1_header_read_timeout_fut: Option<Pin<Box<dyn Sleep>>>,
931950
#[cfg(feature = "server")]
932951
h1_header_read_timeout_running: bool,

src/proto/h1/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ use httparse::ParserConfig;
55
use crate::body::DecodedLength;
66
use crate::proto::{BodyLength, MessageHead};
77

8+
#[cfg(feature = "server")]
9+
use crate::server::conn::http1::Http1ErrorResponder;
10+
#[cfg(feature = "server")]
11+
use std::sync::Arc;
12+
813
pub(crate) use self::conn::Conn;
914
pub(crate) use self::decode::Decoder;
1015
pub(crate) use self::dispatch::Dispatcher;
@@ -35,7 +40,10 @@ pub(crate) trait Http1Transaction {
3540
fn parse(bytes: &mut BytesMut, ctx: ParseContext<'_>) -> ParseResult<Self::Incoming>;
3641
fn encode(enc: Encode<'_, Self::Outgoing>, dst: &mut Vec<u8>) -> crate::Result<Encoder>;
3742

38-
fn on_error(err: &crate::Error) -> Option<MessageHead<Self::Outgoing>>;
43+
fn on_error(
44+
err: &crate::Error,
45+
#[cfg(feature = "server")] responder: &Option<Arc<dyn Http1ErrorResponder>>,
46+
) -> Option<MessageHead<Self::Outgoing>>;
3947

4048
fn is_client() -> bool {
4149
!Self::is_server()

src/proto/h1/role.rs

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use std::mem::MaybeUninit;
22

33
#[cfg(feature = "client")]
44
use std::fmt::{self, Write as _};
5+
#[cfg(feature = "server")]
6+
use std::sync::Arc;
57

68
use bytes::Bytes;
79
use bytes::BytesMut;
@@ -16,6 +18,8 @@ use smallvec::{smallvec, smallvec_inline, SmallVec};
1618
use crate::body::DecodedLength;
1719
#[cfg(feature = "server")]
1820
use crate::common::date;
21+
#[cfg(feature = "server")]
22+
use crate::error::Kind;
1923
use crate::error::Parse;
2024
use crate::ext::HeaderCaseMap;
2125
#[cfg(feature = "ffi")]
@@ -27,6 +31,8 @@ use crate::proto::h1::{
2731
#[cfg(feature = "client")]
2832
use crate::proto::RequestHead;
2933
use crate::proto::{BodyLength, MessageHead, RequestLine};
34+
#[cfg(feature = "server")]
35+
use crate::server::conn::http1::Http1ErrorResponder;
3036

3137
pub(crate) const DEFAULT_MAX_HEADERS: usize = 100;
3238
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
@@ -127,6 +133,30 @@ pub(crate) enum Client {}
127133
#[cfg(feature = "server")]
128134
pub(crate) enum Server {}
129135

136+
#[cfg(feature = "server")]
137+
pub(crate) fn default_error_response(kind: &Kind) -> Option<crate::Response<()>> {
138+
use crate::error::Kind;
139+
use crate::error::Parse;
140+
use http::StatusCode;
141+
let status = match kind {
142+
Kind::Parse(Parse::Method)
143+
| Kind::Parse(Parse::Header(_))
144+
| Kind::Parse(Parse::Uri)
145+
| Kind::Parse(Parse::Version) => StatusCode::BAD_REQUEST,
146+
Kind::Parse(Parse::TooLarge) => StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
147+
Kind::Parse(Parse::UriTooLong) => StatusCode::URI_TOO_LONG,
148+
_ => return None,
149+
};
150+
151+
debug!("building automatic response ({}) for parse error", status);
152+
let msg = MessageHead {
153+
subject: status,
154+
..Default::default()
155+
}
156+
.into_response(());
157+
Some(msg)
158+
}
159+
130160
#[cfg(feature = "server")]
131161
impl Http1Transaction for Server {
132162
type Incoming = RequestLine;
@@ -460,24 +490,19 @@ impl Http1Transaction for Server {
460490
ret.map(|()| encoder)
461491
}
462492

463-
fn on_error(err: &crate::Error) -> Option<MessageHead<Self::Outgoing>> {
464-
use crate::error::Kind;
465-
let status = match *err.kind() {
466-
Kind::Parse(Parse::Method)
467-
| Kind::Parse(Parse::Header(_))
468-
| Kind::Parse(Parse::Uri)
469-
| Kind::Parse(Parse::Version) => StatusCode::BAD_REQUEST,
470-
Kind::Parse(Parse::TooLarge) => StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
471-
Kind::Parse(Parse::UriTooLong) => StatusCode::URI_TOO_LONG,
472-
_ => return None,
473-
};
474-
475-
debug!("sending automatic response ({}) for parse error", status);
476-
let msg = MessageHead {
477-
subject: status,
478-
..Default::default()
479-
};
480-
Some(msg)
493+
fn on_error(
494+
err: &crate::Error,
495+
responder: &Option<Arc<dyn Http1ErrorResponder>>,
496+
) -> Option<MessageHead<Self::Outgoing>> {
497+
use crate::server::conn::http1::Http1ErrorReason;
498+
let reason = Http1ErrorReason::from_kind(err.kind());
499+
responder
500+
.as_ref()
501+
.map_or_else(
502+
|| default_error_response(err.kind()),
503+
|er| er.respond(&reason),
504+
)
505+
.map(|rsp| MessageHead::from_response(rsp))
481506
}
482507

483508
fn is_server() -> bool {
@@ -1216,7 +1241,10 @@ impl Http1Transaction for Client {
12161241
Ok(body)
12171242
}
12181243

1219-
fn on_error(_err: &crate::Error) -> Option<MessageHead<Self::Outgoing>> {
1244+
fn on_error(
1245+
_err: &crate::Error,
1246+
#[cfg(feature = "server")] _responder: &Option<Arc<dyn Http1ErrorResponder>>,
1247+
) -> Option<MessageHead<Self::Outgoing>> {
12201248
// we can't tell the server about any errors it creates
12211249
None
12221250
}

src/proto/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub(crate) enum Dispatched {
6060
Upgrade(crate::upgrade::Pending),
6161
}
6262

63-
#[cfg(all(feature = "client", feature = "http1"))]
63+
#[cfg(all(any(feature = "server", feature = "client"), feature = "http1"))]
6464
impl MessageHead<http::StatusCode> {
6565
fn into_response<B>(self, body: B) -> http::Response<B> {
6666
let mut res = http::Response::new(body);
@@ -70,4 +70,15 @@ impl MessageHead<http::StatusCode> {
7070
*res.extensions_mut() = self.extensions;
7171
res
7272
}
73+
74+
#[cfg(feature = "server")]
75+
fn from_response(response: http::Response<()>) -> Self {
76+
let (parts, _) = response.into_parts();
77+
Self {
78+
version: parts.version,
79+
subject: parts.status,
80+
headers: parts.headers,
81+
extensions: parts.extensions,
82+
}
83+
}
7384
}

src/server/conn/http1.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::sync::Arc;
88
use std::task::{Context, Poll};
99
use std::time::Duration;
1010

11+
use crate::error::Kind;
1112
use crate::rt::{Read, Write};
1213
use crate::upgrade::Upgraded;
1314
use bytes::Bytes;
@@ -70,6 +71,7 @@ pin_project_lite::pin_project! {
7071
#[derive(Clone, Debug)]
7172
pub struct Builder {
7273
h1_parser_config: httparse::ParserConfig,
74+
h1_error_responder: Option<Arc<dyn Http1ErrorResponder>>,
7375
timer: Time,
7476
h1_half_close: bool,
7577
h1_keep_alive: bool,
@@ -83,6 +85,49 @@ pub struct Builder {
8385
date_header: bool,
8486
}
8587

88+
/// Reason an error arose duing client request parsing
89+
#[non_exhaustive]
90+
#[derive(Debug)]
91+
pub enum Http1ErrorReason {
92+
/// Method in the request was invalid or malformed
93+
InvalidMethod,
94+
/// URI was invalid or malformed
95+
InvalidUri,
96+
/// Version was invalid or malformed
97+
InvalidVersion,
98+
/// Header line was invalid or malformed
99+
InvalidHeader,
100+
/// URI exceeded the server's maximum allowed length
101+
UriTooLong,
102+
/// Headers exceeded the server's maximum allowed size
103+
HeadersTooLarge,
104+
/// Internal hyper error occured during processing
105+
InternalError,
106+
}
107+
108+
impl Http1ErrorReason {
109+
pub(crate) fn from_kind(kind: &Kind) -> Self {
110+
use crate::error::Kind;
111+
use crate::error::Parse;
112+
match kind {
113+
Kind::Parse(Parse::Method) => Http1ErrorReason::InvalidMethod,
114+
Kind::Parse(Parse::Header(_)) => Http1ErrorReason::InvalidHeader,
115+
Kind::Parse(Parse::Uri) => Http1ErrorReason::InvalidUri,
116+
Kind::Parse(Parse::Version) => Http1ErrorReason::InvalidVersion,
117+
Kind::Parse(Parse::TooLarge) => Http1ErrorReason::HeadersTooLarge,
118+
Kind::Parse(Parse::UriTooLong) => Http1ErrorReason::UriTooLong,
119+
_ => Http1ErrorReason::InternalError,
120+
}
121+
}
122+
}
123+
124+
/// Customizable error responses for overring server defaults
125+
///
126+
pub trait Http1ErrorResponder: Send + Sync + std::fmt::Debug {
127+
/// Respond to some [`Http1ErrorReason`]
128+
fn respond(&self, cause: &Http1ErrorReason) -> Option<crate::Response<()>>;
129+
}
130+
86131
/// Deconstructed parts of a `Connection`.
87132
///
88133
/// This allows taking apart a `Connection` at a later time, in order to
@@ -233,6 +278,7 @@ impl Builder {
233278
pub fn new() -> Self {
234279
Self {
235280
h1_parser_config: Default::default(),
281+
h1_error_responder: None,
236282
timer: Time::Empty,
237283
h1_half_close: false,
238284
h1_keep_alive: true,
@@ -276,6 +322,16 @@ impl Builder {
276322
self
277323
}
278324

325+
/// Set error responder for this connection.
326+
///
327+
/// The error responder is used to generate custom error responses when the server encounters
328+
/// an error during request processing.
329+
///
330+
pub fn error_responder(&mut self, responder: Arc<dyn Http1ErrorResponder>) -> &mut Self {
331+
self.h1_error_responder = Some(responder);
332+
self
333+
}
334+
279335
/// Set whether HTTP/1 connections will silently ignored malformed header lines.
280336
///
281337
/// If this is enabled and a header line does not start with a valid header
@@ -471,6 +527,10 @@ impl Builder {
471527
conn.set_write_strategy_flatten();
472528
}
473529
}
530+
if let Some(responder) = &self.h1_error_responder {
531+
conn.set_error_responder(responder.clone());
532+
}
533+
474534
conn.set_flush_pipeline(self.pipeline_flush);
475535
if let Some(max) = self.max_buf_size {
476536
conn.set_max_buf_size(max);

0 commit comments

Comments
 (0)