1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
|
=encoding utf-8
=head1 Name
lua-resty-upload - Streaming reader and parser for HTTP file uploading based on ngx_lua cosocket
=head1 Status
This library is considered production ready.
=head1 Description
This Lua library is a streaming file uploading API for the ngx_lua nginx module:
http://wiki.nginx.org/HttpLuaModule
The multipart/form-data MIME type is supported.
The API of this library just returns tokens one by one. The user just needs to call the C<read> method repeatedly until a nil token type is returned. For each token returned from the C<read> method, just check the first return value for the current token type. The token type can be C<header>, C<body>, and C<part end>. Each C<multipart/form-data> form field parsed consists of several C<header> tokens holding each field header, several C<body> tokens holding each body data chunk, and a C<part end> flag indicating the field end.
This is how streaming reading works. Even for giga bytes of file data input, the memory used in the lua land can be small and constant, as long as the user does not accumulate the input data chunks herself.
This Lua library takes advantage of ngx_lua's cosocket API, which ensures
100% nonblocking behavior.
Note that at least L<ngx_lua 0.7.9|https://github.com/chaoslawful/lua-nginx-module/tags> or L<OpenResty 1.2.4.14|http://openresty.org/#Download> is required.
=head1 Synopsis
lua_package_path "/path/to/lua-resty-upload/lib/?.lua;;";
server {
location /test {
content_by_lua '
local upload = require "resty.upload"
local cjson = require "cjson"
local chunk_size = 5 -- should be set to 4096 or 8192
-- for real-world settings
local form, err = upload:new(chunk_size)
if not form then
ngx.log(ngx.ERR, "failed to new upload: ", err)
ngx.exit(500)
end
form:set_timeout(1000) -- 1 sec
while true do
local typ, res, err = form:read()
if not typ then
ngx.say("failed to read: ", err)
return
end
ngx.say("read: ", cjson.encode({typ, res}))
if typ == "eof" then
break
end
end
local typ, res, err = form:read()
ngx.say("read: ", cjson.encode({typ, res}))
';
}
}
A typical output of the /test location defined above is:
read: ["header",["Content-Disposition","form-data; name=\"file1\"; filename=\"a.txt\"","Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\""]]
read: ["header",["Content-Type","text\/plain","Content-Type: text\/plain"]]
read: ["body","Hello"]
read: ["body",", wor"]
read: ["body","ld"]
read: ["part_end"]
read: ["header",["Content-Disposition","form-data; name=\"test\"","Content-Disposition: form-data; name=\"test\""]]
read: ["body","value"]
read: ["body","\r\n"]
read: ["part_end"]
read: ["eof"]
read: ["eof"]
You can use the L<lua-resty-string|https://github.com/agentzh/lua-resty-string> library to compute SHA-1 and MD5 digest of the file data incrementally. Here is such an example:
local resty_sha1 = require "resty.sha1"
local upload = require "resty.upload"
local chunk_size = 4096
local form = upload:new(chunk_size)
local sha1 = resty_sha1:new()
local file
while true do
local typ, res, err = form:read()
if not typ then
ngx.say("failed to read: ", err)
return
end
if typ == "header" then
local file_name = my_get_file_name(res)
if file_name then
file = io.open(file_name, "w+")
if not file then
ngx.say("failed to open file ", file_name)
return
end
end
elseif typ == "body" then
if file then
file:write(res)
sha1:update(res)
end
elseif typ == "part_end" then
file:close()
file = nil
local sha1_sum = sha1:final()
sha1:reset()
my_save_sha1_sum(sha1_sum)
elseif typ == "eof" then
break
else
-- do nothing
end
end
If you want to compute MD5 sums for the uploaded files, just use the
resty.md5 module shipped by the L<lua-resty-string|https://github.com/agentzh/lua-resty-string> library. It has
a similar API as resty.sha1.
For big file uploading, it is important not to buffer all the data in memory.
That is, you should never accumulate data chunks either in a huge Lua string or
in a huge Lua table. You must write the data chunk into files as soon as possible and
throw away the data chunk immediately (to let the Lua GC free it up).
Instead of writing the data chunk into files (as shown in the example above),
you can also write the data chunks to upstream cosocket connections if you do
not want to save the data on local file systems.
=head1 Usage
local upload = require "resty.upload"
local form, err = upload:new(self, chunk_size, max_line_size, preserve_body)
C<chunk_size> defaults to 4096. It is the size used to read data from the socket.
C<max_line_size> defaults to 512. It is the size limit to read the chunked body header.
By Default, C<lua-resty-upload> will consume the request body. For proxy mode this means upstream will not see the body. When C<preserve_body> is set to true, the request body will be preserved. Note that this option is not free. When enabled, it will double the memory usage of C<resty.upload>.
=head1 Author
Yichun "agentzh" Zhang (章亦春) E<lt>agentzh@gmail.comE<gt>, OpenResty Inc.
=head1 Copyright and License
This module is licensed under the BSD license.
Copyright (C) 2012-2017, by Yichun "agentzh" Zhang (章亦春) E<lt>agentzh@gmail.comE<gt>, OpenResty Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
=over
=item *
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
=back
=over
=item *
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
=back
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=head1 See Also
=over
=item *
the L<ngx_lua module|http://wiki.nginx.org/HttpLuaModule>
=item *
the L<lua-resty-string|https://github.com/agentzh/lua-resty-string> library
=item *
the L<lua-resty-memcached|https://github.com/agentzh/lua-resty-memcached> library
=item *
the L<lua-resty-redis|https://github.com/agentzh/lua-resty-redis> library
=item *
the L<lua-resty-mysql|https://github.com/agentzh/lua-resty-mysql> library
=back
|