rack-large-uploads 0.0.1 → 0.0.2
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.
- data/README.md +17 -2
- data/lib/rack/large-uploads.rb +58 -13
- data/lib/rack/large-uploads/uploaded_chunk.rb +7 -0
- data/lib/rack/large-uploads/uploaded_file.rb +3 -0
- data/lib/rack/large-uploads/version.rb +1 -1
- metadata +54 -18
data/README.md
CHANGED
@@ -11,6 +11,16 @@ Based largely on the [`Rack::Uploads` middleware](https://github.com/mutle/rack-
|
|
11
11
|
but with greater expectations regarding conventions, and specific support for
|
12
12
|
large files (dealing with memory issues).
|
13
13
|
|
14
|
+
Implements **(experimental)** handling of chunked uploads, only passing control
|
15
|
+
to, e.g., Rails once all uploads for the request are complete. This is very
|
16
|
+
experimental, because it is currently:
|
17
|
+
|
18
|
+
* dependent upon headers and filename value as set by blueimp's jQuery
|
19
|
+
fileupload plugin
|
20
|
+
* dependent on the presence of `uploader` request param, which is a UUID or
|
21
|
+
similar, and is used to collect the upload chunks
|
22
|
+
* untested
|
23
|
+
|
14
24
|
## Installation
|
15
25
|
|
16
26
|
Add this line to your application's Gemfile:
|
@@ -49,7 +59,7 @@ example nginx configuration:
|
|
49
59
|
# depends on the nginx upload module (http://www.grid.net.ru/nginx/upload.en.html)
|
50
60
|
upload_pass @application;
|
51
61
|
|
52
|
-
# NOTE: if
|
62
|
+
# NOTE: if the request is something other than POST, nginx generates a 405.
|
53
63
|
# use that to pass the request on to the endpoint, instead of try_files
|
54
64
|
# which fails to play nicely with upload_pass.
|
55
65
|
#
|
@@ -57,10 +67,15 @@ example nginx configuration:
|
|
57
67
|
# http://www.nickager.com/blog/File-upload-using-Nginx-and-Seaside---step-1
|
58
68
|
error_page 405 415 = @application;
|
59
69
|
|
70
|
+
# NOTE: cleaning up 201 & 202 means we need to be sure our app is doing
|
71
|
+
# some processing right away. Rack::LargeUploads sends 202 responses for
|
72
|
+
# each chunk received during chunked uploads, and the chunk has been
|
73
|
+
# appended to the combined file by the time we're back with the response.
|
74
|
+
upload_cleanup 201 202 400 404 499 500-505;
|
60
75
|
upload_store /path/to/app/tmp/uploads 1;
|
61
76
|
upload_store_access user:rw group:rw all:rw;
|
62
77
|
|
63
|
-
upload_pass_args on;
|
78
|
+
upload_pass_args on; # NOTE: handles URI params, not form content.
|
64
79
|
upload_pass_form_field "^[a-z_].*"; # ...and here is how we resolve that last.
|
65
80
|
|
66
81
|
# match the request params expected by ActionDispatch
|
data/lib/rack/large-uploads.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
require
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rack/large-uploads/version'
|
2
3
|
|
3
4
|
# TODO: remove these dependencies:
|
4
|
-
require
|
5
|
-
require
|
5
|
+
require 'action_dispatch'
|
6
|
+
require 'active_support/core_ext'
|
6
7
|
|
7
8
|
module Rack
|
8
9
|
class LargeUploads
|
9
|
-
autoload :
|
10
|
+
autoload :UploadedChunk, 'rack/large-uploads/uploaded_chunk'
|
11
|
+
autoload :UploadedFile, 'rack/large-uploads/uploaded_file'
|
10
12
|
|
11
13
|
def initialize(app, options = {}, &block)
|
12
14
|
@app = app
|
@@ -23,18 +25,28 @@ module Rack
|
|
23
25
|
|
24
26
|
def call(env)
|
25
27
|
request = Rack::Request.new(env)
|
28
|
+
params = request.params
|
26
29
|
|
27
30
|
if request.post? && request.form_data?
|
28
|
-
files = extract_files(request,
|
31
|
+
files = extract_files(request, params)
|
29
32
|
end
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
if files.present?
|
35
|
+
if files.all? { |f| f.is_a?(Rack::LargeUploads::UploadedFile) }
|
36
|
+
filter(:before, env, files)
|
37
|
+
response = @app.call(env)
|
38
|
+
filter(:after, env, files)
|
34
39
|
|
35
|
-
|
40
|
+
return response
|
41
|
+
else
|
42
|
+
return [202, {}, []]
|
43
|
+
end
|
44
|
+
else
|
45
|
+
@app.call(env)
|
46
|
+
end
|
36
47
|
end
|
37
48
|
|
49
|
+
|
38
50
|
def before(&block)
|
39
51
|
@filters[:before] = block
|
40
52
|
end
|
@@ -45,8 +57,8 @@ module Rack
|
|
45
57
|
|
46
58
|
private
|
47
59
|
|
48
|
-
def filter(position, files)
|
49
|
-
@filters[position] && @filters[position].call(files)
|
60
|
+
def filter(position, env, files)
|
61
|
+
@filters[position] && @filters[position].call(env, files)
|
50
62
|
end
|
51
63
|
|
52
64
|
def extract_files(request, params, files = [])
|
@@ -61,6 +73,10 @@ module Rack
|
|
61
73
|
files
|
62
74
|
end
|
63
75
|
|
76
|
+
# TODO:
|
77
|
+
# * make chunked check configurable (:filename value at least, maybe other)
|
78
|
+
# * make chunked storage path configurable
|
79
|
+
# * make 'uploader' param configurable, and per-upload rather than per-request
|
64
80
|
def file_from(request, key, value)
|
65
81
|
# direct to rails...
|
66
82
|
# ----->
|
@@ -83,13 +99,42 @@ module Rack
|
|
83
99
|
# "md5" =>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
84
100
|
# "size" =>"5242880000"
|
85
101
|
# }
|
102
|
+
|
86
103
|
if value.is_a?(Hash)
|
87
104
|
attributes = HashWithIndifferentAccess.new(value)
|
88
105
|
tempfile = attributes[:tempfile]
|
89
106
|
|
90
107
|
if tempfile.present?
|
91
|
-
|
92
|
-
|
108
|
+
# check for "chunked" upload
|
109
|
+
if attributes[:filename] == 'blob'
|
110
|
+
temppath = tempfile.is_a?(String) ? tempfile : tempfile.path
|
111
|
+
storage = ::File.expand_path(::File.join(temppath, '../../chunked'))
|
112
|
+
uploader = request.params['uploader']
|
113
|
+
combined = ::File.join(storage, uploader)
|
114
|
+
fullsize = request.env['HTTP_X_FILE_SIZE'].to_i
|
115
|
+
|
116
|
+
# append chunk to "combined" File
|
117
|
+
FileUtils.mkdir_p(storage)
|
118
|
+
::File.open(combined, 'ab') do |f|
|
119
|
+
f.write(::File.read(tempfile))
|
120
|
+
end
|
121
|
+
|
122
|
+
# finished?
|
123
|
+
if ::File.size(combined) == fullsize
|
124
|
+
attributes = {
|
125
|
+
:filename => request.env['HTTP_X_FILE_NAME'],
|
126
|
+
:size => request.env['HTTP_X_FILE_SIZE'],
|
127
|
+
:type => request.env['HTTP_X_FILE_TYPE'],
|
128
|
+
:tempfile => ::File.new(combined)
|
129
|
+
}
|
130
|
+
|
131
|
+
return Rack::LargeUploads::UploadedFile.new(attributes)
|
132
|
+
else
|
133
|
+
return Rack::LargeUploads::UploadedChunk.new(attributes)
|
134
|
+
end
|
135
|
+
else
|
136
|
+
return Rack::LargeUploads::UploadedFile.new(attributes)
|
137
|
+
end
|
93
138
|
end
|
94
139
|
end
|
95
140
|
|
@@ -2,6 +2,9 @@ module Rack
|
|
2
2
|
class LargeUploads
|
3
3
|
class UploadedFile < ActionDispatch::Http::UploadedFile
|
4
4
|
def initialize(hash)
|
5
|
+
tempfile = hash[:tempfile]
|
6
|
+
hash[:tempfile] = ::File.new(tempfile) if tempfile.is_a?(String)
|
7
|
+
|
5
8
|
@uploaded_md5 = hash.delete(:md5)
|
6
9
|
@uploaded_size = hash.delete(:size)
|
7
10
|
super(hash)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-large-uploads
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: rake
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,10 +37,15 @@ dependencies:
|
|
32
37
|
version: '0'
|
33
38
|
type: :development
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: rspec
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ! '>='
|
@@ -43,10 +53,15 @@ dependencies:
|
|
43
53
|
version: '0'
|
44
54
|
type: :development
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
47
62
|
- !ruby/object:Gem::Dependency
|
48
63
|
name: rr
|
49
|
-
requirement:
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
50
65
|
none: false
|
51
66
|
requirements:
|
52
67
|
- - ! '>='
|
@@ -54,10 +69,15 @@ dependencies:
|
|
54
69
|
version: '0'
|
55
70
|
type: :development
|
56
71
|
prerelease: false
|
57
|
-
version_requirements:
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
58
78
|
- !ruby/object:Gem::Dependency
|
59
79
|
name: simplecov
|
60
|
-
requirement:
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
61
81
|
none: false
|
62
82
|
requirements:
|
63
83
|
- - ! '>='
|
@@ -65,10 +85,15 @@ dependencies:
|
|
65
85
|
version: '0'
|
66
86
|
type: :development
|
67
87
|
prerelease: false
|
68
|
-
version_requirements:
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
69
94
|
- !ruby/object:Gem::Dependency
|
70
95
|
name: actionpack
|
71
|
-
requirement:
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
72
97
|
none: false
|
73
98
|
requirements:
|
74
99
|
- - ! '>='
|
@@ -76,10 +101,15 @@ dependencies:
|
|
76
101
|
version: '0'
|
77
102
|
type: :runtime
|
78
103
|
prerelease: false
|
79
|
-
version_requirements:
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
80
110
|
- !ruby/object:Gem::Dependency
|
81
111
|
name: activesupport
|
82
|
-
requirement:
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
83
113
|
none: false
|
84
114
|
requirements:
|
85
115
|
- - ! '>='
|
@@ -87,7 +117,12 @@ dependencies:
|
|
87
117
|
version: '0'
|
88
118
|
type: :runtime
|
89
119
|
prerelease: false
|
90
|
-
version_requirements:
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
91
126
|
description: ! 'Rack middleware for handling large file uploads. Integrates nicely
|
92
127
|
with the Nginx upload module: http://www.grid.net.ru/nginx/upload.en.html'
|
93
128
|
email:
|
@@ -107,6 +142,7 @@ files:
|
|
107
142
|
- bootstrap.gems
|
108
143
|
- lib/rack-large-uploads.rb
|
109
144
|
- lib/rack/large-uploads.rb
|
145
|
+
- lib/rack/large-uploads/uploaded_chunk.rb
|
110
146
|
- lib/rack/large-uploads/uploaded_file.rb
|
111
147
|
- lib/rack/large-uploads/version.rb
|
112
148
|
- rack-large-uploads.gemspec
|
@@ -132,10 +168,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
168
|
version: '0'
|
133
169
|
segments:
|
134
170
|
- 0
|
135
|
-
hash: -
|
171
|
+
hash: -2149263862982449584
|
136
172
|
requirements: []
|
137
173
|
rubyforge_project:
|
138
|
-
rubygems_version: 1.8.
|
174
|
+
rubygems_version: 1.8.24
|
139
175
|
signing_key:
|
140
176
|
specification_version: 3
|
141
177
|
summary: Rack middleware for handling large file uploads
|