down 4.4.0 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
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.