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 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