webmock 3.6.2 → 3.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +37 -0
  3. data/CHANGELOG.md +214 -0
  4. data/Gemfile +1 -1
  5. data/README.md +85 -32
  6. data/Rakefile +12 -2
  7. data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +216 -0
  8. data/lib/webmock/http_lib_adapters/curb_adapter.rb +4 -0
  9. data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +7 -4
  10. data/lib/webmock/http_lib_adapters/excon_adapter.rb +3 -0
  11. data/lib/webmock/http_lib_adapters/http_rb/client.rb +4 -1
  12. data/lib/webmock/http_lib_adapters/http_rb/response.rb +24 -3
  13. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +2 -2
  14. data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +1 -1
  15. data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +23 -6
  16. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +24 -9
  17. data/lib/webmock/http_lib_adapters/net_http.rb +43 -19
  18. data/lib/webmock/request_pattern.rb +82 -47
  19. data/lib/webmock/response.rb +11 -5
  20. data/lib/webmock/rspec.rb +2 -1
  21. data/lib/webmock/stub_registry.rb +26 -11
  22. data/lib/webmock/test_unit.rb +1 -3
  23. data/lib/webmock/util/uri.rb +5 -4
  24. data/lib/webmock/version.rb +1 -1
  25. data/lib/webmock/webmock.rb +5 -3
  26. data/lib/webmock.rb +1 -0
  27. data/spec/acceptance/async_http_client/async_http_client_spec.rb +375 -0
  28. data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
  29. data/spec/acceptance/curb/curb_spec.rb +12 -5
  30. data/spec/acceptance/em_http_request/em_http_request_spec.rb +56 -0
  31. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +1 -1
  32. data/spec/acceptance/excon/excon_spec_helper.rb +2 -0
  33. data/spec/acceptance/http_rb/http_rb_spec.rb +11 -0
  34. data/spec/acceptance/manticore/manticore_spec.rb +51 -0
  35. data/spec/acceptance/net_http/net_http_spec.rb +38 -0
  36. data/spec/acceptance/patron/patron_spec_helper.rb +2 -2
  37. data/spec/acceptance/shared/callbacks.rb +2 -1
  38. data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
  39. data/spec/acceptance/shared/stubbing_requests.rb +35 -0
  40. data/spec/unit/request_pattern_spec.rb +183 -48
  41. data/spec/unit/response_spec.rb +22 -18
  42. data/spec/unit/util/uri_spec.rb +10 -0
  43. data/spec/unit/webmock_spec.rb +52 -11
  44. data/test/test_webmock.rb +6 -0
  45. data/webmock.gemspec +11 -1
  46. metadata +48 -10
  47. data/.travis.yml +0 -19
