Compare commits

...

4 Commits

Author SHA1 Message Date
HoneyryderChuck
330866f102 Merge branch 'issue-203' into 'master'
Added support for multiple JSON parsers

Closes #203

See merge request honeyryderchuck/httpx!218
2022-08-06 21:29:24 +00:00
HoneyryderChuck
25b949cf66 Added support for multiple JSON parsers
When available, httpx will either use `multi_json`, `oj`, or `yajl`,
before it falls back to default `json`.
2022-08-05 22:57:06 +01:00
HoneyryderChuck
12573a16a5 removed support for application/dns-json mime type in the DoH resolver 2022-08-05 22:57:06 +01:00
HoneyryderChuck
6336379837 added support for other json parsers 2022-08-05 22:57:06 +01:00
12 changed files with 128 additions and 27 deletions

24
Gemfile
View File

@ -16,7 +16,7 @@ group :test do
gem "minitest"
gem "minitest-proveit"
gem "ruby-ntlm"
gem "sentry-ruby" if RUBY_VERSION >= "2.4"
gem "sentry-ruby" if RUBY_VERSION >= "2.4.0"
gem "spy"
if RUBY_VERSION < "2.3.0"
gem "webmock", "< 3.15.0"
@ -25,16 +25,16 @@ group :test do
end
gem "websocket-driver"
gem "net-ssh", "~> 4.2.0" if RUBY_VERSION < "2.2"
gem "net-ssh", "~> 4.2.0" if RUBY_VERSION < "2.2.0"
if RUBY_VERSION >= "2.3"
if RUBY_VERSION >= "2.3.0"
gem "ddtrace"
else
gem "ddtrace", "< 1.0"
gem "ddtrace", "< 1.0.0"
end
platform :mri do
if RUBY_VERSION >= "2.3"
if RUBY_VERSION >= "2.3.0"
gem "google-protobuf", "< 3.19.2" if RUBY_VERSION < "2.5.0"
gem "grpc"
gem "logging"
@ -42,6 +42,12 @@ group :test do
gem "mimemagic", require: false
gem "ruby-filemagic", require: false
end
if RUBY_VERSION >= "3.0.0"
gem "multi_json", require: false
gem "oj", require: false
gem "yajl-ruby", require: false
end
end
platform :mri, :truffleruby do
@ -57,7 +63,7 @@ group :test do
end
platform :mri_23 do
if RUBY_VERSION >= "2.3"
if RUBY_VERSION >= "2.3.0"
gem "openssl", "< 2.0.6" # force usage of openssl version we patch against
end
gem "msgpack", "<= 1.3.3"
@ -83,7 +89,7 @@ group :test do
end
group :coverage do
if RUBY_VERSION < "2.2"
if RUBY_VERSION < "2.2.0"
gem "simplecov", "< 0.11.0"
elsif RUBY_VERSION < "2.3"
gem "simplecov", "< 0.11.0"
@ -109,14 +115,14 @@ group :website do
end if RUBY_VERSION > "2.4"
group :assorted do
if RUBY_VERSION < "2.2"
if RUBY_VERSION < "2.2.0"
gem "pry", "~> 0.12.2"
else
gem "pry"
end
platform :mri do
if RUBY_VERSION < "2.2"
if RUBY_VERSION < "2.2.0"
gem "pry-byebug", "~> 3.4.3"
else
gem "debug" if RUBY_VERSION >= "3.1.0"

View File

@ -61,7 +61,7 @@ module HTTPX::Plugins
@state = :idle
end
def call(response, _)
def call(response, *)
response.body.each do |chunk|
@buffer << chunk

View File

@ -102,7 +102,7 @@ module HTTPX
@requests[request] = hostname
resolver_connection.send(request)
@connections << connection
rescue ResolveError, Resolv::DNS::EncodeError, JSON::JSONError => e
rescue ResolveError, Resolv::DNS::EncodeError => e
@queries.delete(hostname)
emit_resolve_error(connection, connection.origin.host, e)
end
@ -129,7 +129,7 @@ module HTTPX
def parse(request, response)
begin
answers = decode_response_body(response)
rescue Resolv::DNS::DecodeError, JSON::JSONError => e
rescue Resolv::DNS::DecodeError => e
host, connection = @queries.first
@queries.delete(host)
emit_resolve_error(connection, connection.origin.host, e)
@ -203,11 +203,6 @@ module HTTPX
def decode_response_body(response)
case response.headers["content-type"]
when "application/dns-json",
"application/json",
%r{^application/x-javascript} # because google...
payload = JSON.parse(response.to_s)
payload["Answer"]
when "application/dns-udpwireformat",
"application/dns-message"
Resolver.decode_dns_answer(response.to_s)

View File

