This is probably just a silly mistake on my part, but I'm trying to write a simple HTTP2 server wrapped in Celluloid (basically Reel for http2).
The issue I'm running into is I always get protocol errors after sending the final response. I tried basically copying the example server implementation in. Everything works fine with the example server/client. I'm not seeing where what I am doing differs significantly from the example. I'm hoping it's not actually celluloid or celluloid-io related, as I am trying to use those to support async IO (namely supporting multiple connections.
Here is the server I have. I test against the example client. Any thoughts on what might be the issue would be greatly appreciated!
require 'socket'
require 'openssl'
require 'http/2'
require 'celluloid/current'
require 'celluloid/io'
Celluloid.boot
module Ratchet
class Server
include Celluloid
include Celluloid::IO
include Celluloid::Internals::Logger
DRAFT = 'h2'.freeze
def initialize host, port, cert: nil, key: nil, **options
if cert and key
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new cert
ctx.key = OpenSSL::PKey::RSA.new 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.tmp_ecdh_callback = lambda do |*_args|
OpenSSL::PKey::EC.new 'prime256v1'
end
server = Celluloid::IO::TCPServer.new(host, port)
@server = Celluloid::IO::SSLServer.new(server, ctx)
else
@server = TCPServer.new host, port
end
end
def run
loop {async.handle_connection @server.accept}
end
def handle_connection sock
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)
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) { info 'client opened new stream' }
stream.on(:close) { info 'stream closed' }
stream.on(:headers) do |h|
req = Hash[*h.flatten]
info "request headers: #{h}"
end
stream.on(:data) do |d|
info "payload chunk: <<#{d}>>"
buffer << d
end
stream.on(:half_close) do
info 'client closed its end of the stream'
response = nil
if req[':method'] == 'POST'
info "Received POST request, payload: #{buffer}"
response = "Hello HTTP 2.0! POST payload: #{buffer}"
else
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)
# split response into multiple DATA frames
stream.data(response.slice!(0, 5), end_stream: false)
stream.data(response)
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 => e
puts "#{e.class} exception: #{e.message} - closing socket."
e.backtrace.each { |l| puts "\t" + l }
sock.close
end
end
end
end
end
key = File.read(File.join(File.dirname(__FILE__), 'test.key'))
cert = File.read(File.join(File.dirname(__FILE__), 'test.crt'))
server = Ratchet::Server.new '0.0.0.0', 8080, key: key, cert: cert
server.run
This is probably just a silly mistake on my part, but I'm trying to write a simple HTTP2 server wrapped in Celluloid (basically Reel for http2).
The issue I'm running into is I always get protocol errors after sending the final response. I tried basically copying the example server implementation in. Everything works fine with the example server/client. I'm not seeing where what I am doing differs significantly from the example. I'm hoping it's not actually celluloid or celluloid-io related, as I am trying to use those to support async IO (namely supporting multiple connections.
Here is the server I have. I test against the example client. Any thoughts on what might be the issue would be greatly appreciated!