Skip to content

Issues properly closing #101

@rfestag

Description

@rfestag

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions