mirror of
https://github.com/HoneyryderChuck/httpx.git
synced 2025-08-18 00:00:53 -04:00
Compare commits
10 Commits
b9ee892b20
...
e4338979a6
Author | SHA1 | Date | |
---|---|---|---|
|
e4338979a6 | ||
|
85f0ac8ed3 | ||
|
e25ac201d2 | ||
|
38b871aa8e | ||
|
0b18bb63e8 | ||
|
afbde420a7 | ||
|
244563720a | ||
|
886c091901 | ||
|
11942b2c74 | ||
|
b2848ea718 |
@ -38,4 +38,8 @@ Naming/AccessorMethodName:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Performance/MethodObjectAsBlock:
|
Performance/MethodObjectAsBlock:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
# TODO: remove after https://github.com/rubocop/rubocop/issues/11980 is fixed
|
||||||
|
Style/RedundantCurrentDirectoryInPath:
|
||||||
Enabled: false
|
Enabled: false
|
@ -24,7 +24,7 @@ require "httpx"
|
|||||||
|
|
||||||
uri = "https://google.com"
|
uri = "https://google.com"
|
||||||
|
|
||||||
responses = HTTPX.new(uri, uri)
|
responses = HTTPX.get(uri, uri)
|
||||||
|
|
||||||
# OR
|
# OR
|
||||||
HTTPX.wrap do |client|
|
HTTPX.wrap do |client|
|
||||||
@ -37,7 +37,7 @@ end
|
|||||||
## Headers
|
## Headers
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
HTTPX.headers("user-agent" => "My Ruby Script").get("https://google.com")
|
HTTPX.with(headers: {"user-agent" => "My Ruby Script"}).get("https://google.com")
|
||||||
```
|
```
|
||||||
|
|
||||||
## HTTP Methods
|
## HTTP Methods
|
||||||
@ -61,7 +61,7 @@ response = HTTPX.plugin(:basic_authentication).basic_authentication("username",
|
|||||||
# Digest Auth
|
# Digest Auth
|
||||||
response = HTTPX.plugin(:digest_authentication).digest_authentication("username", "password").get("https://google.com")
|
response = HTTPX.plugin(:digest_authentication).digest_authentication("username", "password").get("https://google.com")
|
||||||
|
|
||||||
# Token Auth
|
# Bearer Token Auth
|
||||||
response = HTTPX.plugin(:authentication).authentication("eyrandomtoken").get("https://google.com")
|
response = HTTPX.plugin(:authentication).authentication("eyrandomtoken").get("https://google.com")
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -74,11 +74,11 @@ require "httpx"
|
|||||||
response = HTTPX.get("https://google.com/")
|
response = HTTPX.get("https://google.com/")
|
||||||
response.status # => 301
|
response.status # => 301
|
||||||
response.headers["location"] #=> "https://www.google.com/"
|
response.headers["location"] #=> "https://www.google.com/"
|
||||||
response.body # => "<HTML><HEAD><meta http-equiv=\"content-type\" ....
|
response.headers["cache-control"] #=> public, max-age=2592000
|
||||||
response["cache-control"] # => public, max-age=2592000
|
response.body.to_s #=> "<HTML><HEAD><meta http-equiv=\"content-type\" ....
|
||||||
```
|
```
|
||||||
|
|
||||||
## POST form request
|
## POST `application/x-www-form-urlencoded` request
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
require "httpx"
|
require "httpx"
|
||||||
@ -88,16 +88,13 @@ uri = URI.parse("http://example.com/search")
|
|||||||
response = HTTPX.post(uri, form: {"q" => "My query", "per_page" => "50"})
|
response = HTTPX.post(uri, form: {"q" => "My query", "per_page" => "50"})
|
||||||
```
|
```
|
||||||
|
|
||||||
## File upload - input type="file" style
|
## File `multipart/form-data` upload - input type="file" style
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
require "httpx"
|
require "httpx"
|
||||||
|
|
||||||
# uses http_form_data API: https://github.com/httprb/form_data
|
|
||||||
|
|
||||||
path = "/path/to/your/testfile.txt"
|
|
||||||
HTTPX.plugin(:multipart).post("http://something.com/uploads", form: {
|
HTTPX.plugin(:multipart).post("http://something.com/uploads", form: {
|
||||||
name: HTTP::FormData::File.new(path)
|
name: Pathname.new("/path/to/your/testfile.txt")
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -132,8 +129,7 @@ require "httpx"
|
|||||||
|
|
||||||
HTTPX.plugin(:cookies).wrap do |client|
|
HTTPX.plugin(:cookies).wrap do |client|
|
||||||
session_response = client.get("https://translate.google.com/")
|
session_response = client.get("https://translate.google.com/")
|
||||||
response_cookies = session_response.cookie_jar
|
response = client.get("https://translate.google.com/#auto|en|Pardon")
|
||||||
response = client.cookies(response_cookies).get("https://translate.google.com/#auto|en|Pardon")
|
|
||||||
puts response
|
puts response
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
@ -144,7 +140,7 @@ end
|
|||||||
require "httpx"
|
require "httpx"
|
||||||
|
|
||||||
response = HTTPX.plugin(:compression).get("https://www.google.com")
|
response = HTTPX.plugin(:compression).get("https://www.google.com")
|
||||||
puts response.headers["content-encoding"] #=> "gzip"
|
puts response.headers["content-encoding"] #=> "gzip"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -183,7 +179,9 @@ HTTPX.with(resolver_class: :https, resolver_options: {uri: "https://9.9.9.9/dns-
|
|||||||
```ruby
|
```ruby
|
||||||
require "httpx"
|
require "httpx"
|
||||||
|
|
||||||
HTTPX.plugin(:follow_redirects).with(follow_insecure_redirects: false, max_redirects: 4).get("https://www.google.com")
|
HTTPX.plugin(:follow_redirects)
|
||||||
|
.with(follow_insecure_redirects: false, max_redirects: 4)
|
||||||
|
.get("https://www.google.com")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Timeouts
|
## Timeouts
|
||||||
@ -191,12 +189,12 @@ HTTPX.plugin(:follow_redirects).with(follow_insecure_redirects: false, max_redir
|
|||||||
```ruby
|
```ruby
|
||||||
require "httpx"
|
require "httpx"
|
||||||
|
|
||||||
HTTPX.with(timeout: {connect_timeout: 10, operation_timeout: 3}).get("https://google.com")
|
# full E2E request/response timeout, 10 sec to connect to peer
|
||||||
|
HTTPX.with(timeout: {connect_timeout: 10, request_timeout: 3}).get("https://google.com")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Retries
|
## Retries
|
||||||
|
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
require "httpx"
|
require "httpx"
|
||||||
HTTPX.plugin(:retries).max_retries(5).get("https://www.google.com")
|
HTTPX.plugin(:retries).max_retries(5).get("https://www.google.com")
|
||||||
@ -214,4 +212,3 @@ HTTPX.get("https://google.com") #=> udp://10.0.1.2:53...
|
|||||||
|
|
||||||
HTTPX.with(debug_level: 1, debug: $stderr).get("https://google.com")
|
HTTPX.with(debug_level: 1, debug: $stderr).get("https://google.com")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ Gem::Specification.new do |gem|
|
|||||||
"changelog_uri" => "https://os85.gitlab.io/httpx/#release-notes",
|
"changelog_uri" => "https://os85.gitlab.io/httpx/#release-notes",
|
||||||
"documentation_uri" => "https://os85.gitlab.io/httpx/rdoc/",
|
"documentation_uri" => "https://os85.gitlab.io/httpx/rdoc/",
|
||||||
"source_code_uri" => "https://gitlab.com/os85/httpx",
|
"source_code_uri" => "https://gitlab.com/os85/httpx",
|
||||||
"homepage_uri" => "https://os85.gitlab.io/httpx/",
|
"homepage_uri" => "https://honeyryderchuck.gitlab.io/httpx/",
|
||||||
"rubygems_mfa_required" => "true",
|
"rubygems_mfa_required" => "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ module TRACING_MODULE # rubocop:disable Naming/ClassAndModuleCamelCase
|
|||||||
|
|
||||||
module RequestMethods
|
module RequestMethods
|
||||||
def __datadog_enable_trace!
|
def __datadog_enable_trace!
|
||||||
return super if @__datadog_enable_trace
|
return if @__datadog_enable_trace
|
||||||
|
|
||||||
RequestTracer.new(self).call
|
RequestTracer.new(self).call
|
||||||
@__datadog_enable_trace = true
|
@__datadog_enable_trace = true
|
||||||
|
@ -63,7 +63,7 @@ module HTTPX
|
|||||||
# Normalizes a _domain_ using the Punycode algorithm as necessary.
|
# Normalizes a _domain_ using the Punycode algorithm as necessary.
|
||||||
# The result will be a downcased, ASCII-only string.
|
# The result will be a downcased, ASCII-only string.
|
||||||
def normalize(domain)
|
def normalize(domain)
|
||||||
domain = domain.ascii_only? ? domain : domain.chomp(DOT).unicode_normalize(:nfc)
|
domain = domain.chomp(DOT).unicode_normalize(:nfc) unless domain.ascii_only?
|
||||||
Punycode.encode_hostname(domain).downcase
|
Punycode.encode_hostname(domain).downcase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -25,6 +25,10 @@ module HTTPX
|
|||||||
klass.plugin(:"proxy/socks5")
|
klass.plugin(:"proxy/socks5")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def extra_options(options)
|
||||||
|
options.merge(supported_proxy_protocols: [])
|
||||||
|
end
|
||||||
|
|
||||||
if URI::Generic.methods.include?(:use_proxy?)
|
if URI::Generic.methods.include?(:use_proxy?)
|
||||||
def use_proxy?(*args)
|
def use_proxy?(*args)
|
||||||
URI::Generic.use_proxy?(*args)
|
URI::Generic.use_proxy?(*args)
|
||||||
@ -118,6 +122,12 @@ module HTTPX
|
|||||||
def option_proxy(value)
|
def option_proxy(value)
|
||||||
value.is_a?(Parameters) ? value : Hash[value]
|
value.is_a?(Parameters) ? value : Hash[value]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def option_supported_proxy_protocols(value)
|
||||||
|
raise TypeError, ":supported_proxy_protocols must be an Array" unless value.is_a?(Array)
|
||||||
|
|
||||||
|
value.map(&:to_s)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
@ -142,8 +152,12 @@ module HTTPX
|
|||||||
next_proxy = @_proxy_uris.first
|
next_proxy = @_proxy_uris.first
|
||||||
raise Error, "Failed to connect to proxy" unless next_proxy
|
raise Error, "Failed to connect to proxy" unless next_proxy
|
||||||
|
|
||||||
|
next_proxy = URI(next_proxy)
|
||||||
|
|
||||||
|
raise Error,
|
||||||
|
"#{next_proxy.scheme}: unsupported proxy protocol" unless options.supported_proxy_protocols.include?(next_proxy.scheme)
|
||||||
|
|
||||||
if proxy.key?(:no_proxy)
|
if proxy.key?(:no_proxy)
|
||||||
next_proxy = URI(next_proxy)
|
|
||||||
|
|
||||||
no_proxy = proxy[:no_proxy]
|
no_proxy = proxy[:no_proxy]
|
||||||
no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
|
no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
|
||||||
@ -253,10 +267,11 @@ module HTTPX
|
|||||||
end
|
end
|
||||||
|
|
||||||
def send(request)
|
def send(request)
|
||||||
return super unless @options.proxy
|
return super unless (
|
||||||
return super unless connecting?
|
@options.proxy && @state != :idle && connecting?
|
||||||
|
)
|
||||||
|
|
||||||
@pending << request
|
(@proxy_pending ||= []) << request
|
||||||
end
|
end
|
||||||
|
|
||||||
def connecting?
|
def connecting?
|
||||||
@ -294,6 +309,12 @@ module HTTPX
|
|||||||
when :idle
|
when :idle
|
||||||
transition(:connecting)
|
transition(:connecting)
|
||||||
when :connected
|
when :connected
|
||||||
|
if @proxy_pending
|
||||||
|
while (req = @proxy_pendind.shift)
|
||||||
|
send(req)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
transition(:open)
|
transition(:open)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -6,6 +6,12 @@ module HTTPX
|
|||||||
module Plugins
|
module Plugins
|
||||||
module Proxy
|
module Proxy
|
||||||
module HTTP
|
module HTTP
|
||||||
|
class << self
|
||||||
|
def extra_options(options)
|
||||||
|
options.merge(supported_proxy_protocols: options.supported_proxy_protocols + %w[http])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
def with_proxy_basic_auth(opts)
|
def with_proxy_basic_auth(opts)
|
||||||
with(proxy: opts.merge(scheme: "basic"))
|
with(proxy: opts.merge(scheme: "basic"))
|
||||||
|
@ -16,6 +16,12 @@ module HTTPX
|
|||||||
|
|
||||||
Error = Socks4Error
|
Error = Socks4Error
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def extra_options(options)
|
||||||
|
options.merge(supported_proxy_protocols: options.supported_proxy_protocols + PROTOCOLS)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
module ConnectionMethods
|
module ConnectionMethods
|
||||||
def interests
|
def interests
|
||||||
if @state == :connecting
|
if @state == :connecting
|
||||||
|
@ -18,8 +18,14 @@ module HTTPX
|
|||||||
|
|
||||||
Error = Socks5Error
|
Error = Socks5Error
|
||||||
|
|
||||||
def self.load_dependencies(*)
|
class << self
|
||||||
require_relative "../authentication/socks5"
|
def load_dependencies(*)
|
||||||
|
require_relative "../authentication/socks5"
|
||||||
|
end
|
||||||
|
|
||||||
|
def extra_options(options)
|
||||||
|
options.merge(supported_proxy_protocols: options.supported_proxy_protocols + %w[socks5])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module ConnectionMethods
|
module ConnectionMethods
|
||||||
|
@ -9,10 +9,13 @@ module HTTPX
|
|||||||
# redefine the default options static var, which needs to
|
# redefine the default options static var, which needs to
|
||||||
# refresh options_class
|
# refresh options_class
|
||||||
options = proxy_session.class.default_options.to_hash
|
options = proxy_session.class.default_options.to_hash
|
||||||
options.freeze
|
|
||||||
original_verbosity = $VERBOSE
|
original_verbosity = $VERBOSE
|
||||||
$VERBOSE = nil
|
$VERBOSE = nil
|
||||||
|
const_set(:Options, proxy_session.class.default_options.options_class)
|
||||||
|
options[:options_class] = Class.new(options[:options_class])
|
||||||
|
options.freeze
|
||||||
Options.send(:const_set, :DEFAULT_OPTIONS, options)
|
Options.send(:const_set, :DEFAULT_OPTIONS, options)
|
||||||
|
Session.instance_variable_set(:@default_options, Options.new(options))
|
||||||
$VERBOSE = original_verbosity
|
$VERBOSE = original_verbosity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
52
regression_tests/bug_0_24_1_test.rb
Normal file
52
regression_tests/bug_0_24_1_test.rb
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "webrick"
|
||||||
|
require "webrick/httpproxy"
|
||||||
|
require "test_helper"
|
||||||
|
require "support/http_helpers"
|
||||||
|
require "support/proxy_helper"
|
||||||
|
require "support/minitest_extensions"
|
||||||
|
|
||||||
|
class Bug_0_24_1_Test < Minitest::Test
|
||||||
|
include HTTPHelpers
|
||||||
|
include ProxyHelper
|
||||||
|
|
||||||
|
Plugin = Module.new do
|
||||||
|
@requests = []
|
||||||
|
|
||||||
|
class << self
|
||||||
|
attr_accessor :requests
|
||||||
|
end
|
||||||
|
|
||||||
|
self::ConnectionMethods = Module.new do
|
||||||
|
def send(req)
|
||||||
|
Plugin.requests << req
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_proxy_plugin_silencing_conn_send_based_plugin
|
||||||
|
start_test_servlet(WEBrick::HTTPProxyServer) do |server|
|
||||||
|
def server.origin
|
||||||
|
sock = listeners.first
|
||||||
|
_, sock, ip, _ = sock.addr
|
||||||
|
"http://#{ip}:#{sock}"
|
||||||
|
end
|
||||||
|
proxy_uri = server.origin
|
||||||
|
http = HTTPX.plugin(Plugin).plugin(:proxy).plugin(ProxyResponseDetector).with_proxy(uri: proxy_uri)
|
||||||
|
uri = build_uri("/get")
|
||||||
|
response = http.get(uri)
|
||||||
|
verify_status(response, 200)
|
||||||
|
assert response.proxied?
|
||||||
|
|
||||||
|
assert Plugin.requests.size == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def scheme
|
||||||
|
"http://"
|
||||||
|
end
|
||||||
|
end
|
@ -28,6 +28,13 @@ class ProxyTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_proxy_unsupported_scheme
|
||||||
|
ex = assert_raises(HTTPX::HTTPProxyError) do
|
||||||
|
HTTPX.plugin(:proxy).with_proxy(uri: "https://proxy:123").get("http://smth.com")
|
||||||
|
end
|
||||||
|
assert ex.message == "https: unsupported proxy protocol"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def parameters(uri: "http://proxy", **args)
|
def parameters(uri: "http://proxy", **args)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user