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.

Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +38 -93
  3. data/.travis.yml +5 -5
  4. data/.yardopts +2 -0
  5. data/CHANGES.md +21 -5
  6. data/Gemfile +12 -9
  7. data/Guardfile +3 -4
  8. data/LICENSE.txt +1 -1
  9. data/README.md +76 -53
  10. data/Rakefile +1 -1
  11. data/examples/parallel_requests_with_celluloid.rb +1 -1
  12. data/http.gemspec +6 -5
  13. data/lib/http.rb +0 -1
  14. data/lib/http/chainable.rb +54 -20
  15. data/lib/http/client.rb +14 -12
  16. data/lib/http/headers.rb +20 -17
  17. data/lib/http/mime_type/adapter.rb +1 -1
  18. data/lib/http/options.rb +1 -1
  19. data/lib/http/request.rb +23 -16
  20. data/lib/http/request/writer.rb +2 -7
  21. data/lib/http/response.rb +47 -76
  22. data/lib/http/response/body.rb +2 -3
  23. data/lib/http/response/status.rb +122 -0
  24. data/lib/http/response/status/reasons.rb +72 -0
  25. data/lib/http/version.rb +1 -1
  26. data/logo.png +0 -0
  27. data/spec/http/client_spec.rb +13 -47
  28. data/spec/http/content_type_spec.rb +15 -15
  29. data/spec/http/headers/mixin_spec.rb +1 -1
  30. data/spec/http/headers_spec.rb +42 -38
  31. data/spec/http/options/body_spec.rb +1 -1
  32. data/spec/http/options/form_spec.rb +1 -1
  33. data/spec/http/options/headers_spec.rb +2 -2
  34. data/spec/http/options/json_spec.rb +1 -1
  35. data/spec/http/options/merge_spec.rb +1 -1
  36. data/spec/http/options/new_spec.rb +2 -2
  37. data/spec/http/options/proxy_spec.rb +1 -1
  38. data/spec/http/options_spec.rb +1 -1
  39. data/spec/http/redirector_spec.rb +1 -1
  40. data/spec/http/request/writer_spec.rb +72 -24
  41. data/spec/http/request_spec.rb +31 -35
  42. data/spec/http/response/body_spec.rb +1 -1
  43. data/spec/http/response/status_spec.rb +139 -0
  44. data/spec/http/response_spec.rb +7 -7
  45. data/spec/http_spec.rb +41 -37
  46. data/spec/spec_helper.rb +2 -10
  47. data/spec/support/example_server.rb +14 -86
  48. data/spec/support/example_server/servlet.rb +102 -0
  49. metadata +46 -21
  50. data/lib/http/authorization_header.rb +0 -37
  51. data/lib/http/authorization_header/basic_auth.rb +0 -24
  52. data/lib/http/authorization_header/bearer_token.rb +0 -28
  53. data/lib/http/backports.rb +0 -2
  54. data/lib/http/backports/base64.rb +0 -6
  55. data/lib/http/backports/uri.rb +0 -131
  56. data/spec/http/authorization_header/basic_auth_spec.rb +0 -29
  57. data/spec/http/authorization_header/bearer_token_spec.rb +0 -36
  58. data/spec/http/authorization_header_spec.rb +0 -41
  59. data/spec/http/backports/base64_spec.rb +0 -13
  60. data/spec/http/backports/uri_spec.rb +0 -9
  61. data/spec/support/black_hole.rb +0 -5
  62. data/spec/support/create_certs.rb +0 -57
  63. data/spec/support/dummy_server.rb +0 -52
  64. data/spec/support/dummy_server/servlet.rb +0 -30
  65. data/spec/support/servers/config.rb +0 -13
  66. data/spec/support/servers/runner.rb +0 -17
@@ -1,119 +1,90 @@
1
- require 'delegate'
1
+ require 'forwardable'
2
+
2
3
  require 'http/headers'
3
4
  require 'http/content_type'
4
5
  require 'http/mime_type'
6
+ require 'http/response/status'
5
7
 
6
8
  module HTTP
