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
|
require_relative 'helper'
options = { port: 8080 }
OptionParser.new do |opts|
opts.banner = 'Usage: server.rb [options]'
opts.on('-s', '--secure', 'HTTPS mode') do |v|
options[:secure] = v
end
opts.on('-p', '--port [Integer]', 'listen port') do |v|
options[:port] = v
end
opts.on('-u', '--push', 'Push message') do |_v|
options[:push] = true
end
end.parse!
puts "Starting server on port #{options[:port]}"
server = TCPServer.new(options[:port])
if options[:secure]
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/server.crt'))
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/server.key'))
ctx.ssl_version = :TLSv1_2
ctx.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
ctx.ciphers = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers]
ctx.alpn_protocols = ['h2']
ctx.alpn_select_cb = lambda do |protocols|
raise "Protocol #{DRAFT} is required" if protocols.index(DRAFT).nil?
DRAFT
end
ctx.ecdh_curves = 'P-256'
server = OpenSSL::SSL::SSLServer.new(server, ctx)
end
loop do
sock = server.accept
puts 'New TCP connection!'
conn = HTTP2::Server.new
conn.on(:frame) do |bytes|
# puts "Writing bytes: #{bytes.unpack("H*").first}"
sock.is_a?(TCPSocket) ? sock.sendmsg(bytes) : sock.write(bytes)
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(:stream) do |stream|
log = Logger.new(stream.id)
req, buffer = {}, ''
stream.on(:active) { log.info 'client opened new stream' }
stream.on(:close) { log.info 'stream closed' }
stream.on(:headers) do |h|
req = Hash[*h.flatten]
log.info "request headers: #{h}"
end
stream.on(:data) do |d|
log.info "payload chunk: <<#{d}>>"
buffer << d
end
stream.on(:half_close) do
log.info 'client closed its end of the stream'
response = nil
if req[':method'] == 'POST'
log.info "Received POST request, payload: #{buffer}"
response = "Hello HTTP 2.0! POST payload: #{buffer}"
else
log.info 'Received GET request'
response = 'Hello HTTP 2.0! GET request'
end
stream.headers({
':status' => '200',
'content-length' => response.bytesize.to_s,
'content-type' => 'text/plain',
}, end_stream: false)
if options[:push]
push_streams = []
# send 10 promises
10.times do |i|
puts 'sending push'
head = { ':method' => 'GET',
':authority' => 'localhost',
':scheme' => 'https',
':path' => "/other_resource/#{i}" }
stream.promise(head) do |push|
push.headers(':status' => '200', 'content-type' => 'text/plain', 'content-length' => '11')
push_streams << push
end
end
end
# split response into multiple DATA frames
stream.data(response.slice!(0, 5), end_stream: false)
stream.data(response)
if options[:push]
push_streams.each_with_index do |push, i|
sleep 1
push.data("push_data #{i}")
end
end
end
end
while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
data = sock.readpartial(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
end
|