Skip to content

Commit 1c9d2f4

Browse files
authored
Better support for connection upgrade and bi-directional streaming. (#101)
1 parent 6cb9bf6 commit 1c9d2f4

File tree

3 files changed

+64
-8
lines changed

3 files changed

+64
-8
lines changed

lib/webrick/httpresponse.rb

+30-6
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ class InvalidHeader < StandardError
105105

106106
attr_reader :sent_size
107107

108+
##
109+
# Set the response body proc as an streaming/upgrade response.
110+
111+
attr_accessor :upgrade
112+
108113
##
109114
# Creates a new HTTP response object. WEBrick::Config::HTTP is the
110115
# default configuration.
@@ -217,6 +222,16 @@ def keep_alive?
217222
@keep_alive
218223
end
219224

225+
##
226+
# Sets the response to be a streaming/upgrade response.
227+
# This will disable keep-alive and chunked transfer encoding.
228+
229+
def upgrade!(protocol)
230+
@upgrade = protocol
231+
@keep_alive = false
232+
@chunked = false
233+
end
234+
220235
##
221236
# Sends the response on +socket+
222237

@@ -242,6 +257,14 @@ def setup_header() # :nodoc:
242257
@header['server'] ||= @config[:ServerSoftware]
243258
@header['date'] ||= Time.now.httpdate
244259

260+
if @upgrade
261+
@header['connection'] = 'upgrade'
262+
@header['upgrade'] = @upgrade
263+
@keep_alive = false
264+
265+
return
266+
end
267+
245268
# HTTP/0.9 features
246269
if @request_http_version < "1.0"
247270
@http_version = HTTPVersion.new("0.9")
@@ -268,11 +291,10 @@ def setup_header() # :nodoc:
268291
elsif %r{^multipart/byteranges} =~ @header['content-type']
269292
@header.delete('content-length')
270293
elsif @header['content-length'].nil?
271-
if @body.respond_to? :readpartial
272-
elsif @body.respond_to? :call
273-
make_body_tempfile
294+
if @body.respond_to?(:bytesize)
295+
@header['content-length'] = @body.bytesize.to_s
274296
else
275-
@header['content-length'] = (@body ? @body.bytesize : 0).to_s
297+
@header['connection'] = 'close'
276298
end
277299
end
278300

@@ -517,14 +539,16 @@ def send_body_proc(socket)
517539
@body.call(ChunkedWrapper.new(socket, self))
518540
socket.write("0#{CRLF}#{CRLF}")
519541
else
520-
size = @header['content-length'].to_i
521542
if @bodytempfile
522543
@bodytempfile.rewind
523544
IO.copy_stream(@bodytempfile, socket)
524545
else
525546
@body.call(socket)
526547
end
527-
@sent_size = size
548+
549+
if content_length = @header['content-length']
550+
@sent_size = content_length.to_i
551+
end
528552
end
529553
end
530554

test/webrick/test_httpresponse.rb

+33
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,39 @@ def test_send_body_proc_chunked
261261
assert_equal 0, logger.messages.length
262262
end
263263

264+
def test_send_body_proc_upgrade
265+
@res.body = Proc.new { |out| out.write('hello'); out.close }
266+
@res.upgrade!("text")
267+
268+
IO.pipe do |r, w|
269+
@res.send_response(w)
270+
w.close
271+
assert_match /Connection: upgrade\r\nUpgrade: text\r\n\r\nhello/, r.read
272+
end
273+
assert_empty logger.messages
274+
end
275+
276+
def test_send_body_proc_stream
277+
@res.body = Proc.new do |socket|
278+
chunk = socket.read
279+
socket.write(chunk)
280+
socket.close
281+
end
282+
283+
UNIXSocket.pair do |s1, s2|
284+
thread = Thread.new do
285+
@res.send_response(s1)
286+
end
287+
288+
s2.write("hello")
289+
s2.close_write
290+
chunk = s2.read
291+
assert_match /Connection: close\r\n\r\nhello/, chunk
292+
s2.close
293+
end
294+
assert_empty logger.messages
295+
end
296+
264297
def test_set_error
265298
status = 400
266299
message = 'missing attribute'

test/webrick/test_httpserver.rb

+1-2
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,7 @@ def test_response_io_without_chunked_set
377377
:ServerName => "localhost"
378378
}
379379
log_tester = lambda {|log, access_log|
380-
assert_equal(1, log.length)
381-
assert_match(/WARN Could not determine content-length of response body./, log[0])
380+
assert_empty log
382381
}
383382
TestWEBrick.start_httpserver(config, log_tester){|server, addr, port, log|
384383
server.mount_proc("/", lambda { |req, res|

0 commit comments

Comments
 (0)