http 4.4.1 → 5.1.0
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 +205 -0
- data/.yardopts +1 -1
- data/CHANGES.md +188 -3
- data/Gemfile +18 -10
- data/LICENSE.txt +1 -1
- data/README.md +47 -86
- data/Rakefile +2 -10
- data/SECURITY.md +5 -0
- data/http.gemspec +9 -8
- data/lib/http/chainable.rb +23 -17
- data/lib/http/client.rb +44 -34
- data/lib/http/connection.rb +11 -7
- data/lib/http/content_type.rb +12 -7
- data/lib/http/errors.rb +3 -0
- data/lib/http/feature.rb +3 -1
- data/lib/http/features/auto_deflate.rb +6 -6
- 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 +5 -8
- data/lib/http/redirector.rb +51 -2
- data/lib/http/request/body.rb +1 -0
- data/lib/http/request/writer.rb +9 -4
- 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 +74 -62
- data/lib/http/response/status.rb +4 -3
- data/lib/http/response.rb +44 -18
- data/lib/http/timeout/global.rb +20 -36
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +32 -55
- data/lib/http/uri.rb +5 -5
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +153 -30
- 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 +107 -3
- data/spec/lib/http/request/body_spec.rb +3 -3
- 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 +33 -4
- data/spec/lib/http/response/status_spec.rb +3 -3
- data/spec/lib/http/response_spec.rb +80 -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 +5 -5
- data/spec/support/simplecov.rb +19 -0
- data/spec/support/ssl_helper.rb +4 -4
- metadata +23 -15
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -39
data/Gemfile
CHANGED
@@ -5,30 +5,38 @@ ruby RUBY_VERSION
|
|
5
5
|
|
6
6
|
gem "rake"
|
7
7
|
|
8
|
+
# Ruby 3.0 does not ship it anymore.
|
9
|
+
# TODO: We should probably refactor specs to avoid need for it.
|
10
|
+
gem "webrick"
|
11
|
+
|
8
12
|
group :development do
|
9
13
|
gem "guard-rspec", :require => false
|
10
14
|
gem "nokogiri", :require => false
|
11
15
|
gem "pry", :require => false
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
# RSpec formatter
|
18
|
+
gem "fuubar", :require => false
|
19
|
+
|
20
|
+
platform :mri do
|
21
|
+
gem "pry-byebug"
|
16
22
|
end
|
17
23
|
end
|
18
24
|
|
19
25
|
group :test do
|
20
|
-
gem "
|
21
|
-
gem "certificate_authority", :require => false
|
26
|
+
gem "certificate_authority", "~> 1.0", :require => false
|
22
27
|
|
23
28
|
gem "backports"
|
24
29
|
|
25
|
-
gem "
|
26
|
-
gem "
|
30
|
+
gem "rubocop", "~> 1.30.0"
|
31
|
+
gem "rubocop-performance"
|
32
|
+
gem "rubocop-rake"
|
33
|
+
gem "rubocop-rspec"
|
27
34
|
|
28
|
-
gem "
|
29
|
-
gem "
|
35
|
+
gem "simplecov", :require => false
|
36
|
+
gem "simplecov-lcov", :require => false
|
30
37
|
|
31
|
-
gem "
|
38
|
+
gem "rspec", "~> 3.10"
|
39
|
+
gem "rspec-its"
|
32
40
|
|
33
41
|
gem "yardstick"
|
34
42
|
end
|
data/LICENSE.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2011-
|
1
|
+
Copyright (c) 2011-2022 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,85 +1,32 @@
|
|
1
|
-
# 
|
2
2
|
|
3
|
-
[![Gem Version]
|
4
|
-
[](https://github.com/httprb/http/blob/4-x-stable/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 4.x **stable** branch. For the 3.x **stable** branch, please see:_
|
12
|
-
|
13
|
-
https://github.com/httprb/http/tree/3-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, http.rb uses [
|
22
|
-
|
23
|
-
|
24
|
-
natively and outsources the parsing to native extensions.
|
25
|
-
|
26
|
-
[requests]: http://docs.python-requests.org/en/latest/
|
27
|
-
[http_parser.rb]: https://github.com/tmm1/http_parser.rb
|
28
|
-
|
29
|
-
|
30
|
-
## Another Ruby HTTP library? Why should I care?
|
31
|
-
|
32
|
-
There are a lot of HTTP libraries to choose from in the Ruby ecosystem.
|
33
|
-
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.
|
34
19
|
|
35
|
-
|
20
|
+
### Why http.rb?
|
36
21
|
|
37
|
-
|
22
|
+
- **Clean API**: http.rb offers an easy-to-use API that should be a
|
38
23
|
breath of fresh air after using something like Net::HTTP.
|
39
24
|
|
40
|
-
|
25
|
+
- **Maturity**: http.rb is one of the most mature Ruby HTTP clients, supporting
|
41
26
|
features like persistent connections and fine-grained timeouts.
|
42
27
|
|
43
|
-
|
44
|
-
http.rb achieves
|
45
|
-
implements the HTTP protocol in Ruby instead of C:
|
46
|
-
|
47
|
-
| HTTP client | Time | Implementation |
|
48
|
-
|--------------------------|--------|-----------------------|
|
49
|
-
| curb (persistent) | 2.519 | libcurl wrapper |
|
50
|
-
| em-http-request | 2.731 | EM + http_parser.rb |
|
51
|
-
| Typhoeus | 2.851 | libcurl wrapper |
|
52
|
-
| StreamlyFFI (persistent) | 2.853 | libcurl wrapper |
|
53
|
-
| http.rb (persistent) | 2.970 | Ruby + http_parser.rb |
|
54
|
-
| http.rb | 3.588 | Ruby + http_parser.rb |
|
55
|
-
| HTTParty | 3.931 | Net::HTTP wrapper |
|
56
|
-
| Net::HTTP | 3.959 | Pure Ruby |
|
57
|
-
| Net::HTTP (persistent) | 4.043 | Pure Ruby |
|
58
|
-
| open-uri | 4.479 | Net::HTTP wrapper |
|
59
|
-
| Excon (persistent) | 4.618 | Pure Ruby |
|
60
|
-
| Excon | 4.701 | Pure Ruby |
|
61
|
-
| RestClient | 26.838 | Net::HTTP wrapper |
|
62
|
-
|
63
|
-
Benchmarks performed using excon's benchmarking tool
|
64
|
-
|
65
|
-
DISCLAIMER: Most benchmarks you find in READMEs are crap,
|
66
|
-
including this one. These are out-of-date. If you care about
|
67
|
-
performance, benchmark for yourself for your own use cases!
|
68
|
-
|
69
|
-
## Help and Discussion
|
70
|
-
|
71
|
-
If you need help or just want to talk about the http.rb,
|
72
|
-
visit the http.rb Google Group:
|
73
|
-
|
74
|
-
https://groups.google.com/forum/#!forum/httprb
|
75
|
-
|
76
|
-
You can join by email by sending a message to:
|
77
|
-
|
78
|
-
[httprb+subscribe@googlegroups.com](mailto:httprb+subscribe@googlegroups.com)
|
79
|
-
|
80
|
-
If you believe you've found a bug, please report it at:
|
81
|
-
|
82
|
-
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.
|
83
30
|
|
84
31
|
|
85
32
|
## Installation
|
@@ -114,10 +61,9 @@ for more detailed documentation and usage notes.
|
|
114
61
|
|
115
62
|
The following API documentation is also available:
|
116
63
|
|
117
|
-
|
118
|
-
|
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)
|
119
66
|
|
120
|
-
[documentation]: https://github.com/httprb/http/wiki
|
121
67
|
|
122
68
|
### Basic Usage
|
123
69
|
|
@@ -144,7 +90,7 @@ We can also obtain an `HTTP::Response::Body` object for this response:
|
|
144
90
|
```
|
145
91
|
|
146
92
|
The response body can be streamed with `HTTP::Response::Body#readpartial`.
|
147
|
-
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
|
148
94
|
and call `#readpartial` on it repeatedly until it returns `nil`:
|
149
95
|
|
150
96
|
```ruby
|
@@ -161,14 +107,13 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
|
|
161
107
|
|
162
108
|
## Supported Ruby Versions
|
163
109
|
|
164
|
-
This library aims to support and is [tested against][
|
165
|
-
versions:
|
110
|
+
This library aims to support and is [tested against][build-link]
|
111
|
+
the following Ruby versions:
|
166
112
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
* JRuby 9.2.x.x
|
113
|
+
- Ruby 2.6
|
114
|
+
- Ruby 2.7
|
115
|
+
- Ruby 3.0
|
116
|
+
- JRuby 9.2
|
172
117
|
|
173
118
|
If something doesn't work on one of these versions, it's a bug.
|
174
119
|
|
@@ -183,20 +128,36 @@ patches in a timely fashion. If critical issues for a particular implementation
|
|
183
128
|
exist at the time of a major release, support for that Ruby version may be
|
184
129
|
dropped.
|
185
130
|
|
186
|
-
[travis]: http://travis-ci.org/httprb/http
|
187
|
-
|
188
131
|
|
189
132
|
## Contributing to http.rb
|
190
133
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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!
|
197
140
|
|
198
141
|
|
199
142
|
## Copyright
|
200
143
|
|
201
|
-
Copyright
|
144
|
+
Copyright © 2011-2022 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
|
202
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/SECURITY.md
ADDED
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,19 +25,20 @@ 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.6"
|
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
|
|
37
37
|
gem.metadata = {
|
38
|
-
"source_code_uri"
|
39
|
-
"wiki_uri"
|
40
|
-
"bug_tracker_uri"
|
41
|
-
"changelog_uri"
|
38
|
+
"source_code_uri" => "https://github.com/httprb/http",
|
39
|
+
"wiki_uri" => "https://github.com/httprb/http/wiki",
|
40
|
+
"bug_tracker_uri" => "https://github.com/httprb/http/issues",
|
41
|
+
"changelog_uri" => "https://github.com/httprb/http/blob/v#{HTTP::VERSION}/CHANGES.md",
|
42
|
+
"rubygems_mfa_required" => "true"
|
42
43
|
}
|
43
44
|
end
|
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:
|
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
|
|
@@ -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
|