tus-server 2.1.2 → 2.2.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 +16 -0
- data/README.md +153 -60
- data/lib/tus/info.rb +8 -4
- data/lib/tus/server.rb +57 -16
- data/lib/tus/storage/s3.rb +34 -17
- data/tus-server.gemspec +2 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9ba14174a689878aa1224bdb2e526d869b888dc85164ec3e9320c8a171bcd73
|
4
|
+
data.tar.gz: 61e227c29273bd00007a96522a16b5f470fc433741999b072bf1c3642e13289b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4914ab70cda026beee4de2b830778e43ef32131ddb65f470086d5cfd036f9dd046fafb09deced577d64965d539bf08a24ec44b810a77052822efde595610b04
|
7
|
+
data.tar.gz: 3c3f0d9196654541f5ebee297130c3d216dc1948a8a5c29695dc3251d969af85a101b0e249a889ec140e8f837c1fba1269a977649926d537c02dd5cd249d4e14
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
## 2.2.0 (2018-12-02)
|
2
|
+
|
3
|
+
* Add `before_create`, `after_create`, `after_finish`, and `after_terminate` hooks (@janko-m)
|
4
|
+
|
5
|
+
* Rename `Tus::Info#concatenation?` to `Tus::Info#final?` (@janko-m)
|
6
|
+
|
7
|
+
* Use `Storage#concurrency` for parallelized retrieval of partial uploads in `Upload-Concat` validation (@janko-m)
|
8
|
+
|
9
|
+
* Replace `:thread_count` with `:concurrency` in S3 storage (@janko-m)
|
10
|
+
|
11
|
+
* Validate that sum of partial uploads doesn't exceed `Tus-Max-Size` on concatenation (@janko-m)
|
12
|
+
|
13
|
+
* Drop MRI 2.2 support (@janko-m)
|
14
|
+
|
15
|
+
* Accept absolute URLs of partial uploads when creating a final upload (@janko-m)
|
16
|
+
|
1
17
|
## 2.1.2 (2018-10-21)
|
2
18
|
|
3
19
|
* Make tus-ruby-server fully work with non-rewindable Rack input (@janko-m)
|
data/README.md
CHANGED
@@ -18,7 +18,7 @@ gem "tus-server", "~> 2.0"
|
|
18
18
|
|
19
19
|
## Usage
|
20
20
|
|
21
|
-
The gem provides a `Tus::Server`
|
21
|
+
The gem provides a `Tus::Server` [Roda] app, which you can mount inside your
|
22
22
|
main application. If you're using Rails, you can mount it in `config/routes.rb`:
|
23
23
|
|
24
24
|
```rb
|
@@ -46,7 +46,7 @@ endpoint:
|
|
46
46
|
// using tus-js-client
|
47
47
|
new tus.Upload(file, {
|
48
48
|
endpoint: "/files",
|
49
|
-
chunkSize: 5*1024*1024, // required unless using
|
49
|
+
chunkSize: 5*1024*1024, // required unless using Falcon
|
50
50
|
// ...
|
51
51
|
})
|
52
52
|
```
|
@@ -54,79 +54,73 @@ new tus.Upload(file, {
|
|
54
54
|
By default uploaded files will be stored in the `data/` directory. After the
|
55
55
|
upload is complete, you'll probably want to attach the uploaded file to a
|
56
56
|
database record. [Shrine] is currently the only file attachment library that
|
57
|
-
|
58
|
-
walkthrough] that adds resumable uploads from scratch, and for a
|
59
|
-
example you can check out the [demo app][shrine-tus-demo].
|
57
|
+
provides an integration with tus-ruby-server, see [this walkthrough][shrine
|
58
|
+
resumable walkthrough] that adds resumable uploads from scratch, and for a
|
59
|
+
complete example you can check out the [demo app][shrine-tus-demo].
|
60
60
|
|
61
|
-
###
|
61
|
+
### Streaming web server
|
62
62
|
|
63
|
-
Running the tus server alongside your main app using
|
63
|
+
Running the tus server alongside your main app using popular web servers like
|
64
64
|
Puma or Unicorn is probably fine for most cases, however, it does come with a
|
65
65
|
few gotchas. First, since these web servers don't accept partial requests
|
66
66
|
(request where the request body hasn't been fully received), the tus client
|
67
67
|
must be configured to split the upload into multiple requests. Second, since
|
68
68
|
web workers are tied for the duration of the request, serving uploaded files
|
69
|
-
through the tus server app could significantly impact request throughput
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
69
|
+
through the tus server app could significantly impact request throughput; this
|
70
|
+
can be avoided by having your frontend server (Nginx) serve the files if using
|
71
|
+
`Filesystem` storage, or if you're using a cloud service like S3 having
|
72
|
+
download requests redirect to the service file URL.
|
73
|
+
|
74
|
+
That being said, there is a ruby web server that addresses these limitations
|
75
|
+
– [Falcon]. Falcon is part of the [`async` ecosystem][async], and it utilizes
|
76
|
+
non-blocking IO to process requests and responses in a streaming fashion
|
77
|
+
without tying up your web workers. This has several benefits for
|
78
|
+
`tus-ruby-server`:
|
79
|
+
|
80
|
+
* since tus server is called to handle the request as soon as the request
|
81
|
+
headers are received, data from the request body will be uploaded to the
|
82
|
+
configured storage as it's coming in from the client
|
83
|
+
|
84
|
+
* your web workers don't get tied up waiting for the client data, because as
|
85
|
+
soon as the server needs to wait for more data from the client, Falcon's
|
86
|
+
reactor switches to processing another request
|
87
|
+
|
88
|
+
* if the upload request that's in progress gets interrupted, tus server will be
|
89
|
+
able save data that has been received so far, so it's not necessary for the
|
90
|
+
tus client to split the upload into multiple chunks
|
91
|
+
|
92
|
+
* when uploaded files are being downloaded from the tus server, the request
|
93
|
+
throughput won't be impacted by the speed in which the client retrieves the
|
94
|
+
response body, because Falcon's reactor will switch to another request if the
|
95
|
+
client buffer gets full
|
96
|
+
|
97
|
+
Falcon provides a Rack adapter and is compatible with Rails, so you can use it
|
98
|
+
even if you're mounting `tus-ruby-server` inside your main app, just add
|
99
|
+
`falcon` to the Gemfile and run `falcon serve`.
|
87
100
|
|
88
101
|
```rb
|
89
102
|
# Gemfile
|
90
|
-
gem "
|
91
|
-
gem "goliath-rack_proxy", "~> 1.0"
|
92
|
-
```
|
93
|
-
```rb
|
94
|
-
# tus.rb
|
95
|
-
require "tus/server"
|
96
|
-
require "goliath/rack_proxy"
|
97
|
-
|
98
|
-
# any additional Tus::Server configuration you want to put in here
|
99
|
-
|
100
|
-
class GoliathTusServer < Goliath::RackProxy
|
101
|
-
rack_app Tus::Server
|
102
|
-
rewindable_input false # set to true if you're using checksums
|
103
|
-
end
|
103
|
+
gem "falcon"
|
104
104
|
```
|
105
105
|
```sh
|
106
|
-
$
|
106
|
+
$ falcon serve # reads from config.ru and starts the server
|
107
107
|
```
|
108
108
|
|
109
|
-
|
110
|
-
|
109
|
+
Alternatively, you can run `tus-ruby-server` as a standalone app, by creating
|
110
|
+
a separate "rackup" file just for it and pointing Falcon to that rackup file:
|
111
111
|
|
112
112
|
```rb
|
113
|
-
|
114
|
-
|
115
|
-
map("/files") { run Tus::Server }
|
116
|
-
}
|
117
|
-
rewindable_input false # set to true if you're using checksums
|
118
|
-
end
|
119
|
-
```
|
113
|
+
# tus.ru
|
114
|
+
require "tus/server"
|
120
115
|
|
121
|
-
|
122
|
-
Goliath app:
|
116
|
+
# ... configure tus-ruby-server ...
|
123
117
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
118
|
+
map "/files" do
|
119
|
+
run Tus::Server
|
120
|
+
end
|
121
|
+
```
|
122
|
+
```sh
|
123
|
+
$ falcon serve --config tus.ru
|
130
124
|
```
|
131
125
|
|
132
126
|
## Storage
|
@@ -258,6 +252,13 @@ can for example change the `:endpoint` to use S3's accelerate host:
|
|
258
252
|
Tus::Storage::S3.new(endpoint: "https://s3-accelerate.amazonaws.com", **options)
|
259
253
|
```
|
260
254
|
|
255
|
+
If you're using [concatenation], you can specify the concurrency in which S3
|
256
|
+
storage will copy partial uploads to the final upload (defaults to `10`):
|
257
|
+
|
258
|
+
```rb
|
259
|
+
Tus::Storage::S3.new(concurrency: { concatenation: 20 }, **options)
|
260
|
+
```
|
261
|
+
|
261
262
|
### Google Cloud Storage, Microsoft Azure Blob Storage
|
262
263
|
|
263
264
|
While tus-ruby-server doesn't currently ship with integrations for Google Cloud
|
@@ -360,6 +361,84 @@ def delete_file(uid, info = {}) ... end
|
|
360
361
|
def expire_files(expiration_date) ... end
|
361
362
|
```
|
362
363
|
|
364
|
+
## Hooks
|
365
|
+
|
366
|
+
You can register code to be executed on the following events:
|
367
|
+
|
368
|
+
* `before_create` – before upload has been created
|
369
|
+
* `after_create` – before upload has been created
|
370
|
+
* `after_finish` – after the last chunk has been stored
|
371
|
+
* `after_terminate` – after the upload has been deleted
|
372
|
+
|
373
|
+
Each hook also receives two parameters: ID of the upload (`String`) and
|
374
|
+
additional information about the upload (`Tus::Info`).
|
375
|
+
|
376
|
+
```rb
|
377
|
+
Tus::Server.after_finish do |uid, info|
|
378
|
+
uid #=> "c0b67b04a9eccb4b1202000de628964f"
|
379
|
+
info #=> #<Tus::Info>
|
380
|
+
|
381
|
+
info.length #=> 10 (Upload-Length)
|
382
|
+
info.offset #=> 0 (Upload-Offset)
|
383
|
+
info.metadata #=> {...} (Upload-Metadata)
|
384
|
+
info.expires #=> #<Time> (Upload-Expires)
|
385
|
+
|
386
|
+
info.partial? #=> false (Upload-Concat)
|
387
|
+
info.final? #=> false (Upload-Concat)
|
388
|
+
|
389
|
+
info.defer_length? #=> false (Upload-Defer-Length)
|
390
|
+
end
|
391
|
+
```
|
392
|
+
|
393
|
+
Because each hook is evaluated inside the `Tus::Server` instance (which is also
|
394
|
+
a [Roda] instance), you can access request information and set response status
|
395
|
+
and headers:
|
396
|
+
|
397
|
+
```rb
|
398
|
+
Tus::Server.after_terminate do |uid, info|
|
399
|
+
self #=> #<Tus::Server> (and #<Roda>)
|
400
|
+
request #=> #<Roda::Request>
|
401
|
+
response #=> #<Roda::Response>
|
402
|
+
end
|
403
|
+
```
|
404
|
+
|
405
|
+
So, with hooks you could for example add authentication to `Tus::Server`:
|
406
|
+
|
407
|
+
```rb
|
408
|
+
Tus::Server.before_create do |uid, info|
|
409
|
+
authenticated = Authentication.call(request.headers["Authorization"])
|
410
|
+
|
411
|
+
unless authenticated
|
412
|
+
response.status = 403 # Forbidden
|
413
|
+
response.write("Not authenticated")
|
414
|
+
request.halt # return response now
|
415
|
+
end
|
416
|
+
end
|
417
|
+
```
|
418
|
+
|
419
|
+
If you want to add hooks on more types of events, you can use Roda's
|
420
|
+
[hooks][roda hooks] plugin to set `before` or `after` hooks for any request,
|
421
|
+
which are also evaluated in context of the `Roda` instance:
|
422
|
+
|
423
|
+
```rb
|
424
|
+
Tus::Server.plugin :hooks
|
425
|
+
Tus::Server.before do
|
426
|
+
# called before each Roda request
|
427
|
+
end
|
428
|
+
Tus::Server.after do
|
429
|
+
# called after each Roda request
|
430
|
+
end
|
431
|
+
```
|
432
|
+
|
433
|
+
Provided that you're mounting `Tus::Server` inside your main app, any Rack
|
434
|
+
middlewares that your main app uses will also be called for requests routed to
|
435
|
+
the `Tus::Server`. If you want to add Rack middlewares to `Tus::Server`, you
|
436
|
+
can do it by calling `Tus::Server.use`:
|
437
|
+
|
438
|
+
```rb
|
439
|
+
Tus::Server.use SomeRackMiddleware
|
440
|
+
```
|
441
|
+
|
363
442
|
## Maximum size
|
364
443
|
|
365
444
|
By default the size of files the tus server will accept is unlimited, but you
|
@@ -427,6 +506,19 @@ The following checksum algorithms are supported for the `checksum` extension:
|
|
427
506
|
* MD5
|
428
507
|
* CRC32
|
429
508
|
|
509
|
+
## Concatenation
|
510
|
+
|
511
|
+
When validating the `Upload-Concat` header for the final upload,
|
512
|
+
tus-ruby-server needs to first fetch info about all partial uploads in order to
|
513
|
+
check whether everything is in order. By default this retrieval is parallelized
|
514
|
+
with 10 threads, but if you're using S3 storage you can change the
|
515
|
+
`:concurrency`, and `Tus::Server` will automatically pick it up for its
|
516
|
+
validation:
|
517
|
+
|
518
|
+
```rb
|
519
|
+
Tus::Storage::S3.new(concurrency: { concatenation: 20 }, **options)
|
520
|
+
```
|
521
|
+
|
430
522
|
## Tests
|
431
523
|
|
432
524
|
Run tests with
|
@@ -446,6 +538,8 @@ The tus-ruby-server was inspired by [rubytus] and [tusd].
|
|
446
538
|
|
447
539
|
[MIT](/LICENSE.txt)
|
448
540
|
|
541
|
+
[Roda]: https://github.com/jeremyevans/roda
|
542
|
+
[roda hooks]: http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Hooks.html
|
449
543
|
[tus resumable upload protocol]: http://tus.io/
|
450
544
|
[tus-js-client]: https://github.com/tus/tus-js-client
|
451
545
|
[creation]: http://tus.io/protocols/resumable-upload.html#creation
|
@@ -463,9 +557,6 @@ The tus-ruby-server was inspired by [rubytus] and [tusd].
|
|
463
557
|
[`Aws::S3::Client#initialize`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#initialize-instance_method
|
464
558
|
[`Aws::S3::Client#create_multipart_upload`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#create_multipart_upload-instance_method
|
465
559
|
[Range requests]: https://tools.ietf.org/html/rfc7233
|
466
|
-
[Goliath]: https://github.com/postrank-labs/goliath
|
467
|
-
[EventMachine]: https://github.com/eventmachine/eventmachine
|
468
|
-
[goliath-rack_proxy]: https://github.com/janko-m/goliath-rack_proxy
|
469
560
|
[Rack::Sendfile]: https://www.rubydoc.info/github/rack/rack/master/Rack/Sendfile
|
470
561
|
[`Aws::S3::Object#get`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#get-instance_method
|
471
562
|
[shrine resumable walkthrough]: https://github.com/shrinerb/shrine/wiki/Adding-Resumable-Uploads
|
@@ -473,3 +564,5 @@ The tus-ruby-server was inspired by [rubytus] and [tusd].
|
|
473
564
|
[minio gcp]: https://minio.io/gcp.html
|
474
565
|
[minio azure]: https://minio.io/azure.html
|
475
566
|
[tusd]: https://github.com/tus/tusd
|
567
|
+
[Falcon]: https://github.com/socketry/falcon
|
568
|
+
[async]: https://github.com/socketry
|
data/lib/tus/info.rb
CHANGED
@@ -51,12 +51,12 @@ module Tus
|
|
51
51
|
Time.parse(@hash["Upload-Expires"])
|
52
52
|
end
|
53
53
|
|
54
|
-
def
|
55
|
-
@hash["Upload-Concat"]
|
54
|
+
def partial?
|
55
|
+
@hash["Upload-Concat"] == "partial"
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
59
|
-
@hash["Upload-
|
58
|
+
def final?
|
59
|
+
@hash["Upload-Concat"].to_s.start_with?("final")
|
60
60
|
end
|
61
61
|
|
62
62
|
def partial_uploads
|
@@ -64,6 +64,10 @@ module Tus
|
|
64
64
|
urls.map { |url| url.split("/").last }
|
65
65
|
end
|
66
66
|
|
67
|
+
def defer_length?
|
68
|
+
@hash["Upload-Defer-Length"] == "1"
|
69
|
+
end
|
70
|
+
|
67
71
|
def remaining_length
|
68
72
|
length - offset
|
69
73
|
end
|
data/lib/tus/server.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
|
+
|
2
3
|
require "roda"
|
3
4
|
|
4
5
|
require "tus/storage/filesystem"
|
@@ -24,11 +25,13 @@ module Tus
|
|
24
25
|
]
|
25
26
|
SUPPORTED_CHECKSUM_ALGORITHMS = %w[sha1 sha256 sha384 sha512 md5 crc32]
|
26
27
|
RESUMABLE_CONTENT_TYPE = "application/offset+octet-stream"
|
28
|
+
HOOKS = %i[before_create after_create after_finish after_terminate]
|
27
29
|
|
28
30
|
opts[:max_size] = nil
|
29
31
|
opts[:expiration_time] = 7*24*60*60
|
30
32
|
opts[:disposition] = "inline"
|
31
33
|
opts[:redirect_download] = nil
|
34
|
+
opts[:hooks] = {}
|
32
35
|
|
33
36
|
plugin :all_verbs
|
34
37
|
plugin :default_headers, {"Content-Type" => ""}
|
@@ -36,6 +39,16 @@ module Tus
|
|
36
39
|
plugin :request_headers
|
37
40
|
plugin :not_allowed
|
38
41
|
|
42
|
+
HOOKS.each do |hook|
|
43
|
+
define_singleton_method(hook) do |&block|
|
44
|
+
opts[:hooks][hook] = block
|
45
|
+
end
|
46
|
+
|
47
|
+
define_method(hook) do |*args|
|
48
|
+
instance_exec(*args, &opts[:hooks][hook]) if opts[:hooks][hook]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
39
52
|
route do |r|
|
40
53
|
if request.headers["X-HTTP-Method-Override"]
|
41
54
|
request.env["REQUEST_METHOD"] = request.headers["X-HTTP-Method-Override"]
|
@@ -77,7 +90,9 @@ module Tus
|
|
77
90
|
"Upload-Expires" => (Time.now + expiration_time).httpdate,
|
78
91
|
)
|
79
92
|
|
80
|
-
|
93
|
+
before_create(uid, info)
|
94
|
+
|
95
|
+
if info.final?
|
81
96
|
validate_partial_uploads!(info.partial_uploads)
|
82
97
|
|
83
98
|
length = storage.concatenate(uid, info.partial_uploads, info.to_h)
|
@@ -87,8 +102,9 @@ module Tus
|
|
87
102
|
storage.create_file(uid, info.to_h)
|
88
103
|
end
|
89
104
|
|
90
|
-
|
105
|
+
after_create(uid, info)
|
91
106
|
|
107
|
+
storage.update_info(uid, info.to_h)
|
92
108
|
response.headers.update(info.headers)
|
93
109
|
|
94
110
|
file_url = "#{request.url.chomp("/")}/#{uid}"
|
@@ -154,6 +170,8 @@ module Tus
|
|
154
170
|
|
155
171
|
if info.offset == info.length # last chunk
|
156
172
|
storage.finalize_file(uid, info.to_h) if storage.respond_to?(:finalize_file)
|
173
|
+
|
174
|
+
after_finish(uid, info)
|
157
175
|
end
|
158
176
|
|
159
177
|
storage.update_info(uid, info.to_h)
|
@@ -198,6 +216,8 @@ module Tus
|
|
198
216
|
r.delete do
|
199
217
|
storage.delete_file(uid, info.to_h)
|
200
218
|
|
219
|
+
after_terminate(uid, info)
|
220
|
+
|
201
221
|
no_content!
|
202
222
|
end
|
203
223
|
end
|
@@ -285,34 +305,55 @@ module Tus
|
|
285
305
|
if upload_concat.start_with?("final")
|
286
306
|
string = upload_concat.split(";").last
|
287
307
|
string.split(" ").each do |url|
|
288
|
-
error!(400, "Invalid Upload-Concat header") if url !~
|
308
|
+
error!(400, "Invalid Upload-Concat header") if url !~ /#{request.script_name}\/\w+$/
|
289
309
|
end
|
290
310
|
end
|
291
311
|
end
|
292
312
|
|
293
313
|
# Validates that each partial upload exists and is marked as one.
|
294
314
|
def validate_partial_uploads!(part_uids)
|
295
|
-
|
296
|
-
part_uids.each { |part_uid|
|
315
|
+
input = Queue.new
|
316
|
+
part_uids.each { |part_uid| input << part_uid }
|
317
|
+
input.close
|
297
318
|
|
298
|
-
|
319
|
+
results = Queue.new
|
320
|
+
|
321
|
+
thread_count = storage.concurrency[:concatenation] if storage.respond_to?(:concurrency)
|
322
|
+
thread_count ||= 10
|
323
|
+
|
324
|
+
threads = thread_count.times.map do
|
299
325
|
Thread.new do
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
326
|
+
begin
|
327
|
+
loop do
|
328
|
+
part_uid = input.pop or break
|
329
|
+
part_info = storage.read_info(part_uid)
|
330
|
+
results << Tus::Info.new(part_info)
|
331
|
+
end
|
332
|
+
nil
|
333
|
+
rescue => error
|
334
|
+
input.clear
|
335
|
+
error
|
305
336
|
end
|
306
|
-
results
|
307
337
|
end
|
308
338
|
end
|
309
339
|
|
310
|
-
|
311
|
-
|
340
|
+
errors = threads.map(&:value).compact
|
341
|
+
|
342
|
+
if errors.any? { |error| error.is_a?(Tus::NotFound) }
|
343
|
+
error!(400, "One or more partial uploads were not found")
|
344
|
+
elsif errors.any?
|
345
|
+
fail errors.first
|
346
|
+
end
|
347
|
+
|
348
|
+
part_infos = Array.new(results.size) { results.pop } # convert Queue into an Array
|
349
|
+
|
350
|
+
unless part_infos.all?(&:partial?)
|
312
351
|
error!(400, "One or more uploads were not partial")
|
313
352
|
end
|
314
|
-
|
315
|
-
|
353
|
+
|
354
|
+
if max_size && part_infos.map(&:length).inject(0, :+) > max_size
|
355
|
+
error!(400, "The sum of partial upload lengths exceed Tus-Max-Size")
|
356
|
+
end
|
316
357
|
end
|
317
358
|
|
318
359
|
def validate_upload_checksum!(input)
|
data/lib/tus/storage/s3.rb
CHANGED
@@ -16,17 +16,24 @@ module Tus
|
|
16
16
|
class S3
|
17
17
|
MIN_PART_SIZE = 5 * 1024 * 1024 # 5MB is the minimum part size for S3 multipart uploads
|
18
18
|
|
19
|
-
attr_reader :client, :bucket, :prefix, :upload_options
|
19
|
+
attr_reader :client, :bucket, :prefix, :upload_options, :concurrency
|
20
20
|
|
21
21
|
# Initializes an aws-sdk-s3 client with the given credentials.
|
22
|
-
def initialize(bucket:, prefix: nil, upload_options: {}, thread_count:
|
22
|
+
def initialize(bucket:, prefix: nil, upload_options: {}, concurrency: {}, thread_count: nil, **client_options)
|
23
|
+
fail ArgumentError, "the :bucket option was nil" unless bucket
|
24
|
+
|
25
|
+
if thread_count
|
26
|
+
warn "[Tus-Ruby-Server] :thread_count is deprecated and will be removed in the next major version, use :concurrency instead, e.g `concurrency: { concatenation: 20 }`"
|
27
|
+
concurrency[:concatenation] = thread_count
|
28
|
+
end
|
29
|
+
|
23
30
|
resource = Aws::S3::Resource.new(**client_options)
|
24
31
|
|
25
32
|
@client = resource.client
|
26
|
-
@bucket = resource.bucket(bucket)
|
33
|
+
@bucket = resource.bucket(bucket)
|
27
34
|
@prefix = prefix
|
28
35
|
@upload_options = upload_options
|
29
|
-
@
|
36
|
+
@concurrency = concurrency
|
30
37
|
end
|
31
38
|
|
32
39
|
# Initiates multipart upload for the given upload, and stores its
|
@@ -122,7 +129,8 @@ module Tus
|
|
122
129
|
end
|
123
130
|
|
124
131
|
begin
|
125
|
-
|
132
|
+
part = upload_part(chunk, uid, upload_id, part_offset += 1)
|
133
|
+
info["multipart_parts"] << part
|
126
134
|
bytes_uploaded += chunk.bytesize
|
127
135
|
rescue Seahorse::Client::NetworkingError => exception
|
128
136
|
warn "ERROR: #{exception.inspect} occurred during upload"
|
@@ -256,12 +264,21 @@ module Tus
|
|
256
264
|
# given objects into them. It uses a queue and a fixed-size thread pool
|
257
265
|
# which consumes that queue.
|
258
266
|
def copy_parts(objects, multipart_upload)
|
259
|
-
parts
|
260
|
-
|
267
|
+
parts = compute_parts(objects, multipart_upload)
|
268
|
+
input = Queue.new
|
269
|
+
results = Queue.new
|
270
|
+
|
271
|
+
parts.each { |part| input << part }
|
272
|
+
input.close
|
273
|
+
|
274
|
+
thread_count = concurrency[:concatenation] || 10
|
275
|
+
threads = thread_count.times.map { copy_part_thread(input, results) }
|
261
276
|
|
262
|
-
|
277
|
+
errors = threads.map(&:value).compact
|
278
|
+
fail errors.first if errors.any?
|
263
279
|
|
264
|
-
|
280
|
+
part_results = Array.new(results.size) { results.pop } # convert Queue into an Array
|
281
|
+
part_results.sort_by { |part| part.fetch("part_number") }
|
265
282
|
end
|
266
283
|
|
267
284
|
# Computes data required for copying objects into new multipart parts.
|
@@ -279,18 +296,18 @@ module Tus
|
|
279
296
|
|
280
297
|
# Consumes the queue for new multipart part information and issues the
|
281
298
|
# copy requests.
|
282
|
-
def copy_part_thread(
|
299
|
+
def copy_part_thread(input, results)
|
283
300
|
Thread.new do
|
284
301
|
begin
|
285
|
-
results = []
|
286
302
|
loop do
|
287
|
-
part =
|
288
|
-
|
303
|
+
part = input.pop or break
|
304
|
+
part_result = copy_part(part)
|
305
|
+
results << part_result
|
289
306
|
end
|
290
|
-
|
291
|
-
rescue
|
292
|
-
|
293
|
-
|
307
|
+
nil
|
308
|
+
rescue => error
|
309
|
+
input.clear # clear other work
|
310
|
+
error
|
294
311
|
end
|
295
312
|
end
|
296
313
|
end
|
data/tus-server.gemspec
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
2
|
gem.name = "tus-server"
|
3
|
-
gem.version = "2.
|
3
|
+
gem.version = "2.2.0"
|
4
4
|
|
5
|
-
gem.required_ruby_version = ">= 2.
|
5
|
+
gem.required_ruby_version = ">= 2.3"
|
6
6
|
|
7
7
|
gem.summary = "Ruby server implementation of tus.io, the open protocol for resumable file uploads."
|
8
8
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tus-server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.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-
|
11
|
+
date: 2018-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: roda
|
@@ -148,7 +148,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
148
148
|
requirements:
|
149
149
|
- - ">="
|
150
150
|
- !ruby/object:Gem::Version
|
151
|
-
version: '2.
|
151
|
+
version: '2.3'
|
152
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
153
|
requirements:
|
154
154
|
- - ">="
|