7
9
  class Response
10
+ extend Forwardable
11
+
8
12
  include HTTP::Headers::Mixin
9
13
 
10
- STATUS_CODES = {
11
- 100 => 'Continue',
12
- 101 => 'Switching Protocols',
13
- 102 => 'Processing',
14
- 200 => 'OK',
15
- 201 => 'Created',
16
- 202 => 'Accepted',
17
- 203 => 'Non-Authoritative Information',
18
- 204 => 'No Content',
19
- 205 => 'Reset Content',
20
- 206 => 'Partial Content',
21
- 207 => 'Multi-Status',
22
- 226 => 'IM Used',
23
- 300 => 'Multiple Choices',
24
- 301 => 'Moved Permanently',
25
- 302 => 'Found',
26
- 303 => 'See Other',
27
- 304 => 'Not Modified',
28
- 305 => 'Use Proxy',
29
- 306 => 'Reserved',
30
- 307 => 'Temporary Redirect',
31
- 400 => 'Bad Request',
32
- 401 => 'Unauthorized',
33
- 402 => 'Payment Required',
34
- 403 => 'Forbidden',
35
- 404 => 'Not Found',
36
- 405 => 'Method Not Allowed',
37
- 406 => 'Not Acceptable',
38
- 407 => 'Proxy Authentication Required',
39
- 408 => 'Request Timeout',
40
- 409 => 'Conflict',
41
- 410 => 'Gone',
42
- 411 => 'Length Required',
43
- 412 => 'Precondition Failed',
44
- 413 => 'Request Entity Too Large',
45
- 414 => 'Request-URI Too Long',
46
- 415 => 'Unsupported Media Type',
47
- 416 => 'Requested Range Not Satisfiable',
48
- 417 => 'Expectation Failed',
49
- 418 => "I'm a Teapot",
50
- 422 => 'Unprocessable Entity',
51
- 423 => 'Locked',
52
- 424 => 'Failed Dependency',
53
- 426 => 'Upgrade Required',
54
- 500 => 'Internal Server Error',
55
- 501 => 'Not Implemented',
56
- 502 => 'Bad Gateway',
57
- 503 => 'Service Unavailable',
58
- 504 => 'Gateway Timeout',
59
- 505 => 'HTTP Version Not Supported',
60
- 506 => 'Variant Also Negotiates',
61
- 507 => 'Insufficient Storage',
62
- 510 => 'Not Extended'
63
- }
64
- STATUS_CODES.freeze
65
-
66
- SYMBOL_TO_STATUS_CODE = Hash[STATUS_CODES.map { |code, msg| [msg.downcase.gsub(/\s|-/, '_').to_sym, code] }]
67
- SYMBOL_TO_STATUS_CODE.freeze
14
+ # @deprecated Will be removed in 1.0.0
15
+ # Use Status::REASONS
16
+ STATUS_CODES = Status::REASONS
17
+
18
+ # @deprecated Will be removed in 1.0.0
19
+ SYMBOL_TO_STATUS_CODE = Hash[STATUS_CODES.map { |k, v| [v.downcase.gsub(/\s|-/, '_').to_sym, k] }].freeze
68
20
 
21
+ # @return [Status]
69
22
  attr_reader :status
23
+
24
+ # @return [Body]
70
25
  attr_reader :body
71
- attr_reader :uri
72
26
 
73
- # Status aliases! TIMTOWTDI!!! (Want to be idiomatic? Just use status :)
74
- alias_method :code, :status
75
- alias_method :status_code, :status
27
+ # @return [URI, nil]
28
+ attr_reader :uri
76
29
 
77
30
  def initialize(status, version, headers, body, uri = nil) # rubocop:disable ParameterLists
78
- @status, @version, @body, @uri = status, version, body, uri
31
+ @version, @body, @uri = version, body, uri
32
+
33
+ @status = HTTP::Response::Status.new status
79
34
  @headers = HTTP::Headers.coerce(headers || {})
80
35
  end
81
36
 
