http 0.6.4 → 0.7.0
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/.rubocop.yml +38 -93
- data/.travis.yml +5 -5
- data/.yardopts +2 -0
- data/CHANGES.md +21 -5
- data/Gemfile +12 -9
- data/Guardfile +3 -4
- data/LICENSE.txt +1 -1
- data/README.md +76 -53
- data/Rakefile +1 -1
- data/examples/parallel_requests_with_celluloid.rb +1 -1
- data/http.gemspec +6 -5
- data/lib/http.rb +0 -1
- data/lib/http/chainable.rb +54 -20
- data/lib/http/client.rb +14 -12
- data/lib/http/headers.rb +20 -17
- data/lib/http/mime_type/adapter.rb +1 -1
- data/lib/http/options.rb +1 -1
- data/lib/http/request.rb +23 -16
- data/lib/http/request/writer.rb +2 -7
- data/lib/http/response.rb +47 -76
- data/lib/http/response/body.rb +2 -3
- data/lib/http/response/status.rb +122 -0
- data/lib/http/response/status/reasons.rb +72 -0
- data/lib/http/version.rb +1 -1
- data/logo.png +0 -0
- data/spec/http/client_spec.rb +13 -47
- data/spec/http/content_type_spec.rb +15 -15
- data/spec/http/headers/mixin_spec.rb +1 -1
- data/spec/http/headers_spec.rb +42 -38
- data/spec/http/options/body_spec.rb +1 -1
- data/spec/http/options/form_spec.rb +1 -1
- data/spec/http/options/headers_spec.rb +2 -2
- data/spec/http/options/json_spec.rb +1 -1
- data/spec/http/options/merge_spec.rb +1 -1
- data/spec/http/options/new_spec.rb +2 -2
- data/spec/http/options/proxy_spec.rb +1 -1
- data/spec/http/options_spec.rb +1 -1
- data/spec/http/redirector_spec.rb +1 -1
- data/spec/http/request/writer_spec.rb +72 -24
- data/spec/http/request_spec.rb +31 -35
- data/spec/http/response/body_spec.rb +1 -1
- data/spec/http/response/status_spec.rb +139 -0
- data/spec/http/response_spec.rb +7 -7
- data/spec/http_spec.rb +41 -37
- data/spec/spec_helper.rb +2 -10
- data/spec/support/example_server.rb +14 -86
- data/spec/support/example_server/servlet.rb +102 -0
- metadata +46 -21
- data/lib/http/authorization_header.rb +0 -37
- data/lib/http/authorization_header/basic_auth.rb +0 -24
- data/lib/http/authorization_header/bearer_token.rb +0 -28
- data/lib/http/backports.rb +0 -2
- data/lib/http/backports/base64.rb +0 -6
- data/lib/http/backports/uri.rb +0 -131
- data/spec/http/authorization_header/basic_auth_spec.rb +0 -29
- data/spec/http/authorization_header/bearer_token_spec.rb +0 -36
- data/spec/http/authorization_header_spec.rb +0 -41
- data/spec/http/backports/base64_spec.rb +0 -13
- data/spec/http/backports/uri_spec.rb +0 -9
- data/spec/support/black_hole.rb +0 -5
- data/spec/support/create_certs.rb +0 -57
- data/spec/support/dummy_server.rb +0 -52
- data/spec/support/dummy_server/servlet.rb +0 -30
- data/spec/support/servers/config.rb +0 -13
- data/spec/support/servers/runner.rb +0 -17
data/Rakefile
CHANGED
@@ -21,7 +21,7 @@ end
|
|
21
21
|
|
22
22
|
fetcher = HttpFetcher.new
|
23
23
|
|
24
|
-
urls = %w
|
24
|
+
urls = %w(http://ruby-lang.org/ http://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
@@ -3,8 +3,8 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
3
|
require 'http/version'
|
4
4
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
|
-
gem.authors = %w
|
7
|
-
gem.email = %w
|
6
|
+
gem.authors = %w(Tony Arcieri)
|
7
|
+
gem.email = %w(tony.arcieri@gmail.com)
|
8
8
|
|
9
9
|
gem.description = <<-DESCRIPTION.strip.gsub(/\s+/, ' ')
|
10
10
|
An easy-to-use client library for making requests from Ruby.
|
@@ -13,17 +13,18 @@ Gem::Specification.new do |gem|
|
|
13
13
|
DESCRIPTION
|
14
14
|
|
15
15
|
gem.summary = 'HTTP should be easy'
|
16
|
-
gem.homepage = 'https://github.com/tarcieri/http'
|
17
|
-
gem.licenses = %w
|
16
|
+
gem.homepage = 'https://github.com/tarcieri/http.rb'
|
17
|
+
gem.licenses = %w(MIT)
|
18
18
|
|
19
19
|
gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
20
20
|
gem.files = `git ls-files`.split("\n")
|
21
21
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
22
|
gem.name = 'http'
|
23
|
-
gem.require_paths = %w
|
23
|
+
gem.require_paths = %w(lib)
|
24
24
|
gem.version = HTTP::VERSION
|
25
25
|
|
26
26
|
gem.add_runtime_dependency 'http_parser.rb', '~> 0.6.0'
|
27
|
+
gem.add_runtime_dependency 'form_data', '~> 0.0.1'
|
27
28
|
|
28
29
|
gem.add_development_dependency 'bundler', '~> 1.0'
|
29
30
|
end
|
data/lib/http.rb
CHANGED
data/lib/http/chainable.rb
CHANGED
@@ -1,58 +1,80 @@
|
|
1
|
-
require '
|
1
|
+
require 'base64'
|
2
2
|
|
3
3
|
module HTTP
|
4
4
|
module Chainable
|
5
5
|
# Request a get sans response body
|
6
|
+
# @param uri
|
7
|
+
# @option options [Hash]
|
6
8
|
def head(uri, options = {})
|
7
9
|
request :head, uri, options
|
8
10
|
end
|
9
11
|
|
10
12
|
# Get a resource
|
13
|
+
# @param uri
|
14
|
+
# @option options [Hash]
|
11
15
|
def get(uri, options = {})
|
12
16
|
request :get, uri, options
|
13
17
|
end
|
14
18
|
|
15
19
|
# Post to a resource
|
20
|
+
# @param uri
|
21
|
+
# @option options [Hash]
|
16
22
|
def post(uri, options = {})
|
17
23
|
request :post, uri, options
|
18
24
|
end
|
19
25
|
|
20
26
|
# Put to a resource
|
27
|
+
# @param uri
|
28
|
+
# @option options [Hash]
|
21
29
|
def put(uri, options = {})
|
22
30
|
request :put, uri, options
|
23
31
|
end
|
24
32
|
|
25
33
|
# Delete a resource
|
34
|
+
# @param uri
|
35
|
+
# @option options [Hash]
|
26
36
|
def delete(uri, options = {})
|
27
37
|
request :delete, uri, options
|
28
38
|
end
|
29
39
|
|
30
40
|
# Echo the request back to the client
|
41
|
+
# @param uri
|
42
|
+
# @option options [Hash]
|
31
43
|
def trace(uri, options = {})
|
32
44
|
request :trace, uri, options
|
33
45
|
end
|
34
46
|
|
35
47
|
# Return the methods supported on the given URI
|
48
|
+
# @param uri
|
49
|
+
# @option options [Hash]
|
36
50
|
def options(uri, options = {})
|
37
51
|
request :options, uri, options
|
38
52
|
end
|
39
53
|
|
40
54
|
# Convert to a transparent TCP/IP tunnel
|
55
|
+
# @param uri
|
56
|
+
# @option options [Hash]
|
41
57
|
def connect(uri, options = {})
|
42
58
|
request :connect, uri, options
|
43
59
|
end
|
44
60
|
|
45
61
|
# Apply partial modifications to a resource
|
62
|
+
# @param uri
|
63
|
+
# @option options [Hash]
|
46
64
|
def patch(uri, options = {})
|
47
65
|
request :patch, uri, options
|
48
66
|
end
|
49
67
|
|
50
68
|
# Make an HTTP request with the given verb
|
69
|
+
# @param uri
|
70
|
+
# @option options [Hash]
|
51
71
|
def request(verb, uri, options = {})
|
52
72
|
branch(options).request verb, uri
|
53
73
|
end
|
54
74
|
|
55
75
|
# Make a request through an HTTP proxy
|
76
|
+
# @param [Array] proxy
|
77
|
+
# @raise [Request::Error] if HTTP proxy is invalid
|
56
78
|
def via(*proxy)
|
57
79
|
proxy_hash = {}
|
58
80
|
proxy_hash[:proxy_address] = proxy[0] if proxy[0].is_a?(String)
|
@@ -74,68 +96,80 @@ module HTTP
|
|
74
96
|
end
|
75
97
|
|
76
98
|
# Make client follow redirects.
|
77
|
-
# @param opts
|
99
|
+
# @param opts
|
78
100
|
# @return [HTTP::Client]
|
101
|
+
# @see Redirector#initialize
|
79
102
|
def follow(opts = true)
|
80
103
|
branch default_options.with_follow opts
|
81
104
|
end
|
82
105
|
|
83
|
-
# (see #follow)
|
84
106
|
# @deprecated
|
107
|
+
# @see #follow
|
85
108
|
alias_method :with_follow, :follow
|
86
109
|
|
87
110
|
# Make a request with the given headers
|
111
|
+
# @param headers
|
88
112
|
def with_headers(headers)
|
89
113
|
branch default_options.with_headers(headers)
|
90
114
|
end
|
91
115
|
alias_method :with, :with_headers
|
92
116
|
|
93
117
|
# Accept the given MIME type(s)
|
118
|
+
# @param type
|
94
119
|
def accept(type)
|
95
120
|
with :accept => MimeType.normalize(type)
|
96
121
|
end
|
97
122
|
|
98
123
|
# Make a request with the given Authorization header
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
105
|
-
|
124
|
+
# @param [#to_s] value Authorization header value
|
125
|
+
def auth(value, opts = nil)
|
126
|
+
# shim for deprecated auth(:basic, opts).
|
127
|
+
# will be removed in 0.8.0
|
128
|
+
return basic_auth(opts) if :basic == value
|
106
129
|
with :authorization => value.to_s
|
107
130
|
end
|
108
131
|
|
132
|
+
# Make a request with the given Basic authorization header
|
133
|
+
# @see http://tools.ietf.org/html/rfc2617
|
134
|
+
# @param [#fetch] opts
|
135
|
+
# @option opts [#to_s] :user
|
136
|
+
# @option opts [#to_s] :pass
|
137
|
+
def basic_auth(opts)
|
138
|
+
user = opts.fetch :user
|
139
|
+
pass = opts.fetch :pass
|
140
|
+
|
141
|
+
auth('Basic ' << Base64.strict_encode64("#{user}:#{pass}"))
|
142
|
+
end
|
143
|
+
|
144
|
+
# Get options for HTTP
|
145
|
+
# @return [HTTP::Options]
|
109
146
|
def default_options
|
110
147
|
@default_options ||= HTTP::Options.new
|
111
148
|
end
|
112
149
|
|
150
|
+
# Set options for HTTP
|
151
|
+
# @param opts
|
152
|
+
# @return [HTTP::Options]
|
113
153
|
def default_options=(opts)
|
114
154
|
@default_options = HTTP::Options.new(opts)
|
115
155
|
end
|
116
156
|
|
157
|
+
# Get headers of HTTP options
|
117
158
|
def default_headers
|
118
159
|
default_options.headers
|
119
160
|
end
|
120
161
|
|
162
|
+
# Set headers of HTTP options
|
163
|
+
# @param headers
|
121
164
|
def default_headers=(headers)
|
122
165
|
@default_options = default_options.dup do |opts|
|
123
166
|
opts.headers = headers
|
124
167
|
end
|
125
168
|
end
|
126
169
|
|
127
|
-
def default_callbacks
|
128
|
-
default_options.callbacks
|
129
|
-
end
|
130
|
-
|
131
|
-
def default_callbacks=(callbacks)
|
132
|
-
@default_options = default_options.dup do |opts|
|
133
|
-
opts.callbacks = callbacks
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
170
|
private
|
138
171
|
|
172
|
+
# :nodoc:
|
139
173
|
def branch(options)
|
140
174
|
HTTP::Client.new(options)
|
141
175
|
end
|
data/lib/http/client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'cgi'
|
2
2
|
require 'uri'
|
3
|
+
require 'form_data'
|
3
4
|
require 'http/options'
|
4
5
|
require 'http/redirector'
|
5
6
|
|
@@ -51,7 +52,7 @@ module HTTP
|
|
51
52
|
|
52
53
|
# TODO: keep-alive support
|
53
54
|
@socket = options[:socket_class].open(req.socket_host, req.socket_port)
|
54
|
-
@socket = start_tls(@socket,
|
55
|
+
@socket = start_tls(@socket, options) if uri.is_a?(URI::HTTPS) && !req.using_proxy?
|
55
56
|
|
56
57
|
req.stream @socket
|
57
58
|
|
@@ -66,6 +67,9 @@ module HTTP
|
|
66
67
|
end
|
67
68
|
|
68
69
|
# Read a chunk of the body
|
70
|
+
#
|
71
|
+
# @return [String] data chunk
|
72
|
+
# @return [Nil] when no more data left
|
69
73
|
def readpartial(size = BUFFER_SIZE)
|
70
74
|
return unless @socket
|
71
75
|
|
@@ -86,17 +90,12 @@ module HTTP
|
|
86
90
|
private
|
87
91
|
|
88
92
|
# Initialize TLS connection
|
89
|
-
def start_tls(socket,
|
93
|
+
def start_tls(socket, options)
|
90
94
|
# TODO: abstract away SSLContexts so we can use other TLS libraries
|
91
95
|
context = options[:ssl_context] || OpenSSL::SSL::SSLContext.new
|
92
96
|
socket = options[:ssl_socket_class].new(socket, context)
|
93
97
|
|
94
98
|
socket.connect
|
95
|
-
|
96
|
-
if context.verify_mode == OpenSSL::SSL::VERIFY_PEER
|
97
|
-
socket.post_connection_check(host)
|
98
|
-
end
|
99
|
-
|
100
99
|
socket
|
101
100
|
end
|
102
101
|
|
@@ -129,12 +128,15 @@ module HTTP
|
|
129
128
|
|
130
129
|
# Create the request body object to send
|
131
130
|
def make_request_body(opts, headers)
|
132
|
-
|
131
|
+
case
|
132
|
+
when opts.body
|
133
133
|
opts.body
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
134
|
+
when opts.form
|
135
|
+
form = FormData.create opts.form
|
136
|
+
headers['Content-Type'] ||= form.content_type
|
137
|
+
headers['Content-Length'] ||= form.content_length
|
138
|
+
form.to_s
|
139
|
+
when opts.json
|
138
140
|
headers['Content-Type'] ||= 'application/json'
|
139
141
|
MimeType[:json].encode opts.json
|
140
142
|
end
|
data/lib/http/headers.rb
CHANGED
@@ -122,24 +122,27 @@ module HTTP
|
|
122
122
|
dup.tap { |dupped| dupped.merge! other }
|
123
123
|
end
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
object
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
125
|
+
class << self
|
126
|
+
# Initiates new Headers object from given object.
|
127
|
+
#
|
128
|
+
# @raise [Error] if given object can't be coerced
|
129
|
+
# @param [#to_hash, #to_h, #to_a] object
|
130
|
+
# @return [Headers]
|
131
|
+
def coerce(object)
|
132
|
+
unless object.is_a? self
|
133
|
+
object = case
|
134
|
+
when object.respond_to?(:to_hash) then object.to_hash
|
135
|
+
when object.respond_to?(:to_h) then object.to_h
|
136
|
+
when object.respond_to?(:to_a) then object.to_a
|
137
|
+
else fail Error, "Can't coerce #{object.inspect} to Headers"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
headers = new
|
142
|
+
object.each { |k, v| headers.add k, v }
|
143
|
+
headers
|
138
144
|
end
|
139
|
-
|
140
|
-
headers = new
|
141
|
-
object.each { |k, v| headers.add k, v }
|
142
|
-
headers
|
145
|
+
alias_method :[], :coerce
|
143
146
|
end
|
144
147
|
|
145
148
|
private
|
@@ -12,7 +12,7 @@ module HTTP
|
|
12
12
|
def_delegators :instance, :encode, :decode
|
13
13
|
end
|
14
14
|
|
15
|
-
%w
|
15
|
+
%w(encode decode).each do |operation|
|
16
16
|
class_eval <<-RUBY, __FILE__, __LINE__
|
17
17
|
def #{operation}(*)
|
18
18
|
fail Error, "\#{self.class} does not supports ##{operation}"
|
data/lib/http/options.rb
CHANGED
@@ -70,7 +70,7 @@ module HTTP
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
%w
|
73
|
+
%w(proxy params form json body follow).each do |method_name|
|
74
74
|
class_eval <<-RUBY, __FILE__, __LINE__
|
75
75
|
def with_#{method_name}(value)
|
76
76
|
dup { |opts| opts.#{method_name} = value }
|
data/lib/http/request.rb
CHANGED
@@ -53,15 +53,11 @@ module HTTP
|
|
53
53
|
# Scheme is normalized to be a lowercase symbol e.g. :http, :https
|
54
54
|
attr_reader :scheme
|
55
55
|
|
56
|
-
# The following alias may be removed in
|
56
|
+
# The following alias may be removed in two minor versions (0.8.0) or one
|
57
57
|
# major version (1.0.0)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
# major version (1.0.0)
|
62
|
-
def method(*)
|
63
|
-
warn "#{Kernel.caller.first}: [DEPRECATION] HTTP::Request#method is deprecated. Use #verb instead. For Object#method, use #__method__."
|
64
|
-
@verb
|
58
|
+
def __method__(*args)
|
59
|
+
warn "#{Kernel.caller.first}: [DEPRECATION] HTTP::Request#__method__ is deprecated. Use #method instead."
|
60
|
+
method(*args)
|
65
61
|
end
|
66
62
|
|
67
63
|
# "Request URI" as per RFC 2616
|
@@ -73,7 +69,7 @@ module HTTP
|
|
73
69
|
def initialize(verb, uri, headers = {}, proxy = {}, body = nil, version = '1.1') # rubocop:disable ParameterLists
|
74
70
|
@verb = verb.to_s.downcase.to_sym
|
75
71
|
@uri = uri.is_a?(URI) ? uri : URI(uri.to_s)
|
76
|
-
@scheme = @uri.scheme.to_s.downcase.to_sym
|
72
|
+
@scheme = @uri.scheme && @uri.scheme.to_s.downcase.to_sym
|
77
73
|
|
78
74
|
fail(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb)
|
79
75
|
fail(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme)
|
@@ -118,13 +114,7 @@ module HTTP
|
|
118
114
|
|
119
115
|
# Compute HTTP request header for direct or proxy request
|
120
116
|
def request_header
|
121
|
-
|
122
|
-
"#{verb.to_s.upcase} #{uri} HTTP/#{version}"
|
123
|
-
else
|
124
|
-
path = uri.query && !uri.query.empty? ? "#{uri.path}?#{uri.query}" : uri.path
|
125
|
-
path = '/' if path.empty?
|
126
|
-
"#{verb.to_s.upcase} #{path} HTTP/#{version}"
|
127
|
-
end
|
117
|
+
"#{verb.to_s.upcase} #{path_for_request_header} HTTP/#{version}"
|
128
118
|
end
|
129
119
|
|
130
120
|
# Host for tcp socket
|
@@ -139,6 +129,23 @@ module HTTP
|
|
139
129
|
|
140
130
|
private
|
141
131
|
|
132
|
+
def path_for_request_header
|
133
|
+
if using_proxy?
|
134
|
+
uri
|
135
|
+
else
|
136
|
+
uri_path_with_query
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def uri_path_with_query
|
141
|
+
path = uri_has_query? ? "#{uri.path}?#{uri.query}" : uri.path
|
142
|
+
path.empty? ? '/' : path
|
143
|
+
end
|
144
|
+
|
145
|
+
def uri_has_query?
|
146
|
+
uri.query && !uri.query.empty?
|
147
|
+
end
|
148
|
+
|
142
149
|
# Default host (with port if needed) header value.
|
143
150
|
#
|
144
151
|
# @return [String]
|
data/lib/http/request/writer.rb
CHANGED
@@ -34,13 +34,8 @@ module HTTP
|
|
34
34
|
def add_body_type_headers
|
35
35
|
if @body.is_a?(String) && !@headers['Content-Length']
|
36
36
|
@request_header << "Content-Length: #{@body.bytesize}"
|
37
|
-
elsif @body.is_a?(Enumerable)
|
38
|
-
|
39
|
-
if encoding == 'chunked'
|
40
|
-
@request_header << 'Transfer-Encoding: chunked'
|
41
|
-
else
|
42
|
-
fail(RequestError, 'invalid transfer encoding')
|
43
|
-
end
|
37
|
+
elsif @body.is_a?(Enumerable) && 'chunked' != @headers['Transfer-Encoding']
|
38
|
+
fail(RequestError, 'invalid transfer encoding')
|
44
39
|
end
|
45
40
|
end
|
46
41
|
|