1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
# frozen_string_literal: true
require "rack/test"
require "bundler/vendored_net_http"
module Artifice
module Net
# This is an internal object that can receive Rack requests
# to the application using the Rack::Test API
class RackRequest
include Rack::Test::Methods
attr_reader :app
def initialize(app)
@app = app
end
end
class HTTP < ::Gem::Net::HTTP
class << self
attr_accessor :endpoint
end
# Gem::Net::HTTP uses a @newimpl instance variable to decide whether
# to use a legacy implementation. Since we are subclassing
# Gem::Net::HTTP, we must set it
@newimpl = true
# We don't need to connect, so blank out this method
def connect
end
# Replace the Gem::Net::HTTP request method with a method
# that converts the request into a Rack request and
# dispatches it to the Rack endpoint.
#
# @param [Net::HTTPRequest] req A Gem::Net::HTTPRequest
# object, or one if its subclasses
# @param [optional, String, #read] body This should
# be sent as "rack.input". If it's a String, it will
# be converted to a StringIO.
# @return [Net::HTTPResponse]
#
# @yield [Net::HTTPResponse] If a block is provided,
# this method will yield the Gem::Net::HTTPResponse to
# it after the body is read.
def request(req, body = nil, &block)
rack_request = RackRequest.new(self.class.endpoint)
req.each_header do |header, value|
rack_request.header(header, value)
end
scheme = use_ssl? ? "https" : "http"
prefix = "#{scheme}://#{addr_port}"
body_stream_contents = req.body_stream.read if req.body_stream
response = rack_request.request("#{prefix}#{req.path}",
{ method: req.method, input: body || req.body || body_stream_contents })
make_net_http_response(response, &block)
end
private
# This method takes a Rack response and creates a Gem::Net::HTTPResponse
# Instead of trying to mock HTTPResponse directly, we just convert
# the Rack response into a String that looks like a normal HTTP
# response and call Gem::Net::HTTPResponse.read_new
#
# @param [Array(#to_i, Hash, #each)] response a Rack response
# @return [Net::HTTPResponse]
# @yield [Net::HTTPResponse] If a block is provided, yield the
# response to it after the body is read
def make_net_http_response(response)
status = response.status
headers = response.headers
body = response.body
response_string = []
response_string << "HTTP/1.1 #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
headers.each do |header, value|
response_string << "#{header}: #{value}"
end
response_string << "" << body
response_io = ::Gem::Net::BufferedIO.new(StringIO.new(response_string.join("\n")))
res = ::Gem::Net::HTTPResponse.read_new(response_io)
res.reading_body(response_io, true) do
yield res if block_given?
end
res
end
end
end
end
|