From 67e4efff19c85059d70fb5d2949eed758a2ba0d0 Mon Sep 17 00:00:00 2001 From: Alexey Zapparov Date: Sun, 1 Oct 2017 19:37:14 +0200 Subject: [PATCH 1/3] Add regression spec that highlights the problem --- spec/regression_specs.rb | 7 +++++++ 1 file changed, 7 insertions(+) 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 From fa6fcee4a815369b74c82162230bfcee77ecf8c6 Mon Sep 17 00:00:00 2001 From: Alexey Zapparov Date: Sun, 1 Oct 2017 19:41:09 +0200 Subject: [PATCH 2/3] Use ixti's http_parser.rb repo I have updated nodejs/http-parser dependency there. But the problem is that JRuby most likely still affected :(( --- Gemfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 4cff89f5..be26c3ae 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,8 @@ ruby RUBY_VERSION gem "rake" +gem "http_parser.rb", :git => "https://github.com/ixti/http_parser.rb.git" + group :development do gem "guard-rspec", :require => false gem "nokogiri", :require => false From 92fbe47dc2bbd68632ac41324b4b649d908b3d13 Mon Sep 17 00:00:00 2001 From: Alexey Zapparov Date: Sun, 1 Oct 2017 20:37:36 +0200 Subject: [PATCH 3/3] Switch to use FFI HTTP Parser --- Gemfile | 2 -- http.gemspec | 2 +- lib/http.rb | 2 -- lib/http/response/parser.rb | 41 ++++++++++++++++++++++++------------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Gemfile b/Gemfile index be26c3ae..4cff89f5 100644 --- a/Gemfile +++ b/Gemfile @@ -5,8 +5,6 @@ ruby RUBY_VERSION gem "rake" -gem "http_parser.rb", :git => "https://github.com/ixti/http_parser.rb.git" - group :development do gem "guard-rspec", :require => false gem "nokogiri", :require => false diff --git a/http.gemspec b/http.gemspec index 2f1b3615..d097863c 100644 --- a/http.gemspec +++ b/http.gemspec @@ -27,7 +27,7 @@ Gem::Specification.new do |gem| gem.required_ruby_version = ">= 2.2" - 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.0-pre2", "< 3" 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 a1338598..c430127a 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 743cd7a8..90e0d80a 100644 --- a/lib/http/response/parser.rb +++ b/lib/http/response/parser.rb @@ -1,41 +1,53 @@ # frozen_string_literal: true +require "http-parser" + module HTTP class Response 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 def add(data) - @parser << data + @parser.parse(@state, 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 @@ -49,20 +61,21 @@ def chunk 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