Skip to content
This repository has been archived by the owner on Dec 7, 2018. It is now read-only.

Commit

Permalink
Merge 2c28083 into a180b37
Browse files Browse the repository at this point in the history
  • Loading branch information
kenichi committed Jun 17, 2017
2 parents a180b37 + 2c28083 commit e77c041
Show file tree
Hide file tree
Showing 29 changed files with 1,761 additions and 23 deletions.
1 change: 1 addition & 0 deletions Gemfile
Expand Up @@ -12,6 +12,7 @@ group :development do
end

group :development, :test do
gem 'h2', git: 'https://github.com/kenichi/h2'
gem 'pry'
end

Expand Down
21 changes: 21 additions & 0 deletions examples/h2/hello_world.rb
@@ -0,0 +1,21 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# Run with: bundle exec examples/h2/hello_world.rb

require 'bundler/setup'
require 'reel/h2'

Reel::Logger.logger.level = ::Logger::DEBUG
Reel::H2.verbose!

addr, port = '127.0.0.1', 1234

puts "*** Starting server on http://#{addr}:#{port}"
s = Reel::H2::Server::HTTP.new host: addr, port: port do |connection|
connection.each_stream do |stream|
stream.respond :ok, "hello, world!\n"
stream.connection.goaway
end
end

sleep
32 changes: 32 additions & 0 deletions examples/h2/https_hello_world.rb
@@ -0,0 +1,32 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# Run with: bundle exec examples/h2/https_hello_world.rb

require 'bundler/setup'
require 'reel/h2'

port = 1234
addr = Socket.getaddrinfo('localhost', port).first[3]
certs_dir = File.expand_path '../../../tmp/certs', __FILE__

tls = {
cert: certs_dir + '/server.crt',
key: certs_dir + '/server.key',
# :extra_chain_cert => certs_dir + '/chain.pem'
}

puts "*** Starting server on https://#{addr}:#{port}"

s = Reel::H2::Server::HTTPS.new host: addr, port: port, **tls do |connection|
connection.each_stream do |stream|
stream.goaway_on_complete

if stream.request.path == '/favicon.ico'
stream.respond :not_found
else
stream.respond :ok, "hello, world!\n"
end
end
end

sleep
48 changes: 48 additions & 0 deletions examples/h2/push_promise.rb
@@ -0,0 +1,48 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# Run with: bundle exec examples/h2/push_promise.rb

require 'bundler/setup'
require 'reel/h2'

Reel::Logger.logger.level = ::Logger::DEBUG
Reel::H2.verbose!

port = 1234
addr = Socket.getaddrinfo('localhost', port).first[3]
certs_dir = File.expand_path '../../../tmp/certs', __FILE__
logo_png = File.read File.expand_path '../../../logo.png', __FILE__
push_promise = '<html>wait for it...<img src="/logo.png"/><script src="/pushed.js"></script></html>'
pushed_js = '(()=>{ alert("hello h2 push promise!"); })();'

sni = {
'localhost' => {
:cert => certs_dir + '/server.crt',
:key => certs_dir + '/server.key',
# :extra_chain_cert => certs_dir + '/chain.pem'
}
}

puts "*** Starting server on https://#{addr}:#{port}"
s = Reel::H2::Server::HTTPS.new host: addr, port: port, sni: sni do |connection|
connection.each_stream do |stream|

if stream.request.path == '/favicon.ico'
stream.respond :not_found

else
stream.goaway_on_complete

stream.push_promise '/logo.png', { 'content-type' => 'image/png' }, logo_png

js_promise = stream.push_promise_for '/pushed.js', { 'content-type' => 'application/javascript' }, pushed_js
js_promise.make_on stream

stream.respond :ok, push_promise

js_promise.keep
end
end
end

sleep
1 change: 1 addition & 0 deletions lib/reel.rb
Expand Up @@ -2,6 +2,7 @@

require 'http/parser'
require 'http'
require 'celluloid/current'
require 'celluloid/io'

require 'reel/version'
Expand Down
5 changes: 3 additions & 2 deletions lib/reel/connection.rb
Expand Up @@ -16,13 +16,14 @@ class Connection

