Skip to content

Commit 215afce

Browse files
Better support for trailers.
1 parent 4267b99 commit 215afce

4 files changed

Lines changed: 69 additions & 6 deletions

File tree

lib/protocol/http1/connection.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -755,15 +755,15 @@ def write_body(version, body, head = false, trailer = nil)
755755

756756
# While writing the body, we don't know if trailers will be added. We must choose a different body format depending on whether there is the chance of trailers, even if trailer.any? is currently false.
757757
#
758-
# Below you notice `and trailer.nil?`. I tried this but content-length is more important than trailers.
758+
# When trailers are present, we must use chunked encoding (RFC 7230) since Content-Length cannot coexist with trailers. The trailer.nil? checks ensure we only use fixed-length or empty body when no trailers will be sent.
759759

760760
if body.nil?
761761
write_connection_header(version)
762762
write_empty_body(body)
763-
elsif length = body.length # and trailer.nil?
763+
elsif length = body.length and trailer.nil?
764764
write_connection_header(version)
765765
write_fixed_length_body(body, length, head)
766-
elsif body.empty?
766+
elsif body.empty? and trailer.nil?
767767
# Even thought this code is the same as the first clause `body.nil?`, HEAD responses have an empty body but still carry a content length. `write_fixed_length_body` takes care of this appropriately.
768768
write_connection_header(version)
769769
write_empty_body(body)

lib/protocol/http1/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55

66
module Protocol
77
module HTTP1
8-
VERSION = "0.37.0"
8+
VERSION = "0.38.0"
99
end
1010
end

releases.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Releases
22

3+
## Unreleased
4+
5+
- Use chunked encoding when trailers are present, even when body has known length or is empty. Previously, `write_body` would use `write_fixed_length_body` or `write_empty_body` when the body had a length, silently dropping trailers. Per RFC 7230, trailers require chunked transfer encoding since `content-length` cannot coexist with trailers.
6+
37
## v0.37.0
48

59
- `Protocol::HTTP1::BadRequest` now includes `Protocol::HTTP::BadRequest` for better interoperability and handling of bad request errors across different HTTP protocol implementations.

test/protocol/http1/trailer.rb

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,16 @@
2727
server.write_body("HTTP/1.0", body, false, trailer)
2828
end
2929

30-
it "ignores trailers with content length" do
31-
expect(server).to receive(:write_fixed_length_body)
30+
it "uses chunked encoding when trailers are present even with content length" do
31+
expect(server).to receive(:write_chunked_body).with(body, false, trailer)
3232
server.write_body("HTTP/1.1", body, false, trailer)
3333
end
3434

35+
it "uses fixed length when no trailers" do
36+
expect(server).to receive(:write_fixed_length_body)
37+
server.write_body("HTTP/1.1", body, false, nil)
38+
end
39+
3540
it "uses chunked encoding when given trailers without content length" do
3641
expect(body).to receive(:length).and_return(nil)
3742
trailer["foo"] = "bar"
@@ -51,5 +56,59 @@
5156
# Headers are updated:
5257
expect(headers).to be == {"foo" => ["bar"]}
5358
end
59+
60+
it "uses chunked encoding when given trailers with empty body" do
61+
empty_body = Protocol::HTTP::Body::Buffered.new
62+
trailer["grpc-status"] = "2"
63+
64+
expect(server).to receive(:write_chunked_body).with(empty_body, false, trailer)
65+
server.write_body("HTTP/1.1", empty_body, false, trailer)
66+
end
67+
68+
it "sends trailers with empty body (round-trip)" do
69+
empty_body = Protocol::HTTP::Body::Buffered.new
70+
trailer["grpc-status"] = "2"
71+
72+
server.write_response("HTTP/1.1", 200, {})
73+
server.write_body("HTTP/1.1", empty_body, false, trailer)
74+
75+
version, status, reason, headers, body = client.read_response("GET")
76+
77+
expect(version).to be == "HTTP/1.1"
78+
expect(status).to be == 200
79+
expect(headers).to be == {}
80+
81+
# Read all of the response body, including trailers:
82+
body.join
83+
84+
# Headers are updated:
85+
expect(headers).to be == {"grpc-status" => ["2"]}
86+
end
87+
88+
it "uses chunked encoding when given trailers with known body length" do
89+
trailer["grpc-status"] = "0"
90+
91+
expect(server).to receive(:write_chunked_body).with(body, false, trailer)
92+
server.write_body("HTTP/1.1", body, false, trailer)
93+
end
94+
95+
it "sends trailers with known body length (round-trip)" do
96+
trailer["grpc-status"] = "0"
97+
98+
server.write_response("HTTP/1.1", 200, {})
99+
server.write_body("HTTP/1.1", body, false, trailer)
100+
101+
version, status, reason, headers, body = client.read_response("GET")
102+
103+
expect(version).to be == "HTTP/1.1"
104+
expect(status).to be == 200
105+
expect(headers).to be == {}
106+
107+
# Read all of the response body, including trailers:
108+
body.join
109+
110+
# Headers are updated:
111+
expect(headers).to be == {"grpc-status" => ["0"]}
112+
end
54113
end
55114
end

0 commit comments

Comments
 (0)