Skip to content

Commit b9d2dfb

Browse files
committed
HttpError::AlreadyGotBody. Don't log error for disconnect. Add more tests.
1 parent d112057 commit b9d2dfb

File tree

5 files changed

+190
-11
lines changed

5 files changed

+190
-11
lines changed

src/content_type.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ impl ContentType {
5252
match self {
5353
ContentType::Css => "text/css; charset=UTF-8",
5454
ContentType::Csv => "text/csv; charset=UTF-8",
55-
ContentType::EventStream => "text/event-stream; charset=UTF-8",
55+
ContentType::EventStream => "text/event-stream",
5656
ContentType::FormUrlEncoded => "application/x-www-form-urlencoded; charset=UTF-8",
5757
ContentType::Gif => "image/gif",
5858
ContentType::Html => "text/html; charset=UTF-8",

src/http_conn.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ impl HttpConn {
178178
/// - we fail to read the request body
179179
pub async fn read_body_to_vec(&mut self) -> Result<Body, HttpError> {
180180
let result = {
181-
dbg!("read_body_to_vec");
181+
//dbg!("read_body_to_vec");
182182
match self.read_state {
183183
ReadState::Ready => return Err(HttpError::BodyNotAvailable),
184184
ReadState::Bytes(len_u64) => {
@@ -297,16 +297,23 @@ where
297297
//dbg!(&response);
298298
let response = match response {
299299
Response::GetBodyAndReprocess(max_len, mut req) => {
300+
if !req.body().is_pending() {
301+
return Err(HttpError::AlreadyGotBody);
302+
}
300303
let cache_dir = opt_cache_dir.ok_or(HttpError::CacheDirNotConfigured)?;
301304
if max_len < req.body.len() {
302305
//dbg!("returning HttpError::BodyTooLong");
303306
return Err(HttpError::BodyTooLong);
304307
}
305308
req.body = http_conn.read_body_to_file(cache_dir, max_len).await?;
306-
dbg!("request_handler", &req);
307-
request_handler.clone()(req).await
309+
//dbg!("request_handler", &req);
310+
match request_handler.clone()(req).await {
311+
Response::GetBodyAndReprocess(..) => return Err(HttpError::AlreadyGotBody),
312+
Response::Drop => return Err(HttpError::Disconnected),
313+
normal_response @ Response::Normal(..) => normal_response,
314+
}
308315
}
309-
Response::Drop => return Ok(()),
316+
Response::Drop => return Err(HttpError::Disconnected),
310317
normal_response @ Response::Normal(..) => normal_response,
311318
};
312319
//dbg!(&response);

src/http_error.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::io::ErrorKind;
44

55
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
66
pub enum HttpError {
7+
AlreadyGotBody,
78
BodyNotAvailable,
89
BodyNotRead,
910
BodyNotUtf8,
@@ -31,7 +32,8 @@ impl HttpError {
3132
#[must_use]
3233
pub fn is_server_error(&self) -> bool {
3334
match self {
34-
HttpError::BodyNotAvailable
35+
HttpError::AlreadyGotBody
36+
| HttpError::BodyNotAvailable
3537
| HttpError::BodyNotRead
3638
| HttpError::CacheDirNotConfigured
3739
| HttpError::DuplicateContentLengthHeader
@@ -59,6 +61,7 @@ impl HttpError {
5961
#[must_use]
6062
pub fn description(&self) -> String {
6163
match self {
64+
HttpError::AlreadyGotBody => "HttpError::AlreadyGotBody".to_string(),
6265
HttpError::BodyNotAvailable => "HttpError::BodyNotAvailable".to_string(),
6366
HttpError::BodyNotRead => "HttpError::BodyNotRead".to_string(),
6467
HttpError::BodyNotUtf8 => "HttpError::BodyNotUtf8".to_string(),
@@ -118,7 +121,8 @@ impl From<HttpError> for Response {
118121
HttpError::BodyTooLong => Response::text(413, "Uploaded data is too big."),
119122
HttpError::HeadTooLong => Response::text(431, e.description()),
120123
HttpError::UnsupportedProtocol => Response::text(505, e.description()),
121-
HttpError::BodyNotAvailable
124+
HttpError::AlreadyGotBody
125+
| HttpError::BodyNotAvailable
122126
| HttpError::BodyNotRead
123127
| HttpError::CacheDirNotConfigured
124128
| HttpError::DuplicateContentLengthHeader
@@ -137,7 +141,8 @@ impl From<HttpError> for std::io::Error {
137141
HttpError::Truncated => {
138142
std::io::Error::new(ErrorKind::UnexpectedEof, "Incomplete request")
139143
}
140-
HttpError::BodyNotUtf8
144+
HttpError::AlreadyGotBody
145+
| HttpError::BodyNotUtf8
141146
| HttpError::BodyTooLong
142147
| HttpError::HeadTooLong
143148
| HttpError::InvalidContentLength

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@
160160
//!
161161
//! # TO DO
162162
//! - Fix limitations above
163+
//! - Support [HEAD](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD)
164+
//! responses that have Content-Length set and no body.
163165
//!
164166
//! # Release Process
165167
//! 1. Edit `Cargo.toml` and bump version number.

tests/handler.rs

+168-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#![cfg(feature = "internals")]
22

3-
use crate::test_util::{assert_starts_with, TestServer};
4-
use beatrice::Response;
3+
use crate::test_util::{assert_starts_with, check_elapsed, TestServer};
4+
use beatrice::{ContentType, Response};
55
use serde_json::json;
6+
use std::io::Read;
7+
use std::time::{Duration, Instant};
68

79
mod test_util;
810

@@ -16,7 +18,7 @@ fn panics() {
1618
}
1719

1820
#[test]
19-
fn return_empty() {
21+
fn empty() {
2022
let server = TestServer::start(|_req| Response::new(200)).unwrap();
2123
assert_eq!(
2224
server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(),
@@ -54,6 +56,82 @@ fn text() {
5456
);
5557
}
5658

59+
#[test]
60+
fn with_status() {
61+
let server = TestServer::start(|_req| Response::new(200).with_status(201)).unwrap();
62+
assert_eq!(
63+
server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(),
64+
"HTTP/1.1 201 Created\r\n\r\n",
65+
);
66+
}
67+
68+
#[test]
69+
fn with_type() {
70+
let server =
71+
TestServer::start(|_req| Response::new(200).with_type(ContentType::EventStream)).unwrap();
72+
assert_eq!(
73+
server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(),
74+
"HTTP/1.1 200 OK\r\ncontent-type: text/event-stream\r\n\r\n",
75+
);
76+
}
77+
78+
#[test]
79+
fn with_type_and_body() {
80+
let server =
81+
TestServer::start(|_req| Response::text(200, "yo").with_type(ContentType::Markdown))
82+
.unwrap();
83+
assert_eq!(
84+
server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(),
85+
"HTTP/1.1 200 OK\r\ncontent-type: text/markdown; charset=UTF-8\r\ncontent-length: 2\r\n\r\nyo",
86+
);
87+
}
88+
89+
#[test]
90+
fn with_body() {
91+
let server = TestServer::start(|_req| Response::new(200).with_body("abc")).unwrap();
92+
assert_eq!(
93+
server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(),
94+
"HTTP/1.1 200 OK\r\ncontent-length: 3\r\n\r\nabc",
95+
);
96+
}
97+
98+
#[test]
99+
fn with_header() {
100+
let server = TestServer::start(|_req| Response::new(200).with_header("h1", "v1")).unwrap();
101+
assert_eq!(
102+
server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(),
103+
"HTTP/1.1 200 OK\r\nh1: v1\r\n\r\n",
104+
);
105+
}
106+
107+
#[test]
108+
fn with_duplicate_header() {
109+
let server = TestServer::start(|_req| {
110+
Response::new(200)
111+
.with_header("h1", "v1")
112+
.with_header("h1", "v2")
113+
})
114+
.unwrap();
115+
assert_eq!(
116+
server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(),
117+
"HTTP/1.1 200 OK\r\nh1: v2\r\n\r\n",
118+
);
119+
}
120+
121+
#[test]
122+
fn with_duplicate_header_different_case() {
123+
let server = TestServer::start(|_req| {
124+
Response::new(200)
125+
.with_header("h1", "v1")
126+
.with_header("H1", "v2")
127+
})
128+
.unwrap();
129+
assert_eq!(
130+
server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(),
131+
"HTTP/1.1 200 OK\r\nh1: v2\r\n\r\n",
132+
);
133+
}
134+
57135
#[test]
58136
fn method_not_allowed_405() {
59137
let server = TestServer::start(|_req| Response::method_not_allowed_405(&["GET"])).unwrap();
@@ -91,3 +169,90 @@ fn return_drop() {
91169
let server = TestServer::start(|_req| Response::Drop).unwrap();
92170
assert_eq!(server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(), "");
93171
}
172+
173+
#[test]
174+
fn small_body() {
175+
let server = TestServer::start(|req| {
176+
if req.body().is_pending() {
177+
return Response::GetBodyAndReprocess(100, req);
178+
} else {
179+
let len = req.body().reader().unwrap().bytes().count();
180+
Response::text(200, format!("body len={}", len))
181+
}
182+
})
183+
.unwrap();
184+
assert_eq!(
185+
server
186+
.exchange("M / HTTP/1.1\r\ncontent-length:3\r\n\r\nabc")
187+
.unwrap(),
188+
"HTTP/1.1 200 OK\r\ncontent-type: text/plain; charset=UTF-8\r\ncontent-length: 10\r\n\r\nbody len=3",
189+
);
190+
}
191+
192+
#[test]
193+
fn large_body() {
194+
let server = TestServer::start(|req| {
195+
if req.body().is_pending() {
196+
return Response::GetBodyAndReprocess(100, req);
197+
} else {
198+
let len = req.body().reader().unwrap().bytes().count();
199+
Response::text(200, format!("body len={}", len))
200+
}
201+
})
202+
.unwrap();
203+
assert_eq!(
204+
server
205+
.exchange(
206+
"M / HTTP/1.1\r\ncontent-length:65537\r\n\r\n"
207+
.chars()
208+
.chain(std::iter::repeat('a').take(65537))
209+
.collect::<String>()
210+
)
211+
.unwrap(),
212+
"HTTP/1.1 200 OK\r\ncontent-type: text/plain; charset=UTF-8\r\ncontent-length: 10\r\n\r\nbody len=3",
213+
);
214+
}
215+
216+
#[test]
217+
fn body_not_pending() {
218+
let server = TestServer::start(|req| Response::GetBodyAndReprocess(100, req)).unwrap();
219+
assert_eq!(
220+
server
221+
.exchange("M / HTTP/1.1\r\ncontent-length:3\r\n\r\nabc")
222+
.unwrap(),
223+
"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=UTF-8\r\ncontent-length: 21\r\n\r\nInternal server error",
224+
);
225+
}
226+
227+
#[test]
228+
fn already_got_body() {
229+
let server = TestServer::start(|req| Response::GetBodyAndReprocess(100, req)).unwrap();
230+
assert_eq!(
231+
server
232+
.exchange("M / HTTP/1.1\r\ncontent-length:3\r\nexpect:100-continue\r\n\r\nabc")
233+
.unwrap(),
234+
"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=UTF-8\r\ncontent-length: 21\r\n\r\nInternal server error",
235+
);
236+
}
237+
238+
#[test]
239+
fn fast_reply() {
240+
let server = TestServer::start(|_req| Response::new(200)).unwrap();
241+
let before = Instant::now();
242+
let reply = server.exchange("M / HTTP/1.1\r\n\r\n").unwrap();
243+
check_elapsed(before, 0..100).unwrap();
244+
assert_eq!(reply, "HTTP/1.1 200 OK\r\n\r\n",);
245+
}
246+
247+
#[test]
248+
fn slow_reply() {
249+
let server = TestServer::start(|_req| {
250+
std::thread::sleep(Duration::from_millis(100));
251+
Response::new(200)
252+
})
253+
.unwrap();
254+
let before = Instant::now();
255+
let reply = server.exchange("M / HTTP/1.1\r\n\r\n").unwrap();
256+
check_elapsed(before, 100..200).unwrap();
257+
assert_eq!(reply, "HTTP/1.1 200 OK\r\n\r\n",);
258+
}

0 commit comments

Comments
 (0)