@@ -0,0 +1,216 @@
1
+ begin
2
+ require 'async'
3
+ require 'async/http'
4
+ rescue LoadError
5
+ # async-http not found
6
+ end
7
+
8
+ if defined?(Async::HTTP)
9
+ module WebMock
10
+ module HttpLibAdapters
11
+ class AsyncHttpClientAdapter < HttpLibAdapter
12
+ adapter_for :async_http_client
13
+
14
+ OriginalAsyncHttpClient = Async::HTTP::Client unless const_defined?(:OriginalAsyncHttpClient)
15
+
16
+ class << self
17
+ def enable!
18
+ Async::HTTP.send(:remove_const, :Client)
19
+ Async::HTTP.send(:const_set, :Client, Async::HTTP::WebMockClientWrapper)
20
+ end
21
+
22
+ def disable!
23
+ Async::HTTP.send(:remove_const, :Client)
24
+ Async::HTTP.send(:const_set, :Client, OriginalAsyncHttpClient)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ module Async
32
+ module HTTP
33
+ class WebMockClientWrapper < Client
34
+ def initialize(
35
+ endpoint,
36
+ protocol = endpoint.protocol,
37
+ scheme = endpoint.scheme,
38
+ authority = endpoint.authority,
39
+ **options
40
+ )
41
+ webmock_endpoint = WebMockEndpoint.new(scheme, authority, protocol)
42
+
43
+ @network_client = WebMockClient.new(endpoint, **options)
44
+ @webmock_client = WebMockClient.new(webmock_endpoint, **options)
45
+
46
+ @scheme = scheme
47
+ @authority = authority
48
+ end
49
+
50
+ def call(request)
51
+ request.scheme ||= self.scheme
52
+ request.authority ||= self.authority
53
+
54
+ request_signature = build_request_signature(request)
55
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
56
+ webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
57
+ net_connect_allowed = WebMock.net_connect_allowed?(request_signature.uri)
58
+ real_request = false
59
+
60
+ if webmock_response
61
+ webmock_response.raise_error_if_any
62
+ raise Async::TimeoutError, 'WebMock timeout error' if webmock_response.should_timeout
63
+ WebMockApplication.add_webmock_response(request, webmock_response)
64
+ response = @webmock_client.call(request)
65
+ elsif net_connect_allowed
66
+ response = @network_client.call(request)
67
+ real_request = true
68
+ else
69
+ raise WebMock::NetConnectNotAllowedError.new(request_signature) unless webmock_response
70
+ end
71
+
72
+ if WebMock::CallbackRegistry.any_callbacks?
73
+ webmock_response ||= build_webmock_response(response)
74
+ WebMock::CallbackRegistry.invoke_callbacks(
75
+ {
76
+ lib: :async_http_client,
77
+ real_request: real_request
78
+ },
79
+ request_signature,
80
+ webmock_response
81
+ )
82
+ end
83
+
84
+ response
85
+ end
86
+
87
+ def close
88
+ @network_client.close
89
+ @webmock_client.close
90
+ end
91
+
92
+ private
93
+
94
+ def build_request_signature(request)
95
+ body = request.read
96
+ request.body = ::Protocol::HTTP::Body::Buffered.wrap(body)
97
+ WebMock::RequestSignature.new(
98
+ request.method.downcase.to_sym,
99
+ "#{request.scheme}://#{request.authority}#{request.path}",
100
+ headers: request.headers.to_h,
101
+ body: body
102
+ )
103
+ end
104
+
105
+ def build_webmock_response(response)
106
+ body = response.read
107
+ response.body = ::Protocol::HTTP::Body::Buffered.wrap(body)
108
+
109
+ webmock_response = WebMock::Response.new
110
+ webmock_response.status = [
111
+ response.status,
112
+ ::Protocol::HTTP1::Reason::DESCRIPTIONS[response.status]
113
+ ]
114
+ webmock_response.headers = build_webmock_response_headers(response)
115
+ webmock_response.body = body
116
+ webmock_response
117
+ end
118
+
119
+ def build_webmock_response_headers(response)
120
+ response.headers.each.each_with_object({}) do |(k, v), o|
121
+ o[k] ||= []
122
+ o[k] << v
123
+ end
124
+ end
125
+ end
126
+
127
+ class WebMockClient < Client
128
+ end
129
+
130
+ class WebMockEndpoint
131
+ def initialize(scheme, authority, protocol)
132
+ @scheme = scheme
133
+ @authority = authority
134
+ @protocol = protocol
135
+ end
136
+
137
+ attr :scheme, :authority, :protocol
138
+
139
+ def connect
140
+ server_socket, client_socket = create_connected_sockets
141
+ Async(transient: true) do
142
+ accept_socket(server_socket)
143
+ end
144
+ client_socket
145
+ end
146
+
147
+ def inspect
148
+ "\#<#{self.class}> #{scheme}://#{authority} protocol=#{protocol}"
149
+ end
150
+
151
+ private
152
+
153
+ def create_connected_sockets
154
+ Async::IO::Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM).tap do |sockets|
155
+ sockets.each do |socket|
156
+ socket.instance_variable_set :@alpn_protocol, nil
157
+ socket.instance_eval do
158
+ def alpn_protocol
159
+ nil # means HTTP11 will be used for HTTPS
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ def accept_socket(socket)
167
+ server = Async::HTTP::Server.new(WebMockApplication, self)
168
+ server.accept(socket, socket.remote_address)
169
+ end
170
+ end
171
+
172
+ module WebMockApplication
173
+ WEBMOCK_REQUEST_ID_HEADER = 'x-webmock-request-id'.freeze
174
+
175
+ class << self
176
+ def call(request)
177
+ request.read
178
+ webmock_response = get_webmock_response(request)
179
+ build_response(webmock_response)
180
+ end
181
+
182
+ def add_webmock_response(request, webmock_response)
183
+ webmock_request_id = request.object_id.to_s
184
+ request.headers.add(WEBMOCK_REQUEST_ID_HEADER, webmock_request_id)
185
+ webmock_responses[webmock_request_id] = webmock_response
186
+ end
187
+
188
+ def get_webmock_response(request)
189
+ webmock_request_id = request.headers[WEBMOCK_REQUEST_ID_HEADER][0]
190
+ webmock_responses.fetch(webmock_request_id)
191
+ end
192
+
193
+ private
194
+
195
+ def webmock_responses
196
+ @webmock_responses ||= {}
197
+ end
198
+
199
+ def build_response(webmock_response)
200
+ headers = (webmock_response.headers || {}).each_with_object([]) do |(k, value), o|
201
+ Array(value).each do |v|
202
+ o.push [k, v]
203
+ end
204
+ end
205
+
206
+ ::Protocol::HTTP::Response[
207
+ webmock_response.status[0],
208
+ headers,
209
+ webmock_response.body
210
+ ]
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -340,6 +340,10 @@ if defined?(Curl)
340
340
 
