Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add encoding option #273

Merged
merged 4 commits into from
Dec 13, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/http/chainable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ def cookies(cookies)
branch default_options.with_cookies(cookies)
end

# Force a specific encoding for response body
def encoding(encoding)
branch default_options.with_encoding(encoding)
end

# Accept the given MIME type(s)
# @param type
def accept(type)
Expand Down
11 changes: 6 additions & 5 deletions lib/http/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ def perform(req, options)
end

res = Response.new(
@connection.status_code,
@connection.http_version,
@connection.headers,
Response::Body.new(@connection),
req.uri
:status => @connection.status_code,
:version => @connection.http_version,
:headers => @connection.headers,
:connection => @connection,
:encoding => options.encoding,
:uri => req.uri
)

@connection.finish_response if req.verb == :head
Expand Down
7 changes: 6 additions & 1 deletion lib/http/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def initialize(options = {})
:ssl => {},
:keep_alive_timeout => 5,
:headers => {},
:cookies => {}
:cookies => {},
:encoding => nil
}

opts_w_defaults = defaults.merge(options)
Expand All @@ -66,6 +67,10 @@ def initialize(options = {})
end
end

def_option :encoding do |encoding|
self.encoding = Encoding.find(encoding)
end

%w(
proxy params form json body follow response
socket_class ssl_socket_class ssl_context ssl
Expand Down
47 changes: 29 additions & 18 deletions lib/http/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,27 @@ class Response
# @return [URI, nil]
attr_reader :uri

def initialize(status, version, headers, body, uri = nil) # rubocop:disable ParameterLists
@version = version
@body = body
@uri = uri && HTTP::URI.parse(uri)
@status = HTTP::Response::Status.new status
@headers = HTTP::Headers.coerce(headers || {})
# Inits a new instance
#
# @option opts [Integer] :status Status code
# @option opts [String] :version HTTP version
# @option opts [Hash] :headers
# @option opts [HTTP::Connection] :connection
# @option opts [String] :encoding Encoding to use when reading body
# @option opts [String] :body
# @option opts [String] :uri
def initialize(opts)
@version = opts.fetch(:version)
@uri = opts.include?(:uri) && HTTP::URI.parse(opts.fetch(:uri))
@status = HTTP::Response::Status.new opts.fetch(:status)
@headers = HTTP::Headers.coerce(opts.fetch(:headers, {}))

if opts.include?(:connection)
encoding = opts[:encoding] || charset || Encoding::BINARY
@body = Response::Body.new(opts.fetch(:connection), encoding)
else
@body = opts.fetch(:body)
end
end

# @!method reason
Expand Down Expand Up @@ -70,19 +85,15 @@ def content_type
@content_type ||= ContentType.parse headers[Headers::CONTENT_TYPE]
end

# MIME type of response (if any)
#
# @return [String, nil]
def mime_type
@mime_type ||= content_type.mime_type
end
# @!method mime_type
# MIME type of response (if any)
# @return [String, nil]
def_delegator :content_type, :mime_type

# Charset of response (if any)
#
# @return [String, nil]
def charset
@charset ||= content_type.charset
end
# @!method charset
# Charset of response (if any)
# @return [String, nil]
def_delegator :content_type, :charset

def cookies
@cookies ||= headers.each_with_object CookieJar.new do |(k, v), jar|
Expand Down
13 changes: 7 additions & 6 deletions lib/http/response/body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ class Body
include Enumerable
def_delegator :to_s, :empty?

def initialize(client)
@client = client
@streaming = nil
@contents = nil
def initialize(client, encoding)
@client = client
@streaming = nil
@contents = nil
@encoding = encoding
end

# (see HTTP::Client#readpartial)
Expand All @@ -36,9 +37,9 @@ def to_s

begin
@streaming = false
@contents = "".force_encoding(Encoding::UTF_8)
@contents = "".force_encoding(@encoding)
while (chunk = @client.readpartial)
@contents << chunk.force_encoding(Encoding::ASCII_8BIT)
@contents << chunk.force_encoding(@encoding)
end
rescue
@contents = nil
Expand Down
11 changes: 9 additions & 2 deletions spec/lib/http/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,18 @@ def stub(stubs)
end

def redirect_response(location, status = 302)
HTTP::Response.new(status, "1.1", {"Location" => location}, "")
HTTP::Response.new(
:status => status,
:version => "1.1",
:headers => {"Location" => location},
:body => "")
end

def simple_response(body, status = 200)
HTTP::Response.new(status, "1.1", {}, body)
HTTP::Response.new(
:status => status,
:version => "1.1",
:body => body)
end

describe "following redirects" do
Expand Down
3 changes: 2 additions & 1 deletion spec/lib/http/options/merge_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
:socket_class => described_class.default_socket_class,
:ssl_socket_class => described_class.default_ssl_socket_class,
:ssl_context => nil,
:cookies => {})
:cookies => {},
:encoding => nil)
end
end
7 changes: 6 additions & 1 deletion spec/lib/http/redirector_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
RSpec.describe HTTP::Redirector do
def simple_response(status, body = "", headers = {})
HTTP::Response.new(status, "1.1", headers, body)
HTTP::Response.new(
:status => status,
:version => "1.1",
:headers => headers,
:body => body
)
end

