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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
require_relative 'helper'
options = {}
OptionParser.new do |opts|
opts.banner = 'Usage: client.rb [options]'
opts.on('-d', '--data [String]', 'HTTP payload') do |v|
options[:payload] = v
end
end.parse!
uri = URI.parse(ARGV[0] || 'http://localhost:8080/')
tcp = TCPSocket.new(uri.host, uri.port)
sock = nil
if uri.scheme == 'https'
ctx = OpenSSL::SSL::SSLContext.new
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
# For ALPN support, Ruby >= 2.3 and OpenSSL >= 1.0.2 are required
ctx.alpn_protocols = [DRAFT]
ctx.alpn_select_cb = lambda do |protocols|
puts "ALPN protocols supported by server: #{protocols}"
DRAFT if protocols.include? DRAFT
end
sock = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
sock.sync_close = true
sock.hostname = uri.hostname
sock.connect
if sock.alpn_protocol != DRAFT
puts "Failed to negotiate #{DRAFT} via ALPN"
exit
end
else
sock = tcp
end
conn = HTTP2::Client.new
stream = conn.new_stream
log = Logger.new(stream.id)
conn.on(:frame) do |bytes|
# puts "Sending bytes: #{bytes.unpack("H*").first}"
sock.print bytes
sock.flush
end
conn.on(:frame_sent) do |frame|
puts "Sent frame: #{frame.inspect}"
end
conn.on(:frame_received) do |frame|
puts "Received frame: #{frame.inspect}"
end
conn.on(:promise) do |promise|
promise.on(:promise_headers) do |h|
log.info "promise request headers: #{h}"
end
promise.on(:headers) do |h|
log.info "promise headers: #{h}"
end
promise.on(:data) do |d|
log.info "promise data chunk: <<#{d.size}>>"
end
end
conn.on(:altsvc) do |f|
log.info "received ALTSVC #{f}"
end
stream.on(:close) do
log.info 'stream closed'
end
stream.on(:half_close) do
log.info 'closing client-end of the stream'
end
stream.on(:headers) do |h|
log.info "response headers: #{h}"
end
stream.on(:data) do |d|
log.info "response data chunk: <<#{d}>>"
end
stream.on(:altsvc) do |f|
log.info "received ALTSVC #{f}"
end
head = {
':scheme' => uri.scheme,
':method' => (options[:payload].nil? ? 'GET' : 'POST'),
':authority' => [uri.host, uri.port].join(':'),
':path' => uri.path,
'accept' => '*/*',
}
puts 'Sending HTTP 2.0 request'
if head[':method'] == 'GET'
stream.headers(head, end_stream: true)
else
stream.headers(head, end_stream: false)
stream.data(options[:payload])
end
while !sock.closed? && !sock.eof?
data = sock.read_nonblock(1024)
# puts "Received bytes: #{data.unpack("H*").first}"
begin
conn << data
rescue StandardError => e
puts "#{e.class} exception: #{e.message} - closing socket."
e.backtrace.each { |l| puts "\t" + l }
sock.close
end
end
|