http 5.0.0.pre3 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +65 -0
  3. data/.gitignore +6 -10
  4. data/.rspec +0 -4
  5. data/.rubocop.yml +7 -124
  6. data/.rubocop/layout.yml +8 -0
  7. data/.rubocop/style.yml +32 -0
  8. data/.rubocop_todo.yml +192 -0
  9. data/CHANGES.md +48 -1
  10. data/Gemfile +18 -11
  11. data/README.md +13 -17
  12. data/Rakefile +2 -10
  13. data/http.gemspec +2 -2
  14. data/lib/http/chainable.rb +15 -14
  15. data/lib/http/client.rb +18 -11
  16. data/lib/http/connection.rb +7 -3
  17. data/lib/http/content_type.rb +10 -5
  18. data/lib/http/feature.rb +1 -1
  19. data/lib/http/features/instrumentation.rb +1 -1
  20. data/lib/http/features/logging.rb +19 -21
  21. data/lib/http/headers.rb +3 -3
  22. data/lib/http/mime_type/adapter.rb +2 -0
  23. data/lib/http/options.rb +2 -2
  24. data/lib/http/redirector.rb +1 -1
  25. data/lib/http/request.rb +7 -4
  26. data/lib/http/response/body.rb +1 -2
  27. data/lib/http/response/inflater.rb +1 -1
  28. data/lib/http/response/parser.rb +72 -64
  29. data/lib/http/response/status.rb +2 -2
  30. data/lib/http/timeout/global.rb +16 -28
  31. data/lib/http/timeout/null.rb +2 -1
  32. data/lib/http/timeout/per_operation.rb +31 -55
  33. data/lib/http/version.rb +1 -1
  34. data/spec/lib/http/client_spec.rb +75 -41
  35. data/spec/lib/http/features/instrumentation_spec.rb +21 -15
  36. data/spec/lib/http/features/logging_spec.rb +2 -4
  37. data/spec/lib/http/headers_spec.rb +3 -3
  38. data/spec/lib/http/response/parser_spec.rb +2 -2
  39. data/spec/lib/http_spec.rb +20 -2
  40. data/spec/spec_helper.rb +21 -21
  41. data/spec/support/black_hole.rb +1 -1
  42. data/spec/support/dummy_server.rb +1 -1
  43. data/spec/support/dummy_server/servlet.rb +14 -2
  44. data/spec/support/fuubar.rb +21 -0
  45. data/spec/support/simplecov.rb +19 -0
  46. metadata +21 -16
  47. data/.coveralls.yml +0 -1
  48. data/.travis.yml +0 -38