82
- # Obtain the 'Reason-Phrase' for the response
83
- def reason
84
- STATUS_CODES[@status]
85
- end
37
+ # @!method reason
38
+ # @return (see HTTP::Response::Status#reason)
39
+ def_delegator :status, :reason
40
+
41
+ # @!method code
42
+ # @return (see HTTP::Response::Status#code)
43
+ def_delegator :status, :code
44
+
45
+ # @deprecated Will be removed in 1.0.0
46
+ alias_method :status_code, :code
47
+
48
+ # @!method to_s
49
+ # (see HTTP::Response::Body#to_s)
50
+ def_delegator :body, :to_s
51
+ alias_method :to_str, :to_s
52
+
53
+ # @!method readpartial
54
+ # (see HTTP::Response::Body#readpartial)
55
+ def_delegator :body, :readpartial
86
56
 
87
57
  # Returns an Array ala Rack: `[status, headers, body]`
58
+ #
59
+ # @return [Array(Fixnum, Hash, String)]
88
60
  def to_a
89
- [status, headers.to_h, body.to_s]
61
+ [status.to_i, headers.to_h, body.to_s]
90
62
  end
91
63
 
92
- # Return the response body as a string
93
- def to_s
94
- body.to_s
95
- end
96
- alias_method :to_str, :to_s
97
-
98
64
  # Flushes body and returns self-reference
65
+ #
66
+ # @return [Response]
99
67
  def flush
100
68
  body.to_s
101
69
  self
102
70
  end
103
71
 
104
72
  # Parsed Content-Type header
73
+ #
105
74
  # @return [HTTP::ContentType]
106
75
  def content_type
107
76
  @content_type ||= ContentType.parse headers['Content-Type']
108
77
  end
109
78
 
110
79
  # MIME type of response (if any)
80
+ #
111
81
  # @return [String, nil]
112
82
  def mime_type
113
83
  @mime_type ||= content_type.mime_type
114
84
  end
115
85
 
116
86
  # Charset of response (if any)
87
+ #
117
88
  # @return [String, nil]
118
89
  def charset
119
90
  @charset ||= content_type.charset
@@ -131,7 +102,7 @@ module HTTP
131
102
 
132
103
  # Inspect a response
133
104
  def inspect
134
- "#<#{self.class}/#{@version} #{status} #{reason} headers=#{headers.inspect}>"
105
+ "#<#{self.class}/#{@version} #{code} #{reason} #{headers.inspect}>"
135
106
  end
136
107
  end
137
108
  end
@@ -15,8 +15,7 @@ module HTTP
15
15
  @contents = nil
16
16
  end
17
17
 
18
- # Read up to length bytes, but return any data that's available
19
- # @see HTTP::Client#readpartial
18
+ # (see HTTP::Client#readpartial)
20
19
  def readpartial(*args)
21
20
  stream!
22
21
  @client.readpartial(*args)
@@ -29,7 +28,7 @@ module HTTP
29
28
  end
30
29
  end
31
30
 
32
- # Eagerly consume the entire body as a string
31
+ # @return [String] eagerly consume the entire body as a string
33
32
  def to_s
34
33
  return @contents if @contents
35
34
  fail StateError, 'body is being streamed' unless @streaming.nil?
