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
|