http 4.4.1 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) 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 +87 -3
  11. data/Gemfile +18 -10
  12. data/README.md +17 -20
  13. data/Rakefile +2 -10
  14. data/http.gemspec +3 -3
  15. data/lib/http/chainable.rb +23 -17
  16. data/lib/http/client.rb +36 -30
  17. data/lib/http/connection.rb +11 -7
  18. data/lib/http/content_type.rb +12 -7
  19. data/lib/http/feature.rb +3 -1
  20. data/lib/http/features/auto_deflate.rb +6 -6
  21. data/lib/http/features/auto_inflate.rb +6 -5
  22. data/lib/http/features/instrumentation.rb +1 -1
  23. data/lib/http/features/logging.rb +19 -21
  24. data/lib/http/headers.rb +50 -13
  25. data/lib/http/mime_type/adapter.rb +3 -1
  26. data/lib/http/mime_type/json.rb +1 -0
  27. data/lib/http/options.rb +5 -8
  28. data/lib/http/redirector.rb +2 -1
  29. data/lib/http/request/body.rb +1 -0
  30. data/lib/http/request/writer.rb +3 -2
  31. data/lib/http/request.rb +13 -10
  32. data/lib/http/response/body.rb +2 -2
  33. data/lib/http/response/inflater.rb +1 -1
  34. data/lib/http/response/parser.rb +74 -62
  35. data/lib/http/response/status.rb +4 -3
  36. data/lib/http/response.rb +17 -15
  37. data/lib/http/timeout/global.rb +17 -31
  38. data/lib/http/timeout/null.rb +2 -1
  39. data/lib/http/timeout/per_operation.rb +31 -54
  40. data/lib/http/uri.rb +5 -5
  41. data/lib/http/version.rb +1 -1
  42. data/spec/lib/http/client_spec.rb +119 -30
  43. data/spec/lib/http/connection_spec.rb +8 -5
  44. data/spec/lib/http/features/auto_inflate_spec.rb +4 -2
  45. data/spec/lib/http/features/instrumentation_spec.rb +28 -21
  46. data/spec/lib/http/features/logging_spec.rb +8 -9
  47. data/spec/lib/http/headers_spec.rb +53 -18
  48. data/spec/lib/http/options/headers_spec.rb +1 -1
  49. data/spec/lib/http/options/merge_spec.rb +16 -16
  50. data/spec/lib/http/redirector_spec.rb +2 -1
  51. data/spec/lib/http/request/writer_spec.rb +13 -1
  52. data/spec/lib/http/request_spec.rb +5 -5
  53. data/spec/lib/http/response/parser_spec.rb +33 -4
  54. data/spec/lib/http/response/status_spec.rb +3 -3
  55. data/spec/lib/http/response_spec.rb +11 -22
  56. data/spec/lib/http_spec.rb +30 -3
  57. data/spec/spec_helper.rb +21 -21
  58. data/spec/support/black_hole.rb +1 -1
  59. data/spec/support/dummy_server/servlet.rb +17 -6
  60. data/spec/support/dummy_server.rb +7 -7
  61. data/spec/support/fuubar.rb +21 -0
  62. data/spec/support/http_handling_shared.rb +4 -4
  63. data/spec/support/simplecov.rb +19 -0
  64. data/spec/support/ssl_helper.rb +4 -4
  65. metadata +18 -12
  66. data/.coveralls.yml +0 -1
  67. data/.travis.yml +0 -39
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
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
 
@@ -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,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,22 +68,20 @@ module HTTP
68
68
 
69
69
  @state = :dirty
70
70
 
71
- @connection ||= HTTP::Connection.new(req, options)
72
-
73
- unless @connection.failed_proxy_connect?
74
- @connection.send_request(req)
75
- @connection.read_headers!
71
+ begin
72
+ @connection ||= HTTP::Connection.new(req, options)
73
+
74
+ unless @connection.failed_proxy_connect?
75
+ @connection.send_request(req)
76
+ @connection.read_headers!
77
+ end
78
+ rescue Error => e
79
+ options.features.each_value do |feature|
80
+ feature.on_error(req, e)
81
+ end
82
+ raise
76
83
  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
- )
84
+ res = build_response(req, options)
87
85
 
88
86
  res = options.features.inject(res) do |response, (_name, feature)|
89
87
  feature.wrap_response(response)
@@ -99,26 +97,38 @@ module HTTP
99
97
  end
100
98
 