@@ -0,0 +1,122 @@
1
+ require 'delegate'
2
+
3
+ require 'http/response/status/reasons'
4
+
5
+ module HTTP
6
+ class Response
7
+ class Status < ::Delegator
8
+ class << self
9
+ # Coerces given value to Status.
10
+ #
11
+ # @example
12
+ #
13
+ # Status.coerce(:bad_request) # => Status.new(400)
14
+ # Status.coerce("400") # => Status.new(400)
15
+ # Status.coerce(true) # => raises HTTP::Error
16
+ #
17
+ # @raise [Error] if coercion is impossible
18
+ # @param [Symbol, #to_i] object
19
+ # @return [Status]
20
+ def coerce(object)
21
+ code = case
22
+ when object.is_a?(String) then SYMBOL_CODES[symbolize object]
23
+ when object.is_a?(Symbol) then SYMBOL_CODES[object]
24
+ when object.is_a?(Numeric) then object.to_i
25
+ else nil
26
+ end
27
+
28
+ return new code if code
29
+
30
+ fail Error, "Can't coerce #{object.class}(#{object}) to #{self}"
31
+ end
32
+ alias_method :[], :coerce
33
+
34
+ private
35
+
36
+ # Symbolizes given string
37
+ #
38
+ # @example
39
+ #
40
+ # symbolize "Bad Request" # => :bad_request
41
+ # symbolize "Request-URI Too Long" # => :request_uri_too_long
42
+ # symbolize "I'm a Teapot" # => :im_a_teapot
43
+ #
44
+ # @param [#to_s] str
45
+ # @return [Symbol]
46
+ def symbolize(str)
47
+ str.to_s.downcase.gsub(/-/, ' ').gsub(/[^a-z ]/, '').gsub(/\s+/, '_').to_sym
48
+ end
49
+ end
50
+
51
+ # Code to Symbol map
52
+ #
53
+ # @example Usage
54
+ #
55
+ # SYMBOLS[400] # => :bad_request
56
+ # SYMBOLS[414] # => :request_uri_too_long
57
+ # SYMBOLS[418] # => :im_a_teapot
58
+ #
59
+ # @return [Hash<Fixnum => Symbol>]
60
+ SYMBOLS = Hash[REASONS.map { |k, v| [k, symbolize(v)] }].freeze
61
+
62
+ # Reversed {SYMBOLS} map.
63
+ #
64
+ # @example Usage
65
+ #
66
+ # SYMBOL_CODES[:bad_request] # => 400
67
+ # SYMBOL_CODES[:request_uri_too_long] # => 414
68
+ # SYMBOL_CODES[:im_a_teapot] # => 418
69
+ #
70
+ # @return [Hash<Symbol => Fixnum>]
71
+ SYMBOL_CODES = Hash[SYMBOLS.map { |k, v| [v, k] }].freeze
72
+
73
+ # @return [Fixnum] status code
74
+ attr_reader :code
75
+
76
+ if RUBY_VERSION < '1.9.0'
77
+ # @param [#to_i] code
78
+ def initialize(code)
79
+ super __setobj__ code
80
+ end
81
+ end
82
+
83
+ # @see REASONS
84
+ # @return [String, nil] status message
85
+ def reason
86
+ REASONS[code]
87
+ end
88
+
89
+ # Symbolized {#reason}
90
+ #
91
+ # @return [nil] unless code is well-known (see REASONS)
92
+ # @return [Symbol]
93
+ def symbolize
94
+ SYMBOLS[code]
95
+ end
96
+
97
+ # Printable version of HTTP Status, surrounded by quote marks,
98
+ # with special characters escaped.
99
+ #
100
+ # (see String#inspect)
101
+ def inspect
102
+ "#{code} #{reason}".inspect
103
+ end
104
+
105
+ SYMBOLS.each do |code, symbol|
106
+ class_eval <<-RUBY, __FILE__, __LINE__
107
+ def #{symbol}? # def bad_request?
108
+ #{code} == code # 400 == code
109
+ end # end
110
+ RUBY
111
+ end
112
+
113
+ def __setobj__(obj)
114
+ @code = obj.to_i
115
+ end
116
+
117
+ def __getobj__
118
+ @code
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,72 @@
1
+ require 'delegate'
2
+
3
+ module HTTP
4
+ class Response
5
+ class Status < ::Delegator
6
+ # Code to Reason map
7
+ #
8
+ # @example Usage
9
+ #
10
+ # REASONS[400] # => "Bad Request"
11
+ # REASONS[414] # => "Request-URI Too Long"
12
+ # REASONS[418] # => "I'm a Teapot"
13
+ #
14
+ # @return [Hash<Fixnum => String>]
15
+ REASONS = {
16
+ 100 => 'Continue',
17
+ 101 => 'Switching Protocols',
18
+ 102 => 'Processing',
19
+ 200 => 'OK',
20
+ 201 => 'Created',
21
+ 202 => 'Accepted',
22
+ 203 => 'Non-Authoritative Information',
23
+ 204 => 'No Content',
24
+ 205 => 'Reset Content',
25
+ 206 => 'Partial Content',
26
+ 207 => 'Multi-Status',
27
+ 226 => 'IM Used',
28
+ 300 => 'Multiple Choices',
29
+ 301 => 'Moved Permanently',
30
+ 302 => 'Found',
31
+ 303 => 'See Other',
32
+ 304 => 'Not Modified',
33
+ 305 => 'Use Proxy',
34
+ 306 => 'Reserved',
35
+ 307 => 'Temporary Redirect',
36
+ 308 => 'Permanent Redirect',
37
+ 400 => 'Bad Request',
38
+ 401 => 'Unauthorized',
39
+ 402 => 'Payment Required',
40
+ 403 => 'Forbidden',
41
+ 404 => 'Not Found',
42
+ 405 => 'Method Not Allowed',
43
+ 406 => 'Not Acceptable',
44
+ 407 => 'Proxy Authentication Required',
45
+ 408 => 'Request Timeout',
46
+ 409 => 'Conflict',
47
+ 410 => 'Gone',
48
+ 411 => 'Length Required',
49
+ 412 => 'Precondition Failed',
50
+ 413 => 'Request Entity Too Large',
51
+ 414 => 'Request-URI Too Long',
52
+ 415 => 'Unsupported Media Type',
53
+ 416 => 'Requested Range Not Satisfiable',
54
+ 417 => 'Expectation Failed',
55
+ 418 => "I'm a Teapot",
56
+ 422 => 'Unprocessable Entity',
57
+ 423 => 'Locked',
58
+ 424 => 'Failed Dependency',
59
+ 426 => 'Upgrade Required',
60
+ 500 => 'Internal Server Error',
61
+ 501 => 'Not Implemented',
62
+ 502 => 'Bad Gateway',
63
+ 503 => 'Service Unavailable',
64
+ 504 => 'Gateway Timeout',
65
+ 505 => 'HTTP Version Not Supported',
66
+ 506 => 'Variant Also Negotiates',
67
+ 507 => 'Insufficient Storage',
68
+ 510 => 'Not Extended'
69
+ }.each { |_, v| v.freeze }.freeze
70
+ end
71
+ end
72
+ end
@@ -1,3 +1,3 @@
1
1
  module HTTP
