aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2015-10-20 00:22:18 +0900
committerKazuki Yamaguchi <k@rhe.jp>2015-10-20 00:22:18 +0900
commitae271d028eac9664f0820ff28a433e89b789af74 (patch)
treef1a95e1d1560218883b1a0003f5c679a85473d9b
parent99c357d13edb3f2a08ad719f27a09e6ddfd643b2 (diff)
downloadplum-ae271d028eac9664f0820ff28a433e89b789af74.tar.gz
add Rack adapter
-rw-r--r--examples/sinatra.rb15
-rw-r--r--lib/plum/rack_server.rb163
-rw-r--r--lib/rack/handler/plum.rb41
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