Created
June 30, 2015 23:38
-
-
Save tenderlove/6eccbaf4ee89838b7944 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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