2
- VERSION = '0.6.4'
2
+ VERSION = '0.7.0'
3
3
  end
data/logo.png CHANGED
Binary file
@@ -1,9 +1,7 @@
1
1
  require 'spec_helper'
2
- require 'support/dummy_server'
3
2
 
4
- describe HTTP::Client do
5
- let(:test_endpoint) { "http://127.0.0.1:#{ExampleService::PORT}" }
6
- run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
3
+ RSpec.describe HTTP::Client do
4
+ let(:test_endpoint) { "http://#{ExampleServer::ADDR}" }
7
5
 
8
6
  StubbedClient = Class.new(HTTP::Client) do
9
7
  def perform(request, options)
@@ -53,7 +51,7 @@ describe HTTP::Client do
53
51
  'http://example.com/' => redirect_response('/')
54
52
  )
55
53
 
56
- expect { client.get('http://example.com/') } \
54
+ expect { client.get('http://example.com/') }
57
55
  .to raise_error(HTTP::Redirector::EndlessRedirectError)
58
56
  end
59
57
 
@@ -68,7 +66,7 @@ describe HTTP::Client do
68
66
  'http://example.com/6' => simple_response('OK')
69
67
  )
70
68
 
71
- expect { client.get('http://example.com/') } \
69
+ expect { client.get('http://example.com/') }
72
70
  .to raise_error(HTTP::Redirector::TooManyRedirectsError)
73
71
  end
74
72
  end
@@ -79,7 +77,7 @@ describe HTTP::Client do
79
77
 
80
78
  it 'accepts params within the provided URL' do
