Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

add support for chunked requests via request_block

also reduce duplication in rackups by inheriting from shared basic rackup for basic, basic_auth and ssl
  • Loading branch information...
commit 5d343dd379a41e6352718ab40e865233a6663db2 1 parent 2a67955
Wesley Beary geemus authored
19 README.md
View
@@ -62,6 +62,25 @@ If you need to accept as response one or more HTTP status codes you can declare
connection.request(expects => [200, 201], :method => :get, :path => path, :query => {})
+Chunked Requests
+----------------
+
+You can make `Transfer-Encoding: chunked` requests by passing a block that will deliver chunks, delivering an empty chunk to signal completion.
+
+ file = File.open('data')
+
+ chunker = lambda do
+ # Excon::CHUNK_SIZE defaults to 1048576, ie 1MB
+ # to_s will convert the nil receieved after everything is read to the final empty chunk
+ file.read(Excon::CHUNK_SIZE).to_s
+ end
+
+ Excon.post('http://geemus.com', :request_block => chunker)
+
+ file.close
+
+Iterating in this way allows you to have more granular control over writes and to write things where you can not calculate the overall length up front.
+
Streaming Responses
-------------------
42 lib/excon/connection.rb
View
@@ -179,23 +179,22 @@ def request_kernel(params)
# finish first line with "HTTP/1.1\r\n"
request << HTTP_1_1
- # calculate content length and set to handle non-ascii
- unless params[:headers].has_key?('Content-Length')
+ if params.has_key?(:request_block)
+ params[:headers]['Transfer-Encoding'] = 'chunked'
+ elsif ! (params[:method].to_s.casecmp('GET') == 0 && params[:body].nil?)
# The HTTP spec isn't clear on it, but specifically, GET requests don't usually send bodies;
# if they don't, sending Content-Length:0 can cause issues.
- unless (params[:method].to_s.casecmp('GET') == 0 && params[:body].nil?)
- params[:headers]['Content-Length'] = case params[:body]
- when File
- params[:body].binmode
- File.size(params[:body])
- when String
- if FORCE_ENC
- params[:body].force_encoding('BINARY')
- end
- params[:body].length
- else
- 0
+ params[:headers]['Content-Length'] = case params[:body]
+ when File
+ params[:body].binmode
+ File.size(params[:body])
+ when String
+ if FORCE_ENC
+ params[:body].force_encoding('BINARY')
end
+ params[:body].length
+ else
+ 0
end
end
@@ -213,7 +212,20 @@ def request_kernel(params)
socket.write(request)
# write out the body
- unless params[:body].nil?
+ if params.has_key?(:request_block)
+ while true
+ chunk = params[:request_block].call
+ if FORCE_ENC
+ chunk.force_encoding('BINARY')
+ end
+ if chunk.length > 0
+ socket.write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
+ else
+ socket.write('0' << CR_NL << CR_NL)
+ break
+ end
+ end
+ elsif !params[:body].nil?
if params[:body].is_a?(String)
unless params[:body].empty?
socket.write(params[:body])
16 tests/rackups/basic.rb
View
@@ -0,0 +1,16 @@
+require 'sinatra'
+
+class Basic < Sinatra::Base
+ get('/content-length/:value') do |value|
+ headers("Custom" => "Foo: bar")
+ 'x' * value.to_i
+ end
+
+ post('/body-sink') do
+ request.body.read.size.to_s
+ end
+
+ post('/echo') do
+ request.body.read
+ end
+end
15 tests/rackups/basic.ru
View
@@ -1,14 +1,3 @@
-require 'sinatra'
+require File.join(File.dirname(__FILE__), 'basic')
-class App < Sinatra::Base
- get('/content-length/:value') do |value|
- headers("Custom" => "Foo: bar")
- 'x' * value.to_i
- end
-
- post('/body-sink') do
- request.body.read.size.to_s
- end
-end
-
-run App
+run Basic
15 tests/rackups/basic_auth.ru
View
@@ -1,6 +1,6 @@
-require 'sinatra'
+require File.join(File.dirname(__FILE__), 'basic')
-class App < Sinatra::Base
+class BasicAuth < Basic
before do
auth ||= Rack::Auth::Basic::Request.new(request.env)
user, pass = auth.provided? && auth.basic? && auth.credentials
@@ -9,15 +9,6 @@ class App < Sinatra::Base
throw(:halt, [401, "Not authorized\n"])
end
end
-
- get('/content-length/:value') do |value|
- headers("Custom" => "Foo: bar")
- 'x' * value.to_i
- end
-
- post('/body-sink') do
- request.body.read.size.to_s
- end
end
-run App
+run BasicAuth
14 tests/rackups/ssl.ru
View
@@ -1,20 +1,10 @@
require 'openssl'
-require 'sinatra'
require 'webrick'
require 'webrick/https'
-class App < Sinatra::Base
- get('/content-length/:value') do |value|
- headers("Custom" => "Foo: bar")
- 'x' * value.to_i
- end
+require File.join(File.dirname(__FILE__), 'basic')
- post('/body-sink') do
- request.body.read.size.to_s
- end
-end
-
-Rack::Handler::WEBrick.run(App, {
+Rack::Handler::WEBrick.run(Basic, {
:Port => 9443,
:SSLCertName => [["CN", WEBrick::Utils::getservername]],
:SSLEnable => true,
17 tests/test_helper.rb
View
@@ -74,7 +74,24 @@ def basic_tests(url = 'http://127.0.0.1:9292')
response = connection.request(:method => :post, :path => '/body-sink', :headers => { 'Content-Type' => 'text/plain' }, :body => '')
response.body
end
+
end
+
+ tests('POST /echo') do
+
+ connection = Excon.new(url)
+
+ tests('request_block usage').returns('x' * 100) do
+ data = ['x'] * 100
+ request_block = lambda do
+ data.shift.to_s
+ end
+ response = connection.request(:method => :post, :path => '/echo', :request_block => request_block)
+ response.body
+ end
+
+ end
+
end
def rackup_path(*parts)
Please sign in to comment.
Something went wrong with that request. Please try again.