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
data/Rakefile CHANGED
@@ -23,7 +23,7 @@ end
23
23
  require 'yardstick/rake/verify'
24
24
  Yardstick::Rake::Verify.new do |verify|
25
25
  verify.require_exact_threshold = false
26
- verify.threshold = 58
26
+ verify.threshold = 58.8
27
27
  end
28
28
 
29
29
  task :default => [:spec, :rubocop, :verify_measurements]
@@ -21,7 +21,7 @@ end
21
21
 
22
22
  fetcher = HttpFetcher.new
23
23
 
24
- urls = %w[http://ruby-lang.org/ http://rubygems.org/ http://celluloid.io/]
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)] }
@@ -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[Tony Arcieri]
7
- gem.email = %w[tony.arcieri@gmail.com]
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[MIT]
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[lib]
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
@@ -9,7 +9,6 @@ require 'http/request/writer'
9
9
  require 'http/response'
10
10
  require 'http/response/body'
11
11
  require 'http/response/parser'
12
- require 'http/backports'
13
12
 
14
13
  # HTTP should be easy
15
14
  module HTTP
@@ -1,58 +1,80 @@
1
- require 'http/authorization_header'
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 (see Redirector#initialize)
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
- def auth(*args)
100
- value = case args.count
101
- when 1 then args.first
102
- when 2 then AuthorizationHeader.build(*args)
103
- else fail ArgumentError, "wrong number of arguments (#{args.count} for 1..2)"
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
@@ -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, uri.host, options) if uri.is_a?(URI::HTTPS)
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, host, options)
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
- if opts.body
131
+ case
132
+ when opts.body
133
133
  opts.body
134
- elsif opts.form
135
- headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
136
- URI.encode_www_form(opts.form)
137
- elsif opts.json
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
@@ -122,24 +122,27 @@ module HTTP
122
122
  dup.tap { |dupped| dupped.merge! other }
123
123
  end
124
124
 
125
- # Initiates new Headers object from given object.
126
- #
127
- # @raise [Error] if given object can't be coerced
128
- # @param [#to_hash, #to_h, #to_a] object
129
- # @return [Headers]
130
- def self.coerce(object)
131
- unless object.is_a? self
132
- object = case
133
- when object.respond_to?(:to_hash) then object.to_hash
134
- when object.respond_to?(:to_h) then object.to_h
135
- when object.respond_to?(:to_a) then object.to_a
136
- else fail Error, "Can't coerce #{object.inspect} to Headers"
137
- end
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[encode decode].each do |operation|
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}"
@@ -70,7 +70,7 @@ module HTTP
70
70
  end
71
71
  end
72
72
 
73
- %w[proxy params form json body follow].each do |method_name|
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 }
@@ -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 three minor versions (0.8.0) or one
56
+ # The following alias may be removed in two minor versions (0.8.0) or one
57
57
  # major version (1.0.0)
58
- alias_method :__method__, :method
59
-
60
- # The following method may be removed in two minor versions (0.7.0) or one
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 if @uri.scheme
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
- if using_proxy?
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]
@@ -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
- encoding = @headers['Transfer-Encoding']
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