Skip to content

Commit a876f06

Browse files
committed
Close connection on 5xx error.
1 parent 93b7d92 commit a876f06

File tree

6 files changed

+31
-12
lines changed

6 files changed

+31
-12
lines changed

Readme.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ Functions Expressions Impls Traits Methods Dependency
143143
0/0 14/14 1/1 0/0 0/0 ☢️ │ │ └── tracing 0.1.40
144144
0/0 11/191 0/0 0/0 2/2 ☢️ │ │ ├── pin-project-lite 0.2.14
145145
0/0 96/96 5/5 0/0 2/2 ☢️ │ │ └── tracing-core 0.1.32
146+
0/0 0/117 0/9 0/0 0/4 ❓ │ │ └── once_cell 1.20.2
146147
0/0 0/0 0/0 0/0 0/0 ❓ │ └── futures-lite 2.3.0
147148
0/0 0/0 0/0 0/0 0/0 🔒 ├── async-net 2.0.0
148149
0/0 68/114 19/22 1/1 4/8 ☢️ │ ├── async-io 2.3.4
@@ -226,16 +227,17 @@ Functions Expressions Impls Traits Methods Dependency
226227
0/0 8/8 0/0 0/0 0/0 ☢️ ├── percent-encoding 2.3.1
227228
0/0 5/5 0/0 0/0 0/0 ☢️ └── serde 1.0.210
228229
229-
101/547 7066/13878 105/144 3/6 187/322
230+
101/547 7066/13995 105/153 3/6 187/326
230231
231232
```
232233
# Alternatives
233234
See [rust-webserver-comparison.md](https://github.com/mleonhard/servlin/blob/main/rust-webserver-comparison.md).
234235

235236
# Changelog
236237
- v0.7.0 2024-11-06
237-
- `log_request_and_response` to log duration_ms tag.
238+
- `log_request_and_response` to log `duration_ms` tag.
238239
- Fix typo in function name `Response::internal_server_errror_500`.
240+
- Close connection on 5xx error.
239241
- v0.6.1 2024-11-03 - Implement `Into<TagList>` for arrays.
240242
- v0.6.0 2024-11-02
241243
- Remove `servlin::reexports` module.

src/http_conn.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl HttpConn {
5656

5757
#[must_use]
5858
pub fn is_ready(&self) -> bool {
59-
self.read_state == ReadState::Head
59+
self.read_state == ReadState::Head && self.write_state == WriteState::None
6060
}
6161

6262
pub fn shutdown_write(&mut self) {
@@ -243,11 +243,15 @@ impl HttpConn {
243243
WriteState::Shutdown => return Err(HttpError::Disconnected),
244244
}
245245
let mut write_counter = AsyncWriteCounter::new(&mut self.stream);
246-
let result = write_http_response(&mut write_counter, response).await;
246+
let close = (500..=599).contains(&response.code);
247+
let result = write_http_response(&mut write_counter, response, close).await;
247248
if result.is_ok() {
248249
if !response.is_1xx() {
249250
self.write_state = WriteState::None;
250251
}
252+
if close {
253+
self.shutdown_write();
254+
}
251255
} else if write_counter.num_bytes_written() > 0 {
252256
self.shutdown_write();
253257
}

src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@
102102
//!
103103
//! # Changelog
104104
//! - v0.7.0 2024-11-06
105-
//! - `log_request_and_response` to log duration_ms tag.
105+
//! - `log_request_and_response` to log `duration_ms` tag.
106106
//! - Fix typo in function name `Response::internal_server_errror_500`.
107+
//! - Close connection on 5xx error.
107108
//! - v0.6.1 2024-11-03 - Implement `Into<TagList>` for arrays.
108109
//! - v0.6.0 2024-11-02
109110
//! - Remove `servlin::reexports` module.

src/log/mod.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,5 @@ pub fn log_request_and_response<F: FnOnce(Request) -> Result<Response, Error>>(
140140
add_thread_local_log_tags_from_request(&req);
141141
let handler_result = f(req);
142142
add_thread_local_log_tag("duration_ms", before.elapsed().as_millis());
143-
let log_result = log_response(handler_result);
144-
log_result
143+
log_response(handler_result)
145144
}

src/response.rs

+4
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ pub fn reason_phrase(code: u16) -> &'static str {
468468
pub async fn write_http_response(
469469
mut writer: impl AsyncWrite + Unpin,
470470
response: &Response,
471+
close: bool,
471472
) -> Result<(), HttpError> {
472473
//dbg!("write_http_response", &response);
473474
if !response.is_normal() {
@@ -494,6 +495,9 @@ pub async fn write_http_response(
494495
)
495496
.unwrap();
496497
}
498+
if close {
499+
write!(head_bytes, "connection: close\r\n",).unwrap();
500+
}
497501
if let Some(body_len) = response.body.len() {
498502
if response.headers.get_only("content-length").is_some() {
499503
return Err(HttpError::DuplicateContentLengthHeader);

tests/handler.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ fn panics() {
2929
let server = TestServer::start(|_req| panic!("ignore this panic")).unwrap();
3030
assert_eq!(
3131
server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(),
32-
"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=UTF-8\r\ncontent-length: 12\r\n\r\nServer error",
32+
"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=UTF-8\r\nconnection: close\r\ncontent-length: 12\r\n\r\nServer error",
3333
);
3434
}
3535

@@ -149,6 +149,15 @@ fn method_not_allowed_405() {
149149
);
150150
}
151151

152+
#[test]
153+
fn connection_close_on_5xx() {
154+
let server = TestServer::start(|_req| Response::new(500)).unwrap();
155+
assert_eq!(
156+
server.exchange("M / HTTP/1.1\r\n\r\n").unwrap(),
157+
"HTTP/1.1 500 Internal Server Error\r\nconnection: close\r\ncontent-length: 0\r\n\r\n",
158+
);
159+
}
160+
152161
#[test]
153162
fn duplicate_content_type_header() {
154163
let server = TestServer::start(|_req| {
@@ -228,7 +237,7 @@ fn body_not_pending() {
228237
server
229238
.exchange("M / HTTP/1.1\r\ncontent-length:3\r\n\r\nabc")
230239
.unwrap(),
231-
"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",
240+
"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=UTF-8\r\nconnection: close\r\ncontent-length: 21\r\n\r\nInternal server error",
232241
);
233242
}
234243

@@ -237,7 +246,7 @@ fn already_got_body() {
237246
let server = TestServer::start(|_req| Response::get_body_and_reprocess(70_000)).unwrap();
238247
assert_eq!(
239248
server.exchange(req_with_len(66_000)).unwrap(),
240-
"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",
249+
"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=UTF-8\r\nconnection: close\r\ncontent-length: 21\r\n\r\nInternal server error",
241250
);
242251
}
243252

@@ -265,7 +274,7 @@ fn error_writing_body_file() {
265274
std::thread::sleep(Duration::from_millis(100));
266275
assert_eq!(
267276
server.exchange(req_with_len(66_000)).unwrap(),
268-
"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",
277+
"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=UTF-8\r\nconnection: close\r\ncontent-length: 21\r\n\r\nInternal server error",
269278
);
270279
}
271280

@@ -286,7 +295,7 @@ fn error_reading_body_file() {
286295
server.cache_dir.take();
287296
assert_eq!(
288297
read_to_string(&mut tcp_stream).unwrap(),
289-
"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=UTF-8\r\ncontent-length: 12\r\n\r\nServer error",
298+
"HTTP/1.1 500 Internal Server Error\r\ncontent-type: text/plain; charset=UTF-8\r\nconnection: close\r\ncontent-length: 12\r\n\r\nServer error",
290299
);
291300
}
292301

0 commit comments

Comments
 (0)