def redirect_response(status, location)
Expand Down
2 changes: 1 addition & 1 deletion spec/lib/http/response/body_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

before { allow(client).to receive(:readpartial) { chunks.shift } }

subject(:body) { described_class.new client }
subject(:body) { described_class.new client, Encoding::UTF_8 }

it "streams bodies from responses" do
expect(subject.to_s).to eq "Hello, World!"
Expand Down
11 changes: 10 additions & 1 deletion spec/lib/http/response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
let(:body) { "Hello world!" }
let(:uri) { "http://example.com/" }
let(:headers) { {} }
subject(:response) { HTTP::Response.new 200, "1.1", headers, body, uri }

subject(:response) do
HTTP::Response.new(
:status => 200,
:version => "1.1",
:headers => headers,
:body => body,
:uri => uri
)
end

it "includes HTTP::Headers::Mixin" do
expect(described_class).to include HTTP::Headers::Mixin
Expand Down
32 changes: 28 additions & 4 deletions spec/lib/http_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# encoding: UTF-8

require "json"

require "support/dummy_server"
Expand Down Expand Up @@ -160,14 +162,36 @@
context "loading binary data" do
it "is encoded as bytes" do
response = HTTP.get "#{dummy.endpoint}/bytes"
expect(response.to_s.encoding).to eq(Encoding::ASCII_8BIT)
expect(response.to_s.encoding).to eq(Encoding::BINARY)
end
end

context "loading endpoint with charset" do
it "uses charset from headers" do
response = HTTP.get "#{dummy.endpoint}/iso-8859-1"
expect(response.to_s.encoding).to eq(Encoding::ISO8859_1)
expect(response.to_s.encode(Encoding::UTF_8)).to eq("testæ")
end

context "with encoding option" do
it "respects option" do
response = HTTP.get "#{dummy.endpoint}/iso-8859-1", "encoding" => Encoding::BINARY
expect(response.to_s.encoding).to eq(Encoding::BINARY)
end
end
end

context "passing a string encoding type" do
it "finds encoding" do
response = HTTP.get dummy.endpoint, "encoding" => "ascii"
expect(response.to_s.encoding).to eq(Encoding::ASCII)
end
end

context "loading text" do
it "is utf-8 encoded" do
context "loading text with no charset" do
it "is binary encoded" do
response = HTTP.get dummy.endpoint
expect(response.to_s.encoding).to eq(Encoding::UTF_8)
expect(response.to_s.encoding).to eq(Encoding::BINARY)
end
end

Expand Down
7 changes: 7 additions & 0 deletions spec/support/dummy_server/servlet.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# encoding: UTF-8

class DummyServer < WEBrick::HTTPServer
class Servlet < WEBrick::HTTPServlet::AbstractServlet
def self.sockets
Expand Down Expand Up @@ -129,6 +131,11 @@ def do_#{method.upcase}(req, res)
res.body = bytes.pack("c*")
end

get "/iso-8859-1" do |_req, res|
res["Content-Type"] = "text/plain; charset=ISO-8859-1"
res.body = "testæ".encode(Encoding::ISO8859_1)
end

get "/cookies" do |req, res|
res["Set-Cookie"] = "foo=bar"
res.body = req.cookies.map { |c| [c.name, c.value].join ": " }.join("\n")
Expand Down