File: upgrade_client.rb

package info (click to toggle)
ruby-http-2 0.11.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 6,384 kB
  • sloc: ruby: 5,413; makefile: 4
file content (153 lines) | stat: -rw-r--r-- 3,185 bytes parent folder | download | duplicates (2)
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