data/CHANGES.md CHANGED
@@ -1,4 +1,28 @@
1
- ## future is unwritten (master)
1
+ ## 5.0.0 (2021-05-12)
2
+
3
+ * [#656](https://github.com/httprb/http/pull/656)
4
+ Handle connection timeouts in `Features`
5
+ ([@semenyukdmitry])
6
+
7
+ * [#651](https://github.com/httprb/http/pull/651)
8
+ Replace `http-parser` with `llhttp`
9
+ ([@bryanp])
10
+
11
+ * [#647](https://github.com/httprb/http/pull/647)
12
+ Add support for `MKCALENDAR` HTTP verb
13
+ ([@meanphil])
14
+
15
+ * [#632](https://github.com/httprb/http/pull/632)
16
+ Respect the SSL context's `verify_hostname` value
17
+ ([@colemannugent])
18
+
19
+ * [#625](https://github.com/httprb/http/pull/625)
20
+ Fix inflator with empty responses
21
+ ([@LukaszMaslej])
22
+
23
+ * [#599](https://github.com/httprb/http/pull/599)
24
+ Allow passing `HTTP::FormData::{Multipart,UrlEncoded}` object directly.
25
+ ([@ixti])
2
26
 
3
27
  * [#593](https://github.com/httprb/http/pull/593)
4
28
  [#592](https://github.com/httprb/http/issues/592)
@@ -56,6 +80,24 @@
56
80
  ([@ixti])
57
81
 
58
82
 
83
+ ## 4.4.0 (2020-03-25)
84
+
85
+ * Backport [#587](https://github.com/httprb/http/pull/587)
86
+ Fix redirections when server responds with multiple Location headers.
87
+ ([@ixti])
88
+
89
+ * Backport [#599](https://github.com/httprb/http/pull/599)
90
+ Allow passing HTTP::FormData::{Multipart,UrlEncoded} object directly.
91
+ ([@ixti])
92
+
93
+
94
+ ## 4.3.0 (2020-01-09)
95
+
96
+ * Backport [#581](https://github.com/httprb/http/pull/581)
97
+ Add Ruby-2.7 compatibility.
98
+ ([@ixti], [@janko])
99
+
100
+
59
101
  ## 4.2.0 (2019-10-22)
60
102
 
61
103
  * Backport [#489](https://github.com/httprb/http/pull/489)
@@ -840,3 +882,8 @@ end
840
882
  [@mamoonraja]: https://github.com/mamoonraja
841
883
  [@joshuaflanagan]: https://github.com/joshuaflanagan
842
884
  [@antonvolkoff]: https://github.com/antonvolkoff
885
+ [@LukaszMaslej]: https://github.com/LukaszMaslej
886
+ [@colemannugent]: https://github.com/colemannugent
887
+ [@semenyukdmitry]: https://github.com/semenyukdmitry
888
+ [@bryanp]: https://github.com/bryanp
889
+ [@meanphil]: https://github.com/meanphil
data/Gemfile CHANGED
@@ -5,32 +5,39 @@ ruby RUBY_VERSION
5
5
 
6
6
  gem "rake"
7
7
 
8
+ # Ruby 3.0 does not ship it anymore.
9
+ # TODO: We should probably refactor specs to avoid need for it.
10
+ gem "webrick"
11
+
8
12
  group :development do
9
13
  gem "guard-rspec", :require => false
10
14
  gem "nokogiri", :require => false
11
15
  gem "pry", :require => false
12
16
 
13
- platform :ruby_20 do
14
- gem "pry-debugger", :require => false
15
- gem "pry-stack_explorer", :require => false
17
+ # RSpec formatter
18
+ gem "fuubar", :require => false
19
+
20
+ platform :mri do
21
+ gem "pry-byebug"
16
22
  end
17
23
  end
18
24
 
19
25
  group :test do
20
- gem "activemodel", :require => false # Used by certificate_authority
21
- gem "certificate_authority", :require => false
26
+ gem "certificate_authority", "~> 1.0", :require => false
22
27
 
23
28
  gem "backports"
24
29
 
25
- gem "coveralls", :require => false
26
- gem "simplecov", ">= 0.9"
30
+ gem "rubocop"
31
+ gem "rubocop-performance"
32
+ gem "rubocop-rake"
33
+ gem "rubocop-rspec"
34
+
35
+ gem "simplecov", :require => false
36
+ gem "simplecov-lcov", :require => false
27
37
 
28
- gem "rspec", "~> 3.0"
38
+ gem "rspec", "~> 3.10"
29
39
  gem "rspec-its"
30
40
 
31
- gem "rubocop", "= 0.68.1"
32
- gem "rubocop-performance"
33
-
34
41
  gem "yardstick"
35
42
  end
36
43
 
data/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # ![http.rb](https://raw.github.com/httprb/http.rb/master/logo.png)
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/http.svg)](https://rubygems.org/gems/http)
4
- [![Build Status](https://secure.travis-ci.org/httprb/http.svg?branch=master)](https://travis-ci.org/httprb/http)
3
+ [![Gem Version](https://img.shields.io/gem/v/http?logo=ruby)](https://rubygems.org/gems/http)
4
+ [![Build Status](https://github.com/httprb/http/workflows/CI/badge.svg)](https://github.com/httprb/http/actions?query=workflow:CI)
5
5
  [![Code Climate](https://codeclimate.com/github/httprb/http.svg?branch=master)](https://codeclimate.com/github/httprb/http)
6
- [![Coverage Status](https://coveralls.io/repos/httprb/http/badge.svg?branch=master)](https://coveralls.io/r/httprb/http)
7
6
  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/httprb/http/blob/master/LICENSE.txt)
8
7
 
9
8
  [Documentation]
@@ -18,14 +17,12 @@ HTTP (The Gem! a.k.a. http.rb) is an easy-to-use client library for making reque
18
17
  from Ruby. It uses a simple method chaining system for building requests, similar to
19
18
  Python's [Requests].
20
19
 
21
- Under the hood, via [Ruby FFI bindings][http-parser-ffi], http.rb uses the Node.js
22
- [http-parser], a fast HTTP parsing native extension. This library
23
- isn't just yet another wrapper around Net::HTTP. It implements the HTTP protocol
24
- natively and outsources the parsing to native extensions.
20
+ Under the hood, http.rb uses the [llhttp] parser, a fast HTTP parsing native extension.
21
+ This library isn't just yet another wrapper around `Net::HTTP`. It implements the HTTP
22
+ protocol natively and outsources the parsing to native extensions.
25
23
 
26
24
  [requests]: http://docs.python-requests.org/en/latest/
27
- [http-parser]: https://github.com/nodejs/http-parser
28
- [http-parser-ffi]: https://github.com/cotag/http-parser
25
+ [llhttp]: https://llhttp.org/
29
26
 
30
27
 
31
28
  ## Another Ruby HTTP library? Why should I care?
@@ -115,8 +112,8 @@ for more detailed documentation and usage notes.
115
112
 
116
113
  The following API documentation is also available:
117
114
 
118
- * [YARD API documentation](http://www.rubydoc.info/gems/http/frames)
119
- * [Chainable module (all chainable methods)](http://www.rubydoc.info/gems/http/HTTP/Chainable)
115
+ * [YARD API documentation](https://www.rubydoc.info/github/httprb/http)
116
+ * [Chainable module (all chainable methods)](https://www.rubydoc.info/github/httprb/http/HTTP/Chainable)
120
117
 
121
118
  [documentation]: https://github.com/httprb/http/wiki
122
119
 
@@ -165,11 +162,10 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
165
162
  This library aims to support and is [tested against][travis] the following Ruby
166
163
  versions:
167
164
 
168
- * Ruby 2.4.x
169
- * Ruby 2.5.x
170
- * Ruby 2.6.x
171
- * Ruby 2.7.x
172
- * JRuby 9.2.x.x
165
+ * Ruby 2.6
166
+ * Ruby 2.7
167
+ * Ruby 3.0
168
+ * JRuby 9.2
173
169
 
174
170
  If something doesn't work on one of these versions, it's a bug.
175
171
 
@@ -199,5 +195,5 @@ dropped.
199
195
 
200
196
  ## Copyright
201
197
 
202
- Copyright (c) 2011-2019 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
198
+ Copyright (c) 2011-2021 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
203
199
  See LICENSE.txt for further details.
data/Rakefile CHANGED
@@ -35,7 +35,7 @@ task :generate_status_codes do
35
35
  end
36
36
 
37
37
  File.open("./lib/http/response/status/reasons.rb", "w") do |io|
38
- io.puts <<-TPL.gsub(/^[ ]{6}/, "")
38
+ io.puts <<~TPL
39
39
  # AUTO-GENERATED FILE, DO NOT CHANGE IT MANUALLY
40
40
 
41
41
  require "delegate"
@@ -61,12 +61,4 @@ task :generate_status_codes do
61
61
  end
62
62
  end
63
63
 
64
- if ENV["CI"].nil?
65
- task :default => %i[spec rubocop verify_measurements]
66
- else
67
- case ENV["SUITE"]
68
- when "rubocop" then task :default => :rubocop
69
- when "yardstick" then task :default => :verify_measurements
70
- else task :default => :spec
71
- end
72
- end
64
+ task :default => %i[spec rubocop verify_measurements]
data/http.gemspec CHANGED
@@ -25,12 +25,12 @@ Gem::Specification.new do |gem|
25
25
  gem.require_paths = ["lib"]
26
26
  gem.version = HTTP::VERSION
27
27
 
28
- gem.required_ruby_version = ">= 2.3"
28
+ gem.required_ruby_version = ">= 2.5"
29
29
 
30
30
  gem.add_runtime_dependency "addressable", "~> 2.3"
31
31
  gem.add_runtime_dependency "http-cookie", "~> 1.0"
32
32
  gem.add_runtime_dependency "http-form_data", "~> 2.2"
33
- gem.add_runtime_dependency "http-parser", "~> 1.2.0"
33
+ gem.add_runtime_dependency "llhttp-ffi", "~> 0.0.1"
34
34
 
35
35
  gem.add_development_dependency "bundler", "~> 2.0"
36
36
 
@@ -9,63 +9,63 @@ module HTTP
9
9
  # Request a get sans response body
10
10
  # @param uri
11
11
  # @option options [Hash]
12
- def head(uri, options = {}) # rubocop:disable Style/OptionHash
12
+ def head(uri, options = {})
13
13
  request :head, uri, options
14
14
  end
15
15
 
16
16
  # Get a resource
17
17
  # @param uri
18
18
  # @option options [Hash]
19
- def get(uri, options = {}) # rubocop:disable Style/OptionHash
19
+ def get(uri, options = {})
20
20
  request :get, uri, options
21
21
  end
22
22
 
23
23
  # Post to a resource
24
24
  # @param uri
25
25
  # @option options [Hash]
26
- def post(uri, options = {}) # rubocop:disable Style/OptionHash
26
+ def post(uri, options = {})
27
27
  request :post, uri, options
28
28
  end
29
29
 
30
30
  # Put to a resource
31
31
  # @param uri
32
32
  # @option options [Hash]
33
- def put(uri, options = {}) # rubocop:disable Style/OptionHash
33
+ def put(uri, options = {})
34
34
  request :put, uri, options
35
35
  end
36
36
 
37
37
  # Delete a resource
38
38
  # @param uri
39
39
  # @option options [Hash]
40
- def delete(uri, options = {}) # rubocop:disable Style/OptionHash
40
+ def delete(uri, options = {})
41
41
  request :delete, uri, options
42
42
  end
43
43
 
44
44
  # Echo the request back to the client
45
45
  # @param uri
46
46
  # @option options [Hash]
47
- def trace(uri, options = {}) # rubocop:disable Style/OptionHash
47
+ def trace(uri, options = {})
48
48
  request :trace, uri, options
49
49
  end
50
50
 
51
51
  # Return the methods supported on the given URI
52
52
  # @param uri
53
53
  # @option options [Hash]
54
- def options(uri, options = {}) # rubocop:disable Style/OptionHash
54
+ def options(uri, options = {})
55
55
  request :options, uri, options
56
56
  end
57
57
 
58
58
  # Convert to a transparent TCP/IP tunnel
59
59
  # @param uri
60
60
  # @option options [Hash]
61
- def connect(uri, options = {}) # rubocop:disable Style/OptionHash
61
+ def connect(uri, options = {})
62
62
  request :connect, uri, options
63
63
  end
64
64
 
65
65
  # Apply partial modifications to a resource
66
66
  # @param uri
67
67
  # @option options [Hash]
68
- def patch(uri, options = {}) # rubocop:disable Style/OptionHash
68
+ def patch(uri, options = {})
69
69
  request :patch, uri, options
70
70
  end
71
71
 
@@ -148,7 +148,7 @@ module HTTP
148
148
 
149
149
  yield p_client
150
150
  ensure
151
- p_client.close if p_client
151
+ p_client&.close
152
152
  end
153
153
 
154
154
  # Make a request through an HTTP proxy
@@ -173,7 +173,7 @@ module HTTP
173
173
  # @param options
174
174
  # @return [HTTP::Client]
175
175
  # @see Redirector#initialize
176
- def follow(options = {}) # rubocop:disable Style/OptionHash
176
+ def follow(options = {})
177
177
  branch default_options.with_follow options
178
178
  end
179
179
 
@@ -211,10 +211,11 @@ module HTTP
211
211
  # @option opts [#to_s] :user
212
212
  # @option opts [#to_s] :pass
213
213
  def basic_auth(opts)
214
- user = opts.fetch :user
215
- pass = opts.fetch :pass
214
+ user = opts.fetch(:user)
215
+ pass = opts.fetch(:pass)
216
+ creds = "#{user}:#{pass}"
216
217
 
217
- auth("Basic " + Base64.strict_encode64("#{user}:#{pass}"))
218
+ auth("Basic #{Base64.strict_encode64(creds)}")
218
219
  end
219
220
 
220
221
  # Get options for HTTP
data/lib/http/client.rb CHANGED
@@ -25,7 +25,7 @@ module HTTP
25
25
  end
26
26
 
27
27
  # Make an HTTP request
28
- def request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash
28
+ def request(verb, uri, opts = {})
29
29
  opts = @default_options.merge(opts)
30
30
  req = build_request(verb, uri, opts)
31
31
  res = perform(req, opts)
@@ -37,7 +37,7 @@ module HTTP
37
37
  end
38
38
 
39
39
  # Prepare an HTTP request
40
- def build_request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash
40
+ def build_request(verb, uri, opts = {})
41
41
  opts = @default_options.merge(opts)
42
42
  uri = make_request_uri(uri, opts)
43
43
  headers = make_request_headers(opts)
@@ -68,9 +68,9 @@ module HTTP
68
68
 
69
69
  @state = :dirty
70
70
 
71
- @connection ||= HTTP::Connection.new(req, options)
72
-
73
71
  begin
72
+ @connection ||= HTTP::Connection.new(req, options)
73
+
74
74
  unless @connection.failed_proxy_connect?
75
75
  @connection.send_request(req)
76
76
  @connection.read_headers!
@@ -97,7 +97,7 @@ module HTTP
97
97
  end
98
98
 
99
99
  def close
100
- @connection.close if @connection
100
+ @connection&.close
101
101
  @connection = nil
102
102
  @state = :clean
103
103
  end
@@ -120,15 +120,15 @@ module HTTP
120
120
  def verify_connection!(uri)
121
121
  if default_options.persistent? && uri.origin != default_options.persistent
122
122
  raise StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"
123
+ end
124
+
123
125
  # We re-create the connection object because we want to let prior requests
124
126
  # lazily load the body as long as possible, and this mimics prior functionality.
125
- elsif @connection && (!@connection.keep_alive? || @connection.expired?)
126
- close
127
+ return close if @connection && (!@connection.keep_alive? || @connection.expired?)
128
+
127
129
  # If we get into a bad state (eg, Timeout.timeout ensure being killed)
128
130
  # close the connection to prevent potential for mixed responses.
129
- elsif @state == :dirty
130
- close
131
- end
131
+ return close if @state == :dirty
132
132
  end
133
133
 
134
134
  # Merges query params if needed
@@ -175,7 +175,7 @@ module HTTP
175
175
  when opts.body
176
176
  opts.body
177
177
  when opts.form
178
- form = HTTP::FormData.create opts.form
178
+ form = make_form_data(opts.form)
179
179
  headers[Headers::CONTENT_TYPE] ||= form.content_type
180
180
  form
181
181
  when opts.json
@@ -184,5 +184,12 @@ module HTTP
184
184
  body
185
185
  end
186
186
  end
187
+
188
+ def make_form_data(form)
189
+ return form if form.is_a? HTTP::FormData::Multipart
190
+ return form if form.is_a? HTTP::FormData::Urlencoded
191
+
192
+ HTTP::FormData.create(form)
193
+ end
187
194
  end
188
195
  end
@@ -3,7 +3,6 @@
3
3
  require "forwardable"
4
4
 
5
5
  require "http/headers"
6
- require "http/response/parser"
7
6
 
8
7
  module HTTP
9
8
  # A connection to the HTTP server
@@ -68,8 +67,13 @@ module HTTP
68
67
  # @param [Request] req Request to send to the server
69
68
  # @return [nil]
70
69
  def send_request(req)
71
- raise StateError, "Tried to send a request while one is pending already. Make sure you read off the body." if @pending_response
72
- raise StateError, "Tried to send a request while a response is pending. Make sure you read off the body." if @pending_request
70
+ if @pending_response
71
+ raise StateError, "Tried to send a request while one is pending already. Make sure you read off the body."
72
+ end
73
+
74
+ if @pending_request
75
+ raise StateError, "Tried to send a request while a response is pending. Make sure you read off the body."
76
+ end
73
77
 
74
78
  @pending_request = true
75
79
 
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP
4
- ContentType = Struct.new(:mime_type, :charset) do
4
+ class ContentType
5
5
  MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
6
6
  CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
7
7
 
8
+ attr_accessor :mime_type, :charset
9
+
8
10
  class << self
9
11
  # Parse string and return ContentType struct
10
12
  def parse(str)
@@ -15,15 +17,18 @@ module HTTP
15
17
 
16
18
  # :nodoc:
17
19
  def mime_type(str)
18
- m = str.to_s[MIME_TYPE_RE, 1]
19
- m && m.strip.downcase
20
+ str.to_s[MIME_TYPE_RE, 1]&.strip&.downcase
20
21
  end
21
22
 
22
23
  # :nodoc:
23
24
  def charset(str)
24
- m = str.to_s[CHARSET_RE, 1]
25
- m && m.strip.delete('"')
25
+ str.to_s[CHARSET_RE, 1]&.strip&.delete('"')
26
26
  end
27
27
  end
28
+
29
+ def initialize(mime_type = nil, charset = nil)
30
+ @mime_type = mime_type
31
+ @charset = charset
32
+ end
28
33
  end
29
34
  end