down 3.0.0 → 3.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/README.md +100 -65
- data/down.gemspec +1 -0
- data/lib/down/errors.rb +31 -6
- data/lib/down/http.rb +44 -13
- data/lib/down/net_http.rb +76 -15
- data/lib/down/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 29dd2b9e7c612e2be576964dc0b35203d4e6b038
|
|
4
|
+
data.tar.gz: 3f9146bd292d11b4bcd82b196b8c59a4125cff2d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5dba71a205bb2f2e0353774e8801cf8692e1c0e5605bdb62810f9bd1ea16bc0076e8d17f2edaa7986ad019675a723c91b693bd8c8f9f19a8637f6aa2630ad040
|
|
7
|
+
data.tar.gz: d1d810b2defabd1a460dcd614e65d578b9c27f2ac6008efb5fd3d56c3a080cb12d721a4196383b9eb1349c7939cce9ff07716692a6c803f13eb095d136fd9aa4
|
data/README.md
CHANGED
|
@@ -57,29 +57,6 @@ Down.download("http://user:password@example.org")
|
|
|
57
57
|
Down.open("http://user:password@example.org")
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
### Download errors
|
|
61
|
-
|
|
62
|
-
There are a lot of ways in which a download can fail:
|
|
63
|
-
|
|
64
|
-
* Response status was 4xx or 5xx
|
|
65
|
-
* Domain was not found
|
|
66
|
-
* Timeout occurred
|
|
67
|
-
* URL is invalid
|
|
68
|
-
* ...
|
|
69
|
-
|
|
70
|
-
Down attempts to unify all of these exceptions into one `Down::NotFound` error
|
|
71
|
-
(because this is what actually happened from the outside perspective). If you
|
|
72
|
-
want to retrieve the original error raised, in Ruby 2.1+ you can use
|
|
73
|
-
`Exception#cause`:
|
|
74
|
-
|
|
75
|
-
```rb
|
|
76
|
-
begin
|
|
77
|
-
Down.download("http://example.com")
|
|
78
|
-
rescue Down::Error => exception
|
|
79
|
-
exception.cause #=> #<Timeout::Error>
|
|
80
|
-
end
|
|
81
|
-
```
|
|
82
|
-
|
|
83
60
|
## Streaming
|
|
84
61
|
|
|
85
62
|
Down has the ability to retrieve content of the remote file *as it is being
|
|
@@ -162,7 +139,7 @@ Down::ChunkedIO.new(...)
|
|
|
162
139
|
* `:on_close` – called when streaming finishes or IO is closed
|
|
163
140
|
* `:data` - custom data that you want to store (returned by `#data`)
|
|
164
141
|
* `:rewindable` - whether to cache retrieved data into a file (defaults to `true`)
|
|
165
|
-
* `:encoding` - force content to be returned in specified encoding (defaults to
|
|
142
|
+
* `:encoding` - force content to be returned in specified encoding (defaults to `Encoding::BINARY`)
|
|
166
143
|
|
|
167
144
|
Here is an example of wrapping streaming MongoDB files:
|
|
168
145
|
|
|
@@ -182,30 +159,78 @@ io = Down::ChunkedIO.new(
|
|
|
182
159
|
)
|
|
183
160
|
```
|
|
184
161
|
|
|
185
|
-
|
|
162
|
+
### Exceptions
|
|
163
|
+
|
|
164
|
+
Down tries to recognize various types of exceptions and re-raise them as one of
|
|
165
|
+
the `Down::Error` subclasses. This is Down's exception hierarchy:
|
|
166
|
+
|
|
167
|
+
* `Down::Error`
|
|
168
|
+
* `Down::TooLarge`
|
|
169
|
+
* `Down::NotFound`
|
|
170
|
+
* `Down::InvalidUrl`
|
|
171
|
+
* `Down::TooManyRedirects`
|
|
172
|
+
* `Down::ResponseError`
|
|
173
|
+
* `Down::ClientError`
|
|
174
|
+
* `Down::ServerError`
|
|
175
|
+
* `Down::ConnectionError`
|
|
176
|
+
* `Down::TimeoutError`
|
|
177
|
+
* `Down::SSLError`
|
|
178
|
+
|
|
179
|
+
## Backends
|
|
180
|
+
|
|
181
|
+
By default Down implements `Down.download` and `Down.open` using the built-in
|
|
182
|
+
[open-uri] + [Net::HTTP] Ruby standard libraries. However, there are other
|
|
183
|
+
backends as well:
|
|
184
|
+
|
|
185
|
+
```rb
|
|
186
|
+
require "down/net_http" # uses open-uri + Net::HTTP
|
|
187
|
+
require "down/http" # uses HTTP.rb gem
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
When a backend is loaded, is overrides `Down.download` and `Down.open` methods,
|
|
191
|
+
but it's recommended you always use the backends explicitly:
|
|
186
192
|
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
```rb
|
|
194
|
+
# not recommended
|
|
195
|
+
Down.download("...")
|
|
196
|
+
Down.open("...")
|
|
197
|
+
|
|
198
|
+
# recommended
|
|
199
|
+
Down::NetHttp.download("...")
|
|
200
|
+
Down::NetHttp.open("...")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### open-uri + Net::HTTP
|
|
189
204
|
|
|
190
205
|
```rb
|
|
191
|
-
|
|
192
|
-
|
|
206
|
+
gem "down", ">= 3.0"
|
|
207
|
+
```
|
|
208
|
+
```rb
|
|
193
209
|
require "down/net_http"
|
|
210
|
+
|
|
211
|
+
tempfile = Down::NetHttp.download("http://nature.com/forest.jpg")
|
|
212
|
+
tempfile #=> #<Tempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20150925-55456-z7vxqz.jpg>
|
|
213
|
+
|
|
214
|
+
io = Down::NetHttp.open("http://nature.com/forest.jpg")
|
|
215
|
+
io #=> #<Down::ChunkedIO ...>
|
|
194
216
|
```
|
|
195
217
|
|
|
196
|
-
`Down.download` is implemented as a wrapper around open-uri, and fixes
|
|
197
|
-
open-uri's undesired behaviours:
|
|
218
|
+
`Down::NetHttp.download` is implemented as a wrapper around open-uri, and fixes
|
|
219
|
+
some of open-uri's undesired behaviours:
|
|
220
|
+
|
|
221
|
+
* uses `URI::HTTP#open` or `URI::HTTPS#open` directly for [security](https://sakurity.com/blog/2015/02/28/openuri.html)
|
|
222
|
+
* always returns a `Tempfile` object, whereas open-uri returns `StringIO`
|
|
223
|
+
when file is smaller than 10KB
|
|
224
|
+
* gives the extension to the `Tempfile` object from the URL
|
|
225
|
+
* allows you to limit maximum number of redirects
|
|
198
226
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
* open-uri doesn't give any extension to the returned `Tempfile`, but
|
|
202
|
-
`Down.download` adds the extension from the URL
|
|
203
|
-
* ...
|
|
227
|
+
On the other hand `Down::NetHttp.open` is implemented using Net::HTTP directly,
|
|
228
|
+
as open-uri
|
|
204
229
|
|
|
205
|
-
Since open-uri doesn't expose support for partial downloads,
|
|
206
|
-
implemented using `Net::HTTP` directly.
|
|
230
|
+
Since open-uri doesn't expose support for partial downloads,
|
|
231
|
+
`Down::NetHttp.open` is implemented using `Net::HTTP` directly.
|
|
207
232
|
|
|
208
|
-
|
|
233
|
+
#### Redirects
|
|
209
234
|
|
|
210
235
|
`Down.download` turns off open-uri's following redirects, as open-uri doesn't
|
|
211
236
|
have a way to limit the maximum number of hops, and implements its own. By
|
|
@@ -213,28 +238,38 @@ default maximum of 2 redirects will be followed, but you can change it via the
|
|
|
213
238
|
`:max_redirects` option:
|
|
214
239
|
|
|
215
240
|
```rb
|
|
216
|
-
Down.download("http://example.com/image.jpg") # 2 redirects allowed
|
|
217
|
-
Down.download("http://example.com/image.jpg", max_redirects: 5) # 5 redirects allowed
|
|
218
|
-
Down.download("http://example.com/image.jpg", max_redirects: 0) # 0 redirects allowed
|
|
241
|
+
Down::NetHttp.download("http://example.com/image.jpg") # 2 redirects allowed
|
|
242
|
+
Down::NetHttp.download("http://example.com/image.jpg", max_redirects: 5) # 5 redirects allowed
|
|
243
|
+
Down::NetHttp.download("http://example.com/image.jpg", max_redirects: 0) # 0 redirects allowed
|
|
219
244
|
```
|
|
220
245
|
|
|
221
|
-
|
|
246
|
+
#### Proxy
|
|
222
247
|
|
|
223
248
|
Both `Down.download` and `Down.open` support a `:proxy` option, where you can
|
|
224
249
|
specify a URL to an HTTP proxy which should be used when downloading.
|
|
225
250
|
|
|
226
251
|
```rb
|
|
227
|
-
Down.download("http://example.com/image.jpg", proxy: "http://proxy.org")
|
|
228
|
-
Down.open("http://example.com/image.jpg", proxy: "http://user:password@proxy.org")
|
|
252
|
+
Down::NetHttp.download("http://example.com/image.jpg", proxy: "http://proxy.org")
|
|
253
|
+
Down::NetHttp.open("http://example.com/image.jpg", proxy: "http://user:password@proxy.org")
|
|
229
254
|
```
|
|
230
255
|
|
|
231
|
-
|
|
256
|
+
#### Timeouts
|
|
257
|
+
|
|
258
|
+
Both `Down.download` and `Down.open` support `:read_timeout` and `:open_timeout`
|
|
259
|
+
options, which are forwarded to `Net::HTTP`:
|
|
260
|
+
|
|
261
|
+
```rb
|
|
262
|
+
Down::NetHttp.download("http://example.com/image.jpg", open_timeout: 5)
|
|
263
|
+
Down::NetHttp.open("http://example.com/image.jpg", read_timeout: 10)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
#### Additional options
|
|
232
267
|
|
|
233
268
|
Any additional options passed to `Down.download` will be forwarded to
|
|
234
269
|
[open-uri], so you can for example add basic authentication or a timeout:
|
|
235
270
|
|
|
236
271
|
```rb
|
|
237
|
-
Down.download "http://example.com/image.jpg",
|
|
272
|
+
Down::NetHttp.download "http://example.com/image.jpg",
|
|
238
273
|
http_basic_authentication: ['john', 'secret'],
|
|
239
274
|
read_timeout: 5
|
|
240
275
|
```
|
|
@@ -244,19 +279,23 @@ semantics as in open-uri, and any options with String keys will be interpreted
|
|
|
244
279
|
as request headers, like with open-uri.
|
|
245
280
|
|
|
246
281
|
```rb
|
|
247
|
-
Down.open("http://example.com/image.jpg", {"Authorization" => "..."})
|
|
282
|
+
Down::NetHttp.open("http://example.com/image.jpg", {"Authorization" => "..."})
|
|
248
283
|
```
|
|
249
284
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
The [HTTP.rb] backend can be used by requiring `down/http`:
|
|
285
|
+
### HTTP.rb
|
|
253
286
|
|
|
254
287
|
```rb
|
|
288
|
+
gem "down", "~> 3.0"
|
|
255
289
|
gem "http", "~> 2.1"
|
|
256
|
-
gem "down"
|
|
257
290
|
```
|
|
258
291
|
```rb
|
|
259
292
|
require "down/http"
|
|
293
|
+
|
|
294
|
+
tempfile = Down::Http.download("http://nature.com/forest.jpg")
|
|
295
|
+
tempfile #=> #<Tempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20150925-55456-z7vxqz.jpg>
|
|
296
|
+
|
|
297
|
+
io = Down::Http.open("http://nature.com/forest.jpg")
|
|
298
|
+
io #=> #<Down::ChunkedIO ...>
|
|
260
299
|
```
|
|
261
300
|
|
|
262
301
|
Some features that give the HTTP.rb backend an advantage over open-uri +
|
|
@@ -266,9 +305,7 @@ Net::HTTP include:
|
|
|
266
305
|
* Proper support for streaming downloads (`#download` and now reuse `#open`)
|
|
267
306
|
* Proper support for SSL
|
|
268
307
|
* Chaninable HTTP client builder API for setting default options
|
|
269
|
-
*
|
|
270
|
-
* Auto-inflating compressed response bodies
|
|
271
|
-
* ...
|
|
308
|
+
* Support for persistent connections
|
|
272
309
|
|
|
273
310
|
### Default client
|
|
274
311
|
|
|
@@ -290,13 +327,13 @@ All additional options passed to `Down::Download` and `Down.open` will be
|
|
|
290
327
|
forwarded to `HTTP::Client#request`:
|
|
291
328
|
|
|
292
329
|
```rb
|
|
293
|
-
Down.download("http://example.org/image.jpg", headers: {"Accept-Encoding" => "gzip"})
|
|
330
|
+
Down::Http.download("http://example.org/image.jpg", headers: {"Accept-Encoding" => "gzip"})
|
|
294
331
|
```
|
|
295
332
|
|
|
296
333
|
If you prefer to add options using the chainable API, you can pass a block:
|
|
297
334
|
|
|
298
335
|
```rb
|
|
299
|
-
Down.open("http://example.org/image.jpg") do |client|
|
|
336
|
+
Down::Http.open("http://example.org/image.jpg") do |client|
|
|
300
337
|
client.timeout(read: 3)
|
|
301
338
|
end
|
|
302
339
|
```
|
|
@@ -315,23 +352,21 @@ backend is thread safe.
|
|
|
315
352
|
|
|
316
353
|
## Development
|
|
317
354
|
|
|
318
|
-
|
|
319
|
-
downloads. Httpbin is a Python package which is run with GUnicorn:
|
|
320
|
-
|
|
321
|
-
```
|
|
322
|
-
$ pip install gunicorn httpbin
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
Afterwards you can run tests with
|
|
355
|
+
You can run tests with
|
|
326
356
|
|
|
327
357
|
```
|
|
328
358
|
$ rake test
|
|
329
359
|
```
|
|
330
360
|
|
|
361
|
+
The test suite pulls and runs [kennethreitz/httpbin] as a Docker container, so
|
|
362
|
+
you'll need to have Docker installed and running.
|
|
363
|
+
|
|
331
364
|
## License
|
|
332
365
|
|
|
333
366
|
[MIT](LICENSE.txt)
|
|
334
367
|
|
|
335
368
|
[open-uri]: http://ruby-doc.org/stdlib-2.3.0/libdoc/open-uri/rdoc/OpenURI.html
|
|
369
|
+
[Net::HTTP]: https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html
|
|
336
370
|
[HTTP.rb]: https://github.com/httprb/http
|
|
337
371
|
[Addressable::URI]: https://github.com/sporkmonger/addressable
|
|
372
|
+
[kennethreitz/httpbin]: https://github.com/kennethreitz/httpbin
|
data/down.gemspec
CHANGED
data/lib/down/errors.rb
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
module Down
|
|
2
|
-
|
|
3
|
-
end
|
|
2
|
+
# generic error which is a superclass to all other errors
|
|
3
|
+
class Error < StandardError; end
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
end
|
|
5
|
+
# raised when the file is larger than the specified maximum size
|
|
6
|
+
class TooLarge < Error; end
|
|
7
|
+
|
|
8
|
+
# raised when the file failed to be retrieved for whatever reason
|
|
9
|
+
class NotFound < Error; end
|
|
10
|
+
|
|
11
|
+
# raised when the given URL couldn't be parsed
|
|
12
|
+
class InvalidUrl < NotFound; end
|
|
13
|
+
|
|
14
|
+
# raised when the number of redirects was larger than the specified maximum
|
|
15
|
+
class TooManyRedirects < NotFound; end
|
|
7
16
|
|
|
8
|
-
|
|
17
|
+
# raised when response returned 4xx or 5xx response
|
|
18
|
+
class ResponseError < NotFound
|
|
9
19
|
attr_reader :response
|
|
10
20
|
|
|
11
|
-
def initialize(message, response:
|
|
21
|
+
def initialize(message, response:)
|
|
12
22
|
super(message)
|
|
13
23
|
@response = response
|
|
14
24
|
end
|
|
15
25
|
end
|
|
26
|
+
|
|
27
|
+
# raised when response returned 4xx response
|
|
28
|
+
class ClientError < ResponseError; end
|
|
29
|
+
|
|
30
|
+
# raised when response returned 5xx response
|
|
31
|
+
class ServerError < ResponseError; end
|
|
32
|
+
|
|
33
|
+
# raised when there was an error connecting to the server
|
|
34
|
+
class ConnectionError < NotFound; end
|
|
35
|
+
|
|
36
|
+
# raised when connecting to the server too longer than the specified timeout
|
|
37
|
+
class TimeoutError < ConnectionError; end
|
|
38
|
+
|
|
39
|
+
# raised when an SSL error was raised
|
|
40
|
+
class SSLError < NotFound; end
|
|
16
41
|
end
|
data/lib/down/http.rb
CHANGED
|
@@ -65,28 +65,23 @@ module Down
|
|
|
65
65
|
def open(url, **options, &block)
|
|
66
66
|
rewindable = options.delete(:rewindable)
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
begin
|
|
69
|
+
response = get(url, **options, &block)
|
|
70
|
+
response_error!(response) if !response.status.success?
|
|
71
|
+
rescue => exception
|
|
72
|
+
request_error!(exception)
|
|
72
73
|
end
|
|
73
74
|
|
|
74
75
|
down_options = {
|
|
75
|
-
chunks:
|
|
76
|
-
size:
|
|
77
|
-
data:
|
|
76
|
+
chunks: response.body.enum_for(:each),
|
|
77
|
+
size: response.content_length,
|
|
78
|
+
data: { status: response.code, headers: response.headers.to_h, response: response },
|
|
78
79
|
}
|
|
79
80
|
down_options[:encoding] = response.content_type.charset if response.content_type.charset
|
|
80
81
|
down_options[:on_close] = -> { response.connection.close } unless client.persistent?
|
|
81
82
|
down_options[:rewindable] = rewindable if rewindable != nil
|
|
82
83
|
|
|
83
84
|
Down::ChunkedIO.new(down_options)
|
|
84
|
-
rescue HTTP::ConnectionError,
|
|
85
|
-
HTTP::Request::UnsupportedSchemeError,
|
|
86
|
-
HTTP::TimeoutError
|
|
87
|
-
raise Down::NotFound, "file not found"
|
|
88
|
-
rescue HTTP::Redirector::TooManyRedirectsError
|
|
89
|
-
raise Down::NotFound, "too many redirects"
|
|
90
85
|
end
|
|
91
86
|
|
|
92
87
|
def get(url, **options, &block)
|
|
@@ -112,6 +107,42 @@ module Down
|
|
|
112
107
|
Thread.current[:down_client] = value
|
|
113
108
|
end
|
|
114
109
|
|
|
110
|
+
def response_error!(response)
|
|
111
|
+
args = [response.status.to_s, response: response]
|
|
112
|
+
|
|
113
|
+
case response.code
|
|
114
|
+
when 400..499 then raise Down::ClientError.new(*args)
|
|
115
|
+
when 500..599 then raise Down::ServerError.new(*args)
|
|
116
|
+
else raise Down::ResponseError.new(*args)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def request_error!(exception)
|
|
121
|
+
case exception
|
|
122
|
+
when HTTP::Request::UnsupportedSchemeError
|
|
123
|
+
raise Down::InvalidUrl, exception.message
|
|
124
|
+
when Errno::ECONNREFUSED
|
|
125
|
+
raise Down::ConnectionError, "connection was refused"
|
|
126
|
+
when HTTP::ConnectionError,
|
|
127
|
+
Errno::ECONNABORTED,
|
|
128
|
+
Errno::ECONNRESET,
|
|
129
|
+
Errno::EPIPE,
|
|
130
|
+
Errno::EINVAL,
|
|
131
|
+
Errno::EHOSTUNREACH
|
|
132
|
+
raise Down::ConnectionError, exception.message
|
|
133
|
+
when SocketError
|
|
134
|
+
raise Down::ConnectionError, "domain name could not be resolved"
|
|
135
|
+
when HTTP::TimeoutError
|
|
136
|
+
raise Down::TimeoutError, exception.message
|
|
137
|
+
when HTTP::Redirector::TooManyRedirectsError
|
|
138
|
+
raise Down::TooManyRedirects, exception.message
|
|
139
|
+
when defined?(OpenSSL) && OpenSSL::SSL::SSLError
|
|
140
|
+
raise Down::SSLError, exception.message
|
|
141
|
+
else
|
|
142
|
+
raise exception
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
115
146
|
module DownloadedFile
|
|
116
147
|
attr_accessor :url, :headers
|
|
117
148
|
|
data/lib/down/net_http.rb
CHANGED
|
@@ -73,7 +73,7 @@ module Down
|
|
|
73
73
|
uri = URI(uri)
|
|
74
74
|
|
|
75
75
|
if uri.class != URI::HTTP && uri.class != URI::HTTPS
|
|
76
|
-
raise
|
|
76
|
+
raise Down::InvalidUrl, "URL scheme needs to be http or https"
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
if uri.user || uri.password
|
|
@@ -83,24 +83,29 @@ module Down
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
downloaded_file = uri.open(open_uri_options)
|
|
86
|
-
rescue OpenURI::HTTPRedirect =>
|
|
86
|
+
rescue OpenURI::HTTPRedirect => exception
|
|
87
87
|
if (tries -= 1) > 0
|
|
88
|
-
uri =
|
|
88
|
+
uri = exception.uri
|
|
89
89
|
|
|
90
|
-
if !
|
|
91
|
-
open_uri_options["Cookie"] =
|
|
90
|
+
if !exception.io.meta["set-cookie"].to_s.empty?
|
|
91
|
+
open_uri_options["Cookie"] = exception.io.meta["set-cookie"]
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
retry
|
|
95
95
|
else
|
|
96
|
-
raise Down::
|
|
96
|
+
raise Down::TooManyRedirects, "too many redirects"
|
|
97
97
|
end
|
|
98
|
-
rescue OpenURI::HTTPError
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
rescue OpenURI::HTTPError => exception
|
|
99
|
+
code, message = exception.io.status
|
|
100
|
+
response_class = Net::HTTPResponse::CODE_TO_OBJ.fetch(code)
|
|
101
|
+
response = response_class.new(nil, code, message)
|
|
102
|
+
exception.io.metas.each do |name, values|
|
|
103
|
+
values.each { |value| response.add_field(name, value) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
response_error!(response)
|
|
107
|
+
rescue => exception
|
|
108
|
+
request_error!(exception)
|
|
104
109
|
end
|
|
105
110
|
|
|
106
111
|
# open-uri will return a StringIO instead of a Tempfile if the filesize is
|
|
@@ -117,7 +122,15 @@ module Down
|
|
|
117
122
|
end
|
|
118
123
|
|
|
119
124
|
def open(uri, options = {})
|
|
120
|
-
|
|
125
|
+
begin
|
|
126
|
+
uri = URI(uri)
|
|
127
|
+
if uri.class != URI::HTTP && uri.class != URI::HTTPS
|
|
128
|
+
raise Down::InvalidUrl, "URL scheme needs to be http or https"
|
|
129
|
+
end
|
|
130
|
+
rescue URI::InvalidURIError
|
|
131
|
+
raise Down::InvalidUrl, "URL was invalid"
|
|
132
|
+
end
|
|
133
|
+
|
|
121
134
|
http_class = Net::HTTP
|
|
122
135
|
|
|
123
136
|
if options[:proxy]
|
|
@@ -143,6 +156,9 @@ module Down
|
|
|
143
156
|
http.cert_store = store
|
|
144
157
|
end
|
|
145
158
|
|
|
159
|
+
http.read_timeout = options[:read_timeout] if options.key?(:read_timeout)
|
|
160
|
+
http.open_timeout = options[:open_timeout] if options.key?(:open_timeout)
|
|
161
|
+
|
|
146
162
|
request_headers = options.select { |key, value| key.is_a?(String) }
|
|
147
163
|
get = Net::HTTP::Get.new(uri.request_uri, request_headers)
|
|
148
164
|
get.basic_auth(uri.user, uri.password) if uri.user || uri.password
|
|
@@ -156,9 +172,13 @@ module Down
|
|
|
156
172
|
end
|
|
157
173
|
end
|
|
158
174
|
|
|
159
|
-
|
|
175
|
+
begin
|
|
176
|
+
response = request.resume
|
|
160
177
|
|
|
161
|
-
|
|
178
|
+
response_error!(response) unless (200..299).cover?(response.code.to_i)
|
|
179
|
+
rescue => exception
|
|
180
|
+
request_error!(exception)
|
|
181
|
+
end
|
|
162
182
|
|
|
163
183
|
down_params = {
|
|
164
184
|
chunks: response.enum_for(:read_body),
|
|
@@ -193,6 +213,47 @@ module Down
|
|
|
193
213
|
tempfile
|
|
194
214
|
end
|
|
195
215
|
|
|
216
|
+
def response_error!(response)
|
|
217
|
+
code = response.code.to_i
|
|
218
|
+
message = response.message.split(" ").map(&:capitalize).join(" ")
|
|
219
|
+
|
|
220
|
+
args = ["#{code} #{message}", response: response]
|
|
221
|
+
|
|
222
|
+
case response.code.to_i
|
|
223
|
+
when 400..499 then raise Down::ClientError.new(*args)
|
|
224
|
+
when 500..599 then raise Down::ServerError.new(*args)
|
|
225
|
+
else raise Down::ResponseError.new(*args)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def request_error!(exception)
|
|
230
|
+
case exception
|
|
231
|
+
when URI::InvalidURIError
|
|
232
|
+
raise Down::InvalidUrl, "URL was invalid"
|
|
233
|
+
when Errno::ECONNREFUSED
|
|
234
|
+
raise Down::ConnectionError, "connection was refused"
|
|
235
|
+
when EOFError,
|
|
236
|
+
IOError,
|
|
237
|
+
Errno::ECONNABORTED,
|
|
238
|
+
Errno::ECONNRESET,
|
|
239
|
+
Errno::EPIPE,
|
|
240
|
+
Errno::EINVAL,
|
|
241
|
+
Errno::EHOSTUNREACH
|
|
242
|
+
raise Down::ConnectionError, exception.message
|
|
243
|
+
when SocketError
|
|
244
|
+
raise Down::ConnectionError, "domain name could not be resolved"
|
|
245
|
+
when Errno::ETIMEDOUT,
|
|
246
|
+
Timeout::Error,
|
|
247
|
+
Net::OpenTimeout,
|
|
248
|
+
Net::ReadTimeout
|
|
249
|
+
raise Down::TimeoutError, "request timed out"
|
|
250
|
+
when defined?(OpenSSL) && OpenSSL::SSL::SSLError
|
|
251
|
+
raise Down::SSLError, exception.message
|
|
252
|
+
else
|
|
253
|
+
raise exception
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
196
257
|
module DownloadedFile
|
|
197
258
|
def original_filename
|
|
198
259
|
filename_from_content_disposition || filename_from_uri
|
data/lib/down/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: down
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Janko Marohnić
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-
|
|
11
|
+
date: 2017-06-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -52,6 +52,20 @@ dependencies:
|
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '2.1'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: docker-api
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
55
69
|
description:
|
|
56
70
|
email:
|
|
57
71
|
- janko.marohnic@gmail.com
|