# frozen_string_literal: true require_relative "support/http_helpers" class HTTPSTest < Minitest::Test include HTTPHelpers include Requests include Get include Head include WithBody include Headers include ResponseBody include IO include Errors if RUBY_ENGINE == "ruby" include Resolvers if ENV.key?("HTTPX_RESOLVER_URI") # TODO: uncomment as soon as nghttpx supports altsvc for HTTP/2 # include AltSvc if ENV.key?("HTTPBIN_ALTSVC_HOST") include Plugins::Proxy unless ENV.key?("HTTPX_NO_PROXY") include Plugins::Authentication include Plugins::FollowRedirects include Plugins::Cookies include Plugins::Compression include Plugins::PushPromise if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols) include Plugins::Retries include Plugins::Multipart include Plugins::Expect include Plugins::RateLimiter include Plugins::Persistent unless RUBY_ENGINE == "jruby" || RUBY_VERSION < "2.3" include Plugins::Stream include Plugins::AWSAuthentication include Plugins::Upgrade include Plugins::GRPC unless RUBY_ENGINE == "jruby" || RUBY_VERSION < "2.3" def test_connection_coalescing coalesced_origin = "https://#{ENV["HTTPBIN_COALESCING_HOST"]}" HTTPX.plugin(SessionWithPool).wrap do |http| response1 = http.get(origin) verify_status(response1, 200) response2 = http.get(coalesced_origin) verify_status(response2, 200) # introspection time pool = http.pool connections = pool.connections origins = connections.map(&:origins) assert origins.any? { |orgs| orgs.sort == [origin, coalesced_origin].sort }, "connections for #{[origin, coalesced_origin]} didn't coalesce (expected connection with both origins (#{origins}))" end end if ENV.key?("HTTPBIN_COALESCING_HOST") def test_verbose_log log = StringIO.new uri = build_uri("/get") response = HTTPX.get(uri, debug: log, debug_level: 2) verify_status(response, 200) log_output = log.string # assert tls output assert log_output.match(%r{SSL connection using TLSv\d+\.\d+ / \w+}) assert log_output.match(/ALPN, server accepted to use h2/) unless RUBY_ENGINE == "jruby" || RUBY_VERSION < "2.3" assert log_output.match(/Server certificate:/) assert log_output.match(/ subject: .+/) assert log_output.match(/ start date: .+ UTC/) assert log_output.match(/ expire date: .+ UTC/) assert log_output.match(/ issuer: .+/) assert log_output.match(/ SSL certificate verify ok./) return if RUBY_ENGINE == "jruby" || RUBY_VERSION < "2.3" # assert request headers assert log_output.match(/HEADER: :scheme: https/) assert log_output.match(/HEADER: :method: GET/) assert log_output.match(/HEADER: :path: .+/) assert log_output.match(/HEADER: :authority: .+/) assert log_output.match(%r{HEADER: accept: */*}) # assert response headers assert log_output.match(/HEADER: :status: 200/) assert log_output.match(/HEADER: content-type: \w+/) assert log_output.match(/HEADER: content-length: \d+/) end unless RUBY_ENGINE == "jruby" || RUBY_VERSION < "2.3" # HTTP/2-specific tests def test_http2_max_streams uri = build_uri("/get") HTTPX.plugin(SessionWithSingleStream).plugin(SessionWithPool).wrap do |http| http.get(uri, uri) connection_count = http.pool.connection_count assert connection_count == 2, "expected to have 2 connections, instead have #{connection_count}" assert http.connection_exausted, "expected 1 connnection to have exhausted" end end def test_http2_uncoalesce_on_misdirected uri = build_uri("/status/421") HTTPX.plugin(SessionWithPool).wrap do |http| response = http.get(uri) verify_status(response, 421) connection_count = http.pool.connection_count assert connection_count == 2, "expected to have 2 connections, instead have #{connection_count}" assert response.version == "1.1", "request should have been retried with HTTP/1.1" end end def test_http2_settings_timeout uri = build_uri("/get") HTTPX.plugin(SessionWithPool).plugin(SessionWithFrameDelay).wrap do |http| response = http.get(uri) verify_error_response(response, /settings_timeout/) end end def test_http2_request_trailers uri = build_uri("/post") HTTPX.wrap do |http| total_time = start_time = nil trailered = false request = http.build_request(:post, uri, body: %w[this is chunked]) request.on(:headers) do |_written_request| start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) end request.on(:trailers) do |written_request| total_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time written_request.trailers["x-time-spent"] = total_time trailered = true end response = http.request(request) verify_status(response, 200) body = json_body(response) # verify_header(body["headers"], "x-time-spent", total_time.to_s) assert body.key?("data") assert trailered, "trailer callback wasn't called" end end end def test_ssl_wrong_hostname uri = build_uri("/get") response = HTTPX.with(ssl: { hostname: "great-gatsby.com" }).get(uri) verify_error_response(response, /certificate verify failed|does not match the server certificate/) end private def origin(orig = httpbin) "https://#{orig}" end end