341
341
  def reset
342
342
  instance_variable_set(:@body_str, nil)
343
+ instance_variable_set(:@content_type, nil)
344
+ instance_variable_set(:@header_str, nil)
345
+ instance_variable_set(:@last_effective_url, nil)
346
+ instance_variable_set(:@response_code, nil)
343
347
  super
344
348
  end
345
349
  end
@@ -99,6 +99,11 @@ if defined?(EventMachine::HttpClient)
99
99
  end
100
100
  end
101
101
 
102
+ def connection_completed
103
+ @state = :response_header
104
+ send_request(request_signature.headers, request_signature.body)
105
+ end
106
+
102
107
  def send_request(head, body)
103
108
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
104
109
 
@@ -107,7 +112,7 @@ if defined?(EventMachine::HttpClient)
107
112
  @uri ||= nil
108
113
  EM.next_tick {
109
114
  setup(make_raw_response(stubbed_webmock_response), @uri,
110
- stubbed_webmock_response.should_timeout ? "WebMock timeout error" : nil)
115
+ stubbed_webmock_response.should_timeout ? Errno::ETIMEDOUT : nil)
111
116
  }
112
117
  self
113
118
  elsif WebMock.net_connect_allowed?(request_signature.uri)
@@ -164,7 +169,7 @@ if defined?(EventMachine::HttpClient)
164
169
  end
165
170
 
166
171
  def build_request_signature
167
- headers, body = @req.headers, @req.body
172
+ headers, body = build_request, @req.body
168
173
 
169
174
  @conn.middleware.select {|m| m.respond_to?(:request) }.each do |m|
170
175
  headers, body = m.request(self, headers, body)
@@ -178,8 +183,6 @@ if defined?(EventMachine::HttpClient)
178
183
 
179
184
  body = form_encode_body(body) if body.is_a?(Hash)
180
185
 
181
- headers = @req.headers
182
-
183
186
  if headers['authorization'] && headers['authorization'].is_a?(Array)
184
187
  headers['Authorization'] = WebMock::Util::Headers.basic_auth_header(headers.delete('authorization'))
185
188
  end
@@ -159,4 +159,7 @@ if defined?(Excon)
159
159
  end
160
160
  end
161
161
  end
162
+
163
+ # Suppresses Excon connection argument validation warning
164
+ Excon::VALID_CONNECTION_KEYS << :__construction_args
162
165
  end
@@ -4,7 +4,10 @@ module HTTP
4
4
 
5
5
  def perform(request, options)
6
6
  return __perform__(request, options) unless webmock_enabled?
7
- WebMockPerform.new(request) { __perform__(request, options) }.exec
7
+
8
+ response = WebMockPerform.new(request) { __perform__(request, options) }.exec
9
+ options.features.each { |_name, feature| response = feature.wrap_response(response) }
10
+ response
8
11
  end
9
12
 
10
13
  def webmock_enabled?
@@ -11,20 +11,41 @@ module HTTP
11
11
  end
12
12
 
13
13
  class << self