81
79
  expect(HTTP::Request).to receive(:new) do |_, uri|
82
- expect(CGI.parse uri.query).to eq('foo' => %w[bar])
80
+ expect(CGI.parse uri.query).to eq('foo' => %w(bar))
83
81
  end
84
82
 
85
83
  client.get('http://example.com/?foo=bar')
@@ -87,7 +85,7 @@ describe HTTP::Client do
87
85
 
88
86
  it 'combines GET params from the URI with the passed in params' do
89
87
  expect(HTTP::Request).to receive(:new) do |_, uri|
90
- expect(CGI.parse uri.query).to eq('foo' => %w[bar], 'baz' => %w[quux])
88
+ expect(CGI.parse uri.query).to eq('foo' => %w(bar), 'baz' => %w(quux))
91
89
  end
92
90
 
93
91
  client.get('http://example.com/?foo=bar', :params => {:baz => 'quux'})
@@ -111,7 +109,7 @@ describe HTTP::Client do
111
109
 
112
110
  it 'does not corrupts index-less arrays' do
113
111
  expect(HTTP::Request).to receive(:new) do |_, uri|
114
- expect(CGI.parse uri.query).to eq 'a[]' => %w[b c], 'd' => %w[e]
112
+ expect(CGI.parse uri.query).to eq 'a[]' => %w(b c), 'd' => %w(e)
115
113
  end
116
114
 
117
115
  client.get('http://example.com/?a[]=b&a[]=c', :params => {:d => 'e'})
@@ -146,43 +144,11 @@ describe HTTP::Client do
146
144
  end
147
145
  end
148
146
 
149
- describe 'SSL' do
150
- let(:client) do
151
- described_class.new(
152
- :ssl_context => OpenSSL::SSL::SSLContext.new.tap do |context|
153
- context.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
154
-
155
- context.verify_mode = OpenSSL::SSL::VERIFY_PEER
156
- context.ca_file = File.join(certs_dir, 'ca.crt')
157
- context.cert = OpenSSL::X509::Certificate.new(
158
- File.read(File.join(certs_dir, 'client.crt'))
159
- )
160
- context.key = OpenSSL::PKey::RSA.new(
161
- File.read(File.join(certs_dir, 'client.key'))
162
- )
163
- context
164
- end
165
- )
166
- end
167
-
168
- it 'works via SSL' do
169
- response = client.get(dummy_ssl.endpoint)
170
- expect(response.body.to_s).to eq('<!doctype html>')
171
- end
172
-
173
- context 'with a mismatch host' do
174
- it 'errors' do
175
- expect { client.get(dummy_ssl.endpoint.gsub('127.0.0.1', 'localhost')) }
176
- .to raise_error(OpenSSL::SSL::SSLError, /does not match/)
177
- end
178
- end
179
- end
180
-
181
147
  describe '#perform' do
182
148
  let(:client) { described_class.new }
183
149
 
184
150
  it 'calls finish_response before actual performance' do
185
- TCPSocket.stub(:open) { throw :halt }
151
+ allow(TCPSocket).to receive(:open) { throw :halt }
186
152
  expect(client).to receive(:finish_response)
187
153
  catch(:halt) { client.head test_endpoint }
188
154
  end
@@ -278,12 +244,12 @@ describe HTTP::Client do
278
244
  RESPONSE
279
245
  ]
280
246
 
281
- socket_spy.stub(:close) { nil }
282
- socket_spy.stub(:closed?) { true }
283
- socket_spy.stub(:readpartial) { chunks.shift }
284
- socket_spy.stub(:<<) { nil }
247
+ allow(socket_spy).to receive(:close) { nil }
248
+ allow(socket_spy).to receive(:closed?) { true }
249
+ allow(socket_spy).to receive(:readpartial) { chunks.shift }
250
+ allow(socket_spy).to receive(:<<) { nil }
285
251
 
286
- TCPSocket.stub(:open) { socket_spy }
252
+ allow(TCPSocket).to receive(:open) { socket_spy }
287
253
  end
288
254
 
289
255
  it 'properly reads body' do