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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
# frozen_string_literals: true
require_relative 'helper'
require 'http_parser'
OptionParser.new do |opts|
opts.banner = 'Usage: upgrade_client.rb [options]'
end.parse!
uri = URI.parse(ARGV[0] || 'http://localhost:8080/')
sock = TCPSocket.new(uri.host, uri.port)
conn = HTTP2::Client.new
def request_header_hash
Hash.new do |hash, key|
k = key.to_s.downcase
k.tr! '_', '-'
_, value = hash.find { |header_key, _| header_key.downcase == k }
hash[key] = value if value
end
end
conn.on(:frame) do |bytes|
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
# upgrader module
class UpgradeHandler
UPGRADE_REQUEST = <<RESP.freeze
GET %s HTTP/1.1
Connection: Upgrade, HTTP2-Settings
HTTP2-Settings: #{HTTP2::Client.settings_header(settings_max_concurrent_streams: 100)}
Upgrade: h2c
Host: %s
User-Agent: http-2 upgrade
Accept: */*
RESP
attr_reader :complete, :parsing
def initialize(conn, sock)
@conn = conn
@sock = sock
@headers = request_header_hash
@body = ''.b
@complete, @parsing = false, false
@parser = ::HTTP::Parser.new(self)
end
def request(uri)
host = "#{uri.hostname}#{":#{uri.port}" if uri.port != uri.default_port}"
req = format(UPGRADE_REQUEST, uri.request_uri, host)
puts req
@sock << req
end
def <<(data)
@parsing ||= true
@parser << data
return unless complete
upgrade
end
def complete!
@complete = true
end
def on_headers_complete(headers)
@headers.merge!(headers)
puts "received headers: #{headers}"
end
def on_body(chunk)
puts "received chunk: #{chunk}"
@body << chunk
end
def on_message_complete
fail 'could not upgrade to h2c' unless @parser.status_code == 101
@parsing = false
complete!
end
def upgrade
stream = @conn.upgrade
log = Logger.new(stream.id)
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
@conn.on(:promise) do |promise|
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
end
end
uh = UpgradeHandler.new(conn, sock)
puts 'Sending HTTP/1.1 upgrade request'
uh.request(uri)
while !sock.closed? && !sock.eof?
data = sock.read_nonblock(1024)
begin
if !uh.parsing && !uh.complete
uh << data
elsif uh.parsing && !uh.complete
uh << data
elsif uh.complete
conn << data
end
rescue StandardError => e
puts "#{e.class} exception: #{e.message} - closing socket."
e.backtrace.each { |l| puts "\t" + l }
conn.close
sock.close
end
end
|