Skip to content

Commit

Permalink
Add cookies support
Browse files Browse the repository at this point in the history
  • Loading branch information
ixti committed May 11, 2015
1 parent 1ab394f commit d95f0d4
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## master (unreleased)

* _the future is unwritten_
* Add cookies support. (@ixti)
* Enforce stringified body encoding. See #219. (@Connorhd)


Expand Down
1 change: 1 addition & 0 deletions http.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Gem::Specification.new do |gem|

gem.add_runtime_dependency "http_parser.rb", "~> 0.6.0"
gem.add_runtime_dependency "http-form_data", "~> 1.0.1"
gem.add_runtime_dependency "http-cookie", "~> 1.0"
gem.add_runtime_dependency "addressable", "~> 2.3"

gem.add_development_dependency "bundler", "~> 1.0"
Expand Down
5 changes: 5 additions & 0 deletions lib/http/chainable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ def headers(headers)
# @see #headers
alias_method :with_headers, :headers

# Make a request with the given cookies
def cookies(cookies)
branch default_options.with_cookies(cookies)
end

# Accept the given MIME type(s)
# @param type
def accept(type)
Expand Down
5 changes: 3 additions & 2 deletions lib/http/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

require "http/form_data"
require "http/options"
require "http/headers"
require "http/connection"
require "http/redirector"
require "http/uri"
Expand All @@ -31,9 +32,9 @@ def initialize(default_options = {})
def request(verb, uri, opts = {})
opts = @default_options.merge(opts)
uri = make_request_uri(uri, opts)
headers = opts.headers
proxy = opts.proxy
headers = opts.headers.merge(Headers::SET_COOKIE => opts.cookies.values)
body = make_request_body(opts, headers)
proxy = opts.proxy

