diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2015-10-20 00:22:18 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2015-10-20 00:22:18 +0900 |
commit | ae271d028eac9664f0820ff28a433e89b789af74 (patch) | |
tree | f1a95e1d1560218883b1a0003f5c679a85473d9b | |
parent | 99c357d13edb3f2a08ad719f27a09e6ddfd643b2 (diff) | |
download | plum-ae271d028eac9664f0820ff28a433e89b789af74.tar.gz |
add Rack adapter
-rw-r--r-- | examples/sinatra.rb | 15 | ||||
-rw-r--r-- | lib/plum/rack_server.rb | 163 | ||||
-rw-r--r-- | lib/rack/handler/plum.rb | 41 |
3 files changed, 219 insertions, 0 deletions
diff --git a/examples/sinatra.rb b/examples/sinatra.rb new file mode 100644 index 0000000..1c03472 --- /dev/null +++ b/examples/sinatra.rb @@ -0,0 +1,15 @@ +$LOAD_PATH << File.expand_path("../../lib", __FILE__) +require "rack/handler/plum" +require "sinatra" + +set :server, :plum +enable :logging, :dump_errors, :raise_errors + +get "/" do + p request + "get: #{params}" +end + +post "/" do + "post: " + params.to_s +end diff --git a/lib/plum/rack_server.rb b/lib/plum/rack_server.rb new file mode 100644 index 0000000..5342019 --- /dev/null +++ b/lib/plum/rack_server.rb @@ -0,0 +1,163 @@ +require "stringio" +require "rack" +require "plum" + +module Plum + class RackServer + def initialize(app, options) + @app = app + @host = options[:Host].to_s + @port = Integer(options[:Port]) + @debug = !!options[:Debug] + @estream = $stderr + @ostream = $stdout + end + + def error(s) + if s.is_a?(Exception) + @estream.puts s + @estream.puts s.backtrace + else + @estream.puts s + end + end + + def debug(s) + if @debug + @ostream.puts s + end + end + + def start + @tcp_server = ::TCPServer.new(@host, @port) + + while @tcp_server && !@tcp_server.closed? + begin + sock = @tcp_server.accept + id = sock.fileno + debug("#{id}: accept!") + rescue => e + error(e) + next + end + + plum = Plum::HTTPConnection.new(sock) + plum.on(:connection_error) { |ex| error(ex) } + + plum.on(:stream) do |stream| + stream.on(:stream_error) { |ex| error(ex) } + + headers = data = nil + stream.on(:open) { + headers = nil + data = "".b + } + + stream.on(:headers) { |h| + debug("headers: " + h.map {|name, value| "#{name}: #{value}" }.join(" // ")) + headers = h + } + + stream.on(:data) { |d| + debug("data: #{d.bytesize}") + data << d + } + + stream.on(:end_stream) { + env = new_env(headers, data) + r_headers, r_body = new_resp(@app.call(env)) + + if r_body.is_a?(Rack::BodyProxy) + stream.respond(r_headers, end_stream: false) + r_body.each { |part| + stream.send_data(part, end_stream: false) + } + stream.send_data(nil) + else + stream.respond(r_headers, r_body) + end + } + end + + Thread.new { + begin + plum.run + rescue + p $! + puts $!.backtrace + end + } + end + end + + def new_env(h, data) + headers = h.group_by { |k, v| k }.map { |k, kvs| + if k == "cookie" + [k, kvs.map(&:last).join("; ")] + else + [k, kvs.first.last] + end + }.to_h + + cmethod = headers.delete(":method") + cpath = headers.delete(":path") + cpath_name, cpath_query = cpath.split("?", 2).map(&:to_s) + cauthority = headers.delete(":authority") # is not empty + cscheme = headers.delete(":scheme") + ebase = { + "REQUEST_METHOD" => cmethod, + "SCRIPT_NAME" => cpath_name == "/" ? "" : cpath_name, + "PATH_INFO" => cpath, + "QUERY_STRING" => cpath_query, + "SERVER_NAME" => cauthority.split(":").first, + "SERVER_POST" => @port.to_s, + } + + headers.each {|key, value| + ebase["HTTP_" + key.gsub("-", "_").upcase] = value + } + + ebase.merge!({ + "rack.version" => Rack::VERSION, + "rack.url_scheme" => cscheme, + "rack.input" => StringIO.new(data), + "rack.errors" => @estream, + "rack.multithread" => true, + "rack.multiprocess" => false, + "rack.run_once" => false, + "rack.hijack?" => false, + }) + + ebase + end + + def new_resp(app_call) + r_status, r_h, r_body = app_call + + rbase = { + ":status" => r_status, + "server" => "plum/#{Plum::VERSION}", + } + + r_h.each do |key, v_| + if key.start_with?("rack.") + next + end + + key = key.downcase.gsub(/^x-/, "") + vs = v_.split("\n") + if key == "set-cookie" + rbase[key] = vs.join("; ") # RFC 7540 8.1.2.5 + else + rbase[key] = vs.join(",") # RFC 7230 7 + end + end + + [rbase, r_body] + end + + def stop + @tcp_server.close if @tcp_server + end + end +end diff --git a/lib/rack/handler/plum.rb b/lib/rack/handler/plum.rb new file mode 100644 index 0000000..c131269 --- /dev/null +++ b/lib/rack/handler/plum.rb @@ -0,0 +1,41 @@ +require "rack/handler" +require "plum/rack_server" + +module Rack + module Handler + class Plum + def self.run(app, options = {}) + opts = default_options.merge(options) + + puts "Starting Plum::RackServer" + puts "* Plum HTTP/2 server (#{::Plum::VERSION})" + puts "* Debug mode: on" if opts[:Debug] + puts "* Listening on #{opts[:Host]}:#{opts[:Port]}" + + @server = ::Plum::RackServer.new(app, opts) + yield @server if block_given? + @server.start + end + + def self.valid_options + { + "Host=HOST" => "Hostname to listen on (default: #{default_options[:Host]})", + "Port=PORT" => "Port to listen on (default: #{default_options[:Port]})", + "Debug" => "Turn on debug mode (default: #{default_options[:Verbose]})", + } + end + + private + def self.default_options + rack_env = ENV["RACK_ENV"] || "development" + default_options = { + Host: rack_env == "development" ? "localhost" : "0.0.0.0", + Port: 8080, + Debug: false, + } + end + end + + register(:plum, ::Rack::Handler::Plum) + end +end |