14
- def from_webmock(webmock_response, request_signature = nil)
14
+ def from_webmock(request, webmock_response, request_signature = nil)
15
15
  status = Status.new(webmock_response.status.first)
16
16
  headers = webmock_response.headers || {}
17
- body = Body.new Streamer.new webmock_response.body
18
17
  uri = normalize_uri(request_signature && request_signature.uri)
19
18
 
19
+ # HTTP.rb 3.0+ uses a keyword argument to pass the encoding, but 1.x
20
+ # and 2.x use a positional argument, and 0.x don't support supplying
21
+ # the encoding.
22
+ body = if HTTP::VERSION < "1.0.0"
23
+ Body.new(Streamer.new(webmock_response.body))
24
+ elsif HTTP::VERSION < "3.0.0"
25
+ Body.new(Streamer.new(webmock_response.body), webmock_response.body.encoding)
26
+ else
27
+ Body.new(Streamer.new(webmock_response.body), encoding: webmock_response.body.encoding)
28
+ end
29
+
20
30
  return new(status, "1.1", headers, body, uri) if HTTP::VERSION < "1.0.0"
21
31
 
32
+ # 5.0.0 had a breaking change to require request instead of uri.
33
+ if HTTP::VERSION < '5.0.0'
34
+ return new({
35
+ status: status,
36
+ version: "1.1",
37
+ headers: headers,
38
+ body: body,
39
+ uri: uri
40
+ })
41
+ end
42
+
22
43
  new({
23
44
  status: status,
24
45
  version: "1.1",
25
46
  headers: headers,
26
47
  body: body,
27
- uri: uri
48
+ request: request,
28
49
  })
29
50
  end
30
51
 
@@ -5,7 +5,7 @@ module HTTP
5
5
  @io = StringIO.new str
6
6
  end
7
7
 
8
- def readpartial(size = nil)
8
+ def readpartial(size = nil, outbuf = nil)
9
9
  unless size
10
10
  if defined?(HTTP::Client::BUFFER_SIZE)
11
11
  size = HTTP::Client::BUFFER_SIZE
@@ -14,7 +14,7 @@ module HTTP
14
14
  end
15
15
  end
16
16
 
17
- @io.read size
17
+ @io.read size, outbuf
18
18
  end
19
19
 
20
20
  def close
@@ -38,7 +38,7 @@ module HTTP
38
38
  webmock_response.raise_error_if_any
39
39
 
40
40
  invoke_callbacks(webmock_response, real_request: false)
41
- ::HTTP::Response.from_webmock webmock_response, request_signature
41
+ ::HTTP::Response.from_webmock @request, webmock_response, request_signature
42
42
  end
43
43
 
44
44
  def raise_timeout_error
@@ -43,6 +43,9 @@ if defined?(::HTTPClient)
43
43
  end
44
44
 
45
45
  module WebMockHTTPClients
46
+
47
+ REQUEST_RESPONSE_LOCK = Mutex.new
48
+
46
49
  def do_get_block(req, proxy, conn, &block)
47
50
  do_get(req, proxy, conn, false, &block)
48
51
  end
@@ -57,7 +60,7 @@ if defined?(::HTTPClient)
57
60
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
58
61
 
59
62
  if webmock_responses[request_signature]
60
- webmock_response = webmock_responses.delete(request_signature)
63
+ webmock_response = synchronize_request_response { webmock_responses.delete(request_signature) }
61
64
  response = build_httpclient_response(webmock_response, stream, req.header, &block)
62
65
  @request_filter.each do |filter|
63
66
  filter.filter_response(req, response)
@@ -68,7 +71,7 @@ if defined?(::HTTPClient)
68
71
  res
69
72
  elsif WebMock.net_connect_allowed?(request_signature.uri)
70
73
  # in case there is a nil entry in the hash...
71
- webmock_responses.delete(request_signature)
74
+ synchronize_request_response { webmock_responses.delete(request_signature) }
72
75
 
73
76
  res = if stream
74
77
  do_get_stream_without_webmock(req, proxy, conn, &block)
@@ -100,7 +103,7 @@ if defined?(::HTTPClient)
100
103
  def do_request_async(method, uri, query, body, extheader)
101
104
  req = create_request(method, uri, query, body, extheader)
102
105
  request_signature = build_request_signature(req)
103
- webmock_request_signatures << request_signature
106
+ synchronize_request_response { webmock_request_signatures << request_signature }
104
107
 
105
108
  if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
106
109
  super
@@ -184,7 +187,9 @@ if defined?(::HTTPClient)
184
187
 
185
188
  def webmock_responses
186
189
  @webmock_responses ||= Hash.new do |hash, request_signature|
187
- hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
190
+ synchronize_request_response do
191
+ hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
192
+ end
188
193
  end
189
194
  end
190
195
 
@@ -193,8 +198,10 @@ if defined?(::HTTPClient)
193
198
  end
194
199
 
195
200
  def previous_signature_for(signature)
196
- return nil unless index = webmock_request_signatures.index(signature)
197
- webmock_request_signatures.delete_at(index)
201
+ synchronize_request_response do
202
+ return nil unless index = webmock_request_signatures.index(signature)
203
+ webmock_request_signatures.delete_at(index)
204
+ end
198
205
  end
199
206
 
200
207
  private
@@ -209,6 +216,16 @@ if defined?(::HTTPClient)
209
216
  hdrs
210
217
  end
211
218
  end
219
+
220
+ def synchronize_request_response
221
+ if REQUEST_RESPONSE_LOCK.owned?
222
+ yield
223
+ else
224
+ REQUEST_RESPONSE_LOCK.synchronize do
225
+ yield
226
+ end
227
+ end
228
+ end
212
229
  end
213
230
 
214
231
  class WebMockHTTPClient < HTTPClient
@@ -24,6 +24,12 @@ if defined?(Manticore)
24
24
  Manticore.instance_variable_set(:@manticore_facade, OriginalManticoreClient.new)
25
25
  end
26
26
 
27
+ class StubbedTimeoutResponse < Manticore::StubbedResponse
28
+ def call
29
+ @handlers[:failure].call(Manticore::ConnectTimeout.new("Too slow (mocked timeout)"))
30
+ end
31
+ end
32
+
27
33
  class WebMockManticoreClient < Manticore::Client
28
34
  def request(klass, url, options={}, &block)
29
35
  super(klass, WebMock::Util::URI.normalize_uri(url).to_s, format_options(options))
@@ -106,21 +112,30 @@ if defined?(Manticore)
106
112
  end
107
113
 
108
114
  def generate_manticore_response(webmock_response)
109
- raise Manticore::ConnectTimeout if webmock_response.should_timeout
110
-
111
- Manticore::StubbedResponse.stub(
112
- code: webmock_response.status[0],
113
- body: webmock_response.body,
114
- headers: webmock_response.headers,
115
- cookies: {}
116
- )
115
+ if webmock_response.should_timeout
116
+ StubbedTimeoutResponse.new
117
+ else
118
+ Manticore::StubbedResponse.stub(
119
+ code: webmock_response.status[0],
120
+ body: webmock_response.body,
121
+ headers: webmock_response.headers,
122
+ cookies: {}
123
+ )
124
+ end
117
125
  end
118
126
 
119
127
  def generate_webmock_response(manticore_response)
120
128
  webmock_response = WebMock::Response.new
121
129
  webmock_response.status = [manticore_response.code, manticore_response.message]
122
- webmock_response.body = manticore_response.body
123
130
  webmock_response.headers = manticore_response.headers
131
+
132
+ # The attempt to read the body could fail if manticore is used in a streaming mode
133
+ webmock_response.body = begin
134
+ manticore_response.body
135
+ rescue ::Manticore::StreamClosedException
136
+ nil
137
+ end
138
+
124
139
  webmock_response
125
140
  end
126
141
  end
@@ -228,9 +228,9 @@ class PatchedStringIO < StringIO #:nodoc:
228
228
 
229
229
  alias_method :orig_read_nonblock, :read_nonblock
230
230
 
231
- def read_nonblock(size, *args)
231
+ def read_nonblock(size, *args, **kwargs)
232
232
  args.reject! {|arg| !arg.is_a?(Hash)}
233
- orig_read_nonblock(size, *args)
233
+ orig_read_nonblock(size, *args, **kwargs)
234
234
  end
235
235
 
236
236
  end
