rack-large-uploads 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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 there is no upload file in the request, nginx generates a 405.
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; # NOTE: handles URI params, not form content.
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
@@ -1,12 +1,14 @@
1
- require "rack/large-uploads/version"
1
+ require 'fileutils'
2
+ require 'rack/large-uploads/version'
2
3
 
3
4
  # TODO: remove these dependencies:
4
- require "action_dispatch"
5
- require "active_support/core_ext"
5
+ require 'action_dispatch'
6
+ require 'active_support/core_ext'
6
7
 
7
8
  module Rack
8
9
  class LargeUploads
9
- autoload :UploadedFile, 'rack/large-uploads/uploaded_file'
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, request.params)
31
+ files = extract_files(request, params)
29
32
  end
30
33
 
31
- filter(:before, files) if files.present?
32
- response = @app.call(env)
33
- filter(:after, files) if files.present?
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
- response
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
- tempfile = ::File.new(tempfile) if tempfile.is_a?(String)
92
- return Rack::LargeUploads::UploadedFile.new(attributes.merge({ :tempfile => tempfile }))
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
 
@@ -0,0 +1,7 @@
1
+ module Rack
2
+ class LargeUploads
3
+ class UploadedChunk < ActionDispatch::Http::UploadedFile
4
+ # stay tuned
5
+ end
6
+ end
7
+ end
@@ -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)
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class LargeUploads
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
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.1
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-03-30 00:00:00.000000000 Z
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: &70180264879460 !ruby/object:Gem::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: *70180264879460
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: &70180264895180 !ruby/object:Gem::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: *70180264895180
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: &70180264894620 !ruby/object:Gem::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: *70180264894620
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: &70180264894040 !ruby/object:Gem::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: *70180264894040
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: &70180264893460 !ruby/object:Gem::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: *70180264893460
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: &70180264892740 !ruby/object:Gem::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: *70180264892740
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: &70180264892100 !ruby/object:Gem::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: *70180264892100
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: -1884482723306409026
171
+ hash: -2149263862982449584
136
172
  requirements: []
137
173
  rubyforge_project:
138
- rubygems_version: 1.8.11
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