Skip to content

Commit

Permalink
Merge pull request #167 from tarcieri/feature/multipart-post
Browse files Browse the repository at this point in the history
Add multipart/form-data support
  • Loading branch information
tarcieri committed Dec 15, 2014
2 parents e7b41e3 + 1021211 commit dc6c489
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 35 deletions.
1 change: 1 addition & 0 deletions http.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Gem::Specification.new do |gem|
gem.version = HTTP::VERSION

gem.add_runtime_dependency 'http_parser.rb', '~> 0.6.0'
gem.add_runtime_dependency 'form_data', '~> 0.0.1'

gem.add_development_dependency 'bundler', '~> 1.0'
end
14 changes: 9 additions & 5 deletions lib/http/client.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'cgi'
require 'uri'
require 'form_data'
require 'http/options'
require 'http/redirector'

Expand Down Expand Up @@ -127,12 +128,15 @@ def normalize_uri(uri)

# Create the request body object to send
def make_request_body(opts, headers)
if opts.body
case
when opts.body
opts.body
elsif opts.form
headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
URI.encode_www_form(opts.form)
elsif opts.json
when opts.form
form = FormData.create opts.form
headers['Content-Type'] ||= form.content_type
headers['Content-Length'] ||= form.content_length
form.to_s
when opts.json
headers['Content-Type'] ||= 'application/json'
MimeType[:json].encode opts.json
end
Expand Down
9 changes: 2 additions & 7 deletions lib/http/request/writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,8 @@ def stream
def add_body_type_headers
if @body.is_a?(String) && !@headers['Content-Length']
@request_header << "Content-Length: #{@body.bytesize}"
elsif @body.is_a?(Enumerable)
encoding = @headers['Transfer-Encoding']
if encoding == 'chunked'
@request_header << 'Transfer-Encoding: chunked'
else
fail(RequestError, 'invalid transfer encoding')
end
elsif @body.is_a?(Enumerable) && 'chunked' != @headers['Transfer-Encoding']
fail(RequestError, 'invalid transfer encoding')
end
end

Expand Down
94 changes: 71 additions & 23 deletions spec/http/request/writer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,89 @@
require 'spec_helper'

RSpec.describe HTTP::Request::Writer do
let(:io) { StringIO.new }
let(:body) { '' }
let(:headers) { HTTP::Headers.new }
let(:headerstart) { 'GET /test HTTP/1.1' }

subject(:writer) { described_class.new(io, body, headers, headerstart) }

describe '#initalize' do
def construct(body)
HTTP::Request::Writer.new(nil, body, [], '')
end
context 'when body is nil' do
let(:body) { nil }

it "doesn't throw on a nil body" do
expect { construct nil }.not_to raise_error
it 'does not raise an error' do
expect { writer }.not_to raise_error
end
end

it "doesn't throw on a String body" do
expect { construct 'string body' }.not_to raise_error
end
context 'when body is a string' do
let(:body) { 'string body' }

it "doesn't throw on an Enumerable body" do
expect { construct %w(bees cows) }.not_to raise_error
it 'does not raise an error' do
expect { writer }.not_to raise_error
end
end

it "does throw on a body that isn't string, enumerable or nil" do
expect { construct true }.to raise_error
context 'when body is an Enumerable' do
let(:body) { %w(bees cows) }

it 'does not raise an error' do
expect { writer }.not_to raise_error
end
end

it 'writes a chunked request from an Enumerable correctly' do
io = StringIO.new
writer = HTTP::Request::Writer.new(io, %w(bees cows), [], '')
writer.send_request_body
io.rewind
expect(io.string).to eq "4\r\nbees\r\n4\r\ncows\r\n0\r\n\r\n"
context 'when body is not string, enumerable or nil' do
let(:body) { 123 }

it 'raises an error' do
expect { writer }.to raise_error
end
end
end

describe '#add_body_type_headers' do
it 'properly calculates length of unicode string' do
writer = HTTP::Request::Writer.new(nil, 'Привет, мир!', {}, '')
writer.add_body_type_headers
expect(writer.join_headers).to match(/\r\nContent-Length: 21\r\n/)
describe '#stream' do
context 'when body is Enumerable' do
let(:body) { %w(bees cows) }
let(:headers) { HTTP::Headers.coerce 'Transfer-Encoding' => 'chunked' }

it 'writes a chunked request from an Enumerable correctly' do
writer.stream
expect(io.string).to end_with "\r\n4\r\nbees\r\n4\r\ncows\r\n0\r\n\r\n"
end

it 'writes Transfer-Encoding header only once' do
writer.stream
expect(io.string).to start_with "#{headerstart}\r\nTransfer-Encoding: chunked\r\n\r\n"
end

context 'when Transfer-Encoding not set' do
let(:headers) { HTTP::Headers.new }
specify { expect { writer.stream }.to raise_error }
end

context 'when Transfer-Encoding is not chunked' do
let(:headers) { HTTP::Headers.coerce 'Transfer-Encoding' => 'gzip' }
specify { expect { writer.stream }.to raise_error }
end
end

context 'when body is a unicode String' do
let(:body) { 'Привет, мир!' }

it 'properly calculates Content-Length if needed' do
writer.stream
expect(io.string).to start_with "#{headerstart}\r\nContent-Length: 21\r\n\r\n"
end

context 'when Content-Length explicitly set' do
let(:headers) { HTTP::Headers.coerce 'Content-Length' => 12 }

it 'keeps given value' do
writer.stream
expect(io.string).to start_with "#{headerstart}\r\nContent-Length: 12\r\n\r\n"
end
end
end
end
end

0 comments on commit dc6c489

Please sign in to comment.