down 5.3.1 → 5.4.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/CHANGELOG.md +4 -0
- data/README.md +38 -8
- data/down.gemspec +1 -1
- data/lib/down/http.rb +1 -1
- data/lib/down/httpx.rb +175 -0
- data/lib/down/version.rb +1 -1
- metadata +20 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0544a70de3afab2a00c68df6923572a5533f05a0964b8c5186728019d04e55b
|
|
4
|
+
data.tar.gz: 3474da6a6c7a182aa02deb72139002ebf97ac98bee9c4a833de2b0413e373924
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 64d22c0a25ddf60f2dea37cb03264ec5770a8c7877fb7a843052abca4b043ba9d2d0c8eed830fc2cec97bb852338b114e2907c357aaf4b68aad26a6860459f67
|
|
7
|
+
data.tar.gz: 992209a7e4201cab464958bac0960ba4a6dc0344cd410b7ceaec2a02b89ebc1bd876396349204bb0bfe175a279fbdb41900c9fee85eee7e98d74eec7afa2e7a0
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Down
|
|
2
2
|
|
|
3
3
|
Down is a utility tool for streaming, flexible and safe downloading of remote
|
|
4
|
-
files. It can use [open-uri] + `Net::HTTP`, [http.rb] or `wget` as
|
|
5
|
-
HTTP library.
|
|
4
|
+
files. It can use [open-uri] + `Net::HTTP`, [http.rb], [HTTPX], or `wget` as
|
|
5
|
+
the backend HTTP library.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -234,6 +234,7 @@ The following backends are available:
|
|
|
234
234
|
|
|
235
235
|
* [Down::NetHttp](#downnethttp) (default)
|
|
236
236
|
* [Down::Http](#downhttp)
|
|
237
|
+
* [Down::Httpx](#downhttpx)
|
|
237
238
|
* [Down::Wget](#downwget)
|
|
238
239
|
|
|
239
240
|
You can use the backend directly:
|
|
@@ -442,6 +443,28 @@ down = Down::Http.new(method: :post)
|
|
|
442
443
|
down.download("http://example.org/image.jpg")
|
|
443
444
|
```
|
|
444
445
|
|
|
446
|
+
### Down::Httpx
|
|
447
|
+
|
|
448
|
+
The `Down::Httpx` backend implements downloads using the [HTTPX] gem, which
|
|
449
|
+
supports the HTTP/2 protocol, in addition to many other features.
|
|
450
|
+
|
|
451
|
+
```rb
|
|
452
|
+
gem "down", "~> 5.0"
|
|
453
|
+
gem "httpx", "~> 0.22"
|
|
454
|
+
```
|
|
455
|
+
```rb
|
|
456
|
+
require "down/httpx"
|
|
457
|
+
|
|
458
|
+
tempfile = Down::Httpx.download("http://nature.com/forest.jpg")
|
|
459
|
+
tempfile #=> #<Tempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20150925-55456-z7vxqz.jpg>
|
|
460
|
+
|
|
461
|
+
io = Down::Httpx.open("http://nature.com/forest.jpg")
|
|
462
|
+
io #=> #<Down::ChunkedIO ...>
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
It's implemented in much of the same way as `Down::Http`, so be sure to check
|
|
466
|
+
its docs for ways to pass additional options.
|
|
467
|
+
|
|
445
468
|
### Down::Wget (experimental)
|
|
446
469
|
|
|
447
470
|
The `Down::Wget` backend implements downloads using the `wget` command line
|
|
@@ -495,19 +518,25 @@ wget.open("http://nature.com/forest.jpg")
|
|
|
495
518
|
* MRI 2.5
|
|
496
519
|
* MRI 2.6
|
|
497
520
|
* MRI 2.7
|
|
498
|
-
*
|
|
521
|
+
* MRI 3.0
|
|
522
|
+
* MRI 3.1
|
|
523
|
+
* JRuby 9.3
|
|
499
524
|
|
|
500
525
|
## Development
|
|
501
526
|
|
|
502
|
-
|
|
527
|
+
Tests require that a [httpbin] server is running locally, which you can do via Docker:
|
|
528
|
+
|
|
529
|
+
```sh
|
|
530
|
+
$ docker pull kennethreitz/httpbin
|
|
531
|
+
$ docker run -p 80:80 kennethreitz/httpbin
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Then you can run tests:
|
|
503
535
|
|
|
504
536
|
```
|
|
505
537
|
$ bundle exec rake test
|
|
506
538
|
```
|
|
507
539
|
|
|
508
|
-
The test suite pulls and runs [kennethreitz/httpbin] as a Docker container, so
|
|
509
|
-
you'll need to have Docker installed and running.
|
|
510
|
-
|
|
511
540
|
## License
|
|
512
541
|
|
|
513
542
|
[MIT](LICENSE.txt)
|
|
@@ -515,5 +544,6 @@ you'll need to have Docker installed and running.
|
|
|
515
544
|
[open-uri]: http://ruby-doc.org/stdlib-2.3.0/libdoc/open-uri/rdoc/OpenURI.html
|
|
516
545
|
[Net::HTTP]: https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html
|
|
517
546
|
[http.rb]: https://github.com/httprb/http
|
|
547
|
+
[HTTPX]: https://github.com/HoneyryderChuck/httpx
|
|
518
548
|
[Addressable::URI]: https://github.com/sporkmonger/addressable
|
|
519
|
-
[
|
|
549
|
+
[httpbin]: https://github.com/postmanlabs/httpbin
|
data/down.gemspec
CHANGED
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
spec.add_development_dependency "minitest", "~> 5.8"
|
|
21
21
|
spec.add_development_dependency "mocha", "~> 1.5"
|
|
22
22
|
spec.add_development_dependency "rake"
|
|
23
|
+
spec.add_development_dependency "httpx", "~> 0.22", ">= 0.22.2"
|
|
23
24
|
# http 5.0 drop support of ruby 2.3 and 2.4. We still support those versions.
|
|
24
25
|
if RUBY_VERSION >= "2.5"
|
|
25
26
|
spec.add_development_dependency "http", "~> 5.0"
|
|
@@ -28,6 +29,5 @@ Gem::Specification.new do |spec|
|
|
|
28
29
|
end
|
|
29
30
|
spec.add_development_dependency "posix-spawn" unless RUBY_ENGINE == "jruby"
|
|
30
31
|
spec.add_development_dependency "http_parser.rb" unless RUBY_ENGINE == "jruby"
|
|
31
|
-
spec.add_development_dependency "docker-api"
|
|
32
32
|
spec.add_development_dependency "warning" if RUBY_VERSION >= "2.4"
|
|
33
33
|
end
|
data/lib/down/http.rb
CHANGED
data/lib/down/httpx.rb
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require "httpx"
|
|
6
|
+
|
|
7
|
+
require "down/backend"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
module Down
|
|
11
|
+
# Provides streaming downloads implemented with HTTPX.
|
|
12
|
+
class Httpx < Backend
|
|
13
|
+
# Initializes the backend
|
|
14
|
+
|
|
15
|
+
USER_AGENT = "Down/#{Down::VERSION}"
|
|
16
|
+
|
|
17
|
+
def initialize(**options, &block)
|
|
18
|
+
@method = options.delete(:method) || :get
|
|
19
|
+
headers = options.delete(:headers) || {}
|
|
20
|
+
@client = HTTPX
|
|
21
|
+
.plugin(:follow_redirects, max_redirects: 2)
|
|
22
|
+
.plugin(:basic_authentication)
|
|
23
|
+
.plugin(:stream)
|
|
24
|
+
.with(
|
|
25
|
+
headers: { "user-agent": USER_AGENT }.merge(headers),
|
|
26
|
+
timeout: { connect_timeout: 30, write_timeout: 30, read_timeout: 30 },
|
|
27
|
+
**options
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@client = block.call(@client) if block
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Downlods the remote file to disk. Accepts HTTPX options via a hash or a
|
|
35
|
+
# block, and some additional options as well.
|
|
36
|
+
def download(url, max_size: nil, progress_proc: nil, content_length_proc: nil, destination: nil, extension: nil, **options, &block)
|
|
37
|
+
client = @client
|
|
38
|
+
|
|
39
|
+
response = request(client, url, **options, &block)
|
|
40
|
+
|
|
41
|
+
content_length = nil
|
|
42
|
+
|
|
43
|
+
if response.headers.key?("content-length")
|
|
44
|
+
content_length = response.headers["content-length"].to_i
|
|
45
|
+
|
|
46
|
+
content_length_proc.call(content_length) if content_length_proc
|
|
47
|
+
|
|
48
|
+
if max_size && content_length > max_size
|
|
49
|
+
response.close
|
|
50
|
+
raise Down::TooLarge, "file is too large (#{content_length/1024/1024}MB, max is #{max_size/1024/1024}MB)"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
extname = extension ? ".#{extension}" : File.extname(response.uri.path)
|
|
55
|
+
tempfile = Tempfile.new(["down-http", extname], binmode: true)
|
|
56
|
+
|
|
57
|
+
stream_body(response) do |chunk|
|
|
58
|
+
tempfile.write(chunk)
|
|
59
|
+
chunk.clear # deallocate string
|
|
60
|
+
|
|
61
|
+
progress_proc.call(tempfile.size) if progress_proc
|
|
62
|
+
|
|
63
|
+
if max_size && tempfile.size > max_size
|
|
64
|
+
raise Down::TooLarge, "file is too large (#{tempfile.size/1024/1024}MB, max is #{max_size/1024/1024}MB)"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
tempfile.open # flush written content
|
|
69
|
+
|
|
70
|
+
tempfile.extend DownloadedFile
|
|
71
|
+
tempfile.url = response.uri.to_s
|
|
72
|
+
tempfile.headers = normalize_headers(response.headers.to_h)
|
|
73
|
+
tempfile.content_type = response.content_type.mime_type
|
|
74
|
+
tempfile.charset = response.body.encoding
|
|
75
|
+
|
|
76
|
+
download_result(tempfile, destination)
|
|
77
|
+
rescue
|
|
78
|
+
tempfile.close! if tempfile
|
|
79
|
+
raise
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Starts retrieving the remote file and returns an IO-like object which
|
|
83
|
+
# downloads the response body on-demand. Accepts HTTP.rb options via a hash
|
|
84
|
+
# or a block.
|
|
85
|
+
def open(url, rewindable: true, **options, &block)
|
|
86
|
+
response = request(@client, url, stream: true, **options, &block)
|
|
87
|
+
size = response.headers["content-length"]
|
|
88
|
+
size = size.to_i if size
|
|
89
|
+
Down::ChunkedIO.new(
|
|
90
|
+
chunks: enum_for(:stream_body, response),
|
|
91
|
+
size: size,
|
|
92
|
+
encoding: response.body.encoding,
|
|
93
|
+
rewindable: rewindable,
|
|
94
|
+
data: {
|
|
95
|
+
status: response.status,
|
|
96
|
+
headers: normalize_headers(response.headers.to_h),
|
|
97
|
+
response: response
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Yields chunks of the response body to the block.
|
|
105
|
+
def stream_body(response, &block)
|
|
106
|
+
response.each(&block)
|
|
107
|
+
rescue => exception
|
|
108
|
+
request_error!(exception)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def request(client, url, method: @method, **options, &block)
|
|
112
|
+
response = send_request(client, method, url, **options, &block)
|
|
113
|
+
response.raise_for_status
|
|
114
|
+
response_error!(response) unless (200..299).include?(response.status)
|
|
115
|
+
response
|
|
116
|
+
rescue HTTPX::HTTPError
|
|
117
|
+
response_error!(response)
|
|
118
|
+
rescue => error
|
|
119
|
+
request_error!(error)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def send_request(client, method, url, **options, &block)
|
|
123
|
+
uri = URI(url)
|
|
124
|
+
client = @client
|
|
125
|
+
if uri.user || uri.password
|
|
126
|
+
client = client.basic_auth(uri.user, uri.password)
|
|
127
|
+
uri.user = uri.password = nil
|
|
128
|
+
end
|
|
129
|
+
client = block.call(client) if block
|
|
130
|
+
|
|
131
|
+
client.request(method, uri, stream: true, **options)
|
|
132
|
+
rescue => exception
|
|
133
|
+
request_error!(exception)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Raises non-sucessful response as a Down::ResponseError.
|
|
137
|
+
def response_error!(response)
|
|
138
|
+
args = [response.status.to_s, response]
|
|
139
|
+
|
|
140
|
+
case response.status
|
|
141
|
+
when 300..399 then raise Down::TooManyRedirects, "too many redirects"
|
|
142
|
+
when 404 then raise Down::NotFound.new(*args)
|
|
143
|
+
when 400..499 then raise Down::ClientError.new(*args)
|
|
144
|
+
when 500..599 then raise Down::ServerError.new(*args)
|
|
145
|
+
else raise Down::ResponseError.new(*args)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Re-raise HTTP.rb exceptions as Down::Error exceptions.
|
|
150
|
+
def request_error!(exception)
|
|
151
|
+
case exception
|
|
152
|
+
when URI::Error, HTTPX::UnsupportedSchemeError
|
|
153
|
+
raise Down::InvalidUrl, exception.message
|
|
154
|
+
when HTTPX::ConnectionError
|
|
155
|
+
raise Down::ConnectionError, exception.message
|
|
156
|
+
when HTTPX::TimeoutError
|
|
157
|
+
raise Down::TimeoutError, exception.message
|
|
158
|
+
when OpenSSL::SSL::SSLError
|
|
159
|
+
raise Down::SSLError, exception.message
|
|
160
|
+
else
|
|
161
|
+
raise exception
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Defines some additional attributes for the returned Tempfile.
|
|
166
|
+
module DownloadedFile
|
|
167
|
+
attr_accessor :url, :headers, :charset, :content_type
|
|
168
|
+
|
|
169
|
+
def original_filename
|
|
170
|
+
Utils.filename_from_content_disposition(headers["Content-Disposition"]) ||
|
|
171
|
+
Utils.filename_from_path(URI.parse(url).path)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
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: 5.
|
|
4
|
+
version: 5.4.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: 2022-
|
|
11
|
+
date: 2022-12-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|
|
@@ -67,35 +67,41 @@ dependencies:
|
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
70
|
+
name: httpx
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
73
|
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
75
|
+
version: '0.22'
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: 0.22.2
|
|
76
79
|
type: :development
|
|
77
80
|
prerelease: false
|
|
78
81
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
82
|
requirements:
|
|
80
83
|
- - "~>"
|
|
81
84
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
85
|
+
version: '0.22'
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: 0.22.2
|
|
83
89
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
90
|
+
name: http
|
|
85
91
|
requirement: !ruby/object:Gem::Requirement
|
|
86
92
|
requirements:
|
|
87
|
-
- - "
|
|
93
|
+
- - "~>"
|
|
88
94
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
95
|
+
version: '5.0'
|
|
90
96
|
type: :development
|
|
91
97
|
prerelease: false
|
|
92
98
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
99
|
requirements:
|
|
94
|
-
- - "
|
|
100
|
+
- - "~>"
|
|
95
101
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
102
|
+
version: '5.0'
|
|
97
103
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
104
|
+
name: posix-spawn
|
|
99
105
|
requirement: !ruby/object:Gem::Requirement
|
|
100
106
|
requirements:
|
|
101
107
|
- - ">="
|
|
@@ -109,7 +115,7 @@ dependencies:
|
|
|
109
115
|
- !ruby/object:Gem::Version
|
|
110
116
|
version: '0'
|
|
111
117
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
118
|
+
name: http_parser.rb
|
|
113
119
|
requirement: !ruby/object:Gem::Requirement
|
|
114
120
|
requirements:
|
|
115
121
|
- - ">="
|
|
@@ -152,6 +158,7 @@ files:
|
|
|
152
158
|
- lib/down/chunked_io.rb
|
|
153
159
|
- lib/down/errors.rb
|
|
154
160
|
- lib/down/http.rb
|
|
161
|
+
- lib/down/httpx.rb
|
|
155
162
|
- lib/down/net_http.rb
|
|
156
163
|
- lib/down/utils.rb
|
|
157
164
|
- lib/down/version.rb
|
|
@@ -175,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
175
182
|
- !ruby/object:Gem::Version
|
|
176
183
|
version: '0'
|
|
177
184
|
requirements: []
|
|
178
|
-
rubygems_version: 3.
|
|
185
|
+
rubygems_version: 3.4.1
|
|
179
186
|
signing_key:
|
|
180
187
|
specification_version: 4
|
|
181
188
|
summary: Robust streaming downloads using Net::HTTP, HTTP.rb or wget.
|