# Tell the server to keep the conn open
if default_options.persistent?
Expand Down
3 changes: 3 additions & 0 deletions lib/http/headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class Headers
# @see http://tools.ietf.org/html/rfc7230#section-3.2
HEADER_NAME_RE = /^[A-Za-z0-9!#\$%&'*+\-.^_`|~]+$/

# Cookies header name
SET_COOKIE = "Set-Cookie".freeze

# Class constructor.
def initialize
@pile = []
Expand Down
31 changes: 17 additions & 14 deletions lib/http/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,23 @@ def def_option(name, &interpreter)
end

def initialize(options = {})
defaults = {:response => :auto,
:proxy => {},
:timeout_class => self.class.default_timeout_class,
:timeout_options => {},
:socket_class => self.class.default_socket_class,
:ssl_socket_class => self.class.default_ssl_socket_class,
:ssl => {},
:cache => self.class.default_cache,
:keep_alive_timeout => 5,
:headers => {}}
defaults = {
:response => :auto,
:proxy => {},
:timeout_class => self.class.default_timeout_class,
:timeout_options => {},
:socket_class => self.class.default_socket_class,
:ssl_socket_class => self.class.default_ssl_socket_class,
:ssl => {},
:cache => self.class.default_cache,
:keep_alive_timeout => 5,
:headers => {},
:cookies => {}
}

opts_w_defaults = defaults.merge(options)
opts_w_defaults[:headers] = HTTP::Headers.coerce(opts_w_defaults[:headers])

opts_w_defaults.each do |(opt_name, opt_val)|
self[opt_name] = opt_val
end
opts_w_defaults.each { |(k, v)| self[k] = v }
end

def_option :headers do |headers|
Expand Down Expand Up @@ -145,3 +145,6 @@ def argument_error!(message)
end
end
end

# require cookies options
require "http/options/cookies"
20 changes: 20 additions & 0 deletions lib/http/options/cookies.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module HTTP
class Options
# Default path of cookies
DEFAULT_COOKIE_PATH = "/".freeze

def_option :cookies do |cookies|
cookies.each_with_object self.cookies.dup do |(k, v), jar|
cookie = case
when k.is_a?(Cookie) then k
when k.is_a?(Hash) then Cookie.new k
when v.is_a?(Hash) then Cookie.new(k.to_s, v)
else Cookie.new(k.to_s, v.to_s)
end

cookie.path ||= DEFAULT_COOKIE_PATH
jar[cookie.name] = cookie.set_cookie_value
end
end
end
end
10 changes: 9 additions & 1 deletion lib/http/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
require "http/mime_type"
require "http/response/caching"
require "http/response/status"
require "http/uri"
require "http/cookie_jar"
require "time"

module HTTP
Expand Down Expand Up @@ -32,7 +34,7 @@ class Response
def initialize(status, version, headers, body, uri = nil) # rubocop:disable ParameterLists
@version = version
@body = body
@uri = uri
@uri = uri && HTTP::URI.parse(uri)
@status = HTTP::Response::Status.new status
@headers = HTTP::Headers.coerce(headers || {})
end
Expand Down Expand Up @@ -93,6 +95,12 @@ def charset
@charset ||= content_type.charset
end

def cookies
@cookies ||= headers.each_with_object CookieJar.new do |(k, v), jar|
jar.parse(v, uri) if k == Headers::SET_COOKIE
end
end

# Parse response body with corresponding MIME type adapter.
#
# @param [#to_s] as Parse as given MIME type
Expand Down
44 changes: 32 additions & 12 deletions spec/lib/http/response_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
RSpec.describe HTTP::Response do
let(:body) { "Hello world!" }
subject(:response) { HTTP::Response.new 200, "1.1", {}, body }
let(:uri) { "http://example.com/" }
let(:headers) { {} }
subject(:response) { HTTP::Response.new 200, "1.1", headers, body, uri }

it "includes HTTP::Headers::Mixin" do
expect(described_class).to include HTTP::Headers::Mixin
Expand All @@ -9,15 +11,15 @@
describe "to_a" do
let(:body) { "Hello world" }
let(:content_type) { "text/plain" }
subject { HTTP::Response.new(200, "1.1", {"Content-Type" => content_type}, body) }
let(:headers) { {"Content-Type" => content_type} }

it "returns a Rack-like array" do
expect(subject.to_a).to eq([200, {"Content-Type" => content_type}, body])
expect(subject.to_a).to eq([200, headers, body])
end
end

describe "mime_type" do
subject { HTTP::Response.new(200, "1.1", headers, "").mime_type }
subject { response.mime_type }

context "without Content-Type header" do
let(:headers) { {} }
Expand All @@ -36,7 +38,7 @@
end

describe "charset" do
subject { HTTP::Response.new(200, "1.1", headers, "").charset }
subject { response.charset }

context "without Content-Type header" do
let(:headers) { {} }
Expand All @@ -57,7 +59,6 @@
describe "#parse" do
let(:headers) { {"Content-Type" => content_type} }
let(:body) { '{"foo":"bar"}' }
let(:response) { HTTP::Response.new 200, "1.1", headers, body }

context "with known content type" do
let(:content_type) { "application/json" }
Expand Down Expand Up @@ -86,8 +87,7 @@
end

describe "#flush" do
let(:body) { double :to_s => "" }
let(:response) { HTTP::Response.new 200, "1.1", {}, body }
let(:body) { double :to_s => "" }

it "returns response self-reference" do
expect(response.flush).to be response
Expand All @@ -100,11 +100,10 @@
end

describe "#inspect" do
it "returns human0friendly response representation" do
headers = {:content_type => "text/plain"}
body = double :to_s => "foobar"
response = HTTP::Response.new(200, "1.1", headers, body)
let(:headers) { {:content_type => "text/plain"} }
let(:body) { double :to_s => "foobar" }

it "returns human-friendly response representation" do
expect(response.inspect).
to eq '#<HTTP::Response/1.1 200 OK {"Content-Type"=>"text/plain"}>'
end
Expand All @@ -114,4 +113,25 @@
subject { response.caching }
it { is_expected.to be_a HTTP::Response::Caching }
end

describe "#cookies" do
let(:cookies) { ["a=1", "b=2; domain=example.com", "c=3; domain=bad.org"] }
let(:headers) { {"Set-Cookie" => cookies} }

subject(:jar) { response.cookies }

it { is_expected.to be_an HTTP::CookieJar }

it "contains cookies without domain restriction" do
expect(jar.count { |c| "a" == c.name }).to eq 1
end

it "contains cookies limited to domain of request uri" do
expect(jar.count { |c| "b" == c.name }).to eq 1
end

it "does not contains cookies limited to non-requeted uri" do
expect(jar.count { |c| "c" == c.name }).to eq 0
end
end
end

0 comments on commit d95f0d4

Please sign in to comment.