http 5.0.0.pre3 → 5.0.3
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 +7 -124
- data/.rubocop_todo.yml +192 -0
- data/CHANGES.md +123 -1
- data/Gemfile +18 -11
- data/LICENSE.txt +1 -1
- data/README.md +47 -87
- data/Rakefile +2 -10
- data/http.gemspec +3 -3
- data/lib/http/chainable.rb +15 -14
- data/lib/http/client.rb +26 -15
- data/lib/http/connection.rb +7 -3
- data/lib/http/content_type.rb +10 -5
- data/lib/http/feature.rb +1 -1
- data/lib/http/features/auto_inflate.rb +0 -2
- data/lib/http/features/instrumentation.rb +1 -1
- data/lib/http/features/logging.rb +19 -21
- data/lib/http/headers.rb +3 -3
- data/lib/http/mime_type/adapter.rb +2 -0
- data/lib/http/options.rb +2 -2
- data/lib/http/redirector.rb +1 -1
- data/lib/http/request/writer.rb +6 -2
- data/lib/http/request.rb +22 -5
- data/lib/http/response/body.rb +5 -4
- data/lib/http/response/inflater.rb +1 -1
- data/lib/http/response/parser.rb +72 -64
- data/lib/http/response/status.rb +2 -2
- data/lib/http/response.rb +24 -6
- data/lib/http/timeout/global.rb +18 -30
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +31 -55
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +109 -41
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -1
- data/spec/lib/http/features/instrumentation_spec.rb +21 -16
- data/spec/lib/http/features/logging_spec.rb +2 -5
- data/spec/lib/http/headers_spec.rb +3 -3
- data/spec/lib/http/redirector_spec.rb +44 -0
- data/spec/lib/http/request/body_spec.rb +3 -3
- data/spec/lib/http/request/writer_spec.rb +12 -1
- data/spec/lib/http/response/body_spec.rb +5 -5
- data/spec/lib/http/response/parser_spec.rb +5 -5
- data/spec/lib/http/response_spec.rb +62 -10
- data/spec/lib/http_spec.rb +20 -2
- data/spec/spec_helper.rb +21 -21
- data/spec/support/black_hole.rb +1 -1
- data/spec/support/dummy_server/servlet.rb +14 -2
- data/spec/support/dummy_server.rb +1 -1
- data/spec/support/fuubar.rb +21 -0
- data/spec/support/simplecov.rb +19 -0
- metadata +23 -18
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -38
data/README.md
CHANGED
@@ -1,86 +1,32 @@
|
|
1
|
-
# 
|
2
2
|
|
3
|
-
[![Gem Version]
|
4
|
-
[](https://github.com/httprb/http/blob/master/LICENSE.txt)
|
3
|
+
[![Gem Version][gem-image]][gem-link]
|
4
|
+
[![MIT licensed][license-image]][license-link]
|
5
|
+
[![Build Status][build-image]][build-link]
|
6
|
+
[![Code Climate][codeclimate-image]][codeclimate-link]
|
8
7
|
|
9
8
|
[Documentation]
|
10
9
|
|
11
|
-
_NOTE: This is the 5.x **development** branch. For the 4.x **stable** branch, please see:_
|
12
|
-
|
13
|
-
https://github.com/httprb/http/tree/4-x-stable
|
14
|
-
|
15
10
|
## About
|
16
11
|
|
17
12
|
HTTP (The Gem! a.k.a. http.rb) is an easy-to-use client library for making requests
|
18
13
|
from Ruby. It uses a simple method chaining system for building requests, similar to
|
19
14
|
Python's [Requests].
|
20
15
|
|
21
|
-
Under the hood,
|
22
|
-
|
23
|
-
|
24
|
-
natively and outsources the parsing to native extensions.
|
25
|
-
|
26
|
-
[requests]: http://docs.python-requests.org/en/latest/
|
27
|
-
[http-parser]: https://github.com/nodejs/http-parser
|
28
|
-
[http-parser-ffi]: https://github.com/cotag/http-parser
|
29
|
-
|
30
|
-
|
31
|
-
## Another Ruby HTTP library? Why should I care?
|
32
|
-
|
33
|
-
There are a lot of HTTP libraries to choose from in the Ruby ecosystem.
|
34
|
-
So why would you choose this one?
|
16
|
+
Under the hood, http.rb uses the [llhttp] parser, a fast HTTP parsing native extension.
|
17
|
+
This library isn't just yet another wrapper around `Net::HTTP`. It implements the HTTP
|
18
|
+
protocol natively and outsources the parsing to native extensions.
|
35
19
|
|
36
|
-
|
20
|
+
### Why http.rb?
|
37
21
|
|
38
|
-
|
22
|
+
- **Clean API**: http.rb offers an easy-to-use API that should be a
|
39
23
|
breath of fresh air after using something like Net::HTTP.
|
40
24
|
|
41
|
-
|
25
|
+
- **Maturity**: http.rb is one of the most mature Ruby HTTP clients, supporting
|
42
26
|
features like persistent connections and fine-grained timeouts.
|
43
27
|
|
44
|
-
|
45
|
-
http.rb achieves
|
46
|
-
implements the HTTP protocol in Ruby instead of C:
|
47
|
-
|
48
|
-
| HTTP client | Time | Implementation |
|
49
|
-
|--------------------------|--------|-----------------------|
|
50
|
-
| curb (persistent) | 2.519 | libcurl wrapper |
|
51
|
-
| em-http-request | 2.731 | EM + http_parser.rb |
|
52
|
-
| Typhoeus | 2.851 | libcurl wrapper |
|
53
|
-
| StreamlyFFI (persistent) | 2.853 | libcurl wrapper |
|
54
|
-
| http.rb (persistent) | 2.970 | Ruby + http_parser.rb |
|
55
|
-
| http.rb | 3.588 | Ruby + http_parser.rb |
|
56
|
-
| HTTParty | 3.931 | Net::HTTP wrapper |
|
57
|
-
| Net::HTTP | 3.959 | Pure Ruby |
|
58
|
-
| Net::HTTP (persistent) | 4.043 | Pure Ruby |
|
59
|
-
| open-uri | 4.479 | Net::HTTP wrapper |
|
60
|
-
| Excon (persistent) | 4.618 | Pure Ruby |
|
61
|
-
| Excon | 4.701 | Pure Ruby |
|
62
|
-
| RestClient | 26.838 | Net::HTTP wrapper |
|
63
|
-
|
64
|
-
Benchmarks performed using excon's benchmarking tool
|
65
|
-
|
66
|
-
DISCLAIMER: Most benchmarks you find in READMEs are crap,
|
67
|
-
including this one. These are out-of-date. If you care about
|
68
|
-
performance, benchmark for yourself for your own use cases!
|
69
|
-
|
70
|
-
## Help and Discussion
|
71
|
-
|
72
|
-
If you need help or just want to talk about the http.rb,
|
73
|
-
visit the http.rb Google Group:
|
74
|
-
|
75
|
-
https://groups.google.com/forum/#!forum/httprb
|
76
|
-
|
77
|
-
You can join by email by sending a message to:
|
78
|
-
|
79
|
-
[httprb+subscribe@googlegroups.com](mailto:httprb+subscribe@googlegroups.com)
|
80
|
-
|
81
|
-
If you believe you've found a bug, please report it at:
|
82
|
-
|
83
|
-
https://github.com/httprb/http/issues
|
28
|
+
- **Performance**: using native parsers and a clean, lightweight implementation,
|
29
|
+
http.rb achieves high performance while implementing HTTP in Ruby instead of C.
|
84
30
|
|
85
31
|
|
86
32
|
## Installation
|
@@ -115,10 +61,9 @@ for more detailed documentation and usage notes.
|
|
115
61
|
|
116
62
|
The following API documentation is also available:
|
117
63
|
|
118
|
-
|
119
|
-
|
64
|
+
- [YARD API documentation](https://www.rubydoc.info/github/httprb/http)
|
65
|
+
- [Chainable module (all chainable methods)](https://www.rubydoc.info/github/httprb/http/HTTP/Chainable)
|
120
66
|
|
121
|
-
[documentation]: https://github.com/httprb/http/wiki
|
122
67
|
|
123
68
|
### Basic Usage
|
124
69
|
|
@@ -145,7 +90,7 @@ We can also obtain an `HTTP::Response::Body` object for this response:
|
|
145
90
|
```
|
146
91
|
|
147
92
|
The response body can be streamed with `HTTP::Response::Body#readpartial`.
|
148
|
-
In practice, you'll want to bind the HTTP::Response::Body to a local variable
|
93
|
+
In practice, you'll want to bind the `HTTP::Response::Body` to a local variable
|
149
94
|
and call `#readpartial` on it repeatedly until it returns `nil`:
|
150
95
|
|
151
96
|
```ruby
|
@@ -162,14 +107,13 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
|
|
162
107
|
|
163
108
|
## Supported Ruby Versions
|
164
109
|
|
165
|
-
This library aims to support and is [tested against][
|
166
|
-
versions:
|
110
|
+
This library aims to support and is [tested against][build-link]
|
111
|
+
the following Ruby versions:
|
167
112
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
* JRuby 9.2.x.x
|
113
|
+
- Ruby 2.6
|
114
|
+
- Ruby 2.7
|
115
|
+
- Ruby 3.0
|
116
|
+
- JRuby 9.2
|
173
117
|
|
174
118
|
If something doesn't work on one of these versions, it's a bug.
|
175
119
|
|
@@ -184,20 +128,36 @@ patches in a timely fashion. If critical issues for a particular implementation
|
|
184
128
|
exist at the time of a major release, support for that Ruby version may be
|
185
129
|
dropped.
|
186
130
|
|
187
|
-
[travis]: http://travis-ci.org/httprb/http
|
188
|
-
|
189
131
|
|
190
132
|
## Contributing to http.rb
|
191
133
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
134
|
+
- Fork http.rb on GitHub
|
135
|
+
- Make your changes
|
136
|
+
- Ensure all tests pass (`bundle exec rake`)
|
137
|
+
- Send a pull request
|
138
|
+
- If we like them we'll merge them
|
139
|
+
- If we've accepted a patch, feel free to ask for commit access!
|
198
140
|
|
199
141
|
|
200
142
|
## Copyright
|
201
143
|
|
202
|
-
Copyright
|
144
|
+
Copyright © 2011-2021 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
|
203
145
|
See LICENSE.txt for further details.
|
146
|
+
|
147
|
+
|
148
|
+
[//]: # (badges)
|
149
|
+
|
150
|
+
[gem-image]: https://img.shields.io/gem/v/http?logo=ruby
|
151
|
+
[gem-link]: https://rubygems.org/gems/http
|
152
|
+
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg
|
153
|
+
[license-link]: https://github.com/httprb/http/blob/main/LICENSE.txt
|
154
|
+
[build-image]: https://github.com/httprb/http/workflows/CI/badge.svg
|
155
|
+
[build-link]: https://github.com/httprb/http/actions/workflows/ci.yml
|
156
|
+
[codeclimate-image]: https://codeclimate.com/github/httprb/http.svg?branch=main
|
157
|
+
[codeclimate-link]: https://codeclimate.com/github/httprb/http
|
158
|
+
|
159
|
+
[//]: # (links)
|
160
|
+
|
161
|
+
[documentation]: https://github.com/httprb/http/wiki
|
162
|
+
[requests]: http://docs.python-requests.org/en/latest/
|
163
|
+
[llhttp]: https://llhttp.org/
|
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
@@ -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
32
|
gem.add_runtime_dependency "http-form_data", "~> 2.2"
|
33
|
-
gem.add_runtime_dependency "
|
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
|
|
@@ -148,7 +148,7 @@ module HTTP
|
|
148
148
|
|
149
149
|
yield p_client
|
150
150
|
ensure
|
151
|
-
p_client
|
151
|
+
p_client&.close
|
152
152
|
end
|
153
153
|
|
154
154
|
# Make a request through an HTTP proxy
|
@@ -173,7 +173,7 @@ module HTTP
|
|
173
173
|
# @param options
|
174
174
|
# @return [HTTP::Client]
|
175
175
|
# @see Redirector#initialize
|
176
|
-
def follow(options = {})
|
176
|
+
def follow(options = {})
|
177
177
|
branch default_options.with_follow options
|
178
178
|
end
|
179
179
|
|
@@ -211,10 +211,11 @@ module HTTP
|
|
211
211
|
# @option opts [#to_s] :user
|
212
212
|
# @option opts [#to_s] :pass
|
213
213
|
def basic_auth(opts)
|
214
|
-
user
|
215
|
-
pass
|
214
|
+
user = opts.fetch(:user)
|
215
|
+
pass = opts.fetch(:pass)
|
216
|
+
creds = "#{user}:#{pass}"
|
216
217
|
|
217
|
-
auth("Basic
|
218
|
+
auth("Basic #{Base64.strict_encode64(creds)}")
|
218
219
|
end
|
219
220
|
|
220
221
|
# Get options for HTTP
|
data/lib/http/client.rb
CHANGED
@@ -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,9 +66,9 @@ module HTTP
|
|
68
66
|
|
69
67
|
@state = :dirty
|
70
68
|
|
71
|
-
@connection ||= HTTP::Connection.new(req, options)
|
72
|
-
|
73
69
|
begin
|
70
|
+
@connection ||= HTTP::Connection.new(req, options)
|
71
|
+
|
74
72
|
unless @connection.failed_proxy_connect?
|
75
73
|
@connection.send_request(req)
|
76
74
|
@connection.read_headers!
|
@@ -97,13 +95,19 @@ module HTTP
|
|
97
95
|
end
|
98
96
|
|
99
97
|
def close
|
100
|
-
@connection
|
98
|
+
@connection&.close
|
101
99
|
@connection = nil
|
102
100
|
@state = :clean
|
103
101
|
end
|
104
102
|
|
105
103
|
private
|
106
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
|
+
|
107
111
|
def build_response(req, options)
|
108
112
|
Response.new(
|
109
113
|
:status => @connection.status_code,
|
@@ -120,15 +124,15 @@ module HTTP
|
|
120
124
|
def verify_connection!(uri)
|
121
125
|
if default_options.persistent? && uri.origin != default_options.persistent
|
122
126
|
raise StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"
|
127
|
+
end
|
128
|
+
|
123
129
|
# We re-create the connection object because we want to let prior requests
|
124
130
|
# lazily load the body as long as possible, and this mimics prior functionality.
|
125
|
-
|
126
|
-
|
131
|
+
return close if @connection && (!@connection.keep_alive? || @connection.expired?)
|
132
|
+
|
127
133
|
# If we get into a bad state (eg, Timeout.timeout ensure being killed)
|
128
134
|
# close the connection to prevent potential for mixed responses.
|
129
|
-
|
130
|
-
close
|
131
|
-
end
|
135
|
+
return close if @state == :dirty
|
132
136
|
end
|
133
137
|
|
134
138
|
# Merges query params if needed
|
@@ -175,7 +179,7 @@ module HTTP
|
|
175
179
|
when opts.body
|
176
180
|
opts.body
|
177
181
|
when opts.form
|
178
|
-
form =
|
182
|
+
form = make_form_data(opts.form)
|
179
183
|
headers[Headers::CONTENT_TYPE] ||= form.content_type
|
180
184
|
form
|
181
185
|
when opts.json
|
@@ -184,5 +188,12 @@ module HTTP
|
|
184
188
|
body
|
185
189
|
end
|
186
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
|
187
198
|
end
|
188
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
|
@@ -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
|
|
data/lib/http/content_type.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTP
|
4
|
-
ContentType
|
4
|
+
class ContentType
|
5
5
|
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
|
6
6
|
CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
|
7
7
|
|
8
|
+
attr_accessor :mime_type, :charset
|
9
|
+
|
8
10
|
class << self
|
9
11
|
# Parse string and return ContentType struct
|
10
12
|
def parse(str)
|
@@ -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
@@ -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
|
@@ -9,6 +9,20 @@ module HTTP
|
|
9
9
|
# HTTP.use(logging: {logger: Logger.new(STDOUT)}).get("https://example.com/")
|
10
10
|
#
|
11
11
|
class Logging < Feature
|
12
|
+
HTTP::Options.register_feature(:logging, self)
|
13
|
+
|
14
|
+
class NullLogger
|
15
|
+
%w[fatal error warn info debug].each do |level|
|
16
|
+
define_method(level.to_sym) do |*_args|
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method(:"#{level}?") do
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
12
26
|
attr_reader :logger
|
13
27
|
|
14
28
|
def initialize(logger: NullLogger.new)
|
@@ -17,39 +31,23 @@ module HTTP
|
|
17
31
|
|
18
32
|
def wrap_request(request)
|
19
33
|
logger.info { "> #{request.verb.to_s.upcase} #{request.uri}" }
|
20
|
-
logger.debug
|
21
|
-
headers = request.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
|
22
|
-
body = request.body.source
|
34
|
+
logger.debug { "#{stringify_headers(request.headers)}\n\n#{request.body.source}" }
|
23
35
|
|
24
|
-
headers + "\n\n" + body.to_s
|
25
|
-
end
|
26
36
|
request
|
27
37
|
end
|
28
38
|
|
29
39
|
def wrap_response(response)
|
30
40
|
logger.info { "< #{response.status}" }
|
31
|
-
logger.debug
|
32
|
-
headers = response.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
|
33
|
-
body = response.body.to_s
|
41
|
+
logger.debug { "#{stringify_headers(response.headers)}\n\n#{response.body}" }
|
34
42
|
|
35
|
-
headers + "\n\n" + body
|
36
|
-
end
|
37
43
|
response
|
38
44
|
end
|
39
45
|
|
40
|
-
|
41
|
-
%w[fatal error warn info debug].each do |level|
|
42
|
-
define_method(level.to_sym) do |*_args|
|
43
|
-
nil
|
44
|
-
end
|
46
|
+
private
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
end
|
48
|
+
def stringify_headers(headers)
|
49
|
+
headers.map { |name, value| "#{name}: #{value}" }.join("\n")
|
50
50
|
end
|
51
|
-
|
52
|
-
HTTP::Options.register_feature(:logging, self)
|
53
51
|
end
|
54
52
|
end
|
55
53
|
end
|
data/lib/http/headers.rb
CHANGED
@@ -17,7 +17,7 @@ module HTTP
|
|
17
17
|
|
18
18
|
# Matches valid header field name according to RFC.
|
19
19
|
# @see http://tools.ietf.org/html/rfc7230#section-3.2
|
20
|
-
COMPLIANT_NAME_RE = /\A[A-Za-z0-9
|
20
|
+
COMPLIANT_NAME_RE = /\A[A-Za-z0-9!#$%&'*+\-.^_`|~]+\z/.freeze
|
21
21
|
|
22
22
|
# Class constructor.
|
23
23
|
def initialize
|
@@ -111,7 +111,7 @@ module HTTP
|
|
111
111
|
#
|
112
112
|
# @return [Hash]
|
113
113
|
def to_h
|
114
|
-
|
114
|
+
keys.map { |k| [k, self[k]] }.to_h
|
115
115
|
end
|
116
116
|
alias to_hash to_h
|
117
117
|
|
@@ -164,7 +164,7 @@ module HTTP
|
|
164
164
|
|
165
165
|
# @!method hash
|
166
166
|
# Compute a hash-code for this headers container.
|
167
|
-
# Two
|
167
|
+
# Two containers with the same content will have the same hash code.
|
168
168
|
#
|
169
169
|
# @see http://www.ruby-doc.org/core/Object.html#method-i-hash
|
170
170
|
# @return [Fixnum]
|
@@ -14,6 +14,7 @@ module HTTP
|
|
14
14
|
def_delegators :instance, :encode, :decode
|
15
15
|
end
|
16
16
|
|
17
|
+
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
17
18
|
%w[encode decode].each do |operation|
|
18
19
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
19
20
|
def #{operation}(*)
|
@@ -21,6 +22,7 @@ module HTTP
|
|
21
22
|
end
|
22
23
|
RUBY
|
23
24
|
end
|
25
|
+
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
data/lib/http/options.rb
CHANGED
@@ -32,7 +32,7 @@ module HTTP
|
|
32
32
|
|
33
33
|
def def_option(name, reader_only: false, &interpreter)
|
34
34
|
defined_options << name.to_sym
|
35
|
-
interpreter ||=
|
35
|
+
interpreter ||= ->(v) { v }
|
36
36
|
|
37
37
|
if reader_only
|
38
38
|
attr_reader name
|
@@ -47,7 +47,7 @@ module HTTP
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
def initialize(options = {})
|
50
|
+
def initialize(options = {})
|
51
51
|
defaults = {
|
52
52
|
:response => :auto,
|
53
53
|
:proxy => {},
|
data/lib/http/redirector.rb
CHANGED
@@ -39,7 +39,7 @@ module HTTP
|
|
39
39
|
# @param [Hash] opts
|
40
40
|
# @option opts [Boolean] :strict (true) redirector hops policy
|
41
41
|
# @option opts [#to_i] :max_hops (5) maximum allowed amount of hops
|
42
|
-
def initialize(opts = {})
|
42
|
+
def initialize(opts = {})
|
43
43
|
@strict = opts.fetch(:strict, true)
|
44
44
|
@max_hops = opts.fetch(:max_hops, 5).to_i
|
45
45
|
end
|