diff --git a/ffi/README.md b/ffi/README.md index bfcb383..aa59f56 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -33,5 +33,5 @@ parser << "GET / HTTP/1.1\r\n\r\n" # Reset the parser for the next request: # -parser.finish +parser.reset ``` diff --git a/ffi/lib/llhttp.rb b/ffi/lib/llhttp.rb index 917272a..236c81e 100644 --- a/ffi/lib/llhttp.rb +++ b/ffi/lib/llhttp.rb @@ -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 diff --git a/ffi/lib/llhttp/parser.rb b/ffi/lib/llhttp/parser.rb index 9b78aaa..bb2b5fb 100644 --- a/ffi/lib/llhttp/parser.rb +++ b/ffi/lib/llhttp/parser.rb @@ -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 diff --git a/mri/README.md b/mri/README.md index e2e196d..a5a58d1 100644 --- a/mri/README.md +++ b/mri/README.md @@ -33,5 +33,5 @@ parser << "GET / HTTP/1.1\r\n\r\n" # Reset the parser for the next request: # -parser.finish +parser.reset ``` diff --git a/mri/ext/llhttp/llhttp_ext.c b/mri/ext/llhttp/llhttp_ext.c index a2cfdc5..9261c65 100644 --- a/mri/ext/llhttp/llhttp_ext.c +++ b/mri/ext/llhttp/llhttp_ext.c @@ -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; @@ -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); diff --git a/mri/lib/llhttp/parser.rb b/mri/lib/llhttp/parser.rb index 549ff6f..949e4e2 100644 --- a/mri/lib/llhttp/parser.rb +++ b/mri/lib/llhttp/parser.rb @@ -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 diff --git a/spec/acceptance/support/server.rb b/spec/acceptance/support/server.rb index bc39d3a..10de381 100644 --- a/spec/acceptance/support/server.rb +++ b/spec/acceptance/support/server.rb @@ -49,7 +49,7 @@ def run ensure stream.close - @parser.finish + @parser.reset end private def parse_next(stream) diff --git a/spec/integration/no_content_length_spec.rb b/spec/integration/no_content_length_spec.rb new file mode 100644 index 0000000..90cccfd --- /dev/null +++ b/spec/integration/no_content_length_spec.rb @@ -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 diff --git a/spec/integration/reusing_spec.rb b/spec/integration/reusing_spec.rb index c4d6561..6c064a4 100644 --- a/spec/integration/reusing_spec.rb +++ b/spec/integration/reusing_spec.rb @@ -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) diff --git a/spec/integration/support/context/parsing.rb b/spec/integration/support/context/parsing.rb index 4c44441..b230a3f 100644 --- a/spec/integration/support/context/parsing.rb +++ b/spec/integration/support/context/parsing.rb @@ -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" ] } }