Skip to content

Commit 7869ff2

Browse files
author
Zach Anker
committed
Fixed chunk writing if the data is bugger than our buffer (#251)
1 parent 3bdea10 commit 7869ff2

File tree

7 files changed

+57
-12
lines changed

7 files changed

+57
-12
lines changed

lib/http/request/writer.rb

+20-6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class Writer
1212
# Chunked transfer encoding
1313
CHUNKED = "chunked".freeze
1414

15+
# End of a chunked transfer
16+
CHUNKED_END = "#{ZERO}#{CRLF}#{CRLF}".freeze
17+
1518
# Types valid to be used as body source
1619
VALID_BODY_TYPES = [String, NilClass, Enumerable]
1720

@@ -40,7 +43,7 @@ def stream
4043
# Send headers needed to connect through proxy
4144
def connect_through_proxy
4245
add_headers
43-
@socket << join_headers
46+
write(join_headers)
4447
end
4548

4649
# Adds the headers to the header array for the given request body we are working
@@ -65,24 +68,35 @@ def send_request_header
6568
add_headers
6669
add_body_type_headers
6770

68-
@socket << join_headers
71+
write(join_headers)
6972
end
7073

7174
def send_request_body
7275
if @body.is_a?(String)
73-
@socket << @body
76+
write(@body)
7477
elsif @body.is_a?(Enumerable)
7578
@body.each do |chunk|
76-
@socket << chunk.bytesize.to_s(16) << CRLF
77-
@socket << chunk << CRLF
79+
write(chunk.bytesize.to_s(16) << CRLF)
80+
write(chunk << CRLF)
7881
end
7982

80-
@socket << ZERO << CRLF << CRLF
83+
write(CHUNKED_END)
8184
end
8285
end
8386

8487
private
8588

89+
def write(data)
90+
while data.present?
91+
length = @socket.write(data)
92+
if data.length > length
93+
data = data[length..-1]
94+
else
95+
break
96+
end
97+
end
98+
end
99+
86100
def validate_body_type!
87101
return if VALID_BODY_TYPES.any? { |type| @body.is_a? type }
88102
fail RequestError, "body of wrong type: #{@body.class}"

lib/http/timeout/global.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def write(data)
6161
reset_timer
6262

6363
begin
64-
socket.write_nonblock(data)
64+
return socket.write_nonblock(data)
6565
rescue IO::WaitWritable
6666
IO.select(nil, [socket], nil, time_left)
6767
log_time
@@ -97,7 +97,7 @@ def write(data)
9797

9898
loop do
9999
result = socket.write_nonblock(data, :exception => false)
100-
break unless result == :wait_writable
100+
return result unless result == :wait_writable
101101

102102
IO.select(nil, [socket], nil, time_left)
103103
log_time

lib/http/timeout/null.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def readpartial(size)
4545

4646
# Write to the socket
4747
def write(data)
48-
@socket << data
48+
@socket.write(data)
4949
end
5050
alias_method :<<, :write
5151

lib/http/timeout/per_operation.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def readpartial(size)
7575
def write(data)
7676
loop do
7777
result = socket.write_nonblock(data, :exception => false)
78-
break unless result == :wait_writable
78+
return result unless result == :wait_writable
7979

8080
unless IO.select(nil, [socket], nil, write_timeout)
8181
fail TimeoutError, "Read timed out after #{write_timeout} seconds"

spec/lib/http/client_spec.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,8 @@ def simple_response(body, status = 200)
267267

268268
allow(socket_spy).to receive(:close) { nil }
269269
allow(socket_spy).to receive(:closed?) { true }
270-
allow(socket_spy).to receive(:readpartial) { chunks.shift }
271-
allow(socket_spy).to receive(:<<) { nil }
270+
allow(socket_spy).to receive(:readpartial) { chunks[0] }
271+
allow(socket_spy).to receive(:write) { chunks[0].length }
272272

273273
allow(TCPSocket).to receive(:open) { socket_spy }
274274
end

spec/lib/http_spec.rb

+26
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,32 @@
4444
expect(response.to_s.include?("json")).to be true
4545
end
4646
end
47+
48+
context "with a large request body" do
49+
%w(global null per_operation).each do |timeout|
50+
context "with a #{timeout} timeout" do
51+
[16_000, 16_500, 17_000, 34_000, 68_000].each do |size|
52+
[0, rand(0..100), rand(100..1000)].each do |fuzzer|
53+
context "with a #{size} body and #{fuzzer} of fuzzing" do
54+
let(:client) { HTTP.timeout(timeout, :read => 2, :write => 2, :connect => 2) }
55+
56+
let(:characters) { ("A".."Z").to_a }
57+
let(:request_body) do
58+
(size + fuzzer).times.map { |i| characters[i % characters.length] }.join
59+
end
60+
61+
it "returns a large body" do
62+
response = client.post("#{dummy.endpoint}/echo-body", :body => request_body)
63+
64+
expect(response.body.to_s).to eq(request_body)
65+
expect(response.headers["Content-Length"].to_i).to eq(request_body.length)
66+
end
67+
end
68+
end
69+
end
70+
end
71+
end
72+
end
4773
end
4874

4975
describe ".via" do

spec/support/dummy_server/servlet.rb

+5
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,10 @@ def do_#{method.upcase}(req, res)
133133
res["Set-Cookie"] = "foo=bar"
134134
res.body = req.cookies.map { |c| [c.name, c.value].join ": " }.join("\n")
135135
end
136+
137+
post "/echo-body" do |req, res|
138+
res.status = 200
139+
res.body = req.body
140+
end
136141
end
137142
end

0 commit comments

Comments
 (0)