Skip to content

Instantly share code, notes, and snippets.

@tenderlove
Created June 30, 2015 23:38
Show Gist options
  • Save tenderlove/6eccbaf4ee89838b7944 to your computer and use it in GitHub Desktop.
Save tenderlove/6eccbaf4ee89838b7944 to your computer and use it in GitHub Desktop.
require 'socket'
require 'openssl'
require 'puma/server'
require 'ds9'
class Server < DS9::Server
def initialize socket, app
@app = app
@read_streams = {}
@write_streams = {}
@socket = socket
super()
end
def send_event string
@socket.write_nonblock string
end
def recv_event length
case data = @socket.read_nonblock(length, nil, exception: false)
when :wait_readable then DS9::ERR_WOULDBLOCK
when nil then DS9::ERR_EOF
else
data
end
end
def on_begin_headers frame
@read_streams[frame.stream_id] = []
end
def on_data_source_read stream_id, length
@write_streams[stream_id].body.read length
end
def on_stream_close id, error_code
@read_streams.delete id
@write_streams.delete id
end
def submit_push_promise stream_id, headers, block
response = Response.new(self, super(stream_id, headers), [])
block.call Hash[headers], response
@write_streams[response.stream_id] = response
end
def on_header name, value, frame, flags
@read_streams[frame.stream_id] << [name, value]
end
class Response < Struct.new :stream, :stream_id, :body
def push headers, &block
stream.submit_push_promise stream_id, headers, block
end
def submit_response headers
stream.submit_response stream_id, headers
end
def finish str
self.body = StringIO.new str
end
end
def on_frame_recv frame
return unless frame.headers?
req_headers = @read_streams[frame.stream_id]
response = Response.new(self, frame.stream_id, [])
@app.call Hash[req_headers], response
@write_streams[frame.stream_id] = response
end
def run
while want_read? || want_write?
rd, wr, _ = IO.select([@socket], [@socket])
receive
send
end
end
def self.connect_ssl sock, ctx
ssl_sock = OpenSSL::SSL::SSLSocket.new sock, ctx
ssl_sock.accept
ssl_sock
rescue OpenSSL::SSL::SSLError
ssl_sock
end
end
class Context
STR = "This server only supports HTTP2 requests\n"
def initialize host, port
@ctx = OpenSSL::SSL::SSLContext.new
@ctx.npn_protocols = [DS9::NGHTTP2_PROTO_VERSION_ID]
@ctx.cert = OpenSSL::X509::Certificate.new File.read ARGV[0]
@ctx.key = OpenSSL::PKey::RSA.new File.read ARGV[1]
@authority = ['localhost', port.to_s].join ':'
end
def call _, sock
ssl_sock = Server.connect_ssl sock, @ctx
if ssl_sock.npn_protocol == DS9::NGHTTP2_PROTO_VERSION_ID
session = Server.new ssl_sock, ->(headers, response) {
response.push [[":method", "GET"],
[":path", "/favicon.ico"],
[":scheme", "https"],
[":authority", @authority]] do |req, res|
res.submit_response [[':status', '200'],
["server", 'test server'],
["date", 'Sat, 27 Jun 2015 17:29:21 GMT']]
res.finish File.binread "favicon.ico"
end
response.push [[":method", "GET"],
[":path", "/test.png"],
[":scheme", "https"],
[":authority", @authority]] do |req, res|
res.submit_response [[':status', '200'],
["server", 'test server'],
["date", 'Sat, 27 Jun 2015 17:29:21 GMT']]
res.finish File.binread "test.png"
end
response.submit_response [[':status', '200'],
["server", 'test server'],
["content-type", 'text/html'],
["date", 'Sat, 27 Jun 2015 17:29:21 GMT']]
response.finish "<html><body><img src='/test.png' /></body></html>"
}
session.submit_settings []
session.run
ssl_sock.close
else
ssl_sock.write "HTTP/1.1 505 HTTP Version Not Supported\r\n"
ssl_sock.write "Content-Type: text/plain\r\n"
ssl_sock.write "Content-Length: #{STR.bytesize}\r\n"
ssl_sock.write "Connection: close\r\n"
ssl_sock.write "\r\n"
ssl_sock.write STR
ssl_sock.close
end
end
end
PORT = 3212
HOST = "127.0.0.1"
server = Puma::Server.new Context.new(HOST, PORT)
server.add_tcp_listener HOST, PORT
server.tcp_mode!
server.run
sleep
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment