-
Notifications
You must be signed in to change notification settings - Fork 11
Handle response body fallback when no length headers present #304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d1cd930
2fadd78
83328f3
83af87d
a1627f7
17f4da2
29641fd
08c2c6f
7b93ab0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ priv enum BodyKind { | |
| Fixed(Int) | ||
| Chunked(Int) | ||
| PassThrough | ||
| WaitConnectionClose | ||
| } | ||
|
|
||
| ///| | ||
|
|
@@ -90,12 +91,13 @@ impl @io.Reader for Reader with _direct_read(self, buf, offset~, max_len~) { | |
| n | ||
| } | ||
| PassThrough => self.transport._direct_read(buf, offset~, max_len~) | ||
| WaitConnectionClose => self.transport._direct_read(buf, offset~, max_len~) | ||
| } | ||
| } | ||
|
|
||
| ///| | ||
| impl @io.Reader for Reader with _get_internal_buffer(self) { | ||
| if self.body is PassThrough { | ||
| if self.body is PassThrough || self.body is WaitConnectionClose { | ||
| self.transport._get_internal_buffer() | ||
| } else { | ||
| self.read_buf | ||
|
|
@@ -185,6 +187,38 @@ async fn Reader::read_request(self : Reader) -> Request { | |
| { meth, path, headers } | ||
| } | ||
|
|
||
| ///| | ||
| /// Determines if the response body should be read until connection close. | ||
| /// Returns true when: | ||
| /// - No Content-Length or Transfer-Encoding header is present | ||
| /// - Body has not been determined by other means (still Empty) | ||
| /// - Status code is not 1xx, 204, 205, or 304 (which MUST NOT have a message body) | ||
| /// - Not a successful (2xx) response to a CONNECT request (which becomes a tunnel) | ||
| /// - Not a response to a HEAD request (which never has a message body) | ||
| fn should_read_body_until_close( | ||
| code : Int, | ||
| headers : Map[String, String], | ||
| current_body : BodyKind, | ||
| request_method : RequestMethod?, | ||
| ) -> Bool { | ||
| // HEAD responses never have a message body, regardless of headers | ||
| if request_method is Some(Head) { | ||
| return false | ||
| } | ||
| // Successful CONNECT responses (2xx) switch connection to tunnel mode | ||
| // and have no HTTP message body | ||
| if request_method is Some(Connect) && code >= 200 && code < 300 { | ||
| return false | ||
|
Comment on lines
+210
to
+211
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| } | ||
| current_body is Empty && | ||
| headers.get("content-length") is None && | ||
| headers.get("transfer-encoding") is None && | ||
| code >= 200 && | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This predicate classifies all Useful? React with 👍 / 👎. |
||
| code != 204 && | ||
| code != 205 && | ||
| code != 304 | ||
|
Comment on lines
+217
to
+219
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| ///| | ||
| /// Read the header of a HTTP/1.1 response from a HTTP reader. | ||
| /// This function must be called after the body of the last response is consumed, | ||
|
|
@@ -196,7 +230,10 @@ async fn Reader::read_request(self : Reader) -> Request { | |
| /// | ||
| /// HTTP header fields are case insensitive. | ||
| /// In the result of `read_response`, all header fields will be in lower case. | ||
| async fn Reader::read_response(self : Reader) -> Response { | ||
| async fn Reader::read_response( | ||
| self : Reader, | ||
| request_method~ : RequestMethod?, | ||
| ) -> Response { | ||
| guard self.body is Empty | ||
| guard self.transport.read_until("\r\n") is Some(response_line) else { | ||
| raise @io.ReaderClosed | ||
|
|
@@ -213,6 +250,13 @@ async fn Reader::read_response(self : Reader) -> Response { | |
| } | ||
| let reason = response_line[code_len + 1:].to_string() | ||
| let headers = self.read_headers() | ||
|
|
||
| // HTTP/1.1 Spec (RFC 7230 Section 3.3.3): Message Body Length Determination | ||
| // Other cases are handled in `read_headers` when parsing the headers, which will set the body kind accordingly. | ||
| if should_read_body_until_close(code, headers, self.body, request_method) { | ||
| self.body = WaitConnectionClose // Read until connection close | ||
| } | ||
|
|
||
| { code, reason, headers } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new
WaitConnectionClosebranch reads viaself.transport._direct_read, but_get_internal_bufferis not updated to expose the transport buffer for this state. Because headers are parsed withself.transport.read_until, body bytes can already be buffered in the transport reader; bypassing that buffer drops/truncates close-delimited response bodies before EOF is reached.Useful? React with 👍 / 👎.