down 4.4.0 → 4.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a80bdc5c9a560cb80bb1ad7ff6ff5d37c7286516
4
- data.tar.gz: f9015c19819666eaeeaa13dbcf5e9537e63699f9
2
+ SHA256:
3
+ metadata.gz: 82abe7a56a0950e4743ebba457ceb8cadb113303d2942ab2a2cfd34bc065d06a
4
+ data.tar.gz: bf7a15be5a3f4129f1ad6b85e7dba6757ac43f0877b3da4b7e18abc1703bb861
5
5
  SHA512:
6
- metadata.gz: 3a250ed7339d70e339f8591e260a85925395add6f18d8b4c2aa5cea40bfa420442606e0bdfa96b76df2661beec0830b1c011d609a16d15996bca80072b41c5e9
7
- data.tar.gz: 884481d0a865cf7664715a77fcd0ae9a43fc36aa6dc0bc7729197c1dea6c1382aaca80925a2c6411390ca2c72e6ea825e2be41681399c59bd0cc0e1fe403a020
6
+ metadata.gz: ae99273f99ef4ac8a3a23512482f1e5fa2dbaea5697f197681ad99543b7f07b9665c0532ebdf3d87521abf1d45f34ac1a52c21e314c1408e70e5299ed44ca9b7
7
+ data.tar.gz: 946eb4bda59b214c2aea5b885fd2d0ee9f85ac035cce635454a91e859d6d4dbf2e2a6de79ac6d9e631089aa12a7aa0fc7f2b96c73e6711890f3234eabe158489
@@ -1,3 +1,13 @@
1
+ ## 4.5.0 (2018-05-11)
2
+
3
+ * Deprecate passing an `HTTP::Client` object to `Down::Http#initialize` (@janko-m)
4
+
5
+ * Add ability to pass a block to `Down::Http#initialize` for extending default options (@janko-m)
6
+
7
+ * Return empty string when length is zero in `ChunkedIO#read` and `ChunkedIO#readpartial` (@janko-m)
8
+
9
+ * Make `posix-spawn` optional (@janko-m)
10
+
1
11
  ## 4.4.0 (2018-04-12)
2
12
 
3
13
  * Add `:method` option to `Down::Http` for specifying the request method (@janko-m)
data/README.md CHANGED
@@ -7,7 +7,7 @@ HTTP library.
7
7
  ## Installation
8
8
 
9
9
  ```rb
10
- gem "down"
10
+ gem "down", "~> 4.4"
11
11
  ```
12
12
 
13
13
  ## Downloading
@@ -235,7 +235,7 @@ Down.open("...")
235
235
  ### open-uri + Net::HTTP
236
236
 
237
237
  ```rb
238
- gem "down", ">= 3.0"
238
+ gem "down", "~> 4.4"
239
239
  ```
240
240
  ```rb
241
241
  require "down/net_http"
@@ -327,8 +327,8 @@ net_http.open("http://example.com/image.jpg")
327
327
  ### HTTP.rb
328
328
 
329
329
  ```rb
330
- gem "down", ">= 3.0"
331
- gem "http", "~> 2.1"
330
+ gem "down", "~> 4.4"
331
+ gem "http", "~> 3.2"
332
332
  ```
333
333
  ```rb
334
334
  require "down/http"
@@ -340,9 +340,10 @@ io = Down::Http.open("http://nature.com/forest.jpg")
340
340
  io #=> #<Down::ChunkedIO ...>
341
341
  ```
342
342
 
343
- Some features that give the HTTP.rb backend an advantage over open-uri +
344
- Net::HTTP include:
343
+ Some features that give the HTTP.rb backend an advantage over `open-uri` +
344
+ `Net::HTTP` include:
345
345
 
346
+ * Low memory usage (**10x less** than `open-uri`/`Net::HTTP`)
346
347
  * Correct URI parsing with [Addressable::URI]
347
348
  * Proper support for streaming downloads (`#download` and now reuse `#open`)
348
349
  * Proper support for SSL
@@ -371,7 +372,7 @@ You can also initialize the backend with default options:
371
372
  ```rb
372
373
  http = Down::Http.new(headers: { "Foo" => "Bar" })
373
374
  # or
374
- http = Down::Http.new(HTTP.timeout(connect: 3))
375
+ http = Down::Http.new { |client| client.timeout(connect: 3) }
375
376
 
376
377
  http.download("http://example.com/image.jpg")
377
378
  http.open("http://example.com/image.jpg")
@@ -393,7 +394,7 @@ down.download("http://example.org/image.jpg")
393
394
  ### Wget (experimental)
394
395
 
395
396
  ```rb