# Attempt to read this much data
BUFFER_SIZE = 16384
attr_reader :buffer_size
attr_reader :buffer_size, :server

def initialize(socket, buffer_size = nil)
def initialize(socket, buffer_size = nil, server = nil)
@attached = true
@socket = socket
@keepalive = true
@buffer_size = buffer_size || BUFFER_SIZE
@server = server
@parser = Request::Parser.new(self)
@request_fsm = Request::StateMachine.new(@socket)

Expand Down
36 changes: 36 additions & 0 deletions lib/reel/h2.rb
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module Reel

module H2

# http/2 psuedo-headers
#
AUTHORITY_KEY = ':authority'
METHOD_KEY = ':method'
PATH_KEY = ':path'
SCHEME_KEY = ':scheme'
STATUS_KEY = ':status'

# turn on extra verbose debug logging
#
def self.verbose!
@verbose = true
end

def self.verbose?
@verbose = false unless defined?(@verbose)
@verbose
end

end
end

require 'reel'
require 'reel/h2/connection'
require 'reel/h2/push_promise'
require 'reel/h2/server'
require 'reel/h2/server/https'
require 'reel/h2/stream'
require 'reel/h2/stream/request'
require 'reel/h2/stream/response'
153 changes: 153 additions & 0 deletions lib/reel/h2/connection.rb
@@ -0,0 +1,153 @@
require 'http/2'

module Reel
module H2

# handles reading data from the +@socket+ into the +HTTP2::Server+ +@parser+,
# callbacks from the +@parser+, and closing of the +@socket+
#
class Connection

# each +@parser+ event method is wrapped in a block to call a local instance
# method of the same name
#
PARSER_EVENTS = [
:frame,
:frame_sent,
:frame_received,
:stream,
:goaway
]

attr_reader :parser, :server, :socket

def initialize socket:, server:
@socket = socket
@server = server
@parser = ::HTTP2::Server.new
@attached = true

yield self if block_given?

bind_events

Logger.debug "new H2::Connection: #{self}" if H2.verbose?
end

# is this connection still attached to the server reactor?
#
def attached?
@attached
end

# bind parser events to this instance
#
def bind_events
PARSER_EVENTS.each do |e|
on = "on_#{e}".to_sym
@parser.on(e) { |x| __send__ on, x }
end
end

# closes this connection's socket if attached
#
def close
socket.close if socket && attached?
end

# is this connection's socket closed?
#
def closed?
socket.closed?
end

# prevent this server reactor from handling this connection
#
def detach
@attached = false
self
end

# accessor for stream handler
#
def each_stream &block
@each_stream = block if block_given?
@each_stream
end

# queue a goaway frame
#
def goaway
server.async.goaway self
end

# begins the read loop, handling all errors with a log message,
# backtrace, and closing the +@socket+
#
def read
begin
while attached? && !@socket.closed? && !(@socket.eof? rescue true)
data = @socket.readpartial(4096)
@parser << data
end
close

rescue => e
Logger.error "Exception: #{e.message} - closing socket"
STDERR.puts e.backtrace
close

end
end

protected

# +@parser+ event methods

# called by +@parser+ with a binary frame to write to the +@socket+
#
def on_frame bytes
Logger.debug "Writing bytes: #{truncate_string(bytes.unpack("H*").first)}" if Reel::H2.verbose?

# N.B. this is the important bit
#
@socket.write bytes
rescue IOError, Errno::EPIPE => e
Logger.error e.message
close
end

def on_frame_sent f
Logger.debug "Sent frame: #{truncate_frame(f).inspect}" if Reel::H2.verbose?
end

def on_frame_received f
Logger.debug "Received frame: #{truncate_frame(f).inspect}" if Reel::H2.verbose?
end

# the +@parser+ calls this when a new stream has been initiated by the
# client
#
def on_stream stream
Reel::H2::Stream.new connection: self, stream: stream
end

# the +@parser+ calls this when a goaway frame is received from the client
#
def on_goaway event
close
end

private

def truncate_string s
(String === s && s.length > 64) ? "#{s[0,64]}..." : s
end

def truncate_frame f
f.reduce({}) { |h, (k, v)| h[k] = truncate_string(v); h }
end

end
end
end

0 comments on commit e77c041

Please sign in to comment.