http 4.2.0 → 5.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +65 -0
- data/.gitignore +6 -10
- data/.rspec +0 -4
- data/.rubocop/layout.yml +8 -0
- data/.rubocop/style.yml +32 -0
- data/.rubocop.yml +8 -110
- data/.rubocop_todo.yml +192 -0
- data/.yardopts +1 -1
- data/CHANGES.md +168 -0
- data/Gemfile +18 -10
- data/LICENSE.txt +1 -1
- data/README.md +17 -20
- data/Rakefile +2 -10
- data/http.gemspec +5 -5
- data/lib/http/chainable.rb +23 -17
- data/lib/http/client.rb +52 -35
- data/lib/http/connection.rb +12 -8
- data/lib/http/content_type.rb +12 -7
- data/lib/http/feature.rb +3 -1
- data/lib/http/features/auto_deflate.rb +7 -7
- data/lib/http/features/auto_inflate.rb +6 -7
- data/lib/http/features/instrumentation.rb +1 -1
- data/lib/http/features/logging.rb +19 -21
- data/lib/http/headers.rb +50 -13
- data/lib/http/mime_type/adapter.rb +3 -1
- data/lib/http/mime_type/json.rb +1 -0
- data/lib/http/options.rb +6 -9
- data/lib/http/redirector.rb +4 -2
- data/lib/http/request/body.rb +1 -0
- data/lib/http/request/writer.rb +8 -3
- data/lib/http/request.rb +28 -11
- data/lib/http/response/body.rb +6 -4
- data/lib/http/response/inflater.rb +1 -1
- data/lib/http/response/parser.rb +75 -49
- data/lib/http/response/status.rb +4 -3
- data/lib/http/response.rb +35 -15
- data/lib/http/timeout/global.rb +42 -38
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +56 -58
- data/lib/http/uri.rb +5 -5
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +173 -35
- data/spec/lib/http/connection_spec.rb +8 -5
- data/spec/lib/http/features/auto_inflate_spec.rb +3 -2
- data/spec/lib/http/features/instrumentation_spec.rb +27 -21
- data/spec/lib/http/features/logging_spec.rb +8 -10
- data/spec/lib/http/headers_spec.rb +53 -18
- data/spec/lib/http/options/headers_spec.rb +1 -1
- data/spec/lib/http/options/merge_spec.rb +16 -16
- data/spec/lib/http/redirector_spec.rb +59 -1
- data/spec/lib/http/request/writer_spec.rb +25 -2
- data/spec/lib/http/request_spec.rb +5 -5
- data/spec/lib/http/response/body_spec.rb +5 -5
- data/spec/lib/http/response/parser_spec.rb +74 -0
- data/spec/lib/http/response/status_spec.rb +3 -3
- data/spec/lib/http/response_spec.rb +44 -3
- data/spec/lib/http_spec.rb +30 -3
- data/spec/spec_helper.rb +21 -21
- data/spec/support/black_hole.rb +1 -1
- data/spec/support/dummy_server/servlet.rb +17 -6
- data/spec/support/dummy_server.rb +7 -7
- data/spec/support/fuubar.rb +21 -0
- data/spec/support/http_handling_shared.rb +4 -4
- data/spec/support/simplecov.rb +19 -0
- data/spec/support/ssl_helper.rb +4 -4
- metadata +24 -16
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -37
data/LICENSE.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright (c) 2011-
|
|
1
|
+
Copyright (c) 2011-2021 Tony Arcieri, Erik Michaels-Ober, Alexey V. Zapparov, Zachary Anker
|
|
2
2
|
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
4
|
a copy of this software and associated documentation files (the
|
data/README.md
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# 
|
|
2
2
|
|
|
3
|
-
[](https://github.com/httprb/http/blob/4-x-stable/LICENSE.txt)
|
|
3
|
+
[](https://rubygems.org/gems/http)
|
|
4
|
+
[](https://github.com/httprb/http/actions?query=workflow:CI)
|
|
5
|
+
[](https://codeclimate.com/github/httprb/http)
|
|
6
|
+
[](https://github.com/httprb/http/blob/master/LICENSE.txt)
|
|
8
7
|
|
|
9
8
|
[Documentation]
|
|
10
9
|
|
|
11
|
-
_NOTE: This is the
|
|
10
|
+
_NOTE: This is the 5.x **development** branch. For the 4.x **stable** branch, please see:_
|
|
12
11
|
|
|
13
|
-
https://github.com/httprb/http/tree/
|
|
12
|
+
https://github.com/httprb/http/tree/4-x-stable
|
|
14
13
|
|
|
15
14
|
## About
|
|
16
15
|
|
|
@@ -18,13 +17,12 @@ HTTP (The Gem! a.k.a. http.rb) is an easy-to-use client library for making reque
|
|
|
18
17
|
from Ruby. It uses a simple method chaining system for building requests, similar to
|
|
19
18
|
Python's [Requests].
|
|
20
19
|
|
|
21
|
-
Under the hood, http.rb uses [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
natively and outsources the parsing to native extensions.
|
|
20
|
+
Under the hood, http.rb uses the [llhttp] parser, a fast HTTP parsing native extension.
|
|
21
|
+
This library isn't just yet another wrapper around `Net::HTTP`. It implements the HTTP
|
|
22
|
+
protocol natively and outsources the parsing to native extensions.
|
|
25
23
|
|
|
26
24
|
[requests]: http://docs.python-requests.org/en/latest/
|
|
27
|
-
[
|
|
25
|
+
[llhttp]: https://llhttp.org/
|
|
28
26
|
|
|
29
27
|
|
|
30
28
|
## Another Ruby HTTP library? Why should I care?
|
|
@@ -114,8 +112,8 @@ for more detailed documentation and usage notes.
|
|
|
114
112
|
|
|
115
113
|
The following API documentation is also available:
|
|
116
114
|
|
|
117
|
-
* [YARD API documentation](
|
|
118
|
-
* [Chainable module (all chainable methods)](
|
|
115
|
+
* [YARD API documentation](https://www.rubydoc.info/github/httprb/http)
|
|
116
|
+
* [Chainable module (all chainable methods)](https://www.rubydoc.info/github/httprb/http/HTTP/Chainable)
|
|
119
117
|
|
|
120
118
|
[documentation]: https://github.com/httprb/http/wiki
|
|
121
119
|
|
|
@@ -164,11 +162,10 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
|
|
|
164
162
|
This library aims to support and is [tested against][travis] the following Ruby
|
|
165
163
|
versions:
|
|
166
164
|
|
|
167
|
-
* Ruby 2.
|
|
168
|
-
* Ruby 2.
|
|
169
|
-
* Ruby
|
|
170
|
-
*
|
|
171
|
-
* JRuby 9.2.x.x
|
|
165
|
+
* Ruby 2.6
|
|
166
|
+
* Ruby 2.7
|
|
167
|
+
* Ruby 3.0
|
|
168
|
+
* JRuby 9.2
|
|
172
169
|
|
|
173
170
|
If something doesn't work on one of these versions, it's a bug.
|
|
174
171
|
|
|
@@ -198,5 +195,5 @@ dropped.
|
|
|
198
195
|
|
|
199
196
|
## Copyright
|
|
200
197
|
|
|
201
|
-
Copyright (c) 2011-
|
|
198
|
+
Copyright (c) 2011-2021 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
|
|
202
199
|
See LICENSE.txt for further details.
|
data/Rakefile
CHANGED
|
@@ -35,7 +35,7 @@ task :generate_status_codes do
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
File.open("./lib/http/response/status/reasons.rb", "w") do |io|
|
|
38
|
-
io.puts
|
|
38
|
+
io.puts <<~TPL
|
|
39
39
|
# AUTO-GENERATED FILE, DO NOT CHANGE IT MANUALLY
|
|
40
40
|
|
|
41
41
|
require "delegate"
|
|
@@ -61,12 +61,4 @@ task :generate_status_codes do
|
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
task :default => %i[spec rubocop verify_measurements]
|
|
66
|
-
else
|
|
67
|
-
case ENV["SUITE"]
|
|
68
|
-
when "rubocop" then task :default => :rubocop
|
|
69
|
-
when "yardstick" then task :default => :verify_measurements
|
|
70
|
-
else task :default => :spec
|
|
71
|
-
end
|
|
72
|
-
end
|
|
64
|
+
task :default => %i[spec rubocop verify_measurements]
|
data/http.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
lib = File.expand_path("
|
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
require "http/version"
|
|
6
6
|
|
|
@@ -25,12 +25,12 @@ Gem::Specification.new do |gem|
|
|
|
25
25
|
gem.require_paths = ["lib"]
|
|
26
26
|
gem.version = HTTP::VERSION
|
|
27
27
|
|
|
28
|
-
gem.required_ruby_version = ">= 2.
|
|
28
|
+
gem.required_ruby_version = ">= 2.5"
|
|
29
29
|
|
|
30
|
-
gem.add_runtime_dependency "addressable", "~> 2.
|
|
30
|
+
gem.add_runtime_dependency "addressable", "~> 2.8"
|
|
31
31
|
gem.add_runtime_dependency "http-cookie", "~> 1.0"
|
|
32
|
-
gem.add_runtime_dependency "http-form_data", "~> 2.
|
|
33
|
-
gem.add_runtime_dependency "
|
|
32
|
+
gem.add_runtime_dependency "http-form_data", "~> 2.2"
|
|
33
|
+
gem.add_runtime_dependency "llhttp-ffi", "~> 0.4.0"
|
|
34
34
|
|
|
35
35
|
gem.add_development_dependency "bundler", "~> 2.0"
|
|
36
36
|
|
data/lib/http/chainable.rb
CHANGED
|
@@ -9,63 +9,63 @@ module HTTP
|
|
|
9
9
|
# Request a get sans response body
|
|
10
10
|
# @param uri
|
|
11
11
|
# @option options [Hash]
|
|
12
|
-
def head(uri, options = {})
|
|
12
|
+
def head(uri, options = {})
|
|
13
13
|
request :head, uri, options
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
# Get a resource
|
|
17
17
|
# @param uri
|
|
18
18
|
# @option options [Hash]
|
|
19
|
-
def get(uri, options = {})
|
|
19
|
+
def get(uri, options = {})
|
|
20
20
|
request :get, uri, options
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Post to a resource
|
|
24
24
|
# @param uri
|
|
25
25
|
# @option options [Hash]
|
|
26
|
-
def post(uri, options = {})
|
|
26
|
+
def post(uri, options = {})
|
|
27
27
|
request :post, uri, options
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# Put to a resource
|
|
31
31
|
# @param uri
|
|
32
32
|
# @option options [Hash]
|
|
33
|
-
def put(uri, options = {})
|
|
33
|
+
def put(uri, options = {})
|
|
34
34
|
request :put, uri, options
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Delete a resource
|
|
38
38
|
# @param uri
|
|
39
39
|
# @option options [Hash]
|
|
40
|
-
def delete(uri, options = {})
|
|
40
|
+
def delete(uri, options = {})
|
|
41
41
|
request :delete, uri, options
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# Echo the request back to the client
|
|
45
45
|
# @param uri
|
|
46
46
|
# @option options [Hash]
|
|
47
|
-
def trace(uri, options = {})
|
|
47
|
+
def trace(uri, options = {})
|
|
48
48
|
request :trace, uri, options
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# Return the methods supported on the given URI
|
|
52
52
|
# @param uri
|
|
53
53
|
# @option options [Hash]
|
|
54
|
-
def options(uri, options = {})
|
|
54
|
+
def options(uri, options = {})
|
|
55
55
|
request :options, uri, options
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Convert to a transparent TCP/IP tunnel
|
|
59
59
|
# @param uri
|
|
60
60
|
# @option options [Hash]
|
|
61
|
-
def connect(uri, options = {})
|
|
61
|
+
def connect(uri, options = {})
|
|
62
62
|
request :connect, uri, options
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# Apply partial modifications to a resource
|
|
66
66
|
# @param uri
|
|
67
67
|
# @option options [Hash]
|
|
68
|
-
def patch(uri, options = {})
|
|
68
|
+
def patch(uri, options = {})
|
|
69
69
|
request :patch, uri, options
|
|
70
70
|
end
|
|
71
71
|
|
|
@@ -93,7 +93,7 @@ module HTTP
|
|
|
93
93
|
def timeout(options)
|
|
94
94
|
klass, options = case options
|
|
95
95
|
when Numeric then [HTTP::Timeout::Global, {:global => options}]
|
|
96
|
-
when Hash then [HTTP::Timeout::PerOperation, options]
|
|
96
|
+
when Hash then [HTTP::Timeout::PerOperation, options.dup]
|
|
97
97
|
when :null then [HTTP::Timeout::Null, {}]
|
|
98
98
|
else raise ArgumentError, "Use `.timeout(global_timeout_in_seconds)` or `.timeout(connect: x, write: y, read: z)`."
|
|
99
99
|
|
|
@@ -101,11 +101,12 @@ module HTTP
|
|
|
101
101
|
|
|
102
102
|
%i[global read write connect].each do |k|
|
|
103
103
|
next unless options.key? k
|
|
104
|
+
|
|
104
105
|
options["#{k}_timeout".to_sym] = options.delete k
|
|
105
106
|
end
|
|
106
107
|
|
|
107
108
|
branch default_options.merge(
|
|
108
|
-
:timeout_class
|
|
109
|
+
:timeout_class => klass,
|
|
109
110
|
:timeout_options => options
|
|
110
111
|
)
|
|
111
112
|
end
|
|
@@ -144,9 +145,10 @@ module HTTP
|
|
|
144
145
|
options = {:keep_alive_timeout => timeout}
|
|
145
146
|
p_client = branch default_options.merge(options).with_persistent host
|
|
146
147
|
return p_client unless block_given?
|
|
148
|
+
|
|
147
149
|
yield p_client
|
|
148
150
|
ensure
|
|
149
|
-
p_client
|
|
151
|
+
p_client&.close
|
|
150
152
|
end
|
|
151
153
|
|
|
152
154
|
# Make a request through an HTTP proxy
|
|
@@ -168,10 +170,10 @@ module HTTP
|
|
|
168
170
|
alias through via
|
|
169
171
|
|
|
170
172
|
# Make client follow redirects.
|
|
171
|
-
# @param
|
|
173
|
+
# @param options
|
|
172
174
|
# @return [HTTP::Client]
|
|
173
175
|
# @see Redirector#initialize
|
|
174
|
-
def follow(options = {})
|
|
176
|
+
def follow(options = {})
|
|
175
177
|
branch default_options.with_follow options
|
|
176
178
|
end
|
|
177
179
|
|
|
@@ -209,10 +211,11 @@ module HTTP
|
|
|
209
211
|
# @option opts [#to_s] :user
|
|
210
212
|
# @option opts [#to_s] :pass
|
|
211
213
|
def basic_auth(opts)
|
|
212
|
-
user
|
|
213
|
-
pass
|
|
214
|
+
user = opts.fetch(:user)
|
|
215
|
+
pass = opts.fetch(:pass)
|
|
216
|
+
creds = "#{user}:#{pass}"
|
|
214
217
|
|
|
215
|
-
auth("Basic
|
|
218
|
+
auth("Basic #{Base64.strict_encode64(creds)}")
|
|
216
219
|
end
|
|
217
220
|
|
|
218
221
|
# Get options for HTTP
|
|
@@ -236,6 +239,9 @@ module HTTP
|
|
|
236
239
|
# Turn on given features. Available features are:
|
|
237
240
|
# * auto_inflate
|
|
238
241
|
# * auto_deflate
|
|
242
|
+
# * instrumentation
|
|
243
|
+
# * logging
|
|
244
|
+
# * normalize_uri
|
|
239
245
|
# @param features
|
|
240
246
|
def use(*features)
|
|
241
247
|
branch default_options.with_features(features)
|
data/lib/http/client.rb
CHANGED
|
@@ -16,7 +16,7 @@ module HTTP
|
|
|
16
16
|
extend Forwardable
|
|
17
17
|
include Chainable
|
|
18
18
|
|
|
19
|
-
HTTP_OR_HTTPS_RE = %r{^https?://}i
|
|
19
|
+
HTTP_OR_HTTPS_RE = %r{^https?://}i.freeze
|
|
20
20
|
|
|
21
21
|
def initialize(default_options = {})
|
|
22
22
|
@default_options = HTTP::Options.new(default_options)
|
|
@@ -25,19 +25,19 @@ module HTTP
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# Make an HTTP request
|
|
28
|
-
def request(verb, uri, opts = {})
|
|
28
|
+
def request(verb, uri, opts = {})
|
|
29
29
|
opts = @default_options.merge(opts)
|
|
30
30
|
req = build_request(verb, uri, opts)
|
|
31
31
|
res = perform(req, opts)
|
|
32
32
|
return res unless opts.follow
|
|
33
33
|
|
|
34
34
|
Redirector.new(opts.follow).perform(req, res) do |request|
|
|
35
|
-
perform(request, opts)
|
|
35
|
+
perform(wrap_request(request, opts), opts)
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# Prepare an HTTP request
|
|
40
|
-
def build_request(verb, uri, opts = {})
|
|
40
|
+
def build_request(verb, uri, opts = {})
|
|
41
41
|
opts = @default_options.merge(opts)
|
|
42
42
|
uri = make_request_uri(uri, opts)
|
|
43
43
|
headers = make_request_headers(opts)
|
|
@@ -52,9 +52,7 @@ module HTTP
|
|
|
52
52
|
:body => body
|
|
53
53
|
)
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
feature.wrap_request(request)
|
|
57
|
-
end
|
|
55
|
+
wrap_request(req, opts)
|
|
58
56
|
end
|
|
59
57
|
|
|
60
58
|
# @!method persistent?
|
|
@@ -68,22 +66,20 @@ module HTTP
|
|
|
68
66
|
|
|
69
67
|
@state = :dirty
|
|
70
68
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
@connection.
|
|
75
|
-
|
|
69
|
+
begin
|
|
70
|
+
@connection ||= HTTP::Connection.new(req, options)
|
|
71
|
+
|
|
72
|
+
unless @connection.failed_proxy_connect?
|
|
73
|
+
@connection.send_request(req)
|
|
74
|
+
@connection.read_headers!
|
|
75
|
+
end
|
|
76
|
+
rescue Error => e
|
|
77
|
+
options.features.each_value do |feature|
|
|
78
|
+
feature.on_error(req, e)
|
|
79
|
+
end
|
|
80
|
+
raise
|
|
76
81
|
end
|
|
77
|
-
|
|
78
|
-
res = Response.new(
|
|
79
|
-
:status => @connection.status_code,
|
|
80
|
-
:version => @connection.http_version,
|
|
81
|
-
:headers => @connection.headers,
|
|
82
|
-
:proxy_headers => @connection.proxy_response_headers,
|
|
83
|
-
:connection => @connection,
|
|
84
|
-
:encoding => options.encoding,
|
|
85
|
-
:uri => req.uri
|
|
86
|
-
)
|
|
82
|
+
res = build_response(req, options)
|
|
87
83
|
|
|
88
84
|
res = options.features.inject(res) do |response, (_name, feature)|
|
|
89
85
|
feature.wrap_response(response)
|
|
@@ -99,26 +95,44 @@ module HTTP
|
|
|
99
95
|
end
|
|
100
96
|
|
|
101
97
|
def close
|
|
102
|
-
@connection
|
|
98
|
+
@connection&.close
|
|
103
99
|
@connection = nil
|
|
104
100
|
@state = :clean
|
|
105
101
|
end
|
|
106
102
|
|
|
107
103
|
private
|
|
108
104
|
|
|
105
|
+
def wrap_request(req, opts)
|
|
106
|
+
opts.features.inject(req) do |request, (_name, feature)|
|
|
107
|
+
feature.wrap_request(request)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_response(req, options)
|
|
112
|
+
Response.new(
|
|
113
|
+
:status => @connection.status_code,
|
|
114
|
+
:version => @connection.http_version,
|
|
115
|
+
:headers => @connection.headers,
|
|
116
|
+
:proxy_headers => @connection.proxy_response_headers,
|
|
117
|
+
:connection => @connection,
|
|
118
|
+
:encoding => options.encoding,
|
|
119
|
+
:request => req
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
109
123
|
# Verify our request isn't going to be made against another URI
|
|
110
124
|
def verify_connection!(uri)
|
|
111
125
|
if default_options.persistent? && uri.origin != default_options.persistent
|
|
112
126
|
raise StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"
|
|
127
|
+
end
|
|
128
|
+
|
|
113
129
|
# We re-create the connection object because we want to let prior requests
|
|
114
130
|
# lazily load the body as long as possible, and this mimics prior functionality.
|
|
115
|
-
|
|
116
|
-
|
|
131
|
+
return close if @connection && (!@connection.keep_alive? || @connection.expired?)
|
|
132
|
+
|
|
117
133
|
# If we get into a bad state (eg, Timeout.timeout ensure being killed)
|
|
118
134
|
# close the connection to prevent potential for mixed responses.
|
|
119
|
-
|
|
120
|
-
close
|
|
121
|
-
end
|
|
135
|
+
return close if @state == :dirty
|
|
122
136
|
end
|
|
123
137
|
|
|
124
138
|
# Merges query params if needed
|
|
@@ -128,15 +142,11 @@ module HTTP
|
|
|
128
142
|
def make_request_uri(uri, opts)
|
|
129
143
|
uri = uri.to_s
|
|
130
144
|
|
|
131
|
-
if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
|
|
132
|
-
uri = "#{default_options.persistent}#{uri}"
|
|
133
|
-
end
|
|
145
|
+
uri = "#{default_options.persistent}#{uri}" if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
|
|
134
146
|
|
|
135
147
|
uri = HTTP::URI.parse uri
|
|
136
148
|
|
|
137
|
-
if opts.params && !opts.params.empty?
|
|
138
|
-
uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a)
|
|
139
|
-
end
|
|
149
|
+
uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a) if opts.params && !opts.params.empty?
|
|
140
150
|
|
|
141
151
|
# Some proxies (seen on WEBRick) fail if URL has
|
|
142
152
|
# empty path (e.g. `http://example.com`) while it's RFC-complaint:
|
|
@@ -169,7 +179,7 @@ module HTTP
|
|
|
169
179
|
when opts.body
|
|
170
180
|
opts.body
|
|
171
181
|
when opts.form
|
|
172
|
-
form =
|
|
182
|
+
form = make_form_data(opts.form)
|
|
173
183
|
headers[Headers::CONTENT_TYPE] ||= form.content_type
|
|
174
184
|
form
|
|
175
185
|
when opts.json
|
|
@@ -178,5 +188,12 @@ module HTTP
|
|
|
178
188
|
body
|
|
179
189
|
end
|
|
180
190
|
end
|
|
191
|
+
|
|
192
|
+
def make_form_data(form)
|
|
193
|
+
return form if form.is_a? HTTP::FormData::Multipart
|
|
194
|
+
return form if form.is_a? HTTP::FormData::Urlencoded
|
|
195
|
+
|
|
196
|
+
HTTP::FormData.create(form)
|
|
197
|
+
end
|
|
181
198
|
end
|
|
182
199
|
end
|
data/lib/http/connection.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require "forwardable"
|
|
4
4
|
|
|
5
5
|
require "http/headers"
|
|
6
|
-
require "http/response/parser"
|
|
7
6
|
|
|
8
7
|
module HTTP
|
|
9
8
|
# A connection to the HTTP server
|
|
@@ -45,8 +44,8 @@ module HTTP
|
|
|
45
44
|
send_proxy_connect_request(req)
|
|
46
45
|
start_tls(req, options)
|
|
47
46
|
reset_timer
|
|
48
|
-
rescue IOError, SocketError, SystemCallError =>
|
|
49
|
-
raise ConnectionError, "failed to connect: #{
|
|
47
|
+
rescue IOError, SocketError, SystemCallError => e
|
|
48
|
+
raise ConnectionError, "failed to connect: #{e}", e.backtrace
|
|
50
49
|
end
|
|
51
50
|
|
|
52
51
|
# @see (HTTP::Response::Parser#status_code)
|
|
@@ -68,8 +67,13 @@ module HTTP
|
|
|
68
67
|
# @param [Request] req Request to send to the server
|
|
69
68
|
# @return [nil]
|
|
70
69
|
def send_request(req)
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
if @pending_response
|
|
71
|
+
raise StateError, "Tried to send a request while one is pending already. Make sure you read off the body."
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if @pending_request
|
|
75
|
+
raise StateError, "Tried to send a request while a response is pending. Make sure you read off the body."
|
|
76
|
+
end
|
|
73
77
|
|
|
74
78
|
@pending_request = true
|
|
75
79
|
|
|
@@ -93,7 +97,7 @@ module HTTP
|
|
|
93
97
|
chunk = @parser.read(size)
|
|
94
98
|
finish_response if finished
|
|
95
99
|
|
|
96
|
-
chunk.
|
|
100
|
+
chunk || "".b
|
|
97
101
|
end
|
|
98
102
|
|
|
99
103
|
# Reads data from socket up until headers are loaded
|
|
@@ -216,8 +220,8 @@ module HTTP
|
|
|
216
220
|
elsif value
|
|
217
221
|
@parser << value
|
|
218
222
|
end
|
|
219
|
-
rescue IOError, SocketError, SystemCallError =>
|
|
220
|
-
raise ConnectionError, "error reading from socket: #{
|
|
223
|
+
rescue IOError, SocketError, SystemCallError => e
|
|
224
|
+
raise ConnectionError, "error reading from socket: #{e}", e.backtrace
|
|
221
225
|
end
|
|
222
226
|
end
|
|
223
227
|
end
|
data/lib/http/content_type.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HTTP
|
|
4
|
-
ContentType
|
|
5
|
-
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}
|
|
6
|
-
CHARSET_RE = /;\s*charset=([^;]+)/i
|
|
4
|
+
class ContentType
|
|
5
|
+
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
|
|
6
|
+
CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
|
|
7
|
+
|
|
8
|
+
attr_accessor :mime_type, :charset
|
|
7
9
|
|
|
8
10
|
class << self
|
|
9
11
|
# Parse string and return ContentType struct
|
|
@@ -15,15 +17,18 @@ module HTTP
|
|
|
15
17
|
|
|
16
18
|
# :nodoc:
|
|
17
19
|
def mime_type(str)
|
|
18
|
-
|
|
19
|
-
m && m.strip.downcase
|
|
20
|
+
str.to_s[MIME_TYPE_RE, 1]&.strip&.downcase
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
# :nodoc:
|
|
23
24
|
def charset(str)
|
|
24
|
-
|
|
25
|
-
m && m.strip.delete('"')
|
|
25
|
+
str.to_s[CHARSET_RE, 1]&.strip&.delete('"')
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
def initialize(mime_type = nil, charset = nil)
|
|
30
|
+
@mime_type = mime_type
|
|
31
|
+
@charset = charset
|
|
32
|
+
end
|
|
28
33
|
end
|
|
29
34
|
end
|
data/lib/http/feature.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module HTTP
|
|
4
4
|
class Feature
|
|
5
|
-
def initialize(opts = {})
|
|
5
|
+
def initialize(opts = {})
|
|
6
6
|
@opts = opts
|
|
7
7
|
end
|
|
8
8
|
|
|
@@ -13,6 +13,8 @@ module HTTP
|
|
|
13
13
|
def wrap_response(response)
|
|
14
14
|
response
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
def on_error(request, error); end
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
20
|
|
|
@@ -10,7 +10,7 @@ module HTTP
|
|
|
10
10
|
class AutoDeflate < Feature
|
|
11
11
|
attr_reader :method
|
|
12
12
|
|
|
13
|
-
def initialize(
|
|
13
|
+
def initialize(**)
|
|
14
14
|
super
|
|
15
15
|
|
|
16
16
|
@method = @opts.key?(:method) ? @opts[:method].to_s : "gzip"
|
|
@@ -27,12 +27,12 @@ module HTTP
|
|
|
27
27
|
request.headers[Headers::CONTENT_ENCODING] = method
|
|
28
28
|
|
|
29
29
|
Request.new(
|
|
30
|
-
:version
|
|
31
|
-
:verb
|
|
32
|
-
:uri
|
|
33
|
-
:headers
|
|
34
|
-
:proxy
|
|
35
|
-
:body
|
|
30
|
+
:version => request.version,
|
|
31
|
+
:verb => request.verb,
|
|
32
|
+
:uri => request.uri,
|
|
33
|
+
:headers => request.headers,
|
|
34
|
+
:proxy => request.proxy,
|
|
35
|
+
:body => deflated_body(request.body),
|
|
36
36
|
:uri_normalizer => request.uri_normalizer
|
|
37
37
|
)
|
|
38
38
|
end
|
|
@@ -12,16 +12,15 @@ module HTTP
|
|
|
12
12
|
return response unless supported_encoding?(response)
|
|
13
13
|
|
|
14
14
|
options = {
|
|
15
|
-
:status
|
|
16
|
-
:version
|
|
17
|
-
:headers
|
|
15
|
+
:status => response.status,
|
|
16
|
+
:version => response.version,
|
|
17
|
+
:headers => response.headers,
|
|
18
18
|
:proxy_headers => response.proxy_headers,
|
|
19
|
-
:connection
|
|
20
|
-
:body
|
|
19
|
+
:connection => response.connection,
|
|
20
|
+
:body => stream_for(response.connection),
|
|
21
|
+
:request => response.request
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
options[:uri] = response.uri if response.uri
|
|
24
|
-
|
|
25
24
|
Response.new(options)
|
|
26
25
|
end
|
|
27
26
|
|
|
@@ -29,7 +29,7 @@ module HTTP
|
|
|
29
29
|
def wrap_request(request)
|
|
30
30
|
# Emit a separate "start" event, so a logger can print the request
|
|
31
31
|
# being run without waiting for a response
|
|
32
|
-
instrumenter.instrument("start_#{name}", :request => request)
|
|
32
|
+
instrumenter.instrument("start_#{name}", :request => request)
|
|
33
33
|
instrumenter.start(name, :request => request)
|
|
34
34
|
request
|
|
35
35
|
end
|