@@ -252,12 +252,19 @@ class StubSocket #:nodoc:
252
252
  def readuntil(*args)
253
253
  end
254
254
 
255
+ def io
256
+ @io ||= StubIO.new
257
+ end
258
+
259
+ class StubIO
260
+ def setsockopt(*args); end
261
+ end
255
262
  end
256
263
 
257
264
  module Net #:nodoc: all
258
265
 
259
266
  class WebMockNetBufferedIO < BufferedIO
260
- def initialize(io, *args)
267
+ def initialize(io, *args, **kwargs)
261
268
  io = case io
262
269
  when Socket, OpenSSL::SSL::SSLSocket, IO
263
270
  io
@@ -268,23 +275,33 @@ module Net #:nodoc: all
268
275
  end
269
276
  raise "Unable to create local socket" unless io
270
277
 
271
- super
278
+ # Prior to 2.4.0 `BufferedIO` only takes a single argument (`io`) with no
279
+ # options. Here we pass through our full set of arguments only if we're
280
+ # on 2.4.0 or later, and use a simplified invocation otherwise.
281
+ if RUBY_VERSION >= '2.4.0'
282
+ super
283
+ else
284
+ super(io)
285
+ end
272
286
  end
273
287
 
274
288
  if RUBY_VERSION >= '2.6.0'
289
+ # https://github.com/ruby/ruby/blob/7d02441f0d6e5c9d0a73a024519eba4f69e36dce/lib/net/protocol.rb#L208
290
+ # Modified version of method from ruby, so that nil is always passed into orig_read_nonblock to avoid timeout
275
291
  def rbuf_fill
276
- current_thread_id = Thread.current.object_id
277
-
278
- trace = TracePoint.trace(:line) do |tp|
279
- next unless Thread.current.object_id == current_thread_id
280
- if tp.binding.local_variable_defined?(:tmp)
281
- tp.binding.local_variable_set(:tmp, nil)
282
- end
283
- end
284
-
285
- super
286
- ensure
287
- trace.disable
292
+ case rv = @io.read_nonblock(BUFSIZE, nil, exception: false)
293
+ when String
294
+ return if rv.nil?
295
+ @rbuf << rv
296
+ rv.clear
297
+ return
298
+ when :wait_readable
299
+ @io.to_io.wait_readable(@read_timeout) or raise Net::ReadTimeout
300
+ when :wait_writable
301
+ @io.to_io.wait_writable(@read_timeout) or raise Net::ReadTimeout
302
+ when nil
303
+ raise EOFError, 'end of file reached'
304
+ end while true
288
305
  end
289
306
  end
290
307
  end
@@ -296,8 +313,6 @@ module WebMock
296
313
  module NetHTTPUtility
297
314
 
298
315
  def self.request_signature_from_request(net_http, request, body = nil)
299
- protocol = net_http.use_ssl? ? "https" : "http"
300
-
301
316
  path = request.path
302
317
 
303
318
  if path.respond_to?(:request_uri) #https://github.com/bblimke/webmock/issues/288
@@ -306,7 +321,7 @@ module WebMock
306
321
 
307
322
  path = WebMock::Util::URI.heuristic_parse(path).request_uri if path =~ /^http/
308
323
 
309
- uri = "#{protocol}://#{net_http.address}:#{net_http.port}#{path}"
324
+ uri = get_uri(net_http, path)
310
325
  method = request.method.downcase.to_sym
311
326
 
312
327
  headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]
@@ -326,6 +341,15 @@ module WebMock
326
341
  WebMock::RequestSignature.new(method, uri, body: request.body, headers: headers)
327
342
  end
328
343
 
344
+ def self.get_uri(net_http, path)
345
+ protocol = net_http.use_ssl? ? "https" : "http"
346
+
347
+ hostname = net_http.address
348
+ hostname = "[#{hostname}]" if /\A\[.*\]\z/ !~ hostname && /:/ =~ hostname
349
+
350
+ "#{protocol}://#{hostname}:#{net_http.port}#{path}"
351
+ end
352
+
329
353
  def self.validate_headers(headers)
330
354
  # For Ruby versions < 2.3.0, if you make a request with headers that are symbols
331
355
  # Net::HTTP raises a NoMethodError