Last active
August 14, 2024 06:09
-
-
Save tenderlove/79e7e0de4b2097e43356 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 'webrick' | |
require 'webrick/https' | |
require 'ds9' | |
class HTTP2Server < WEBrick::HTTPServer | |
SETTINGS = [ [DS9::Settings::MAX_CONCURRENT_STREAMS, 100] ] | |
class HTTP2Response < WEBrick::HTTPResponse | |
def initialize config, ctx, stream_id | |
@ctx = ctx | |
@stream_id = stream_id | |
super(config) | |
end | |
def send_header socket | |
@header.delete 'connection' | |
headers = [[':status', @status.to_s]] + @header.map { |key, value| | |
[key.downcase, value.to_s] | |
} + @cookies.map { |cookie| ['set-cookie', cookie.to_s] } | |
@ctx.submit_response @stream_id, headers | |
end | |
end | |
class HTTP2Request < WEBrick::HTTPRequest | |
def parse socket, headers | |
@socket = socket | |
begin | |
@peeraddr = socket.respond_to?(:peeraddr) ? socket.peeraddr : [] | |
@addr = socket.respond_to?(:addr) ? socket.addr : [] | |
rescue Errno::ENOTCONN | |
raise HTTPStatus::EOFError | |
end | |
@request_time = Time.now | |
@request_method = headers[':method'] | |
@unparsed_uri = headers[':path'] | |
@http_version = WEBrick::HTTPVersion.new '2.0' | |
@request_line = "#{headers[':method']} #{headers[':path']} HTTP/2.0" | |
@request_uri = URI.parse "#{headers[':scheme']}://#{headers[':authority']}#{headers[':path']}" | |
@header = headers.each_with_object(Hash.new([].freeze)) do |(k,v), h| | |
h[k] = [v] | |
end | |
@header['cookie'].each{|cookie| | |
@cookies += WEBrick::Cookie::parse(cookie) | |
} | |
@accept = WEBrick::HTTPUtils.parse_qvalues(self['accept']) | |
@accept_charset = WEBrick::HTTPUtils.parse_qvalues(self['accept-charset']) | |
@accept_encoding = WEBrick::HTTPUtils.parse_qvalues(self['accept-encoding']) | |
@accept_language = WEBrick::HTTPUtils.parse_qvalues(self['accept-language']) | |
return if @request_method == "CONNECT" | |
return if @unparsed_uri == "*" | |
begin | |
setup_forwarded_info | |
@path = WEBrick::HTTPUtils::unescape(@request_uri.path) | |
@path = WEBrick::HTTPUtils::normalize_path(@path) | |
@host = @request_uri.host | |
@port = @request_uri.port | |
@query_string = @request_uri.query | |
@script_name = "" | |
@path_info = @path.dup | |
rescue | |
raise WEBrick::HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'." | |
end | |
@keep_alive = true | |
end | |
end | |
class MySession < DS9::Server | |
def initialize sock, server | |
super() | |
@sock = sock | |
@config = server.config | |
@server = server | |
@write_streams = {} | |
@read_streams = {} | |
end | |
def on_data_source_read stream_id, length | |
@write_streams[stream_id].read(length) | |
end | |
def on_stream_close id, error_code | |
@write_streams.delete id | |
@read_streams.delete id | |
end | |
def on_begin_headers frame | |
@read_streams[frame.stream_id] = {} | |
end | |
def on_header name, value, frame, flags | |
@read_streams[frame.stream_id][name] = value | |
end | |
def on_frame_recv frame | |
return unless frame.headers? | |
rd, wr = IO.pipe | |
@write_streams[frame.stream_id] = rd | |
res = HTTP2Response.new(@config, self, frame.stream_id) | |
req = HTTP2Request.new(@config) | |
req.parse @sock, @read_streams[frame.stream_id] | |
res.request_method = req.request_method | |
res.request_uri = req.request_uri | |
res.request_http_version = req.http_version | |
res.keep_alive = false | |
begin | |
@server.service req, res | |
rescue WEBrick::HTTPStatus::EOFError, WEBrick::HTTPStatus::RequestTimeout => ex | |
res.set_error(ex) | |
rescue WEBrick::HTTPStatus::Error => ex | |
@server.logger.error(ex.message) | |
res.set_error(ex) | |
rescue WEBrick::HTTPStatus::Status => ex | |
res.status = ex.code | |
rescue StandardError => ex | |
@server.logger.error(ex) | |
res.set_error(ex, true) | |
ensure | |
res.send_response(wr) | |
wr.close | |
@server.access_log(@config, req, res) | |
end | |
true | |
end | |
def send_event string | |
@sock.write string | |
end | |
def recv_event length | |
return '' unless want_read? || want_write? | |
case data = @sock.read_nonblock(length, nil, exception: false) | |
when :wait_readable | |
DS9::ERR_WOULDBLOCK | |
when nil | |
DS9::ERR_EOF | |
else | |
data | |
end | |
end | |
def run | |
while want_read? || want_write? | |
if want_read? | |
@sock.to_io.wait_readable | |
begin | |
return if @sock.eof? | |
rescue OpenSSL::SSL::SSLError | |
return | |
end | |
receive | |
end | |
if want_write? | |
@sock.to_io.wait_writable | |
send | |
end | |
end | |
end | |
end | |
def run socket | |
return super unless socket.npn_protocol == 'h2' | |
session = MySession.new socket, self | |
session.submit_settings SETTINGS | |
session.run | |
end | |
def setup_ssl_context config | |
ctx = super | |
ctx.ssl_version = "SSLv23_server" | |
ctx.npn_protocols = [DS9::PROTO_VERSION_ID] | |
ctx.tmp_ecdh_callback = ->(ssl, export, len) { PKEY } | |
ctx | |
end | |
end | |
PKEY = OpenSSL::PKey::EC.new "prime256v1" | |
CERT, KEY = WEBrick::Utils.create_self_signed_cert(2048, | |
[['CN', 'localhost']], | |
'such secure!') | |
server = HTTP2Server.new SSLEnable: true, | |
SSLCertificate: CERT, | |
SSLPrivateKey: KEY, | |
Port: 8080 | |
server.mount_proc('/hi-mom') do |req, res| | |
res.body = 'HI MOM!!!' | |
end | |
server.start |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment