lib/http-parser/parser.rb



# frozen_string_literal: true

module HttpParser
    class Parser
        CALLBACKS = [
            :on_message_begin, :on_url, :on_status, :on_header_field, :on_header_value,
            :on_headers_complete, :on_body, :on_message_complete, :on_chunk_header, :on_chunk_complete
        ]

        #
        # Returns a new request/response instance variable
        #
        def self.new_instance(&block)
            ::HttpParser::Instance.new(&block)
        end


        #
        # Initializes the Parser instance.
        #
        def initialize(callback_obj = nil)
            @settings = ::HttpParser::Settings.new
            @callbacks = {} # so GC doesn't clean them up on java

            if not callback_obj.nil?
                CALLBACKS.each do |callback|
                    self.__send__(callback, &callback_obj.method(callback)) if callback_obj.respond_to? callback
                end
            end

            yield self if block_given?
        end

        #
        # Registers an `on_message_begin` callback.
        #
        # @yield [instance]
        #   The given block will be called when the HTTP message begins.
        #
        # @yieldparam [HttpParser::Instance] instance
        #   The state so far of the request / response being processed.
        #
        def on_message_begin(&block)
            cb = Callback.new(&block)
            @callbacks[:on_message_begin] = cb
            @settings[:on_message_begin] = cb
        end

        #
        # Registers an `on_url` callback.
        #
        # @yield [instance, url]
        #   The given block will be called when the Request URI is recognized
        #   within the Request-Line.
        #
        # @yieldparam [HttpParser::Instance] instance
        #   The state so far of the request / response being processed.
        #
        # @yieldparam [String] url
        #   The recognized Request URI.
        #
        # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
        #
        def on_url(&block)
            cb = DataCallback.new(&block)
            @callbacks[:on_url] = cb
            @settings[:on_url] = cb
        end

        #
        # Registers an `on_status_complete` callback.
        #
        # @yield [instance]
        #   The given block will be called when the status is recognized.
        #
        # @yieldparam [HttpParser::Instance] instance
        #   The state so far of the request / response being processed.
        #
        def on_status(&block)
            cb = DataCallback.new(&block)
            @callbacks[:on_status] = cb
            @settings[:on_status] = cb
        end

        #
        # Registers an `on_header_field` callback.
        #
        # @yield [instance, field]
        #   The given block will be called when a Header name is recognized
        #   in the Headers.
        #
        # @yieldparam [HttpParser::Instance] instance
        #   The state so far of the request / response being processed.
        #
        # @yieldparam [String] field
        #   A recognized Header name.
        #
        # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.5
        #
        def on_header_field(&block)
            cb = DataCallback.new(&block)
            @callbacks[:on_header_field] = cb
            @settings[:on_header_field] = cb
        end

        #
        # Registers an `on_header_value` callback.
        #
        # @yield [instance, value]
        #   The given block will be called when a Header value is recognized
        #   in the Headers.
        #
        # @yieldparam [HttpParser::Instance] instance
        #   The state so far of the request / response being processed.
        #
        # @yieldparam [String] value
        #   A recognized Header value.
        #
        # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.5
        #
        def on_header_value(&block)
            cb = DataCallback.new(&block)
            @callbacks[:on_header_value] = cb
            @settings[:on_header_value] = cb
        end

        #
        # Registers an `on_headers_complete` callback.
        #
        # @yield [instance]
        #   The given block will be called when the Headers stop.
        #
        # @yieldparam [HttpParser::Instance] instance
        #   The state so far of the request / response being processed.
        #
        def on_headers_complete(&block)
            cb = Callback.new(&block)
            @callbacks[:on_headers_complete] = cb
            @settings[:on_headers_complete] = cb
        end

        #
        # Registers an `on_body` callback.
        #
        # @yield [instance, body]
        #   The given block will be called when the body is recognized in the
        #   message body.
        #
        # @yieldparam [HttpParser::Instance] instance
        #   The state so far of the request / response being processed.
        #
        # @yieldparam [String] body
        #   The full body or a chunk of the body from a chunked
        #   Transfer-Encoded stream.
        #
        # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.5
        #
        def on_body(&block)
            cb = DataCallback.new(&block)
            @callbacks[:on_body] = cb
            @settings[:on_body] = cb
        end

        #
        # Registers an `on_message_begin` callback.
        #
        # @yield [instance]
        #   The given block will be called when the message completes.
        #
        # @yieldparam [HttpParser::Instance] instance
        #   The state so far of the request / response being processed.
        #
        def on_message_complete(&block)
            cb = Callback.new(&block)
            @callbacks[:on_message_complete] = cb
            @settings[:on_message_complete] = cb
        end

        #
        # Registers an `on_chunk_header` callback.
        #
        # @yield [instance]
        #   The given block will be called when a new chunk header is received.
        #
        # @yieldparam [HttpParser::Instance] instance
        #   The state so far of the request / response being processed.
        #
        def on_chunk_header(&block)
            cb = Callback.new(&block)
            @callbacks[:on_message_complete] = cb
            @settings[:on_message_complete] = cb
        end

        #
        # Registers an `on_chunk_complete` callback.
        #
        # @yield [instance]
        #   The given block will be called when the current chunk completes.
        #
        # @yieldparam [HttpParser::Instance] instance
        #   The state so far of the request / response being processed.
        #
        def on_chunk_complete(&block)
            cb = Callback.new(&block)
            @callbacks[:on_message_complete] = cb
            @settings[:on_message_complete] = cb
        end

        #
        # Parses data.
        #
        # @param [HttpParser::Instance] inst
        #   The state so far of the request / response being processed.
        #
        # @param [String] data
        #   The data to parse against the instance specified.
        #
        # @return [Boolean]
        #   Returns true if the data was parsed successfully.
        #
        def parse(inst, data)
            ::HttpParser.http_parser_execute(inst, @settings, data, data.length)
            return inst.error?
        end


        protected


        class Callback < ::FFI::Function
            #
            # Creates a new Parser callback.
            #
            def self.new(&block)
                super(:int, [::HttpParser::Instance.ptr]) do |parser|
                    begin
                        catch(:return) { yield(parser); 0 }
                    rescue
                        -1
                    end
                end
            end
        end

        class DataCallback < ::FFI::Function
            def self.new(&block)
                super(:int, [::HttpParser::Instance.ptr, :pointer, :size_t]) do |parser, buffer, length|
                    begin
                        data = buffer.get_bytes(0, length)
                        catch(:return) { yield(parser, data); 0 }
                    rescue
                        -1
                    end
                end
            end
        end
    end
end