101
99
  def close
102
- @connection.close if @connection
100
+ @connection&.close
103
101
  @connection = nil
104
102
  @state = :clean
105
103
  end
106
104
 
107
105
  private
108
106
 
107
+ def build_response(req, options)
108
+ Response.new(
109
+ :status => @connection.status_code,
110
+ :version => @connection.http_version,
111
+ :headers => @connection.headers,
112
+ :proxy_headers => @connection.proxy_response_headers,
113
+ :connection => @connection,
114
+ :encoding => options.encoding,
115
+ :request => req
116
+ )
117
+ end
118
+
109
119
  # Verify our request isn't going to be made against another URI
110
120
  def verify_connection!(uri)
111
121
  if default_options.persistent? && uri.origin != default_options.persistent
112
122
  raise StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"
123
+ end
124
+
113
125
  # We re-create the connection object because we want to let prior requests
114
126
  # lazily load the body as long as possible, and this mimics prior functionality.
115
- elsif @connection && (!@connection.keep_alive? || @connection.expired?)
116
- close
127
+ return close if @connection && (!@connection.keep_alive? || @connection.expired?)
128
+
117
129
  # If we get into a bad state (eg, Timeout.timeout ensure being killed)
118
130
  # close the connection to prevent potential for mixed responses.
119
- elsif @state == :dirty
120
- close
121
- end
131
+ return close if @state == :dirty
122
132
  end
123
133
 
124
134
  # Merges query params if needed
@@ -128,15 +138,11 @@ module HTTP
128
138
  def make_request_uri(uri, opts)
129
139
  uri = uri.to_s
130
140
 
131
- if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
132
- uri = "#{default_options.persistent}#{uri}"
133
- end
141
+ uri = "#{default_options.persistent}#{uri}" if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
134
142
 
135
143
  uri = HTTP::URI.parse uri
136
144
 
137
- if opts.params && !opts.params.empty?
138
- uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a)
139
- end
145
+ uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a) if opts.params && !opts.params.empty?
140
146
 
141
147
  # Some proxies (seen on WEBRick) fail if URL has
142
148
  # empty path (e.g. `http://example.com`) while it's RFC-complaint:
@@ -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
 
@@ -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
 
@@ -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,12 +12,13 @@ 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
24
  options[:uri] = response.uri if response.uri
@@ -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
@@ -9,6 +9,20 @@ module HTTP
9
9
  # HTTP.use(logging: {logger: Logger.new(STDOUT)}).get("https://example.com/")
10
10
  #
11
11
  class Logging < Feature
12
+ HTTP::Options.register_feature(:logging, self)
13
+
14
+ class NullLogger
15
+ %w[fatal error warn info debug].each do |level|
16
+ define_method(level.to_sym) do |*_args|
17
+ nil
18
+ end
19
+
20
+ define_method(:"#{level}?") do
21
+ true
22
+ end
23
+ end
24
+ end
25
+
12
26
  attr_reader :logger
13
27
 
14
28
  def initialize(logger: NullLogger.new)
@@ -17,39 +31,23 @@ module HTTP
17
31
 
18
32
  def wrap_request(request)
19
33
  logger.info { "> #{request.verb.to_s.upcase} #{request.uri}" }
20
- logger.debug do
21
- headers = request.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
22
- body = request.body.source
34
+ logger.debug { "#{stringify_headers(request.headers)}\n\n#{request.body.source}" }
23
35
 
24
- headers + "\n\n" + body.to_s
25
- end
26
36
  request
27
37
  end
28
38
 
29
39
  def wrap_response(response)
30
40
  logger.info { "< #{response.status}" }
31
- logger.debug do
32
- headers = response.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
33
- body = response.body.to_s
41
+ logger.debug { "#{stringify_headers(response.headers)}\n\n#{response.body}" }
34
42
 
35
- headers + "\n\n" + body
36
- end
37
43
  response
38
44
  end
39
45
 
40
- class NullLogger
41
- %w[fatal error warn info debug].each do |level|
42
- define_method(level.to_sym) do |*_args|
43
- nil
44
- end
46
+ private
45
47
 
46
- define_method(:"#{level}?") do
47
- true
48
- end
49
- end
48
+ def stringify_headers(headers)
49
+ headers.map { |name, value| "#{name}: #{value}" }.join("\n")
50
50
  end
51
-
52
- HTTP::Options.register_feature(:logging, self)
53
51
  end
54
52
  end
55
53
  end