http 0.8.3 → 0.8.4
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/.rubocop.yml +2 -2
- data/CHANGES.md +10 -0
- data/README.md +36 -39
- data/lib/http/cache/headers.rb +8 -8
- data/lib/http/chainable.rb +17 -4
- data/lib/http/client.rb +5 -4
- data/lib/http/connection.rb +35 -5
- data/lib/http/options.rb +4 -4
- data/lib/http/request.rb +27 -3
- data/lib/http/request/caching.rb +5 -5
- data/lib/http/request/writer.rb +7 -2
- data/lib/http/response/caching.rb +3 -3
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/cache_spec.rb +6 -6
- data/spec/lib/http/client_spec.rb +13 -13
- data/spec/lib/http/headers_spec.rb +16 -16
- data/spec/lib/http/redirector_spec.rb +24 -24
- data/spec/lib/http/response_spec.rb +2 -2
- data/spec/lib/http_spec.rb +38 -4
- data/spec/support/http_handling_shared.rb +2 -2
- metadata +40 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b829e0c5ca1c70ca3f55b33080088ab248c52fc
|
4
|
+
data.tar.gz: 248df6f0b8df5a75bfa872d5f24a263568f63e5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4e37035a7a6b692d103a24e6f75a30509a5d2a92aa1b4c21a2c0a393886c648c5b28df3e36779aa274430243dbde29af69ea4eaff25c5ff4aecd3c880bc0068
|
7
|
+
data.tar.gz: 76fae19bd0b8f31e9825a3514e21f83f006bc205c2e118ec4fec8ebd644900d3bbbf2f7119dc800ac4782b4ba9665be64a588083585d9bf12bae36e97188c69b
|
data/.rubocop.yml
CHANGED
@@ -3,7 +3,7 @@ Metrics/BlockNesting:
|
|
3
3
|
|
4
4
|
Metrics/ClassLength:
|
5
5
|
CountComments: false
|
6
|
-
Max:
|
6
|
+
Max: 120
|
7
7
|
|
8
8
|
Metrics/PerceivedComplexity:
|
9
9
|
Max: 8
|
@@ -37,7 +37,7 @@ Style/Documentation:
|
|
37
37
|
Enabled: false
|
38
38
|
|
39
39
|
Style/DotPosition:
|
40
|
-
EnforcedStyle:
|
40
|
+
EnforcedStyle: trailing
|
41
41
|
|
42
42
|
Style/DoubleNegation:
|
43
43
|
Enabled: false
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## master (unreleased)
|
2
|
+
|
3
|
+
|
4
|
+
## 0.8.4 (2015-04-23)
|
5
|
+
|
6
|
+
* Deprecate `#default_headers` and `#default_headers=`. (@ixti)
|
7
|
+
* Deprecate chainable methods with `with_` prefix. See #207. (@ixti)
|
8
|
+
* Add support of HTTPS connections through proxy. See #186. (@Connorhd)
|
9
|
+
|
10
|
+
|
1
11
|
## 0.8.3 (2015-04-07)
|
2
12
|
|
3
13
|
* Fix request headline. See #206. (@ixti)
|
data/README.md
CHANGED
@@ -40,7 +40,7 @@ Installation
|
|
40
40
|
|
41
41
|
Add this line to your application's Gemfile:
|
42
42
|
|
43
|
-
gem
|
43
|
+
gem "http"
|
44
44
|
|
45
45
|
And then execute:
|
46
46
|
|
@@ -52,7 +52,7 @@ Or install it yourself as:
|
|
52
52
|
|
53
53
|
Inside of your Ruby program do:
|
54
54
|
|
55
|
-
require
|
55
|
+
require "http"
|
56
56
|
|
57
57
|
...to pull it in as a dependency.
|
58
58
|
|
@@ -70,7 +70,7 @@ Here's some simple examples to get you started:
|
|
70
70
|
### GET requests
|
71
71
|
|
72
72
|
```ruby
|
73
|
-
>> HTTP.get(
|
73
|
+
>> HTTP.get("https://github.com").to_s
|
74
74
|
=> "<html><head><meta http-equiv=\"content-type\" content=..."
|
75
75
|
```
|
76
76
|
|
@@ -78,7 +78,7 @@ That's all it takes! To obtain an `HTTP::Response` object instead of the respons
|
|
78
78
|
body, all we have to do is omit the #to_s on the end:
|
79
79
|
|
80
80
|
```ruby
|
81
|
-
>> HTTP.get(
|
81
|
+
>> HTTP.get("https://github.com")
|
82
82
|
=> #<HTTP/1.0 200 OK @headers={"Content-Type"=>"text/html; charset=UTF-8", "Date"=>"Fri, ...>
|
83
83
|
=> #<HTTP::Response/1.1 200 OK @headers={"Content-Type"=>"text/html; ...>
|
84
84
|
```
|
@@ -86,51 +86,51 @@ body, all we have to do is omit the #to_s on the end:
|
|
86
86
|
We can also obtain an `HTTP::Response::Body` object for this response:
|
87
87
|
|
88
88
|
```ruby
|
89
|
-
>> HTTP.get(
|
89
|
+
>> HTTP.get("https://github.com").body
|
90
90
|
=> #<HTTP::Response::Body:814d7aac @streaming=false>
|
91
91
|
```
|
92
92
|
|
93
93
|
The response body can be streamed with `HTTP::Response::Body#readpartial`:
|
94
94
|
|
95
95
|
```ruby
|
96
|
-
>> HTTP.get(
|
96
|
+
>> HTTP.get("https://github.com").body.readpartial
|
97
97
|
=> "<!doctype html><html "
|
98
98
|
```
|
99
99
|
|
100
100
|
In practice you'll want to bind the HTTP::Response::Body to a local variable (e.g.
|
101
|
-
"body") and call readpartial on it repeatedly until it returns nil
|
101
|
+
"body") and call readpartial on it repeatedly until it returns `nil`.
|
102
102
|
|
103
103
|
### POST requests
|
104
104
|
|
105
105
|
Making POST requests is simple too. Want to POST a form?
|
106
106
|
|
107
107
|
```ruby
|
108
|
-
HTTP.post(
|
108
|
+
HTTP.post("http://example.com/resource", :form => {:foo => "42"})
|
109
109
|
```
|
110
110
|
Making GET requests with query string parameters is as simple.
|
111
111
|
|
112
112
|
```ruby
|
113
|
-
HTTP.get(
|
113
|
+
HTTP.get("http://example.com/resource", :params => {:foo => "bar"})
|
114
114
|
```
|
115
115
|
|
116
116
|
Want to POST with a specific body, JSON for instance?
|
117
117
|
|
118
118
|
```ruby
|
119
|
-
HTTP.post(
|
119
|
+
HTTP.post("http://example.com/resource", :json => { :foo => "42" })
|
120
120
|
```
|
121
121
|
|
122
122
|
Or just a plain body?
|
123
123
|
|
124
124
|
```ruby
|
125
|
-
HTTP.post(
|
125
|
+
HTTP.post("http://example.com/resource", :body => "foo=42&bar=baz")
|
126
126
|
```
|
127
127
|
|
128
128
|
Posting a file?
|
129
129
|
|
130
130
|
``` ruby
|
131
|
-
HTTP.post(
|
132
|
-
:username =>
|
133
|
-
:avatar => HTTP::FormData::File.new(
|
131
|
+
HTTP.post("http://examplc.com/resource", :form => {
|
132
|
+
:username => "ixti",
|
133
|
+
:avatar => HTTP::FormData::File.new("/home/ixit/avatar.png")
|
134
134
|
})
|
135
135
|
```
|
136
136
|
|
@@ -142,15 +142,15 @@ Making request behind proxy is as simple as making them directly. Just specify
|
|
142
142
|
hostname (or IP address) of your proxy server and its port, and here you go:
|
143
143
|
|
144
144
|
```ruby
|
145
|
-
HTTP.via(
|
146
|
-
.get(
|
145
|
+
HTTP.via("proxy-hostname.local", 8080)
|
146
|
+
.get("http://example.com/resource")
|
147
147
|
```
|
148
148
|
|
149
149
|
Proxy needs authentication? No problem:
|
150
150
|
|
151
151
|
```ruby
|
152
|
-
HTTP.via(
|
153
|
-
.get(
|
152
|
+
HTTP.via("proxy-hostname.local", 8080, "username", "password")
|
153
|
+
.get("http://example.com/resource")
|
154
154
|
```
|
155
155
|
|
156
156
|
### Adding Headers
|
@@ -160,7 +160,7 @@ you want to get the latest commit of this library from GitHub in JSON format.
|
|
160
160
|
One way we could do this is by tacking a filename on the end of the URL:
|
161
161
|
|
162
162
|
```ruby
|
163
|
-
HTTP.get(
|
163
|
+
HTTP.get("https://github.com/httprb/http.rb/commit/HEAD.json")
|
164
164
|
```
|
165
165
|
|
166
166
|
The GitHub API happens to support this approach, but really this is a bit of a
|
@@ -170,21 +170,18 @@ the full, raw power of HTTP, we can perform content negotiation the way HTTP
|
|
170
170
|
intends us to, by using the Accept header:
|
171
171
|
|
172
172
|
```ruby
|
173
|
-
HTTP.
|
174
|
-
get(
|
173
|
+
HTTP.headers(:accept => "application/json")
|
174
|
+
.get("https://github.com/httprb/http.rb/commit/HEAD")
|
175
175
|
```
|
176
176
|
|
177
177
|
This requests JSON from GitHub. GitHub is smart enough to understand our
|
178
|
-
request and returns a response with Content-Type: application/json
|
178
|
+
request and returns a response with `Content-Type: application/json`.
|
179
179
|
|
180
|
-
Shorter
|
180
|
+
Shorter alias exists for `HTTP.headers`:
|
181
181
|
|
182
182
|
```ruby
|
183
|
-
HTTP
|
184
|
-
get(
|
185
|
-
|
186
|
-
HTTP[:accept => 'application/json'].
|
187
|
-
get('https://github.com/httprb/http.rb/commit/HEAD')
|
183
|
+
HTTP[:accept => "application/json"]
|
184
|
+
.get("https://github.com/httprb/http.rb/commit/HEAD")
|
188
185
|
```
|
189
186
|
|
190
187
|
### Authorization Header
|
@@ -193,23 +190,23 @@ With [HTTP Basic Authentication](http://tools.ietf.org/html/rfc2617) using
|
|
193
190
|
a username and password:
|
194
191
|
|
195
192
|
```ruby
|
196
|
-
HTTP.basic_auth(:user =>
|
193
|
+
HTTP.basic_auth(:user => "user", :pass => "pass")
|
197
194
|
# <HTTP::Headers {"Authorization"=>"Basic dXNlcjpwYXNz"}>
|
198
195
|
```
|
199
196
|
|
200
197
|
Or with plain as-is value:
|
201
198
|
|
202
199
|
```ruby
|
203
|
-
HTTP.auth(
|
200
|
+
HTTP.auth("Bearer VGhlIEhUVFAgR2VtLCBST0NLUw")
|
204
201
|
# <HTTP::Headers {"Authorization"=>"Bearer VGhlIEhUVFAgR2VtLCBST0NLUw"}>
|
205
202
|
```
|
206
203
|
|
207
204
|
And Chain all together!
|
208
205
|
|
209
206
|
```ruby
|
210
|
-
HTTP.basic_auth(:user =>
|
211
|
-
.
|
212
|
-
.get(
|
207
|
+
HTTP.basic_auth(:user => "user", :pass => "pass")
|
208
|
+
.headers("Cookie" => "9wq3w")
|
209
|
+
.get("https://example.com")
|
213
210
|
```
|
214
211
|
|
215
212
|
### Content Negotiation
|
@@ -219,7 +216,7 @@ right? But usually it's not, and so we end up adding ".json" onto the ends of
|
|
219
216
|
our URLs because the existing mechanisms make it too hard. It should be easy:
|
220
217
|
|
221
218
|
```ruby
|
222
|
-
HTTP.accept(:json).get(
|
219
|
+
HTTP.accept(:json).get("https://github.com/httprb/http.rb/commit/HEAD")
|
223
220
|
```
|
224
221
|
|
225
222
|
This adds the appropriate Accept header for retrieving a JSON response for the
|
@@ -232,8 +229,8 @@ Celluloid::IO actor. Here's a parallel HTTP fetcher combining http.rb with
|
|
232
229
|
Celluloid::IO:
|
233
230
|
|
234
231
|
```ruby
|
235
|
-
require
|
236
|
-
require
|
232
|
+
require "celluloid/io"
|
233
|
+
require "http"
|
237
234
|
|
238
235
|
class HttpFetcher
|
239
236
|
include Celluloid::IO
|
@@ -256,10 +253,10 @@ http.rb provides caching of HTTP request (per
|
|
256
253
|
so.
|
257
254
|
|
258
255
|
```ruby
|
259
|
-
require
|
256
|
+
require "http"
|
260
257
|
|
261
|
-
http = HTTP.
|
262
|
-
|
258
|
+
http = HTTP.cache(:metastore => "file:/var/cache/my-app-http/meta",
|
259
|
+
:entitystore => "file:/var/cache/my-app-http/entity")
|
263
260
|
|
264
261
|
http.get("http://example.com/") # makes request
|
265
262
|
http.get("http://example.com/") # skips making request and returns
|
data/lib/http/cache/headers.rb
CHANGED
@@ -69,9 +69,9 @@ module HTTP
|
|
69
69
|
# ---
|
70
70
|
# Some servers send a "Expire: -1" header which must be treated as expired
|
71
71
|
def seconds_til_expires
|
72
|
-
get("Expires")
|
73
|
-
|
74
|
-
|
72
|
+
get("Expires").
|
73
|
+
map { |e| http_date_to_ttl(e) }.
|
74
|
+
max
|
75
75
|
end
|
76
76
|
|
77
77
|
def http_date_to_ttl(t_str)
|
@@ -89,11 +89,11 @@ module HTTP
|
|
89
89
|
|
90
90
|
# @return [Numeric] the value of the max-age component of cache control
|
91
91
|
def explicit_max_age
|
92
|
-
get("Cache-Control")
|
93
|
-
|
94
|
-
.
|
95
|
-
|
96
|
-
|
92
|
+
get("Cache-Control").
|
93
|
+
map { |v| (/max-age=(\d+)/i).match(v) }.
|
94
|
+
compact.
|
95
|
+
map { |m| m[1].to_i }.
|
96
|
+
max
|
97
97
|
end
|
98
98
|
end
|
99
99
|
end
|
data/lib/http/chainable.rb
CHANGED
@@ -140,20 +140,31 @@ module HTTP
|
|
140
140
|
branch default_options.with_follow opts
|
141
141
|
end
|
142
142
|
|
143
|
-
# @deprecated
|
143
|
+
# @deprecated will be removed in 1.0.0
|
144
144
|
# @see #follow
|
145
145
|
alias_method :with_follow, :follow
|
146
146
|
|
147
|
-
def
|
147
|
+
def cache(cache)
|
148
148
|
branch default_options.with_cache(cache)
|
149
149
|
end
|
150
150
|
|
151
|
+
# @deprecated will be removed in 1.0.0
|
152
|
+
# @see #cache
|
153
|
+
alias_method :with_cache, :cache
|
154
|
+
|
151
155
|
# Make a request with the given headers
|
152
156
|
# @param headers
|
153
|
-
def
|
157
|
+
def headers(headers)
|
154
158
|
branch default_options.with_headers(headers)
|
155
159
|
end
|
156
|
-
|
160
|
+
|
161
|
+
# @deprecated will be removed in 1.0.0
|
162
|
+
# @see #headers
|
163
|
+
alias_method :with, :headers
|
164
|
+
|
165
|
+
# @deprecated will be removed in 1.0.0
|
166
|
+
# @see #headers
|
167
|
+
alias_method :with_headers, :headers
|
157
168
|
|
158
169
|
# Accept the given MIME type(s)
|
159
170
|
# @param type
|
@@ -195,12 +206,14 @@ module HTTP
|
|
195
206
|
@default_options = HTTP::Options.new(opts)
|
196
207
|
end
|
197
208
|
|
209
|
+
# @deprecated Will be removed in 1.0.0; Use `#default_options#headers`
|
198
210
|
# Get headers of HTTP options
|
199
211
|
def default_headers
|
200
212
|
default_options.headers
|
201
213
|
end
|
202
214
|
|
203
215
|
# Set headers of HTTP options
|
216
|
+
# @deprecated Will be removed in 1.0.0; Use `#headers`
|
204
217
|
# @param headers
|
205
218
|
def default_headers=(headers)
|
206
219
|
@default_options = default_options.dup do |opts|
|
data/lib/http/client.rb
CHANGED
@@ -21,8 +21,6 @@ module HTTP
|
|
21
21
|
|
22
22
|
HTTP_OR_HTTPS_RE = %r{^https?://}i
|
23
23
|
|
24
|
-
attr_reader :default_options
|
25
|
-
|
26
24
|
def initialize(default_options = {})
|
27
25
|
@default_options = HTTP::Options.new(default_options)
|
28
26
|
@connection = nil
|
@@ -72,8 +70,11 @@ module HTTP
|
|
72
70
|
@state = :dirty
|
73
71
|
|
74
72
|
@connection ||= HTTP::Connection.new(req, options)
|
75
|
-
|
76
|
-
@connection.
|
73
|
+
|
74
|
+
unless @connection.failed_proxy_connect?
|
75
|
+
@connection.send_request(req)
|
76
|
+
@connection.read_headers!
|
77
|
+
end
|
77
78
|
|
78
79
|
res = Response.new(
|
79
80
|
@connection.status_code,
|
data/lib/http/connection.rb
CHANGED
@@ -6,6 +6,7 @@ module HTTP
|
|
6
6
|
# A connection to the HTTP server
|
7
7
|
class Connection
|
8
8
|
extend Forwardable
|
9
|
+
|
9
10
|
# Attempt to read this much data
|
10
11
|
BUFFER_SIZE = 16_384
|
11
12
|
|
@@ -18,16 +19,18 @@ module HTTP
|
|
18
19
|
# @param [HTTP::Request] req
|
19
20
|
# @param [HTTP::Options] options
|
20
21
|
def initialize(req, options)
|
21
|
-
@persistent
|
22
|
-
@keep_alive_timeout
|
23
|
-
@pending_request
|
24
|
-
@pending_response
|
22
|
+
@persistent = options.persistent?
|
23
|
+
@keep_alive_timeout = options[:keep_alive_timeout].to_f
|
24
|
+
@pending_request = false
|
25
|
+
@pending_response = false
|
26
|
+
@failed_proxy_connect = false
|
25
27
|
|
26
28
|
@parser = Response::Parser.new
|
27
29
|
|
28
30
|
@socket = options[:timeout_class].new(options[:timeout_options])
|
29
31
|
@socket.connect(options[:socket_class], req.socket_host, req.socket_port)
|
30
32
|
|
33
|
+
send_proxy_connect_request(req)
|
31
34
|
start_tls(req, options)
|
32
35
|
reset_timer
|
33
36
|
end
|
@@ -41,6 +44,11 @@ module HTTP
|
|
41
44
|
# @see (HTTP::Response::Parser#headers)
|
42
45
|
def_delegator :@parser, :headers
|
43
46
|
|
47
|
+
# @return [Boolean] whenever proxy connect failed
|
48
|
+
def failed_proxy_connect?
|
49
|
+
@failed_proxy_connect
|
50
|
+
end
|
51
|
+
|
44
52
|
# Send a request to the server
|
45
53
|
#
|
46
54
|
# @param [Request] Request to send to the server
|
@@ -129,7 +137,7 @@ module HTTP
|
|
129
137
|
# @param (see #initialize)
|
130
138
|
# @return [void]
|
131
139
|
def start_tls(req, options)
|
132
|
-
return unless req.uri.https? && !
|
140
|
+
return unless req.uri.https? && !failed_proxy_connect?
|
133
141
|
|
134
142
|
ssl_context = options[:ssl_context]
|
135
143
|
|
@@ -141,6 +149,28 @@ module HTTP
|
|
141
149
|
@socket.start_tls(req.uri.host, options[:ssl_socket_class], ssl_context)
|
142
150
|
end
|
143
151
|
|
152
|
+
# Open tunnel through proxy
|
153
|
+
def send_proxy_connect_request(req)
|
154
|
+
return unless req.uri.https? && req.using_proxy?
|
155
|
+
|
156
|
+
@pending_request = true
|
157
|
+
|
158
|
+
req.connect_using_proxy @socket
|
159
|
+
|
160
|
+
@pending_request = false
|
161
|
+
@pending_response = true
|
162
|
+
|
163
|
+
read_headers!
|
164
|
+
|
165
|
+
if @parser.status_code == 200
|
166
|
+
@parser.reset
|
167
|
+
@pending_response = false
|
168
|
+
return
|
169
|
+
end
|
170
|
+
|
171
|
+
@failed_proxy_connect = true
|
172
|
+
end
|
173
|
+
|
144
174
|
# Resets expiration of persistent connection.
|
145
175
|
# @return [void]
|
146
176
|
def reset_timer
|
data/lib/http/options.rb
CHANGED
@@ -30,7 +30,7 @@ module HTTP
|
|
30
30
|
|
31
31
|
def def_option(name, &interpreter)
|
32
32
|
defined_options << name.to_sym
|
33
|
-
interpreter ||=
|
33
|
+
interpreter ||= lambda { |v| v }
|
34
34
|
|
35
35
|
attr_accessor name
|
36
36
|
protected :"#{name}="
|
@@ -118,9 +118,9 @@ module HTTP
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def to_hash
|
121
|
-
hash_pairs = self.class
|
122
|
-
.
|
123
|
-
|
121
|
+
hash_pairs = self.class.
|
122
|
+
defined_options.
|
123
|
+
flat_map { |opt_name| [opt_name, self[opt_name]] }
|
124
124
|
Hash[*hash_pairs]
|
125
125
|
end
|
126
126
|
|
data/lib/http/request.rb
CHANGED
@@ -90,7 +90,7 @@ module HTTP
|
|
90
90
|
|
91
91
|
# Stream the request to a socket
|
92
92
|
def stream(socket)
|
93
|
-
include_proxy_authorization_header if using_authenticated_proxy?
|
93
|
+
include_proxy_authorization_header if using_authenticated_proxy? && !@uri.https?
|
94
94
|
Request::Writer.new(socket, body, headers, request_header).stream
|
95
95
|
end
|
96
96
|
|
@@ -106,8 +106,17 @@ module HTTP
|
|
106
106
|
|
107
107
|
# Compute and add the Proxy-Authorization header
|
108
108
|
def include_proxy_authorization_header
|
109
|
-
|
110
|
-
|
109
|
+
headers["Proxy-Authorization"] = proxy_authorization_header
|
110
|
+
end
|
111
|
+
|
112
|
+
def proxy_authorization_header
|
113
|
+
digest = Base64.strict_encode64("#{proxy[:proxy_username]}:#{proxy[:proxy_password]}")
|
114
|
+
"Basic #{digest}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Setup tunnel through proxy for SSL request
|
118
|
+
def connect_using_proxy(socket)
|
119
|
+
Request::Writer.new(socket, nil, proxy_connect_headers, proxy_connect_header).connect_through_proxy
|
111
120
|
end
|
112
121
|
|
113
122
|
# Compute HTTP request header for direct or proxy request
|
@@ -116,6 +125,21 @@ module HTTP
|
|
116
125
|
"#{verb.to_s.upcase} #{request_uri} HTTP/#{version}"
|
117
126
|
end
|
118
127
|
|
128
|
+
# Compute HTTP request header SSL proxy connection
|
129
|
+
def proxy_connect_header
|
130
|
+
"CONNECT #{@uri.host}:#{@uri.port} HTTP/#{version}"
|
131
|
+
end
|
132
|
+
|
133
|
+
# Headers to send with proxy connect request
|
134
|
+
def proxy_connect_headers
|
135
|
+
connect_headers = HTTP::Headers.coerce(
|
136
|
+
"Host" => headers["Host"],
|
137
|
+
"User-Agent" => headers["User-Agent"]
|
138
|
+
)
|
139
|
+
connect_headers["Proxy-Authorization"] = proxy_authorization_header if using_authenticated_proxy?
|
140
|
+
connect_headers
|
141
|
+
end
|
142
|
+
|
119
143
|
# Host for tcp socket
|
120
144
|
def socket_host
|
121
145
|
using_proxy? ? proxy[:proxy_address] : host
|
data/lib/http/request/caching.rb
CHANGED
@@ -70,7 +70,7 @@ module HTTP
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def env
|
73
|
-
{"rack-cache.cache_key" =>
|
73
|
+
{"rack-cache.cache_key" => lambda { |r| r.uri.to_s }}
|
74
74
|
end
|
75
75
|
|
76
76
|
private
|
@@ -80,11 +80,11 @@ module HTTP
|
|
80
80
|
def conditional_headers_for(cached_response)
|
81
81
|
headers = HTTP::Headers.new
|
82
82
|
|
83
|
-
cached_response.headers.get("Etag")
|
84
|
-
|
83
|
+
cached_response.headers.get("Etag").
|
84
|
+
each { |etag| headers.add("If-None-Match", etag) }
|
85
85
|
|
86
|
-
cached_response.headers.get("Last-Modified")
|
87
|
-
|
86
|
+
cached_response.headers.get("Last-Modified").
|
87
|
+
each { |last_mod| headers.add("If-Modified-Since", last_mod) }
|
88
88
|
|
89
89
|
headers.add("Cache-Control", "max-age=0") if cache_headers.forces_revalidation?
|
90
90
|
|
data/lib/http/request/writer.rb
CHANGED
@@ -29,6 +29,12 @@ module HTTP
|
|
29
29
|
send_request_body
|
30
30
|
end
|
31
31
|
|
32
|
+
# Send headers needed to connect through proxy
|
33
|
+
def connect_through_proxy
|
34
|
+
add_headers
|
35
|
+
@socket << join_headers
|
36
|
+
end
|
37
|
+
|
32
38
|
# Adds the headers to the header array for the given request body we are working
|
33
39
|
# with
|
34
40
|
def add_body_type_headers
|
@@ -50,9 +56,8 @@ module HTTP
|
|
50
56
|
def send_request_header
|
51
57
|
add_headers
|
52
58
|
add_body_type_headers
|
53
|
-
header = join_headers
|
54
59
|
|
55
|
-
@socket <<
|
60
|
+
@socket << join_headers
|
56
61
|
end
|
57
62
|
|
58
63
|
def send_request_body
|
@@ -123,9 +123,9 @@ module HTTP
|
|
123
123
|
|
124
124
|
# @return [Time] the time at which the server generated this response.
|
125
125
|
def server_response_time
|
126
|
-
headers.get("Date")
|
127
|
-
|
128
|
-
|
126
|
+
headers.get("Date").
|
127
|
+
map(&method(:to_time_or_epoch)).
|
128
|
+
max || begin
|
129
129
|
# set it if it is not already set
|
130
130
|
headers["Date"] = received_at.httpdate
|
131
131
|
received_at
|
data/lib/http/version.rb
CHANGED
data/spec/lib/http/cache_spec.rb
CHANGED
@@ -6,8 +6,8 @@ RSpec.describe HTTP::Cache do
|
|
6
6
|
subject { described_class }
|
7
7
|
|
8
8
|
it "allows metastore and entitystore" do
|
9
|
-
expect(subject.new(:metastore => "heap:/", :entitystore => "heap:/"))
|
10
|
-
|
9
|
+
expect(subject.new(:metastore => "heap:/", :entitystore => "heap:/")).
|
10
|
+
to be_kind_of HTTP::Cache
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -151,10 +151,10 @@ RSpec.describe HTTP::Cache do
|
|
151
151
|
|
152
152
|
it "makes request with conditional request headers" do
|
153
153
|
subject.perform(request, opts) do |actual_request, _|
|
154
|
-
expect(actual_request.headers["If-None-Match"])
|
155
|
-
|
156
|
-
expect(actual_request.headers["If-Modified-Since"])
|
157
|
-
|
154
|
+
expect(actual_request.headers["If-None-Match"]).
|
155
|
+
to eq cached_response.headers["Etag"]
|
156
|
+
expect(actual_request.headers["If-Modified-Since"]).
|
157
|
+
to eq cached_response.headers["Last-Modified"]
|
158
158
|
|
159
159
|
origin_response
|
160
160
|
end
|
@@ -60,8 +60,8 @@ RSpec.describe HTTP::Client do
|
|
60
60
|
"http://example.com/" => redirect_response("/")
|
61
61
|
)
|
62
62
|
|
63
|
-
expect { client.get("http://example.com/") }
|
64
|
-
|
63
|
+
expect { client.get("http://example.com/") }.
|
64
|
+
to raise_error(HTTP::Redirector::EndlessRedirectError)
|
65
65
|
end
|
66
66
|
|
67
67
|
it "fails if max amount of hops reached" do
|
@@ -75,8 +75,8 @@ RSpec.describe HTTP::Client do
|
|
75
75
|
"http://example.com/6" => simple_response("OK")
|
76
76
|
)
|
77
77
|
|
78
|
-
expect { client.get("http://example.com/") }
|
79
|
-
|
78
|
+
expect { client.get("http://example.com/") }.
|
79
|
+
to raise_error(HTTP::Redirector::TooManyRedirectsError)
|
80
80
|
end
|
81
81
|
|
82
82
|
context "with non-ASCII URLs" do
|
@@ -103,17 +103,17 @@ RSpec.describe HTTP::Client do
|
|
103
103
|
it "returns cached responses if they exist" do
|
104
104
|
cached_response = simple_response("cached").caching
|
105
105
|
StubbedClient.new(:cache =>
|
106
|
-
HTTP::Cache.new(:metastore => "heap:/", :entitystore => "heap:/"))
|
107
|
-
|
108
|
-
|
106
|
+
HTTP::Cache.new(:metastore => "heap:/", :entitystore => "heap:/")).
|
107
|
+
stub("http://example.com/#{sn}" => cached_response).
|
108
|
+
get("http://example.com/#{sn}")
|
109
109
|
|
110
110
|
# cache is now warm
|
111
111
|
|
112
|
-
client = StubbedClient.new(:cache => HTTP::Cache.new(:metastore => "heap:/", :entitystore => "heap:/"))
|
113
|
-
|
112
|
+
client = StubbedClient.new(:cache => HTTP::Cache.new(:metastore => "heap:/", :entitystore => "heap:/")).
|
113
|
+
stub("http://example.com/#{sn}" => simple_response("OK"))
|
114
114
|
|
115
|
-
expect(client.get("http://example.com/#{sn}").body.to_s)
|
116
|
-
|
115
|
+
expect(client.get("http://example.com/#{sn}").body.to_s).
|
116
|
+
to eq cached_response.body.to_s
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
@@ -226,8 +226,8 @@ RSpec.describe HTTP::Client do
|
|
226
226
|
end
|
227
227
|
|
228
228
|
it "fails with OpenSSL::SSL::SSLError if host mismatch" do
|
229
|
-
expect { client.get(dummy_ssl.endpoint.gsub("127.0.0.1", "localhost")) }
|
230
|
-
|
229
|
+
expect { client.get(dummy_ssl.endpoint.gsub("127.0.0.1", "localhost")) }.
|
230
|
+
to raise_error(OpenSSL::SSL::SSLError, /does not match/)
|
231
231
|
end
|
232
232
|
|
233
233
|
context "with SSL options instead of a context" do
|
@@ -29,13 +29,13 @@ RSpec.describe HTTP::Headers do
|
|
29
29
|
end
|
30
30
|
|
31
31
|
it "fails with empty header name" do
|
32
|
-
expect { headers.set "", "foo bar" }
|
33
|
-
|
32
|
+
expect { headers.set "", "foo bar" }.
|
33
|
+
to raise_error HTTP::InvalidHeaderNameError
|
34
34
|
end
|
35
35
|
|
36
36
|
it "fails with invalid header name" do
|
37
|
-
expect { headers.set "foo bar", "baz" }
|
38
|
-
|
37
|
+
expect { headers.set "foo bar", "baz" }.
|
38
|
+
to raise_error HTTP::InvalidHeaderNameError
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -77,13 +77,13 @@ RSpec.describe HTTP::Headers do
|
|
77
77
|
end
|
78
78
|
|
79
79
|
it "fails with empty header name" do
|
80
|
-
expect { headers.delete "" }
|
81
|
-
|
80
|
+
expect { headers.delete "" }.
|
81
|
+
to raise_error HTTP::InvalidHeaderNameError
|
82
82
|
end
|
83
83
|
|
84
84
|
it "fails with invalid header name" do
|
85
|
-
expect { headers.delete "foo bar" }
|
86
|
-
|
85
|
+
expect { headers.delete "foo bar" }.
|
86
|
+
to raise_error HTTP::InvalidHeaderNameError
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
@@ -111,13 +111,13 @@ RSpec.describe HTTP::Headers do
|
|
111
111
|
end
|
112
112
|
|
113
113
|
it "fails with empty header name" do
|
114
|
-
expect { headers.add "", "foobar" }
|
115
|
-
|
114
|
+
expect { headers.add "", "foobar" }.
|
115
|
+
to raise_error HTTP::InvalidHeaderNameError
|
116
116
|
end
|
117
117
|
|
118
118
|
it "fails with invalid header name" do
|
119
|
-
expect { headers.add "foo bar", "baz" }
|
120
|
-
|
119
|
+
expect { headers.add "foo bar", "baz" }.
|
120
|
+
to raise_error HTTP::InvalidHeaderNameError
|
121
121
|
end
|
122
122
|
end
|
123
123
|
|
@@ -139,13 +139,13 @@ RSpec.describe HTTP::Headers do
|
|
139
139
|
end
|
140
140
|
|
141
141
|
it "fails with empty header name" do
|
142
|
-
expect { headers.get "" }
|
143
|
-
|
142
|
+
expect { headers.get "" }.
|
143
|
+
to raise_error HTTP::InvalidHeaderNameError
|
144
144
|
end
|
145
145
|
|
146
146
|
it "fails with invalid header name" do
|
147
|
-
expect { headers.get "foo bar" }
|
148
|
-
|
147
|
+
expect { headers.get "foo bar" }.
|
148
|
+
to raise_error HTTP::InvalidHeaderNameError
|
149
149
|
end
|
150
150
|
end
|
151
151
|
|
@@ -33,24 +33,24 @@ RSpec.describe HTTP::Redirector do
|
|
33
33
|
req = HTTP::Request.new :head, "http://example.com"
|
34
34
|
res = proc { |prev_req| redirect_response(301, "#{prev_req.uri}/1") }
|
35
35
|
|
36
|
-
expect { redirector.perform(req, res.call(req), &res) }
|
37
|
-
|
36
|
+
expect { redirector.perform(req, res.call(req), &res) }.
|
37
|
+
to raise_error HTTP::Redirector::TooManyRedirectsError
|
38
38
|
end
|
39
39
|
|
40
40
|
it "fails with EndlessRedirectError if endless loop detected" do
|
41
41
|
req = HTTP::Request.new :head, "http://example.com"
|
42
42
|
res = redirect_response(301, req.uri)
|
43
43
|
|
44
|
-
expect { redirector.perform(req, res) { res } }
|
45
|
-
|
44
|
+
expect { redirector.perform(req, res) { res } }.
|
45
|
+
to raise_error HTTP::Redirector::EndlessRedirectError
|
46
46
|
end
|
47
47
|
|
48
48
|
it "fails with StateError if there were no Location header" do
|
49
49
|
req = HTTP::Request.new :head, "http://example.com"
|
50
50
|
res = simple_response(301)
|
51
51
|
|
52
|
-
expect { |b| redirector.perform(req, res, &b) }
|
53
|
-
|
52
|
+
expect { |b| redirector.perform(req, res, &b) }.
|
53
|
+
to raise_error HTTP::StateError
|
54
54
|
end
|
55
55
|
|
56
56
|
it "returns first non-redirect response" do
|
@@ -86,24 +86,24 @@ RSpec.describe HTTP::Redirector do
|
|
86
86
|
req = HTTP::Request.new :put, "http://example.com"
|
87
87
|
res = redirect_response 300, "http://example.com/1"
|
88
88
|
|
89
|
-
expect { redirector.perform(req, res) { simple_response 200 } }
|
90
|
-
|
89
|
+
expect { redirector.perform(req, res) { simple_response 200 } }.
|
90
|
+
to raise_error HTTP::StateError
|
91
91
|
end
|
92
92
|
|
93
93
|
it "raises StateError if original request was POST" do
|
94
94
|
req = HTTP::Request.new :post, "http://example.com"
|
95
95
|
res = redirect_response 300, "http://example.com/1"
|
96
96
|
|
97
|
-
expect { redirector.perform(req, res) { simple_response 200 } }
|
98
|
-
|
97
|
+
expect { redirector.perform(req, res) { simple_response 200 } }.
|
98
|
+
to raise_error HTTP::StateError
|
99
99
|
end
|
100
100
|
|
101
101
|
it "raises StateError if original request was DELETE" do
|
102
102
|
req = HTTP::Request.new :delete, "http://example.com"
|
103
103
|
res = redirect_response 300, "http://example.com/1"
|
104
104
|
|
105
|
-
expect { redirector.perform(req, res) { simple_response 200 } }
|
106
|
-
|
105
|
+
expect { redirector.perform(req, res) { simple_response 200 } }.
|
106
|
+
to raise_error HTTP::StateError
|
107
107
|
end
|
108
108
|
end
|
109
109
|
|
@@ -170,24 +170,24 @@ RSpec.describe HTTP::Redirector do
|
|
170
170
|
req = HTTP::Request.new :put, "http://example.com"
|
171
171
|
res = redirect_response 301, "http://example.com/1"
|
172
172
|
|
173
|
-
expect { redirector.perform(req, res) { simple_response 200 } }
|
174
|
-
|
173
|
+
expect { redirector.perform(req, res) { simple_response 200 } }.
|
174
|
+
to raise_error HTTP::StateError
|
175
175
|
end
|
176
176
|
|
177
177
|
it "raises StateError if original request was POST" do
|
178
178
|
req = HTTP::Request.new :post, "http://example.com"
|
179
179
|
res = redirect_response 301, "http://example.com/1"
|
180
180
|
|
181
|
-
expect { redirector.perform(req, res) { simple_response 200 } }
|
182
|
-
|
181
|
+
expect { redirector.perform(req, res) { simple_response 200 } }.
|
182
|
+
to raise_error HTTP::StateError
|
183
183
|
end
|
184
184
|
|
185
185
|
it "raises StateError if original request was DELETE" do
|
186
186
|
req = HTTP::Request.new :delete, "http://example.com"
|
187
187
|
res = redirect_response 301, "http://example.com/1"
|
188
188
|
|
189
|
-
expect { redirector.perform(req, res) { simple_response 200 } }
|
190
|
-
|
189
|
+
expect { redirector.perform(req, res) { simple_response 200 } }.
|
190
|
+
to raise_error HTTP::StateError
|
191
191
|
end
|
192
192
|
end
|
193
193
|
|
@@ -254,24 +254,24 @@ RSpec.describe HTTP::Redirector do
|
|
254
254
|
req = HTTP::Request.new :put, "http://example.com"
|
255
255
|
res = redirect_response 302, "http://example.com/1"
|
256
256
|
|
257
|
-
expect { redirector.perform(req, res) { simple_response 200 } }
|
258
|
-
|
257
|
+
expect { redirector.perform(req, res) { simple_response 200 } }.
|
258
|
+
to raise_error HTTP::StateError
|
259
259
|
end
|
260
260
|
|
261
261
|
it "raises StateError if original request was POST" do
|
262
262
|
req = HTTP::Request.new :post, "http://example.com"
|
263
263
|
res = redirect_response 302, "http://example.com/1"
|
264
264
|
|
265
|
-
expect { redirector.perform(req, res) { simple_response 200 } }
|
266
|
-
|
265
|
+
expect { redirector.perform(req, res) { simple_response 200 } }.
|
266
|
+
to raise_error HTTP::StateError
|
267
267
|
end
|
268
268
|
|
269
269
|
it "raises StateError if original request was DELETE" do
|
270
270
|
req = HTTP::Request.new :delete, "http://example.com"
|
271
271
|
res = redirect_response 302, "http://example.com/1"
|
272
272
|
|
273
|
-
expect { redirector.perform(req, res) { simple_response 200 } }
|
274
|
-
|
273
|
+
expect { redirector.perform(req, res) { simple_response 200 } }.
|
274
|
+
to raise_error HTTP::StateError
|
275
275
|
end
|
276
276
|
end
|
277
277
|
|
@@ -105,8 +105,8 @@ RSpec.describe HTTP::Response do
|
|
105
105
|
body = double :to_s => "foobar"
|
106
106
|
response = HTTP::Response.new(200, "1.1", headers, body)
|
107
107
|
|
108
|
-
expect(response.inspect)
|
109
|
-
|
108
|
+
expect(response.inspect).
|
109
|
+
to eq '#<HTTP::Response/1.1 200 OK {"Content-Type"=>"text/plain"}>'
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
data/spec/lib/http_spec.rb
CHANGED
@@ -5,6 +5,11 @@ require "support/proxy_server"
|
|
5
5
|
|
6
6
|
RSpec.describe HTTP do
|
7
7
|
run_server(:dummy) { DummyServer.new }
|
8
|
+
run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
|
9
|
+
|
10
|
+
let(:ssl_client) do
|
11
|
+
HTTP::Client.new :ssl_context => SSLHelper.client_context
|
12
|
+
end
|
8
13
|
|
9
14
|
context "getting resources" do
|
10
15
|
it "is easy" do
|
@@ -63,6 +68,18 @@ RSpec.describe HTTP do
|
|
63
68
|
response = HTTP.via(proxy.addr, proxy.port, "username", "password").get dummy.endpoint
|
64
69
|
expect(response.to_s).to match(/<!doctype html>/)
|
65
70
|
end
|
71
|
+
|
72
|
+
context "ssl" do
|
73
|
+
it "responds with the endpoint's body" do
|
74
|
+
response = ssl_client.via(proxy.addr, proxy.port).get dummy_ssl.endpoint
|
75
|
+
expect(response.to_s).to match(/<!doctype html>/)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "ignores credentials" do
|
79
|
+
response = ssl_client.via(proxy.addr, proxy.port, "username", "password").get dummy_ssl.endpoint
|
80
|
+
expect(response.to_s).to match(/<!doctype html>/)
|
81
|
+
end
|
82
|
+
end
|
66
83
|
end
|
67
84
|
|
68
85
|
context "proxy with authentication" do
|
@@ -87,6 +104,23 @@ RSpec.describe HTTP do
|
|
87
104
|
response = HTTP.via(proxy.addr, proxy.port).get dummy.endpoint
|
88
105
|
expect(response.status).to eq(407)
|
89
106
|
end
|
107
|
+
|
108
|
+
context "ssl" do
|
109
|
+
it "responds with the endpoint's body" do
|
110
|
+
response = ssl_client.via(proxy.addr, proxy.port, "username", "password").get dummy_ssl.endpoint
|
111
|
+
expect(response.to_s).to match(/<!doctype html>/)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "responds with 407 when wrong credentials given" do
|
115
|
+
response = ssl_client.via(proxy.addr, proxy.port, "user", "pass").get dummy_ssl.endpoint
|
116
|
+
expect(response.status).to eq(407)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "responds with 407 if no credentials given" do
|
120
|
+
response = ssl_client.via(proxy.addr, proxy.port).get dummy_ssl.endpoint
|
121
|
+
expect(response.status).to eq(407)
|
122
|
+
end
|
123
|
+
end
|
90
124
|
end
|
91
125
|
end
|
92
126
|
|
@@ -151,15 +185,15 @@ RSpec.describe HTTP do
|
|
151
185
|
|
152
186
|
it "sets Authorization header with proper BasicAuth value" do
|
153
187
|
client = HTTP.basic_auth :user => "foo", :pass => "bar"
|
154
|
-
expect(client.default_headers[:authorization])
|
155
|
-
|
188
|
+
expect(client.default_headers[:authorization]).
|
189
|
+
to match(%r{^Basic [A-Za-z0-9+/]+=*$})
|
156
190
|
end
|
157
191
|
end
|
158
192
|
|
159
|
-
describe ".
|
193
|
+
describe ".cache" do
|
160
194
|
it "sets cache option" do
|
161
195
|
cache = double(:cache, :perform => nil)
|
162
|
-
client = HTTP.
|
196
|
+
client = HTTP.cache cache
|
163
197
|
expect(client.default_options[:cache]).to eq cache
|
164
198
|
end
|
165
199
|
end
|
@@ -82,8 +82,8 @@ RSpec.shared_context "HTTP handling" do
|
|
82
82
|
end
|
83
83
|
|
84
84
|
it "errors if reading takes too long" do
|
85
|
-
expect { client.get("#{server.endpoint}/sleep").body.to_s }
|
86
|
-
|
85
|
+
expect { client.get("#{server.endpoint}/sleep").body.to_s }.
|
86
|
+
to raise_error(HTTP::TimeoutError, /Timed out/)
|
87
87
|
end
|
88
88
|
end
|
89
89
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Arcieri
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-04-
|
13
|
+
date: 2015-04-23 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: http_parser.rb
|
@@ -180,9 +180,45 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
180
|
version: '0'
|
181
181
|
requirements: []
|
182
182
|
rubyforge_project:
|
183
|
-
rubygems_version: 2.
|
183
|
+
rubygems_version: 2.4.5
|
184
184
|
signing_key:
|
185
185
|
specification_version: 4
|
186
186
|
summary: HTTP should be easy
|
187
|
-
test_files:
|
187
|
+
test_files:
|
188
|
+
- spec/lib/http/cache/headers_spec.rb
|
189
|
+
- spec/lib/http/cache_spec.rb
|
190
|
+
- spec/lib/http/client_spec.rb
|
191
|
+
- spec/lib/http/content_type_spec.rb
|
192
|
+
- spec/lib/http/headers/mixin_spec.rb
|
193
|
+
- spec/lib/http/headers_spec.rb
|
194
|
+
- spec/lib/http/options/body_spec.rb
|
195
|
+
- spec/lib/http/options/form_spec.rb
|
196
|
+
- spec/lib/http/options/headers_spec.rb
|
197
|
+
- spec/lib/http/options/json_spec.rb
|
198
|
+
- spec/lib/http/options/merge_spec.rb
|
199
|
+
- spec/lib/http/options/new_spec.rb
|
200
|
+
- spec/lib/http/options/proxy_spec.rb
|
201
|
+
- spec/lib/http/options_spec.rb
|
202
|
+
- spec/lib/http/redirector_spec.rb
|
203
|
+
- spec/lib/http/request/caching_spec.rb
|
204
|
+
- spec/lib/http/request/writer_spec.rb
|
205
|
+
- spec/lib/http/request_spec.rb
|
206
|
+
- spec/lib/http/response/body_spec.rb
|
207
|
+
- spec/lib/http/response/caching_spec.rb
|
208
|
+
- spec/lib/http/response/io_body_spec.rb
|
209
|
+
- spec/lib/http/response/status_spec.rb
|
210
|
+
- spec/lib/http/response/string_body_spec.rb
|
211
|
+
- spec/lib/http/response_spec.rb
|
212
|
+
- spec/lib/http_spec.rb
|
213
|
+
- spec/spec_helper.rb
|
214
|
+
- spec/support/black_hole.rb
|
215
|
+
- spec/support/capture_warning.rb
|
216
|
+
- spec/support/connection_reuse_shared.rb
|
217
|
+
- spec/support/dummy_server.rb
|
218
|
+
- spec/support/dummy_server/servlet.rb
|
219
|
+
- spec/support/http_handling_shared.rb
|
220
|
+
- spec/support/proxy_server.rb
|
221
|
+
- spec/support/servers/config.rb
|
222
|
+
- spec/support/servers/runner.rb
|
223
|
+
- spec/support/ssl_helper.rb
|
188
224
|
has_rdoc:
|