File: client_server.rb

package info (click to toggle)
ruby-http-2 1.1.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,360 kB
  • sloc: ruby: 6,031; makefile: 4
file content (190 lines) | stat: -rw-r--r-- 4,272 bytes parent folder | download
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# frozen_string_literal: true

require "uri"
require "http/2"

DEBUG = ENV.key?("DEBUG")
BENCHMARK = ENV.fetch("BENCH", "profile")
ITERATIONS = 5000

METHOD = "GET"
BODY = "bang"
URL = URI.parse(ARGV[0] || "http://localhost:8080/")
CLIENT = HTTP2::Client.new
SERVER = HTTP2::Server.new

CLIENT_BUFFER = "".b
SERVER_BUFFER = "".b

def log
  return unless DEBUG

  puts yield
end

log { "build client..." }
CLIENT.on(:frame) do |bytes|
  log { "(client) sending bytes: #{bytes.size}" }
  CLIENT_BUFFER << bytes
end
CLIENT.on(:frame_sent) do |frame|
  log { "(client) Sent frame: #{frame.inspect}" }
end
CLIENT.on(:frame_received) do |frame|
  log { "(client) Received frame: #{frame.inspect}" }
end

CLIENT.on(:altsvc) do |f|
  log { "(client) received ALTSVC #{f}" }
end

log { "build server..." }
SERVER.on(:frame) do |bytes|
  log { "(server) sending bytes: #{bytes.bytesize}" }
  SERVER_BUFFER << bytes
end
SERVER.on(:frame_sent) do |frame|
  log { "(server) Sent frame: #{frame.inspect}" }
end
SERVER.on(:frame_received) do |frame|
  log { "(server) Received frame: #{frame.inspect}" }
end

SERVER.on(:goaway) do
  log { "(server) goaway received" }
end

SERVER.on(:stream) do |stream|
  req = {}
  buffer = "".b

  stream.on(:active) { log { "(server stream:#{stream.id}) client opened new stream" } }
  stream.on(:close)  { log { "(server stream:#{stream.id}) stream closed" } }

  stream.on(:headers) do |h|
    log { "(server stream:#{stream.id}) request headers: #{Hash[*h.flatten]}" }
  end

  stream.on(:data) do |d|
    log { "(server stream:#{stream.id}) payload chunk: <<#{d}>>" }
    buffer << d
  end

  stream.on(:half_close) do
    log { "(server stream:#{stream.id}) client closed its end of the stream" }

    response = nil
    if req[":method"] == "POST"
      log { "(server stream:#{stream.id}) Received POST request, payload: #{buffer}" }
      response = "(server stream:#{stream.id}) Hello HTTP 2.0! POST payload: #{buffer}"
    else
      log { "Received GET request" }
      response = "(server stream:#{stream.id}) Hello HTTP 2.0! GET request"
    end

    stream.headers(
      {
        ":status" => "200",
        "content-length" => response.bytesize.to_s,
        "content-type" => "text/plain",
        "x-stream-id" => "stream-#{stream.id}"
      }, end_stream: false
    )

    # split response into multiple DATA frames
    stream.data(response[0, 5], end_stream: false)
    stream.data(response[5, -1] || "")
  end
end

def send_request
  stream = CLIENT.new_stream

  stream.on(:close) do
    log { "(client stream:#{stream.id}) stream closed" }
  end

  stream.on(:half_close) do
    log { "(client stream:#{stream.id}) closing client-end of the stream" }
  end

  stream.on(:headers) do |h|
    log { "(client stream:#{stream.id}) response headers: #{h}" }
  end

  stream.on(:data) do |d|
    log { "(client stream:#{stream.id}) response data chunk: <<#{d}>>" }
  end

  stream.on(:altsvc) do |f|
    log { "(client stream:#{stream.id}) received ALTSVC #{f}" }
  end

  head = {
    ":scheme" => URL.scheme,
    ":method" => METHOD,
    ":authority" => [URL.host, URL.port].join(":"),
    ":path" => URL.path,
    "accept" => "*/*"
  }

  log { "Sending HTTP 2.0 request" }

  if head[":method"] == "GET"
    stream.headers(head, end_stream: true)
  else
    stream.headers(head, end_stream: false)
    stream.data(BODY)
  end

  until CLIENT_BUFFER.empty? && SERVER_BUFFER.empty?
    unless CLIENT_BUFFER.empty?
      SERVER << CLIENT_BUFFER
      CLIENT_BUFFER.clear
    end

    unless SERVER_BUFFER.empty?
      CLIENT << SERVER_BUFFER
      SERVER_BUFFER.clear
    end
  end
end

def benchmark(bench_type, &block)
  return yield if DEBUG

  case bench_type
  when "profile"
    require "singed"
    Singed.output_directory = "tmp/"

    flamegraph(&block)
  when "memory"
    require "memory_profiler"
    MemoryProfiler.report(allow_files: ["lib/http/2"], &block).pretty_print

  when "benchmark"
    require "benchmark"
    puts Benchmark.measure(&block)
  end
end

GC.start
GC.disable

puts "warmup..."
ITERATIONS.times do
  # start client stream
  send_request
end

puts "bench!"
# Benchmark.bmbm do |x|
benchmark(BENCHMARK) do
  ITERATIONS.times do
    # start client stream
    send_request
  end

  CLIENT.goaway
end