diff options
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | lib/plum/connection.rb | 2 | ||||
-rw-r--r-- | lib/plum/connection_utils.rb | 3 | ||||
-rw-r--r-- | lib/plum/event_emitter.rb | 4 | ||||
-rw-r--r-- | lib/plum/flow_control.rb | 2 | ||||
-rw-r--r-- | lib/plum/frame.rb | 9 | ||||
-rw-r--r-- | lib/plum/frame_factory.rb | 34 | ||||
-rw-r--r-- | lib/plum/frame_utils.rb | 3 | ||||
-rw-r--r-- | lib/plum/http_connection.rb | 1 | ||||
-rw-r--r-- | lib/plum/https_connection.rb | 1 | ||||
-rw-r--r-- | lib/plum/rack/cli.rb | 5 | ||||
-rw-r--r-- | lib/plum/rack/server.rb | 15 | ||||
-rw-r--r-- | lib/plum/stream.rb | 1 | ||||
-rw-r--r-- | lib/plum/stream_utils.rb | 4 |
14 files changed, 61 insertions, 27 deletions
@@ -16,6 +16,7 @@ See examples in `examples/` ### As a Rack-compatible server Insert `require "plum/rack"` to your `config.ru` + ```ruby require "plum/rack" @@ -29,10 +30,13 @@ App = -> env { run App ``` + Then run it with: + ```sh % plum -e production -p 8080 --https config.ru ``` + By default, Plum generates a dummy server certificate if `--cert` and `--key` options are not specified. ## Examples diff --git a/lib/plum/connection.rb b/lib/plum/connection.rb index 507ef75..bfd6890 100644 --- a/lib/plum/connection.rb +++ b/lib/plum/connection.rb @@ -44,7 +44,6 @@ module Plum end # Receives the specified data and process. - # # @param new_data [String] The data received from the peer. def receive(new_data) return if new_data.empty? @@ -66,7 +65,6 @@ module Plum alias << receive # Reserves a new stream to server push. - # # @param args [Hash] The argument to pass to Stram.new. def reserve_stream(**args) next_id = @max_even_stream_id + 2 diff --git a/lib/plum/connection_utils.rb b/lib/plum/connection_utils.rb index 1f837dd..8ab90e1 100644 --- a/lib/plum/connection_utils.rb +++ b/lib/plum/connection_utils.rb @@ -3,7 +3,6 @@ using Plum::BinaryString module Plum module ConnectionUtils # Sends local settings to the peer. - # # @param kwargs [Hash<Symbol, Integer>] def settings(**kwargs) send_immediately Frame.settings(**kwargs) @@ -11,7 +10,6 @@ module Plum end # Sends a PING frame to the peer. - # # @param data [String] Must be 8 octets. # @raise [ArgumentError] If the data is not 8 octets. def ping(data = "plum\x00\x00\x00\x00") @@ -19,7 +17,6 @@ module Plum end # Sends GOAWAY frame to the peer and closes the connection. - # # @param error_type [Symbol] The error type to be contained in the GOAWAY frame. def goaway(error_type = :no_error) last_id = @max_odd_stream_id > @max_even_stream_id ? @max_odd_stream_id : @max_even_stream_id diff --git a/lib/plum/event_emitter.rb b/lib/plum/event_emitter.rb index 90484fc..572900e 100644 --- a/lib/plum/event_emitter.rb +++ b/lib/plum/event_emitter.rb @@ -1,12 +1,14 @@ module Plum module EventEmitter # Registers an event handler to specified event. An event can have multiple handlers. - # @param name [String] The name of event. + # @param name [Symbol] The name of event. # @yield Gives event-specific parameters. def on(name, &blk) (callbacks[name] ||= []) << blk end + # Invokes an event and call handlers with args. + # @param name [Symbol] The identifier of event. def callback(name, *args) (cbs = callbacks[name]) && cbs.each {|cb| cb.call(*args) } end diff --git a/lib/plum/flow_control.rb b/lib/plum/flow_control.rb index 251507d..2a93341 100644 --- a/lib/plum/flow_control.rb +++ b/lib/plum/flow_control.rb @@ -5,7 +5,6 @@ module Plum attr_reader :send_remaining_window, :recv_remaining_window # Sends frame respecting inner-stream flow control. - # # @param frame [Frame] The frame to be sent. def send(frame) if frame.type == :data @@ -25,7 +24,6 @@ module Plum end # Increases receiving window size. Sends WINDOW_UPDATE frame to the peer. - # # @param wsi [Integer] The amount to increase receiving window size. The legal range is 1 to 2^32-1. def window_update(wsi) @recv_remaining_window += wsi diff --git a/lib/plum/frame.rb b/lib/plum/frame.rb index 72fc2b9..981bf55 100644 --- a/lib/plum/frame.rb +++ b/lib/plum/frame.rb @@ -17,6 +17,8 @@ module Plum window_update: 0x08, continuation: 0x09 }.freeze + + # @!visibility private FRAME_TYPES_INVERSE = FRAME_TYPES.invert.freeze FRAME_FLAGS = { @@ -49,6 +51,7 @@ module Plum }.freeze }.freeze + # @!visibility private FRAME_FLAGS_MAP = FRAME_FLAGS.values.inject(:merge).freeze SETTINGS_TYPE = { @@ -75,9 +78,9 @@ module Plum attr_accessor :type_value # [Integer] Flags. 8-bit attr_accessor :flags_value - # [Integer] Stream Identifier. unsigned 31-bit integer + # [Integer] Stream Identifier. Unsigned 31-bit integer attr_reader :stream_id - # [String] The payload. + # [String] The payload. Value is frozen. attr_reader :payload def initialize(type: nil, type_value: nil, flags: nil, flags_value: nil, stream_id: nil, payload: nil) @@ -126,7 +129,6 @@ module Plum end # Frame#flag_name?() == Frame#flags().include?(:flag_name) - # TODO FRAME_FLAGS_MAP.each { |name, value| class_eval <<-EOS, __FILE__, __LINE__ + 1 def #{name}? @@ -150,7 +152,6 @@ module Plum end # Parses a frame from given buffer. It changes given buffer. - # # @param buffer [String] The buffer stored the data received from peer. Encoding must be Encoding::BINARY. # @return [Frame, nil] The parsed frame or nil if the buffer is imcomplete. def self.parse!(buffer) diff --git a/lib/plum/frame_factory.rb b/lib/plum/frame_factory.rb index 3a9fa97..686f88e 100644 --- a/lib/plum/frame_factory.rb +++ b/lib/plum/frame_factory.rb @@ -2,11 +2,19 @@ using Plum::BinaryString module Plum module FrameFactory + # Creates a RST_STREAM frame. + # @param stream_id [Integer] The stream ID. + # @param error_type [Symbol] The error type defined in RFC 7540 Section 7. def rst_stream(stream_id, error_type) payload = "".push_uint32(HTTPError::ERROR_CODES[error_type]) Frame.new(type: :rst_stream, stream_id: stream_id, payload: payload) end + # Creates a GOAWAY frame. + # @param last_id [Integer] The biggest processed stream ID. + # @param error_type [Symbol] The error type defined in RFC 7540 Section 7. + # @param message [String] Additional debug data. + # @see RFC 7540 Section 6.8 def goaway(last_id, error_type, message = "") payload = "".push_uint32((last_id || 0) | (0 << 31)) .push_uint32(HTTPError::ERROR_CODES[error_type]) @@ -14,6 +22,9 @@ module Plum Frame.new(type: :goaway, stream_id: 0, payload: payload) end + # Creates a SETTINGS frame. + # @param ack [Symbol] Pass :ack to create an ACK frame. + # @param args [Hash<Symbol, Integer>] The settings values to send. def settings(ack = nil, **args) payload = args.inject("") {|payload, (key, value)| id = Frame::SETTINGS_TYPE[key] or raise ArgumentError.new("invalid settings type") @@ -23,6 +34,12 @@ module Plum Frame.new(type: :settings, stream_id: 0, flags: [ack], payload: payload) end + # Creates a PING frame. + # @overload ping(ack, payload) + # @param ack [Symbol] Pass :ack to create an ACK frame. + # @param payload [String] 8 bytes length data to send. + # @overload ping(payload = "plum\x00\x00\x00\x00") + # @param payload [String] 8 bytes length data to send. def ping(arg1 = "plum\x00\x00\x00\x00", arg2 = nil) if !arg2 raise ArgumentError.new("data must be 8 octets") if arg1.bytesize != 8 @@ -32,20 +49,37 @@ module Plum end end + # Creates a DATA frame. + # @param stream_id [Integer] The stream ID. + # @param payload [String] Payload. + # @param flags [Array<Symbol>] Flags. def data(stream_id, payload, *flags) Frame.new(type: :data, stream_id: stream_id, flags: flags, payload: payload) end + # Creates a DATA frame. + # @param stream_id [Integer] The stream ID. + # @param encoded [String] Headers. + # @param flags [Array<Symbol>] Flags. def headers(stream_id, encoded, *flags) Frame.new(type: :headers, stream_id: stream_id, flags: flags, payload: encoded) end + # Creates a PUSH_PROMISE frame. + # @param stream_id [Integer] The stream ID. + # @param new_id [Integer] The stream ID to create. + # @param encoded [String] Request headers. + # @param flags [Array<Symbol>] Flags. def push_promise(stream_id, new_id, encoded, *flags) payload = "".push_uint32(0 << 31 | new_id) .push(encoded) Frame.new(type: :push_promise, stream_id: stream_id, flags: flags, payload: payload) end + # Creates a CONTINUATION frame. + # @param stream_id [Integer] The stream ID. + # @param payload [String] Payload. + # @param flags [Array<Symbol>] Flags. def continuation(stream_id, payload, *flags) Frame.new(type: :continuation, stream_id: stream_id, flags: flags, payload: payload) end diff --git a/lib/plum/frame_utils.rb b/lib/plum/frame_utils.rb index 69ae3d2..e52d444 100644 --- a/lib/plum/frame_utils.rb +++ b/lib/plum/frame_utils.rb @@ -3,7 +3,6 @@ using Plum::BinaryString module Plum module FrameUtils # Splits the DATA frame into multiple frames if the payload size exceeds max size. - # # @param max [Integer] The maximum size of a frame payload. # @return [Array<Frame>] The splitted frames. def split_data(max) @@ -18,7 +17,6 @@ module Plum end # Splits the HEADERS or PUSH_PROMISE frame into multiple frames if the payload size exceeds max size. - # # @param max [Integer] The maximum size of a frame payload. # @return [Array<Frame>] The splitted frames. def split_headers(max) @@ -34,7 +32,6 @@ module Plum end # Parses SETTINGS frame payload. Ignores unknown settings type (see RFC7540 6.5.2). - # # @return [Hash<Symbol, Integer>] The parsed strings. def parse_settings settings = {} diff --git a/lib/plum/http_connection.rb b/lib/plum/http_connection.rb index 1ec1a6a..9af246d 100644 --- a/lib/plum/http_connection.rb +++ b/lib/plum/http_connection.rb @@ -20,6 +20,7 @@ module Plum end end + # Closes the socket. def close super @sock.close diff --git a/lib/plum/https_connection.rb b/lib/plum/https_connection.rb index 5f1cc14..d7d1819 100644 --- a/lib/plum/https_connection.rb +++ b/lib/plum/https_connection.rb @@ -22,6 +22,7 @@ module Plum end end + # Closes the socket. def close super @sock.close diff --git a/lib/plum/rack/cli.rb b/lib/plum/rack/cli.rb index 967764f..bc5a767 100644 --- a/lib/plum/rack/cli.rb +++ b/lib/plum/rack/cli.rb @@ -3,7 +3,11 @@ require "rack/builder" module Plum module Rack + # CLI runner. Parses command line options and start ::Plum::Rack::Server. class CLI + # Creates new CLI runner and parses command line. + # + # @param argv [Array<String>] ARGV def initialize(argv) @argv = argv @options = {} @@ -11,6 +15,7 @@ module Plum parse! end + # Starts ::Plum::Rack::Server def run @server.start end diff --git a/lib/plum/rack/server.rb b/lib/plum/rack/server.rb index a353db7..608bf18 100644 --- a/lib/plum/rack/server.rb +++ b/lib/plum/rack/server.rb @@ -27,7 +27,7 @@ module Plum end rescue Errno::EBADF, Errno::ENOTSOCK, IOError => e # closed rescue StandardError => e - @logger.error("#{e.class}: #{e.message}\n#{e.backtrace.map { |b| "\t#{b}" }.join("\n")}") + log_exception(e) end end end @@ -46,20 +46,21 @@ module Plum sock = sock.accept if sock.respond_to?(:accept) plum = svr.plum(sock) - #require "lineprof" - #Lineprof.profile(/plum/) { - con = Connection.new(@app, plum, @logger) - con.run - #} + con = Connection.new(@app, plum, @logger) + con.run rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINVAL => e # closed sock.close if sock rescue StandardError => e - @logger.error("#{e.class}: #{e.message}\n#{e.backtrace.map { |b| "\t#{b}" }.join("\n")}") + log_exception(e) sock.close if sock end } rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINVAL => e # closed rescue StandardError => e + log_exception(e) + end + + def log_exception(e) @logger.error("#{e.class}: #{e.message}\n#{e.backtrace.map { |b| "\t#{b}" }.join("\n")}") end end diff --git a/lib/plum/stream.rb b/lib/plum/stream.rb index 632751e..45bd613 100644 --- a/lib/plum/stream.rb +++ b/lib/plum/stream.rb @@ -54,7 +54,6 @@ module Plum end # Closes this stream. Sends RST_STREAM frame to the peer. - # # @param error_type [Symbol] The error type to be contained in the RST_STREAM frame. def close(error_type = :no_error) @state = :closed diff --git a/lib/plum/stream_utils.rb b/lib/plum/stream_utils.rb index 44ed945..eede307 100644 --- a/lib/plum/stream_utils.rb +++ b/lib/plum/stream_utils.rb @@ -3,7 +3,6 @@ using Plum::BinaryString module Plum module StreamUtils # Responds to a HTTP request. - # # @param headers [Enumerable<String, String>] The response headers. # @param body [String, IO] The response body. def respond(headers, body = nil, end_stream: true) # TODO: priority, padding @@ -16,7 +15,6 @@ module Plum end # Reserves a stream to server push. Sends PUSH_PROMISE and create new stream. - # # @param headers [Enumerable<String, String>] The *request* headers. It must contain all of them: ':authority', ':method', ':scheme' and ':path'. # @return [Stream] The stream to send push response. def promise(headers) @@ -30,7 +28,6 @@ module Plum end # Sends response headers. If the encoded frame is larger than MAX_FRAME_SIZE, the headers will be splitted into HEADERS frame and CONTINUATION frame(s). - # # @param headers [Enumerable<String, String>] The response headers. # @param end_stream [Boolean] Set END_STREAM flag or not. def send_headers(headers, end_stream:) @@ -44,7 +41,6 @@ module Plum end # Sends DATA frame. If the data is larger than MAX_FRAME_SIZE, DATA frame will be splitted. - # # @param data [String, IO] The data to send. # @param end_stream [Boolean] Set END_STREAM flag or not. def send_data(data, end_stream: true) |