Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for resetting the parser #25

Merged
merged 1 commit into from
Sep 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ffi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ parser << "GET / HTTP/1.1\r\n\r\n"

# Reset the parser for the next request:
#
parser.finish
parser.reset
```
1 change: 1 addition & 0 deletions ffi/lib/llhttp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ class Callbacks < FFI::Struct
attach_function :llhttp_get_error_reason, [:pointer], :string
attach_function :llhttp_should_keep_alive, [:pointer], :int
attach_function :llhttp_finish, [:pointer], :int
attach_function :llhttp_reset, [:pointer], :void
end
8 changes: 7 additions & 1 deletion ffi/lib/llhttp/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,18 @@ def keep_alive?
LLHttp.llhttp_should_keep_alive(@pointer) == 1
end

# [public] Get ready to parse the next request.
# [public] Tells the parser we are finished.
#
def finish
LLHttp.llhttp_finish(@pointer)
end

# [public] Get ready to parse the next request/response.
#
def reset
LLHttp.llhttp_reset(@pointer)
end

CALLBACKS.each do |callback|
class_eval(
<<~RB, __FILE__, __LINE__ + 1
Expand Down
2 changes: 1 addition & 1 deletion mri/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ parser << "GET / HTTP/1.1\r\n\r\n"

# Reset the parser for the next request:
#
parser.finish
parser.reset
```
13 changes: 13 additions & 0 deletions mri/ext/llhttp/llhttp_ext.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,18 @@ VALUE rb_llhttp_finish(VALUE self) {
return Qtrue;
}

VALUE rb_llhttp_reset(VALUE self) {
llhttp_t *parser;

Data_Get_Struct(self, llhttp_t, parser);

llhttp_settings_t *settings = parser->settings;

llhttp_reset(parser);

return Qtrue;
}

VALUE rb_llhttp_content_length(VALUE self) {
llhttp_t *parser;

Expand Down Expand Up @@ -301,6 +313,7 @@ void Init_llhttp_ext(void) {
rb_define_method(cParser, "<<", rb_llhttp_parse, 1);
rb_define_method(cParser, "parse", rb_llhttp_parse, 1);
rb_define_method(cParser, "finish", rb_llhttp_finish, 0);
rb_define_method(cParser, "reset", rb_llhttp_reset, 0);

rb_define_method(cParser, "content_length", rb_llhttp_content_length, 0);
rb_define_method(cParser, "method_name", rb_llhttp_method_name, 0);
Expand Down
4 changes: 4 additions & 0 deletions mri/lib/llhttp/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ module LLHttp
#
# Call `LLHttp::Parser#finish` when processing is complete for the current request or response.
#
# Resetting
#
# Call `LLHttp::Parser#reset` to reset the parser for the next request or response.
#
class Parser
LLHTTP_TYPES = {both: 0, request: 1, response: 2}.freeze

Expand Down
2 changes: 1 addition & 1 deletion spec/acceptance/support/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def run
ensure
stream.close

@parser.finish
@parser.reset
end

private def parse_next(stream)
Expand Down
64 changes: 64 additions & 0 deletions spec/integration/no_content_length_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

require_relative "support/context/parsing"

RSpec.describe "parsing responses without a content length" do
include_context "parsing"

shared_examples "examples" do
let(:extension) {
proc {
def on_message_begin
@calls << :on_message_begin
end

def on_header_field(_)
@calls << :on_header_field
end

def on_header_value(_)
@calls << :on_header_value
end

def on_headers_complete
@calls << :on_headers_complete
end

def on_body(_)
@calls << :on_body
end

def on_message_complete
@calls << :on_message_complete
end
}
}

it "parses correctly" do
10_000.times do
parse

instance.reset
end

expect(delegate.calls.count(:on_message_begin)).to eq(10_000)
expect(delegate.calls.count(:on_header_field)).to eq(0)
expect(delegate.calls.count(:on_header_value)).to eq(0)
expect(delegate.calls.count(:on_headers_complete)).to eq(10_000)
expect(delegate.calls.count(:on_body)).to eq(0)
expect(delegate.calls.count(:on_message_complete)).to eq(0)
end
end

context "response" do
let(:type) {
:response
}

let(:fixture) {
:response_sans_content_length
}

include_examples "examples"
end
end
2 changes: 1 addition & 1 deletion spec/integration/reusing_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def on_message_complete
10_000.times do
parse

instance.finish
instance.reset
end

expect(delegate.calls.count(:on_message_begin)).to eq(10_000)
Expand Down
4 changes: 4 additions & 0 deletions spec/integration/support/context/parsing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ def initialize(context)
"body2\n",
"body3\n",
"\r\n"
],

response_sans_content_length: [
"HTTP/1.1 200 OK\r\n\r\n"
]
}
}
Expand Down