http 0.5.1 → 0.6.0.pre
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of http might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +3 -3
- data/.rspec +3 -2
- data/.rubocop.yml +101 -0
- data/.travis.yml +19 -8
- data/Gemfile +24 -6
- data/LICENSE.txt +1 -1
- data/README.md +144 -29
- data/Rakefile +23 -1
- data/examples/parallel_requests_with_celluloid.rb +2 -2
- data/http.gemspec +14 -14
- data/lib/http.rb +5 -4
- data/lib/http/authorization_header.rb +37 -0
- data/lib/http/authorization_header/basic_auth.rb +24 -0
- data/lib/http/authorization_header/bearer_token.rb +29 -0
- data/lib/http/backports.rb +2 -0
- data/lib/http/backports/base64.rb +6 -0
- data/lib/http/{uri_backport.rb → backports/uri.rb} +10 -10
- data/lib/http/chainable.rb +24 -25
- data/lib/http/client.rb +97 -67
- data/lib/http/content_type.rb +27 -0
- data/lib/http/errors.rb +13 -0
- data/lib/http/headers.rb +154 -0
- data/lib/http/headers/mixin.rb +11 -0
- data/lib/http/mime_type.rb +61 -36
- data/lib/http/mime_type/adapter.rb +24 -0
- data/lib/http/mime_type/json.rb +23 -0
- data/lib/http/options.rb +21 -48
- data/lib/http/redirector.rb +12 -7
- data/lib/http/request.rb +82 -33
- data/lib/http/request/writer.rb +79 -0
- data/lib/http/response.rb +39 -68
- data/lib/http/response/body.rb +62 -0
- data/lib/http/{response_parser.rb → response/parser.rb} +3 -1
- data/lib/http/version.rb +1 -1
- data/logo.png +0 -0
- data/spec/http/authorization_header/basic_auth_spec.rb +29 -0
- data/spec/http/authorization_header/bearer_token_spec.rb +36 -0
- data/spec/http/authorization_header_spec.rb +41 -0
- data/spec/http/backports/base64_spec.rb +13 -0
- data/spec/http/client_spec.rb +181 -0
- data/spec/http/content_type_spec.rb +47 -0
- data/spec/http/headers/mixin_spec.rb +36 -0
- data/spec/http/headers_spec.rb +417 -0
- data/spec/http/options/body_spec.rb +6 -7
- data/spec/http/options/form_spec.rb +4 -5
- data/spec/http/options/headers_spec.rb +9 -17
- data/spec/http/options/json_spec.rb +17 -0
- data/spec/http/options/merge_spec.rb +18 -19
- data/spec/http/options/new_spec.rb +5 -19
- data/spec/http/options/proxy_spec.rb +6 -6
- data/spec/http/options_spec.rb +3 -9
- data/spec/http/redirector_spec.rb +100 -0
- data/spec/http/request/writer_spec.rb +25 -0
- data/spec/http/request_spec.rb +54 -14
- data/spec/http/response/body_spec.rb +24 -0
- data/spec/http/response_spec.rb +61 -32
- data/spec/http_spec.rb +77 -86
- data/spec/spec_helper.rb +25 -2
- data/spec/support/example_server.rb +58 -49
- data/spec/support/proxy_server.rb +27 -11
- metadata +60 -55
- data/lib/http/header.rb +0 -11
- data/lib/http/mime_types/json.rb +0 -19
- data/lib/http/request_stream.rb +0 -77
- data/spec/http/options/callbacks_spec.rb +0 -62
- data/spec/http/options/response_spec.rb +0 -24
- data/spec/http/request_stream_spec.rb +0 -25
data/Rakefile
CHANGED
@@ -4,4 +4,26 @@ require 'bundler/gem_tasks'
|
|
4
4
|
require 'rspec/core/rake_task'
|
5
5
|
RSpec::Core::RakeTask.new
|
6
6
|
|
7
|
-
task :
|
7
|
+
task :test => :spec
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'rubocop/rake_task'
|
11
|
+
Rubocop::RakeTask.new
|
12
|
+
rescue LoadError
|
13
|
+
task :rubocop do
|
14
|
+
$stderr.puts 'Rubocop is disabled'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'yardstick/rake/measurement'
|
19
|
+
Yardstick::Rake::Measurement.new do |measurement|
|
20
|
+
measurement.output = 'measurement/report.txt'
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'yardstick/rake/verify'
|
24
|
+
Yardstick::Rake::Verify.new do |verify|
|
25
|
+
verify.require_exact_threshold = false
|
26
|
+
verify.threshold = 58
|
27
|
+
end
|
28
|
+
|
29
|
+
task :default => [:spec, :rubocop, :verify_measurements]
|
@@ -15,13 +15,13 @@ class HttpFetcher
|
|
15
15
|
def fetch(url)
|
16
16
|
# Note: For SSL support specify:
|
17
17
|
# ssl_socket_class: Celluloid::IO::SSLSocket
|
18
|
-
HTTP.get(url, socket_class
|
18
|
+
HTTP.get(url, :socket_class => Celluloid::IO::TCPSocket).response
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
fetcher = HttpFetcher.new
|
23
23
|
|
24
|
-
urls = %w
|
24
|
+
urls = %w[http://www.ruby-lang.org/ http://www.rubygems.org/ http://celluloid.io/]
|
25
25
|
|
26
26
|
# Kick off a bunch of future calls to HttpFetcher to grab the URLs in parallel
|
27
27
|
futures = urls.map { |u| [u, fetcher.future.fetch(u)] }
|
data/http.gemspec
CHANGED
@@ -1,23 +1,23 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'http/version'
|
3
4
|
|
4
5
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors = [
|
6
|
-
gem.email = [
|
7
|
-
gem.description =
|
8
|
-
gem.summary =
|
9
|
-
gem.homepage =
|
6
|
+
gem.authors = %w[Tony Arcieri]
|
7
|
+
gem.email = %w[tony.arcieri@gmail.com]
|
8
|
+
gem.description = 'HTTP so awesome it will lure Catherine Zeta Jones into your unicorn petting zoo'
|
9
|
+
gem.summary = 'HTTP should be easy'
|
10
|
+
gem.homepage = 'https://github.com/tarcieri/http'
|
11
|
+
gem.licenses = %w[MIT]
|
10
12
|
|
11
|
-
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
12
14
|
gem.files = `git ls-files`.split("\n")
|
13
15
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
-
gem.name =
|
15
|
-
gem.require_paths = [
|
16
|
+
gem.name = 'http'
|
17
|
+
gem.require_paths = %w[lib]
|
16
18
|
gem.version = HTTP::VERSION
|
17
19
|
|
18
|
-
gem.add_runtime_dependency 'http_parser.rb'
|
20
|
+
gem.add_runtime_dependency 'http_parser.rb', '~> 0.6.0'
|
19
21
|
|
20
|
-
gem.add_development_dependency '
|
21
|
-
gem.add_development_dependency 'rspec', '>= 2.11'
|
22
|
-
gem.add_development_dependency 'json'
|
22
|
+
gem.add_development_dependency 'bundler', '~> 1.0'
|
23
23
|
end
|
data/lib/http.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
require 'http/parser'
|
2
2
|
|
3
|
+
require 'http/errors'
|
3
4
|
require 'http/chainable'
|
4
5
|
require 'http/client'
|
5
|
-
require 'http/mime_type'
|
6
6
|
require 'http/options'
|
7
7
|
require 'http/request'
|
8
|
-
require 'http/
|
8
|
+
require 'http/request/writer'
|
9
9
|
require 'http/response'
|
10
|
-
require 'http/
|
11
|
-
require 'http/
|
10
|
+
require 'http/response/body'
|
11
|
+
require 'http/response/parser'
|
12
|
+
require 'http/backports' if RUBY_VERSION < '1.9.0'
|
12
13
|
|
13
14
|
# HTTP should be easy
|
14
15
|
module HTTP
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module HTTP
|
2
|
+
# Authorization header value builders
|
3
|
+
module AuthorizationHeader
|
4
|
+
class << self
|
5
|
+
# Associate type with given builder.
|
6
|
+
# @param [#to_sym] type
|
7
|
+
# @param [Class] klass
|
8
|
+
# @return [void]
|
9
|
+
def register(type, klass)
|
10
|
+
builders[type.to_sym] = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
# Builds Authorization header value with associated builder.
|
14
|
+
# @param [#to_sym] type
|
15
|
+
# @param [Object] opts
|
16
|
+
# @return [String]
|
17
|
+
def build(type, opts)
|
18
|
+
klass = builders[type.to_sym]
|
19
|
+
|
20
|
+
fail Error, "Unknown authorization type #{type}" unless klass
|
21
|
+
|
22
|
+
klass.new opts
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# :nodoc:
|
28
|
+
def builders
|
29
|
+
@builders ||= {}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# built-in builders
|
36
|
+
require 'http/authorization_header/basic_auth'
|
37
|
+
require 'http/authorization_header/bearer_token'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
module AuthorizationHeader
|
5
|
+
# Basic authorization header builder
|
6
|
+
# @see http://tools.ietf.org/html/rfc2617
|
7
|
+
class BasicAuth
|
8
|
+
# @param [#fetch] opts
|
9
|
+
# @option opts [#to_s] :user
|
10
|
+
# @option opts [#to_s] :pass
|
11
|
+
def initialize(opts)
|
12
|
+
@user = opts.fetch :user
|
13
|
+
@pass = opts.fetch :pass
|
14
|
+
end
|
15
|
+
|
16
|
+
# :nodoc:
|
17
|
+
def to_s
|
18
|
+
'Basic ' << Base64.strict_encode64("#{@user}:#{@pass}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
register :basic, BasicAuth
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
module AuthorizationHeader
|
5
|
+
# OAuth2 Bearer token authorization header builder
|
6
|
+
# @see http://tools.ietf.org/html/rfc6750
|
7
|
+
class BearerToken
|
8
|
+
# @param [#fetch] opts
|
9
|
+
# @option opts [#to_s] :token
|
10
|
+
# @option opts [#to_s] :encode (false)
|
11
|
+
def initialize(opts)
|
12
|
+
@encode = opts.fetch :encode, false
|
13
|
+
@token = opts.fetch :token
|
14
|
+
end
|
15
|
+
|
16
|
+
def token
|
17
|
+
return Base64.strict_encode64 @token if @encode
|
18
|
+
@token
|
19
|
+
end
|
20
|
+
|
21
|
+
# :nodoc:
|
22
|
+
def to_s
|
23
|
+
"Bearer #{token}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
register :bearer, BearerToken
|
28
|
+
end
|
29
|
+
end
|
@@ -9,17 +9,17 @@ require 'uri'
|
|
9
9
|
module URI
|
10
10
|
TBLENCWWWCOMP_ = {} # :nodoc:
|
11
11
|
256.times do |i|
|
12
|
-
TBLENCWWWCOMP_[i.chr] = '%%%02X'
|
12
|
+
TBLENCWWWCOMP_[i.chr] = format('%%%02X', i)
|
13
13
|
end
|
14
14
|
TBLENCWWWCOMP_[' '] = '+'
|
15
15
|
TBLENCWWWCOMP_.freeze
|
16
16
|
TBLDECWWWCOMP_ = {} # :nodoc:
|
17
17
|
256.times do |i|
|
18
|
-
h, l = i>>4, i&15
|
19
|
-
TBLDECWWWCOMP_['%%%X%X'
|
20
|
-
TBLDECWWWCOMP_['%%%x%X'
|
21
|
-
TBLDECWWWCOMP_['%%%X%x'
|
22
|
-
TBLDECWWWCOMP_['%%%x%x'
|
18
|
+
h, l = i >> 4, i & 15
|
19
|
+
TBLDECWWWCOMP_[format('%%%X%X', h, l)] = i.chr
|
20
|
+
TBLDECWWWCOMP_[format('%%%x%X', h, l)] = i.chr
|
21
|
+
TBLDECWWWCOMP_[format('%%%X%x', h, l)] = i.chr
|
22
|
+
TBLDECWWWCOMP_[format('%%%x%x', h, l)] = i.chr
|
23
23
|
end
|
24
24
|
TBLDECWWWCOMP_['+'] = ' '
|
25
25
|
TBLDECWWWCOMP_.freeze
|
@@ -43,7 +43,7 @@ module URI
|
|
43
43
|
#
|
44
44
|
# See URI.encode_www_form_component, URI.decode_www_form
|
45
45
|
def self.decode_www_form_component(str)
|
46
|
-
|
46
|
+
fail(ArgumentError, "invalid %-encoding (#{str})") unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
|
47
47
|
str.gsub(/\+|%\h\h/) { |chr| TBLDECWWWCOMP_[chr] }
|
48
48
|
end
|
49
49
|
|
@@ -76,7 +76,7 @@ module URI
|
|
76
76
|
#
|
77
77
|
# See URI.encode_www_form_component, URI.decode_www_form
|
78
78
|
def self.encode_www_form(enum)
|
79
|
-
enum.map do |k,v|
|
79
|
+
enum.map do |k, v|
|
80
80
|
if v.nil?
|
81
81
|
encode_www_form_component(k)
|
82
82
|
elsif v.respond_to?(:to_ary)
|
@@ -120,11 +120,11 @@ module URI
|
|
120
120
|
def self.decode_www_form(str)
|
121
121
|
return [] if str.empty?
|
122
122
|
unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/o =~ str
|
123
|
-
|
123
|
+
fail(ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})")
|
124
124
|
end
|
125
125
|
ary = []
|
126
126
|
$&.scan(/([^=;&]+)=([^;&]*)/) do
|
127
|
-
ary << [decode_www_form_component(
|
127
|
+
ary << [decode_www_form_component(Regexp.last_match[1]), decode_www_form_component(Regexp.last_match[2])]
|
128
128
|
end
|
129
129
|
ary
|
130
130
|
end
|
data/lib/http/chainable.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'http/authorization_header'
|
2
|
+
|
1
3
|
module HTTP
|
2
4
|
module Chainable
|
3
5
|
# Request a get sans response body
|
@@ -50,34 +52,26 @@ module HTTP
|
|
50
52
|
branch(options).request verb, uri
|
51
53
|
end
|
52
54
|
|
53
|
-
# Make a request invoking the given event callbacks
|
54
|
-
def on(event, &block)
|
55
|
-
branch default_options.with_callback(event, block)
|
56
|
-
end
|
57
|
-
|
58
55
|
# Make a request through an HTTP proxy
|
59
56
|
def via(*proxy)
|
60
57
|
proxy_hash = {}
|
61
|
-
proxy_hash[:proxy_address]
|
62
|
-
proxy_hash[:proxy_port]
|
63
|
-
proxy_hash[:proxy_username]= proxy[2] if proxy[2].is_a?
|
64
|
-
proxy_hash[:proxy_password]= proxy[3] if proxy[3].is_a?
|
58
|
+
proxy_hash[:proxy_address] = proxy[0] if proxy[0].is_a?(String)
|
59
|
+
proxy_hash[:proxy_port] = proxy[1] if proxy[1].is_a?(Integer)
|
60
|
+
proxy_hash[:proxy_username] = proxy[2] if proxy[2].is_a?(String)
|
61
|
+
proxy_hash[:proxy_password] = proxy[3] if proxy[3].is_a?(String)
|
65
62
|
|
66
|
-
if proxy_hash.keys.size
|
63
|
+
if [2, 4].include?(proxy_hash.keys.size)
|
67
64
|
branch default_options.with_proxy(proxy_hash)
|
68
65
|
else
|
69
|
-
|
66
|
+
fail(RequestError, "invalid HTTP proxy: #{proxy_hash}")
|
70
67
|
end
|
71
68
|
end
|
72
69
|
alias_method :through, :via
|
73
70
|
|
74
|
-
# Specify the kind of response to return (:auto, :object, :body, :parsed_body)
|
75
|
-
def with_response(response_type)
|
76
|
-
branch default_options.with_response(response_type)
|
77
|
-
end
|
78
|
-
|
79
71
|
# Alias for with_response(:object)
|
80
|
-
def stream
|
72
|
+
def stream
|
73
|
+
with_response(:object)
|
74
|
+
end
|
81
75
|
|
82
76
|
def with_follow(follow)
|
83
77
|
branch default_options.with_follow(follow)
|
@@ -91,13 +85,18 @@ module HTTP
|
|
91
85
|
|
92
86
|
# Accept the given MIME type(s)
|
93
87
|
def accept(type)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
88
|
+
with :accept => MimeType.normalize(type)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Make a request with the given Authorization header
|
92
|
+
def auth(*args)
|
93
|
+
value = case args.count
|
94
|
+
when 1 then args.first
|
95
|
+
when 2 then AuthorizationHeader.build(*args)
|
96
|
+
else fail ArgumentError, "wrong number of arguments (#{args.count} for 1..2)"
|
97
|
+
end
|
98
|
+
|
99
|
+
with :authorization => value.to_s
|
101
100
|
end
|
102
101
|
|
103
102
|
def default_options
|
@@ -128,7 +127,7 @@ module HTTP
|
|
128
127
|
end
|
129
128
|
end
|
130
129
|
|
131
|
-
|
130
|
+
private
|
132
131
|
|
133
132
|
def branch(options)
|
134
133
|
HTTP::Client.new(options)
|
data/lib/http/client.rb
CHANGED
@@ -7,108 +7,138 @@ module HTTP
|
|
7
7
|
class Client
|
8
8
|
include Chainable
|
9
9
|
|
10
|
-
|
10
|
+
# Input buffer size
|
11
|
+
BUFFER_SIZE = 16_384
|
11
12
|
|
12
13
|
attr_reader :default_options
|
13
14
|
|
14
15
|
def initialize(default_options = {})
|
15
16
|
@default_options = HTTP::Options.new(default_options)
|
16
|
-
|
17
|
-
|
18
|
-
def body(opts, headers)
|
19
|
-
if opts.body
|
20
|
-
body = opts.body
|
21
|
-
elsif opts.form
|
22
|
-
headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
|
23
|
-
body = URI.encode_www_form(opts.form)
|
24
|
-
end
|
17
|
+
@parser = HTTP::Response::Parser.new
|
18
|
+
@socket = nil
|
25
19
|
end
|
26
20
|
|
27
21
|
# Make an HTTP request
|
28
|
-
def request(
|
22
|
+
def request(verb, uri, options = {})
|
29
23
|
opts = @default_options.merge(options)
|
30
|
-
host = URI.parse(uri).host
|
31
|
-
opts.headers["Host"] = host
|
32
24
|
headers = opts.headers
|
33
25
|
proxy = opts.proxy
|
34
26
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
27
|
+
request_body = make_request_body(opts, headers)
|
28
|
+
uri, opts = normalize_get_params(uri, opts) if verb == :get
|
29
|
+
|
30
|
+
uri = "#{uri}?#{URI.encode_www_form(opts.params)}" if opts.params && !opts.params.empty?
|
39
31
|
|
40
|
-
request = HTTP::Request.new
|
41
|
-
|
32
|
+
request = HTTP::Request.new(verb, uri, headers, proxy, request_body)
|
33
|
+
perform request, opts
|
34
|
+
end
|
42
35
|
|
43
|
-
|
36
|
+
# Perform the HTTP request (following redirects if needed)
|
37
|
+
def perform(req, options)
|
38
|
+
res = perform_without_following_redirects req, options
|
44
39
|
|
45
|
-
if
|
46
|
-
|
47
|
-
|
40
|
+
if options.follow
|
41
|
+
res = Redirector.new(options.follow).perform req, res do |request|
|
42
|
+
perform_without_following_redirects request, options
|
48
43
|
end
|
49
44
|
end
|
50
45
|
|
51
|
-
|
52
|
-
format_response method, response, opts.response
|
46
|
+
res
|
53
47
|
end
|
54
48
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
socket = options[:socket_class].open(uri.host, uri.port) # TODO: proxy support
|
49
|
+
# Read a chunk of the body
|
50
|
+
def readpartial(size = BUFFER_SIZE)
|
51
|
+
return unless @socket
|
59
52
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
53
|
+
read_more size
|
54
|
+
chunk = @parser.chunk
|
55
|
+
|
56
|
+
finish_response if @parser.finished?
|
57
|
+
|
58
|
+
chunk
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Perform a single (no follow) HTTP request
|
64
|
+
def perform_without_following_redirects(req, options)
|
65
|
+
# finish previous response if client was re-used
|
66
|
+
# TODO: this is pretty wrong, as socket shoud be part of response
|
67
|
+
# connection, so that re-use of client will not break multiple
|
68
|
+
# chunked responses
|
69
|
+
finish_response
|
69
70
|
|
70
|
-
|
71
|
+
uri = req.uri
|
72
|
+
|
73
|
+
# TODO: keep-alive support
|
74
|
+
@socket = options[:socket_class].open(req.socket_host, req.socket_port)
|
75
|
+
@socket = start_tls(@socket, options) if uri.is_a?(URI::HTTPS)
|
76
|
+
|
77
|
+
req.stream @socket
|
71
78
|
|
72
79
|
begin
|
73
|
-
|
80
|
+
read_more BUFFER_SIZE until @parser.headers
|
74
81
|
rescue IOError, Errno::ECONNRESET, Errno::EPIPE => ex
|
75
82
|
raise IOError, "problem making HTTP request: #{ex}"
|
76
83
|
end
|
77
84
|
|
78
|
-
|
79
|
-
|
80
|
-
chunk = parser.chunk || begin
|
81
|
-
parser << socket.readpartial(BUFFER_SIZE)
|
82
|
-
parser.chunk || ""
|
83
|
-
end
|
85
|
+
body = Response::Body.new(self)
|
86
|
+
res = Response.new(@parser.status_code, @parser.http_version, @parser.headers, body, uri)
|
84
87
|
|
85
|
-
|
86
|
-
@body_remaining = nil if @body_remaining && @body_remaining < 1
|
88
|
+
finish_response if :head == req.verb
|
87
89
|
|
88
|
-
|
89
|
-
|
90
|
+
res
|
91
|
+
end
|
92
|
+
|
93
|
+
# Initialize TLS connection
|
94
|
+
def start_tls(socket, options)
|
95
|
+
# TODO: abstract away SSLContexts so we can use other TLS libraries
|
96
|
+
context = options[:ssl_context] || OpenSSL::SSL::SSLContext.new
|
97
|
+
socket = options[:ssl_socket_class].new(socket, context)
|
98
|
+
|
99
|
+
socket.connect
|
100
|
+
socket
|
101
|
+
end
|
102
|
+
|
103
|
+
# Create the request body object to send
|
104
|
+
def make_request_body(opts, headers)
|
105
|
+
if opts.body
|
106
|
+
opts.body
|
107
|
+
elsif opts.form
|
108
|
+
headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
|
109
|
+
URI.encode_www_form(opts.form)
|
110
|
+
elsif opts.json
|
111
|
+
headers['Content-Type'] ||= 'application/json'
|
112
|
+
MimeType[:json].encode opts.json
|
90
113
|
end
|
114
|
+
end
|
91
115
|
|
92
|
-
|
93
|
-
|
116
|
+
# Callback for when we've reached the end of a response
|
117
|
+
def finish_response
|
118
|
+
@socket.close if @socket && !@socket.closed?
|
119
|
+
@parser.reset
|
120
|
+
|
121
|
+
@socket = nil
|
94
122
|
end
|
95
123
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
124
|
+
# Feeds some more data into parser
|
125
|
+
def read_more(size)
|
126
|
+
@parser << @socket.readpartial(size) unless @parser.finished?
|
127
|
+
return true
|
128
|
+
rescue EOFError
|
129
|
+
return false
|
130
|
+
end
|
131
|
+
|
132
|
+
# Moves uri get params into the opts.params hash
|
133
|
+
# @return [Array<URI, Hash>]
|
134
|
+
def normalize_get_params(uri, opts)
|
135
|
+
uri = URI(uri) unless uri.is_a?(URI)
|
136
|
+
if uri.query
|
137
|
+
extracted_params_from_uri = Hash[URI.decode_www_form(uri.query)]
|
138
|
+
opts = opts.with_params(extracted_params_from_uri.merge(opts.params || {}))
|
139
|
+
uri.query = nil
|
111
140
|
end
|
141
|
+
[uri, opts]
|
112
142
|
end
|
113
143
|
end
|
114
144
|
end
|