diff --git a/http.gemspec b/http.gemspec index 77b240db..80e5d2f3 100644 --- a/http.gemspec +++ b/http.gemspec @@ -27,7 +27,7 @@ Gem::Specification.new do |gem| gem.required_ruby_version = ">= 2.3" - gem.add_runtime_dependency "http_parser.rb", "~> 0.6.0" + gem.add_runtime_dependency "http-parser", "~> 1.2.0" gem.add_runtime_dependency "http-form_data", "~> 2.0" gem.add_runtime_dependency "http-cookie", "~> 1.0" gem.add_runtime_dependency "addressable", "~> 2.3" diff --git a/lib/http.rb b/lib/http.rb index 9b8ec5b4..afc57fe6 100644 --- a/lib/http.rb +++ b/lib/http.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "http/parser" - require "http/errors" require "http/timeout/null" require "http/timeout/per_operation" diff --git a/lib/http/response/parser.rb b/lib/http/response/parser.rb index 1dbe3afe..19989d59 100644 --- a/lib/http/response/parser.rb +++ b/lib/http/response/parser.rb @@ -1,41 +1,66 @@ # frozen_string_literal: true +require "http-parser" + module HTTP class Response + # @api private + # + # NOTE(ixti): This class is a subject of future refactoring, thus don't + # expect this class API to be stable until this message disappears and + # class is not marked as private anymore. class Parser attr_reader :headers def initialize - @parser = HTTP::Parser.new(self) + @state = HttpParser::Parser.new_instance { |i| i.type = :response } + @parser = HttpParser::Parser.new(self) + reset end + # @return [self] def add(data) - @parser << data + # XXX(ixti): API doc of HttpParser::Parser is misleading, it says that + # it returns boolean true if data was parsed successfully, but instead + # it's response tells if there was an error; So when it's `true` that + # means parse failed, and `false` means parse was successful. + # case of success. + return self unless @parser.parse(@state, data) + + raise IOError, "Could not parse data" end alias << add def headers? - !!@headers + @finished[:headers] end def http_version - @parser.http_version.join(".") + @state.http_version end def status_code - @parser.status_code + @state.http_status end # # HTTP::Parser callbacks # - def on_headers_complete(headers) - @headers = headers + def on_header_field(_response, field) + @field = field + end + + def on_header_value(_response, value) + @headers.add(@field, value) if @field + end + + def on_headers_complete(_reposse) + @finished[:headers] = true end - def on_body(chunk) + def on_body(_response, chunk) if @chunk @chunk << chunk else @@ -57,20 +82,21 @@ def read(size) chunk end - def on_message_complete - @finished = true + def on_message_complete(_response) + @finished[:message] = true end def reset - @parser.reset! + @state.reset! - @finished = false - @headers = nil + @finished = Hash.new(false) + @headers = HTTP::Headers.new + @field = nil @chunk = nil end def finished? - @finished + @finished[:message] end end end diff --git a/spec/regression_specs.rb b/spec/regression_specs.rb index eae2b5a6..67fd46a2 100644 --- a/spec/regression_specs.rb +++ b/spec/regression_specs.rb @@ -14,4 +14,11 @@ expect { HTTP.get(google_uri).to_s }.not_to raise_error end end + + describe "#422" do + it "reads body when 200 OK response contains Upgrade header" do + res = HTTP.get("https://httpbin.org/response-headers?Upgrade=h2,h2c") + expect(res.parse(:json)).to include("Upgrade" => "h2,h2c") + end + end end