http 4.2.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) 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/layout.yml +8 -0
  6. data/.rubocop/style.yml +32 -0
  7. data/.rubocop.yml +8 -110
  8. data/.rubocop_todo.yml +192 -0
  9. data/.yardopts +1 -1
  10. data/CHANGES.md +168 -0
  11. data/Gemfile +18 -10
  12. data/LICENSE.txt +1 -1
  13. data/README.md +17 -20
  14. data/Rakefile +2 -10
  15. data/http.gemspec +5 -5
  16. data/lib/http/chainable.rb +23 -17
  17. data/lib/http/client.rb +52 -35
  18. data/lib/http/connection.rb +12 -8
  19. data/lib/http/content_type.rb +12 -7
  20. data/lib/http/feature.rb +3 -1
  21. data/lib/http/features/auto_deflate.rb +7 -7
  22. data/lib/http/features/auto_inflate.rb +6 -7
  23. data/lib/http/features/instrumentation.rb +1 -1
  24. data/lib/http/features/logging.rb +19 -21
  25. data/lib/http/headers.rb +50 -13
  26. data/lib/http/mime_type/adapter.rb +3 -1
  27. data/lib/http/mime_type/json.rb +1 -0
  28. data/lib/http/options.rb +6 -9
  29. data/lib/http/redirector.rb +4 -2
  30. data/lib/http/request/body.rb +1 -0
  31. data/lib/http/request/writer.rb +8 -3
  32. data/lib/http/request.rb +28 -11
  33. data/lib/http/response/body.rb +6 -4
  34. data/lib/http/response/inflater.rb +1 -1
  35. data/lib/http/response/parser.rb +75 -49
  36. data/lib/http/response/status.rb +4 -3
  37. data/lib/http/response.rb +35 -15
  38. data/lib/http/timeout/global.rb +42 -38
  39. data/lib/http/timeout/null.rb +2 -1
  40. data/lib/http/timeout/per_operation.rb +56 -58
  41. data/lib/http/uri.rb +5 -5
  42. data/lib/http/version.rb +1 -1
  43. data/spec/lib/http/client_spec.rb +173 -35
  44. data/spec/lib/http/connection_spec.rb +8 -5
  45. data/spec/lib/http/features/auto_inflate_spec.rb +3 -2
  46. data/spec/lib/http/features/instrumentation_spec.rb +27 -21
  47. data/spec/lib/http/features/logging_spec.rb +8 -10
  48. data/spec/lib/http/headers_spec.rb +53 -18
  49. data/spec/lib/http/options/headers_spec.rb +1 -1
  50. data/spec/lib/http/options/merge_spec.rb +16 -16
  51. data/spec/lib/http/redirector_spec.rb +59 -1
  52. data/spec/lib/http/request/writer_spec.rb +25 -2
  53. data/spec/lib/http/request_spec.rb +5 -5
  54. data/spec/lib/http/response/body_spec.rb +5 -5
  55. data/spec/lib/http/response/parser_spec.rb +74 -0
  56. data/spec/lib/http/response/status_spec.rb +3 -3
  57. data/spec/lib/http/response_spec.rb +44 -3
  58. data/spec/lib/http_spec.rb +30 -3
  59. data/spec/spec_helper.rb +21 -21
  60. data/spec/support/black_hole.rb +1 -1
  61. data/spec/support/dummy_server/servlet.rb +17 -6
  62. data/spec/support/dummy_server.rb +7 -7
  63. data/spec/support/fuubar.rb +21 -0
  64. data/spec/support/http_handling_shared.rb +4 -4
  65. data/spec/support/simplecov.rb +19 -0
  66. data/spec/support/ssl_helper.rb +4 -4
  67. metadata +24 -16
  68. data/.coveralls.yml +0 -1
  69. data/.travis.yml +0 -37
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2016 Tony Arcieri, Erik Michaels-Ober, Alexey V. Zapparov, Zachary Anker
1
+ Copyright (c) 2011-2021 Tony Arcieri, Erik Michaels-Ober, Alexey V. Zapparov, Zachary Anker
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,16 +1,15 @@
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=4-x-stable)](https://travis-ci.org/httprb/http)
5
- [![Code Climate](https://codeclimate.com/github/httprb/http.svg?branch=4-x-stable)](https://codeclimate.com/github/httprb/http)
6
- [![Coverage Status](https://coveralls.io/repos/httprb/http/badge.svg?branch=4-x-stable)](https://coveralls.io/r/httprb/http)
7
- [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/httprb/http/blob/4-x-stable/LICENSE.txt)
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
+ [![Code Climate](https://codeclimate.com/github/httprb/http.svg?branch=master)](https://codeclimate.com/github/httprb/http)
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]
10
9
 
11
- _NOTE: This is the 4.x **stable** branch. For the 3.x **stable** branch, please see:_
10
+ _NOTE: This is the 5.x **development** branch. For the 4.x **stable** branch, please see:_
12
11
 
13
- https://github.com/httprb/http/tree/3-x-stable
12
+ https://github.com/httprb/http/tree/4-x-stable
14
13
 
15
14
  ## About
16
15
 
@@ -18,13 +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, http.rb uses [http_parser.rb], a fast HTTP parsing native
22
- extension based on the Node.js parser and a Java port thereof. 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.rb]: https://github.com/tmm1/http_parser.rb
25
+ [llhttp]: https://llhttp.org/
28
26
 
29
27
 
30
28
  ## Another Ruby HTTP library? Why should I care?
@@ -114,8 +112,8 @@ for more detailed documentation and usage notes.
114
112
 
115
113
  The following API documentation is also available:
116
114
 
117
- * [YARD API documentation](http://www.rubydoc.info/gems/http/frames)
118
- * [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)
119
117
 
120
118
  [documentation]: https://github.com/httprb/http/wiki
121
119
 
@@ -164,11 +162,10 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
164
162
  This library aims to support and is [tested against][travis] the following Ruby
165
163
  versions:
166
164
 
167
- * Ruby 2.3.x
168
- * Ruby 2.4.x
169
- * Ruby 2.5.x
170
- * Ruby 2.6.x
171
- * JRuby 9.2.x.x
165
+ * Ruby 2.6
166
+ * Ruby 2.7
167
+ * Ruby 3.0
168
+ * JRuby 9.2
172
169
 
173
170
  If something doesn't work on one of these versions, it's a bug.
174
171
 
@@ -198,5 +195,5 @@ dropped.
198
195
 
199
196
  ## Copyright
200
197
 
201
- 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.
202
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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path("lib", __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require "http/version"
6
6
 
@@ -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
- gem.add_runtime_dependency "addressable", "~> 2.3"
30
+ gem.add_runtime_dependency "addressable", "~> 2.8"
31
31
  gem.add_runtime_dependency "http-cookie", "~> 1.0"
32
- gem.add_runtime_dependency "http-form_data", "~> 2.0"
33
- gem.add_runtime_dependency "http-parser", "~> 1.2.0"
32
+ gem.add_runtime_dependency "http-form_data", "~> 2.2"
33
+ gem.add_runtime_dependency "llhttp-ffi", "~> 0.4.0"
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
 
@@ -93,7 +93,7 @@ module HTTP
93
93
  def timeout(options)
94
94
  klass, options = case options
95
95
  when Numeric then [HTTP::Timeout::Global, {:global => options}]
96
- when Hash then [HTTP::Timeout::PerOperation, options]
96
+ when Hash then [HTTP::Timeout::PerOperation, options.dup]
97
97
  when :null then [HTTP::Timeout::Null, {}]
98
98
  else raise ArgumentError, "Use `.timeout(global_timeout_in_seconds)` or `.timeout(connect: x, write: y, read: z)`."
99
99
 
@@ -101,11 +101,12 @@ module HTTP
101
101
 
102
102
  %i[global read write connect].each do |k|
103
103
  next unless options.key? k
104
+
104
105
  options["#{k}_timeout".to_sym] = options.delete k
105
106
  end
106
107
 
107
108
  branch default_options.merge(
108
- :timeout_class => klass,
109
+ :timeout_class => klass,
109
110
  :timeout_options => options
110
111
  )
111
112
  end
@@ -144,9 +145,10 @@ module HTTP
144
145
  options = {:keep_alive_timeout => timeout}
145
146
  p_client = branch default_options.merge(options).with_persistent host
146
147
  return p_client unless block_given?
148
+
147
149
  yield p_client
148
150
  ensure
149
- p_client.close if p_client
151
+ p_client&.close
150
152
  end
151
153
 
152
154
  # Make a request through an HTTP proxy
@@ -168,10 +170,10 @@ module HTTP
168
170
  alias through via
169
171
 
170
172
  # Make client follow redirects.
171
- # @param opts
173
+ # @param options
172
174
  # @return [HTTP::Client]
173
175
  # @see Redirector#initialize
174
- def follow(options = {}) # rubocop:disable Style/OptionHash
176
+ def follow(options = {})
175
177
  branch default_options.with_follow options
176
178
  end
177
179
 
@@ -209,10 +211,11 @@ module HTTP
209
211
  # @option opts [#to_s] :user
210
212
  # @option opts [#to_s] :pass
211
213
  def basic_auth(opts)
212
- user = opts.fetch :user
213
- pass = opts.fetch :pass
214
+ user = opts.fetch(:user)
215
+ pass = opts.fetch(:pass)
216
+ creds = "#{user}:#{pass}"
214
217
 
215
- auth("Basic " + Base64.strict_encode64("#{user}:#{pass}"))
218
+ auth("Basic #{Base64.strict_encode64(creds)}")
216
219
  end
217
220
 
218
221
  # Get options for HTTP
@@ -236,6 +239,9 @@ module HTTP
236
239
  # Turn on given features. Available features are:
237
240
  # * auto_inflate
238
241
  # * auto_deflate
242
+ # * instrumentation
243
+ # * logging
244
+ # * normalize_uri
239
245
  # @param features
240
246
  def use(*features)
241
247
  branch default_options.with_features(features)
data/lib/http/client.rb CHANGED
@@ -16,7 +16,7 @@ module HTTP
16
16
  extend Forwardable
17
17
  include Chainable
18
18
 
19
- HTTP_OR_HTTPS_RE = %r{^https?://}i
19
+ HTTP_OR_HTTPS_RE = %r{^https?://}i.freeze
20
20
 
21
21
  def initialize(default_options = {})
22
22
  @default_options = HTTP::Options.new(default_options)
@@ -25,19 +25,19 @@ 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)
32
32
  return res unless opts.follow
33
33
 
34
34
  Redirector.new(opts.follow).perform(req, res) do |request|
35
- perform(request, opts)
35
+ perform(wrap_request(request, opts), opts)
36
36
  end
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)
@@ -52,9 +52,7 @@ module HTTP
52
52
  :body => body
53
53
  )
54
54
 
55
- opts.features.inject(req) do |request, (_name, feature)|
56
- feature.wrap_request(request)
57
- end
55
+ wrap_request(req, opts)
58
56
  end
59
57
 
60
58
  # @!method persistent?
@@ -68,22 +66,20 @@ module HTTP
68
66
 
69
67
  @state = :dirty
70
68
 
71
- @connection ||= HTTP::Connection.new(req, options)
72
-
73
- unless @connection.failed_proxy_connect?
74
- @connection.send_request(req)
75
- @connection.read_headers!
69
+ begin
70
+ @connection ||= HTTP::Connection.new(req, options)
71
+
72
+ unless @connection.failed_proxy_connect?
73
+ @connection.send_request(req)
74
+ @connection.read_headers!
75
+ end
76
+ rescue Error => e
77
+ options.features.each_value do |feature|
78
+ feature.on_error(req, e)
79
+ end
80
+ raise
76
81
  end
77
-
78
- res = Response.new(
79
- :status => @connection.status_code,
80
- :version => @connection.http_version,
81
- :headers => @connection.headers,
82
- :proxy_headers => @connection.proxy_response_headers,
83
- :connection => @connection,
84
- :encoding => options.encoding,
85
- :uri => req.uri
86
- )
82
+ res = build_response(req, options)
87
83
 
88
84
  res = options.features.inject(res) do |response, (_name, feature)|
89
85
  feature.wrap_response(response)
@@ -99,26 +95,44 @@ module HTTP
99
95
  end
100
96
 
101
97
  def close
102
- @connection.close if @connection
98
+ @connection&.close
103
99
  @connection = nil
104
100
  @state = :clean
105
101
  end
106
102
 
107
103
  private
108
104
 
105
+ def wrap_request(req, opts)
106
+ opts.features.inject(req) do |request, (_name, feature)|
107
+ feature.wrap_request(request)
108
+ end
109
+ end
110
+
111
+ def build_response(req, options)
112
+ Response.new(
113
+ :status => @connection.status_code,
114
+ :version => @connection.http_version,
115
+ :headers => @connection.headers,
116
+ :proxy_headers => @connection.proxy_response_headers,
117
+ :connection => @connection,
118
+ :encoding => options.encoding,
119
+ :request => req
120
+ )
121
+ end
122
+
109
123
  # Verify our request isn't going to be made against another URI
110
124
  def verify_connection!(uri)
111
125
  if default_options.persistent? && uri.origin != default_options.persistent
112
126
  raise StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"
127
+ end
128
+
113
129
  # We re-create the connection object because we want to let prior requests
114
130
  # lazily load the body as long as possible, and this mimics prior functionality.
115
- elsif @connection && (!@connection.keep_alive? || @connection.expired?)
116
- close
131
+ return close if @connection && (!@connection.keep_alive? || @connection.expired?)
132
+
117
133
  # If we get into a bad state (eg, Timeout.timeout ensure being killed)
118
134
  # close the connection to prevent potential for mixed responses.
119
- elsif @state == :dirty
120
- close
121
- end
135
+ return close if @state == :dirty
122
136
  end
123
137
 
124
138
  # Merges query params if needed
@@ -128,15 +142,11 @@ module HTTP
128
142
  def make_request_uri(uri, opts)
129
143
  uri = uri.to_s
130
144
 
131
- if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
132
- uri = "#{default_options.persistent}#{uri}"
133
- end
145
+ uri = "#{default_options.persistent}#{uri}" if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
134
146
 
135
147
  uri = HTTP::URI.parse uri
136
148
 
137
- if opts.params && !opts.params.empty?
138
- uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a)
139
- end
149
+ uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a) if opts.params && !opts.params.empty?
140
150
 
141
151
  # Some proxies (seen on WEBRick) fail if URL has
142
152
  # empty path (e.g. `http://example.com`) while it's RFC-complaint:
@@ -169,7 +179,7 @@ module HTTP
169
179
  when opts.body
170
180
  opts.body
171
181
  when opts.form
172
- form = HTTP::FormData.create opts.form
182
+ form = make_form_data(opts.form)
173
183
  headers[Headers::CONTENT_TYPE] ||= form.content_type
174
184
  form
175
185
  when opts.json
@@ -178,5 +188,12 @@ module HTTP
178
188
  body
179
189
  end
180
190
  end
191
+
192
+ def make_form_data(form)
193
+ return form if form.is_a? HTTP::FormData::Multipart
194
+ return form if form.is_a? HTTP::FormData::Urlencoded
195
+
196
+ HTTP::FormData.create(form)
197
+ end
181
198
  end
182
199
  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
@@ -45,8 +44,8 @@ module HTTP
45
44
  send_proxy_connect_request(req)
46
45
  start_tls(req, options)
47
46
  reset_timer
48
- rescue IOError, SocketError, SystemCallError => ex
49
- raise ConnectionError, "failed to connect: #{ex}", ex.backtrace
47
+ rescue IOError, SocketError, SystemCallError => e
48
+ raise ConnectionError, "failed to connect: #{e}", e.backtrace
50
49
  end
51
50
 
52
51
  # @see (HTTP::Response::Parser#status_code)
@@ -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
 
@@ -93,7 +97,7 @@ module HTTP
93
97
  chunk = @parser.read(size)
94
98
  finish_response if finished
95
99
 
96
- chunk.to_s
100
+ chunk || "".b
97
101
  end
98
102
 
99
103
  # Reads data from socket up until headers are loaded
@@ -216,8 +220,8 @@ module HTTP
216
220
  elsif value
217
221
  @parser << value
218
222
  end
219
- rescue IOError, SocketError, SystemCallError => ex
220
- raise ConnectionError, "error reading from socket: #{ex}", ex.backtrace
223
+ rescue IOError, SocketError, SystemCallError => e
224
+ raise ConnectionError, "error reading from socket: #{e}", e.backtrace
221
225
  end
222
226
  end
223
227
  end
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP
4
- ContentType = Struct.new(:mime_type, :charset) do
5
- MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}
6
- CHARSET_RE = /;\s*charset=([^;]+)/i
4
+ class ContentType
5
+ MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
6
+ CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
7
+
8
+ attr_accessor :mime_type, :charset
7
9
 
8
10
  class << self
9
11
  # Parse string and return ContentType struct
@@ -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
data/lib/http/feature.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module HTTP
4
4
  class Feature
5
- def initialize(opts = {}) # rubocop:disable Style/OptionHash
5
+ def initialize(opts = {})
6
6
  @opts = opts
7
7
  end
8
8
 
@@ -13,6 +13,8 @@ module HTTP
13
13
  def wrap_response(response)
14
14
  response
15
15
  end
16
+
17
+ def on_error(request, error); end
16
18
  end
17
19
  end
18
20
 
@@ -10,7 +10,7 @@ module HTTP
10
10
  class AutoDeflate < Feature
11
11
  attr_reader :method
12
12
 
13
- def initialize(*)
13
+ def initialize(**)
14
14
  super
15
15
 
16
16
  @method = @opts.key?(:method) ? @opts[:method].to_s : "gzip"
@@ -27,12 +27,12 @@ module HTTP
27
27
  request.headers[Headers::CONTENT_ENCODING] = method
28
28
 
29
29
  Request.new(
30
- :version => request.version,
31
- :verb => request.verb,
32
- :uri => request.uri,
33
- :headers => request.headers,
34
- :proxy => request.proxy,
35
- :body => deflated_body(request.body),
30
+ :version => request.version,
31
+ :verb => request.verb,
32
+ :uri => request.uri,
33
+ :headers => request.headers,
34
+ :proxy => request.proxy,
35
+ :body => deflated_body(request.body),
36
36
  :uri_normalizer => request.uri_normalizer
37
37
  )
38
38
  end
@@ -12,16 +12,15 @@ module HTTP
12
12
  return response unless supported_encoding?(response)
13
13
 
14
14
  options = {
15
- :status => response.status,
16
- :version => response.version,
17
- :headers => response.headers,
15
+ :status => response.status,
16
+ :version => response.version,
17
+ :headers => response.headers,
18
18
  :proxy_headers => response.proxy_headers,
19
- :connection => response.connection,
20
- :body => stream_for(response.connection)
19
+ :connection => response.connection,
20
+ :body => stream_for(response.connection),
21
+ :request => response.request
21
22
  }
22
23
 
23
- options[:uri] = response.uri if response.uri
24
-
25
24
  Response.new(options)
26
25
  end
27
26
 
@@ -29,7 +29,7 @@ module HTTP
29
29
  def wrap_request(request)
30
30
  # Emit a separate "start" event, so a logger can print the request
31
31
  # being run without waiting for a response
32
- instrumenter.instrument("start_#{name}", :request => request) {}
32
+ instrumenter.instrument("start_#{name}", :request => request)
33
33
  instrumenter.start(name, :request => request)
34
34
  request
35
35
  end