Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support --out http://.... #1395

Merged
merged 24 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 46 additions & 15 deletions lib/cucumber/formatter/http_io.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,42 @@ class << self
# Returns an IO that will write to a HTTP request's body
def open(url, https_verify_mode = nil)
@https_verify_mode = https_verify_mode
uri, method, headers = build_uri_and_headers(url)
uri, method, headers = build_uri_method_headers(url)

method_class_name = "#{method[0].upcase}#{method[1..-1].downcase}"
@req = Net::HTTP.const_get(method_class_name).new(uri)
headers.each do |header, value|
@req[header] = value
end

@http = Net::HTTP.new(uri.hostname, uri.port)
if uri.scheme == 'https'
@http.use_ssl = true
@http.verify_mode = https_verify_mode if https_verify_mode
end
@req = build_request(uri, method, headers)
@http = build_client(uri, https_verify_mode)

read_io, write_io = IO.pipe
@req.body_stream = read_io

Thread.new do
@http.request(@req)
class << write_io
attr_writer :request_thread

def start_request(http, req)
@req_thread = Thread.new do
begin
res = http.request(req)
if res.code.to_i >= 400
raise Exception.new("request to #{req.uri} failed with status #{res.code}")
aslakhellesoy marked this conversation as resolved.
Show resolved Hide resolved
end
rescue Exception => err
@http_error = err
end
end
end

def close
super
@req_thread.join rescue nil
raise @http_error unless @http_error.nil?
end
end
write_io.start_request(@http, @req)

write_io
end

def build_uri_and_headers(url)
def build_uri_method_headers(url)
uri = URI(url)
query_pairs = uri.query ? URI.decode_www_form(uri.query) : []

Expand All @@ -54,6 +65,26 @@ def build_uri_and_headers(url)
uri.query = URI.encode_www_form(new_query_hash) unless new_query_hash.empty?
[uri, method, headers]
end

private

def build_request(uri, method, headers)
method_class_name = "#{method[0].upcase}#{method[1..-1].downcase}"
req = Net::HTTP.const_get(method_class_name).new(uri)
headers.each do |header, value|
req[header] = value
end
req
end

def build_client(uri, https_verify_mode)
http = Net::HTTP.new(uri.hostname, uri.port)
if uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = https_verify_mode if https_verify_mode
end
http
end
end
end
end
Expand Down
39 changes: 30 additions & 9 deletions spec/cucumber/formatter/http_io_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ def start_server(url)
uri = URI(url)
@received_body_io = StringIO.new

rd, wt = IO.pipe
webrick_options = {
Port: uri.port,
Logger: WEBrick::Log.new(File.open(File::NULL, 'w')),
AccessLog: []
Port: uri.port,
Logger: WEBrick::Log.new(File.open(File::NULL, 'w')),
AccessLog: [],
StartCallback: Proc.new {
wt.write(1) # write "1", signal a server start message
wt.close
}
}
if uri.scheme == 'https'
webrick_options[:SSLEnable] = true
Expand All @@ -30,54 +35,70 @@ def start_server(url)
@server.mount_proc '/' do |req, _res|
IO.copy_stream(req.body_reader, @received_body_io)
end
@server.mount_proc '/404' do |_req, res|
res.status = 404
end

Thread.new do
@server.start
end
rd.read(1) # read a byte for the server start signal
rd.close
end

context 'created by Io#ensure_io' do
it 'creates an IO that POSTs with HTTP' do
url = 'http://localhost:9987'
start_server(url)
sent_body = 'X' * 10 # 10Mb
sent_body = 'X' * 10_000_000 # 10Mb

io = ensure_io(url)
io.write(sent_body)
io.flush
io.close
sleep(0.1)
@received_body_io.rewind
received_body = @received_body_io.read
expect(received_body).to eq(sent_body)
end

it 'notifies user if the server responds with error' do
url = 'http://localhost:9987/404'
start_server(url)
io = ensure_io(url)
expect { io.close }.to(raise_error('request to http://localhost:9987/404 failed with status 404'))
end

it 'notifies user if the server is unreachable' do
url = 'http://localhost:9987'
io = ensure_io(url)
expect { io.close }.to(raise_error(/Failed to open TCP connection to localhost:9987/))
end
end

context 'created with constructor (because we need to relax SSL verification during testing)' do
it 'POSTs with HTTPS' do
url = 'https://localhost:9987'
start_server(url)
sent_body = 'X' * 10 # 10Mb
sent_body = 'X' * 10_000_000 # 10Mb

io = HTTPIO.open(url, OpenSSL::SSL::VERIFY_NONE)
io.write(sent_body)
io.flush
io.close
sleep(0.1)
@received_body_io.rewind
received_body = @received_body_io.read
expect(received_body).to eq(sent_body)
end
end

it 'sets HTTP method when http-method is set' do
uri, method, = HTTPIO.build_uri_and_headers('http://localhost:9987?http-method=PUT&foo=bar')
uri, method, = HTTPIO.build_uri_method_headers('http://localhost:9987?http-method=PUT&foo=bar')
expect(method).to eq('PUT')
expect(uri.to_s).to eq('http://localhost:9987?foo=bar')
end

it 'sets Content-Type header when http-content-type query parameter set' do
uri, _method, headers = HTTPIO.build_uri_and_headers('http://localhost:9987?http-content-type=text/plain&foo=bar')
uri, _method, headers = HTTPIO.build_uri_method_headers('http://localhost:9987?http-content-type=text/plain&foo=bar')
expect(headers['content-type']).to eq('text/plain')
expect(uri.to_s).to eq('http://localhost:9987?foo=bar')
end
Expand Down