@ -76,8 +76,8 @@ module HTTPX
raise err
end
def json(options = nil)
decode("json", options)
def json(*args)
decode("json", *args)
end
def form
@ -86,7 +86,7 @@ module HTTPX
private
def decode(format, options = nil)
def decode(format, *args)
# TODO: check if content-type is a valid format, i.e. "application/json" for json parsing
transcoder = Transcoder.registry(format)
@ -96,7 +96,7 @@ module HTTPX
raise Error, "no decoder available for \"#{format}\"" unless decoder
decoder.call(self, options)
decoder.call(self, *args)
rescue Registry::Error
raise Error, "no decoder available for \"#{format}\""
end

View File

@ -36,7 +36,7 @@ module HTTPX::Transcoder
module Decoder
module_function
def call(response, _)
def call(response, *)
URI.decode_www_form(response.to_s).each_with_object({}) do |(field, value), params|
HTTPX::Transcoder.normalize_query(params, field, value, PARAM_DEPTH_LIMIT)
end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true
require "forwardable"
require "json"
module HTTPX::Transcoder
module JSON
@ -19,7 +18,7 @@ module HTTPX::Transcoder
def_delegator :@raw, :bytesize
def initialize(json)
@raw = ::JSON.dump(json)
@raw = JSON.json_dump(json)
@charset = @raw.encoding.name.downcase
end
@ -37,8 +36,25 @@ module HTTPX::Transcoder
raise HTTPX::Error, "invalid json mime type (#{content_type})" unless JSON_REGEX.match?(content_type)
::JSON.method(:parse)
method(:json_load)
end
# rubocop:disable Style/SingleLineMethods
if defined?(MultiJson)
def json_load(*args); MultiJson.load(*args); end
def json_dump(*args); MultiJson.dump(*args); end
elsif defined?(Oj)
def json_load(response, *args); Oj.load(response.to_s, *args); end
def json_dump(*args); Oj.dump(*args); end
elsif defined?(Yajl)
def json_load(response, *args); Yajl::Parser.new(*args).parse(response.to_s); end
def json_dump(*args); Yajl::Encoder.encode(*args); end
else
require "json"
def json_load(*args); ::JSON.parse(*args); end
def json_dump(*args); ::JSON.dump(*args); end
end
# rubocop:enable Style/SingleLineMethods
end
register "json", JSON
end

View File

@ -61,7 +61,7 @@ module HTTPX
@boundary: String
@intermediate_boundary: String
def call: (Response response, untyped) -> Hash[String, untyped]
def call: (Response response, *untyped) -> Hash[String, untyped]
private

View File

@ -22,7 +22,7 @@ module HTTPX
end
interface _Decoder
def call: (Response response, untyped options) -> untyped
def call: (Response response, *untyped) -> untyped
end
end
end

View File

@ -5,6 +5,9 @@ module HTTPX::Transcoder
def self?.encode: (_ToJson json) -> Encoder
def self?.decode: (HTTPX::Response response) -> _Decoder
def self?.json_load: (string source, ?json_options) -> untyped
def self?.json_dump: (_ToJson obj, *untyped) -> String
class Encoder
extend Forwardable
include _Encoder

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
require "multi_json"
require "test_helper"
class ResponseYajlTest < Minitest::Test
include HTTPX
def test_response_decoders
json_response = Response.new(request, 200, "2.0", { "content-type" => "application/json" })
json_response << %({"a": "b"})
assert json_response.json == { "a" => "b" }
assert json_response.json(symbolize_keys: true) == { :a => "b" }
json_response << "bogus"
assert_raises(MultiJson::ParseError) { json_response.json }
end
private
def request(verb = :get, uri = "http://google.com")
Request.new(verb, uri)
end
def response(*args)
Response.new(*args)
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
require "oj"
require "test_helper"
class ResponseOjTest < Minitest::Test
include HTTPX
def test_response_decoders
json_response = Response.new(request, 200, "2.0", { "content-type" => "application/json" })
json_response << %({"a": "b"})
assert json_response.json == { "a" => "b" }
assert json_response.json(symbol_keys: true) == { :a => "b" }
json_response << "bogus"
assert_raises(Oj::ParseError) { json_response.json }
end
private
def request(verb = :get, uri = "http://google.com")
Request.new(verb, uri)
end
def response(*args)
Response.new(*args)
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
require "yajl"
require "test_helper"
class ResponseYajlTest < Minitest::Test
include HTTPX
def test_response_decoders
json_response = Response.new(request, 200, "2.0", { "content-type" => "application/json" })
json_response << %({"a": "b"})
assert json_response.json == { "a" => "b" }
assert json_response.json(symbolize_keys: true) == { :a => "b" }
json_response << "bogus"
assert_raises(Yajl::ParseError) { json_response.json }
end
private
def request(verb = :get, uri = "http://google.com")
Request.new(verb, uri)
end
def response(*args)
Response.new(*args)
end
end