396
- gem "down", ">= 3.0"
397
+ gem "down", "~> 4.4"
397
398
  gem "posix-spawn" # omit if on JRuby
398
399
  gem "http_parser.rb"
399
400
  ```
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.require_path = "lib"
17
17
 
18
18
  spec.add_development_dependency "minitest", "~> 5.8"
19
- spec.add_development_dependency "mocha"
19
+ spec.add_development_dependency "mocha", "~> 1.5"
20
20
  spec.add_development_dependency "rake"
21
21
  spec.add_development_dependency "http", "~> 3.0"
22
22
  spec.add_development_dependency "posix-spawn" unless RUBY_ENGINE == "jruby"
@@ -3,6 +3,7 @@
3
3
  require "down/version"
4
4
  require "down/chunked_io"
5
5
  require "down/errors"
6
+ require "down/utils"
6
7
 
7
8
  require "fileutils"
8
9
 
@@ -77,7 +77,7 @@ module Down
77
77
  remaining_length = length - data.bytesize if length
78
78
  end
79
79
 
80
- data.to_s unless length && (data.nil? || data.empty?)
80
+ data.to_s unless length && length > 0 && (data.nil? || data.empty?)
81
81
  end
82
82
 
83
83
  # Implements IO#gets semantics. Without arguments it retrieves lines of
@@ -150,6 +150,8 @@ module Down
150
150
 
151
151
  data = outbuf.clear.force_encoding(@encoding) if outbuf
152
152
 
153
+ return data.to_s if length == 0
154
+
153
155
  if cache && !cache.eof?
154
156
  data = cache.read(length, outbuf)
155
157
  data.force_encoding(@encoding)
@@ -163,11 +165,13 @@ module Down
163
165
  remaining_length = data && length ? length - data.bytesize : length
164
166
 
165
167
  unless @buffer.nil? || remaining_length == 0
166
- buffered_data = if remaining_length && remaining_length < @buffer.bytesize
167
- @buffer.byteslice(0, remaining_length)
168
- else
169
- @buffer
170
- end
168
+ if remaining_length && remaining_length < @buffer.bytesize
169
+ buffered_data = @buffer.byteslice(0, remaining_length)
170
+ @buffer = @buffer.byteslice(remaining_length..-1)
171
+ else
172
+ buffered_data = @buffer
173
+ @buffer = nil
174
+ end
171
175
 
172
176
  if data
173
177
  data << buffered_data
@@ -177,12 +181,6 @@ module Down
177
181
 
178
182
  cache.write(buffered_data) if cache
179
183
 
180
- if buffered_data.bytesize < @buffer.bytesize
181
- @buffer = @buffer.byteslice(buffered_data.bytesize..-1)
182
- else
183
- @buffer = nil
184
- end
185
-
186
184
  buffered_data.clear unless buffered_data.equal?(data)
187
185
  end
188
186
 
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ gem "http", ">= 2.1.0", "< 4"
4
+
3
5
  require "http"
4
6
 
5
7
  require "down/backend"
@@ -8,40 +10,39 @@ require "tempfile"
8
10
  require "cgi"
9
11
  require "base64"
10
12
 
11
- if Gem::Version.new(HTTP::VERSION) < Gem::Version.new("2.1.0")
12
- fail "Down::Http requires HTTP.rb version 2.1.0 or higher"
13
- end
14
-
15
13
  module Down
16
14
  class Http < Backend
17
- def initialize(client_or_options = {})
18
- options = client_or_options.is_a?(HTTP::Client) ? client_or_options.default_options.to_hash : client_or_options
19
-
20
- @method = options.delete(:method) || :get
21
- @options = {
22
- headers: { "User-Agent" => "Down/#{Down::VERSION}" },
23
- follow: { max_hops: 2 },
24
- timeout_class: HTTP::Timeout::PerOperation,
25
- timeout_options: { write_timeout: 30, connect_timeout: 30, read_timeout: 30 }
26
- }.merge(options)
15
+ def initialize(options = {}, &block)
16
+ if options.is_a?(HTTP::Client)
17
+ warn "[Down] Passing an HTTP::Client object to Down::Http#initialize is deprecated and won't be supported in Down 5. Use the block initialization instead."
18
+ options = options.default_options.to_hash
19
+ end
20
+
21
+ @method = options.delete(:method) || :get
22
+ @client = HTTP
23
+ .headers("User-Agent" => "Down/#{Down::VERSION}")
24
+ .follow(max_hops: 2)
25
+ .timeout(connect: 30, write: 30, read: 30)
26
+
27
+ @client = HTTP::Client.new(@client.default_options.merge(options)) if options.any?
28
+ @client = block.call(@client) if block
27
29
  end
28
30
 
29
31
  def download(url, max_size: nil, progress_proc: nil, content_length_proc: nil, destination: nil, **options, &block)
30
- io = open(url, **options, rewindable: false, &block)
32
+ response = request(url, **options, &block)
31
33
 
32
- content_length_proc.call(io.size) if content_length_proc && io.size
34
+ content_length_proc.call(response.content_length) if content_length_proc && response.content_length
33
35
 
34
- if max_size && io.size && io.size > max_size
36
+ if max_size && response.content_length && response.content_length > max_size
35
37
  raise Down::TooLarge, "file is too large (max is #{max_size/1024/1024}MB)"
36
38
  end
37
39
 
38
- extname = File.extname(io.data[:response].uri.path)
40
+ extname = File.extname(response.uri.path)
39
41
  tempfile = Tempfile.new(["down-http", extname], binmode: true)
40
42
 
41
- until io.eof?
42
- chunk = io.readpartial(nil, buffer ||= String.new)
43
-
43
+ stream_body(response) do |chunk|
44
44
  tempfile.write(chunk)
45
+ chunk.clear # deallocate string
45
46
 
46
47
  progress_proc.call(tempfile.size) if progress_proc
47
48
 
@@ -53,45 +54,42 @@ module Down
53
54
  tempfile.open # flush written content
54
55
 
55
56
  tempfile.extend Down::Http::DownloadedFile
56
- tempfile.url = io.data[:response].uri.to_s
57
- tempfile.headers = io.data[:headers]
57
+ tempfile.url = response.uri.to_s
58
+ tempfile.headers = response.headers.to_h
58
59
 
59
60
  download_result(tempfile, destination)
60
61
  rescue
61
62
  tempfile.close! if tempfile
62
63
  raise
63
- ensure
64
- io.close if io
65
64
  end
66
65
 
67
66
  def open(url, rewindable: true, **options, &block)
68
67
  response = request(url, **options, &block)
69
68
 
70
- response_error!(response) unless response.status.success?
71
-
72
69
  Down::ChunkedIO.new(
73
70
  chunks: enum_for(:stream_body, response),
74
71
  size: response.content_length,
75
72
  encoding: response.content_type.charset,
76
73
  rewindable: rewindable,
77
- on_close: (-> { response.connection.close } unless default_client.persistent?),
78
74
  data: { status: response.code, headers: response.headers.to_h, response: response },
79
75
  )
80
76
  end
81
77
 
82
78
  private
83
79
 
84
- def default_client
85
- @default_client ||= HTTP::Client.new(@options)
80
+ def request(url, method: @method, **options, &block)
81
+ response = send_request(method, url, **options, &block)
82
+ response_error!(response) unless response.status.success?
83
+ response
86
84
  end
87
85
 
88
- def request(url, method: @method, **options, &block)
86
+ def send_request(method, url, **options, &block)
89
87
  url = process_url(url, options)
90
88
 
91
- client = default_client
89
+ client = @client
92
90
  client = block.call(client) if block
93
91
 
94
- client.send(method.to_s.downcase, url, options)
92
+ client.request(method, url, options)
95
93
  rescue => exception
96
94
  request_error!(exception)
97
95
  end
@@ -100,6 +98,8 @@ module Down
100
98
  response.body.each(&block)
101
99
  rescue => exception
102
100
  request_error!(exception)
101
+ ensure
102
+ response.connection.close unless @client.persistent?
103
103
  end
104
104
 
105
105
  def process_url(url, options)
@@ -147,34 +147,16 @@ module Down
147
147
  attr_accessor :url, :headers
148
148
 
149
149
  def original_filename
150
- filename_from_content_disposition || filename_from_url
150
+ Utils.filename_from_content_disposition(headers["Content-Disposition"]) ||
151
+ Utils.filename_from_path(HTTP::URI.parse(url).path)
151
152
  end
152
153
 
153
154
  def content_type
154
- content_type_header.mime_type
155
+ HTTP::ContentType.parse(headers["Content-Type"]).mime_type
155
156
  end
156
157
 
157
158
  def charset
158
- content_type_header.charset
159
- end
160
-
161
- private
162
-
163
- def content_type_header
164
- HTTP::ContentType.parse(headers["Content-Type"])
165
- end
166
-
167
- def filename_from_content_disposition
168
- content_disposition = headers["Content-Disposition"].to_s
169
- filename = content_disposition[/filename="([^"]*)"/, 1] || content_disposition[/filename=(.+)/, 1]
170
- filename = CGI.unescape(filename.to_s.strip)
171
- filename unless filename.empty?
172
- end
173
-
174
- def filename_from_url
175
- path = HTTP::URI.parse(url).path
176
- filename = path.split("/").last
177
- CGI.unescape(filename) if filename
159
+ HTTP::ContentType.parse(headers["Content-Type"]).charset
178
160
  end
179
161
  end
180
162
  end
@@ -266,27 +266,13 @@ module Down
266
266
 
267
267
  module DownloadedFile
268
268
  def original_filename
269
- filename_from_content_disposition || filename_from_uri
269
+ Utils.filename_from_content_disposition(meta["content-disposition"]) ||
270
+ Utils.filename_from_path(base_uri.path)
270
271
  end
271
272
 
272
273
  def content_type
273
274
  super unless meta["content-type"].to_s.empty?
274
275
  end
275
-
276
- private
277
-
278
- def filename_from_content_disposition
279
- content_disposition = meta["content-disposition"].to_s
280
- filename = content_disposition[/filename="([^"]*)"/, 1] || content_disposition[/filename=(.+)/, 1]
281
- filename = CGI.unescape(filename.to_s.strip)
282
- filename unless filename.empty?
283
- end
284
-
285
- def filename_from_uri
286
- path = base_uri.path
287
- filename = path.split("/").last
288
- CGI.unescape(filename) if filename
289
- end
290
276
  end
291
277
  end
292
278
  end
@@ -0,0 +1,21 @@
1
+ require "cgi"
2
+
3
+ module Down
4
+ module Utils
5
+ module_function
6
+
7
+ def filename_from_content_disposition(content_disposition)
8
+ content_disposition = content_disposition.to_s
9
+
10
+ filename = content_disposition[/filename="([^"]*)"/, 1] || content_disposition[/filename=(.+)/, 1]
11
+ filename = CGI.unescape(filename.to_s.strip)
12
+
13
+ filename unless filename.empty?
14
+ end
15
+
16
+ def filename_from_path(path)
17
+ filename = path.split("/").last
18
+ CGI.unescape(filename) if filename
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Down
4
- VERSION = "4.4.0"
4
+ VERSION = "4.5.0"
5
5
  end
@@ -1,9 +1,9 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- if RUBY_ENGINE == "jruby"
4
- require "open3"
5
- else
3
+ begin
6
4
  require "posix-spawn"
5
+ rescue LoadError
6
+ require "open3"
7
7
  end
8
8
  require "http_parser"
9
9
 
@@ -209,7 +209,8 @@ module Down
209
209
  attr_accessor :url, :headers
210
210
 
211
211
  def original_filename
212
- filename_from_content_disposition || filename_from_url
212
+ Utils.filename_from_content_disposition(headers["Content-Disposition"]) ||
213
+ Utils.filename_from_path(URI.parse(url).path)
213
214
  end
214
215
 
215
216
  def content_type
@@ -219,21 +220,6 @@ module Down
219
220
  def charset
220
221
  headers["Content-Type"].to_s[/;\s*charset=([^;]+)/i, 1]
221
222
  end
222
-
223
- private
224
-
225
- def filename_from_content_disposition
226
- content_disposition = headers["Content-Disposition"].to_s
227
- filename = content_disposition[/filename="([^"]*)"/, 1] || content_disposition[/filename=(.+)/, 1]
228
- filename = CGI.unescape(filename.to_s.strip)
229
- filename unless filename.empty?
230
- end
231
-
232
- def filename_from_url
233
- path = URI(url).path
234
- filename = path.split("/").last
235
- CGI.unescape(filename) if filename
236
- end
237
223
  end
238
224
  end
239
225
  end
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: 4.4.0
4
+ version: 4.5.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: 2018-04-12 00:00:00.000000000 Z
11
+ date: 2018-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: mocha
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '1.5'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '1.5'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -125,6 +125,7 @@ files:
125
125
  - lib/down/errors.rb
126
126
  - lib/down/http.rb
127
127
  - lib/down/net_http.rb
128
+ - lib/down/utils.rb
128
129
  - lib/down/version.rb
129
130
  - lib/down/wget.rb
130
131
  homepage: https://github.com/janko-m/down
@@ -147,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
148
  version: '0'
148
149
  requirements: []
149
150
  rubyforge_project:
150
- rubygems_version: 2.6.11
151
+ rubygems_version: 2.7.6
151
152
  signing_key:
152
153
  specification_version: 4
153
154
  summary: Robust streaming downloads using Net::HTTP, HTTP.rb or wget.