330 lines
12 KiB
Text
330 lines
12 KiB
Text
|
|
Nginx Upload Progress Module
|
||
|
|
============================
|
||
|
|
|
||
|
|
Introduction
|
||
|
|
============
|
||
|
|
|
||
|
|
nginx_uploadprogress_module is an implementation of an upload progress system, that monitors
|
||
|
|
RFC1867 POST upload as they are transmitted to upstream servers.
|
||
|
|
|
||
|
|
It works by tracking the uploads proxied by Nginx to upstream servers without
|
||
|
|
analysing the uploaded content and offers a web API to report upload progress in Javscript, Json or any
|
||
|
|
other format (through the help of templates).
|
||
|
|
|
||
|
|
It works because Nginx acts as an accelerator of an upstream server, storing uploaded POST content
|
||
|
|
on disk, before transmitting it to the upstream server. Each individual POST upload request
|
||
|
|
should contain a progress unique identifier.
|
||
|
|
|
||
|
|
This module is Copyright (c) 2007-2012 Brice Figureau, and is licensed under the BSD license (see LICENSE).
|
||
|
|
* rbtree and shm_zone code is based on Igor Sysoev limit_zone Nginx module.
|
||
|
|
* expire header code is based on Igor Sysoev header_filter Nginx module.
|
||
|
|
|
||
|
|
The JSON idea and the mechanism idea are based on Lighttpd mod_uploadprogress:
|
||
|
|
http://blog.lighttpd.net/articles/2006/08/01/mod_uploadprogress-is-back
|
||
|
|
|
||
|
|
|
||
|
|
WARNING:
|
||
|
|
* when compiled with --with-debug, this module will produce high number of log messages.
|
||
|
|
|
||
|
|
INCOMPATIBLE CHANGES
|
||
|
|
====================
|
||
|
|
|
||
|
|
v0.9.0:
|
||
|
|
|
||
|
|
JSONP is now the default output of the progress probes. If you rely on this module serving
|
||
|
|
the deprecated java output use:
|
||
|
|
upload_progress_java_output
|
||
|
|
in the progress probe location.
|
||
|
|
|
||
|
|
|
||
|
|
Installation
|
||
|
|
============
|
||
|
|
|
||
|
|
nginx_uploadprogress_module has been tested with Nginx 0.6.x, 0.7.x, 0.8.x and 1.0.x.
|
||
|
|
|
||
|
|
Download the Nginx sources from http://nginx.net/ and unpack it.
|
||
|
|
|
||
|
|
To build Nginx, change to the directory which contains the Nginx
|
||
|
|
sources, and run the configuration script making sure to add the path
|
||
|
|
to the nginx_uploadprogress_module sources using the --add-module option: ::
|
||
|
|
|
||
|
|
$ ./configure --add-module=/path/to/nginx_uploadprogress_module/
|
||
|
|
|
||
|
|
Now you can build and install the software:
|
||
|
|
|
||
|
|
$ make
|
||
|
|
|
||
|
|
and as root:
|
||
|
|
|
||
|
|
$ make install
|
||
|
|
|
||
|
|
|
||
|
|
Configuration
|
||
|
|
=============
|
||
|
|
|
||
|
|
Each upload request should be assigned a unique identifier. This unique identifier will be used
|
||
|
|
to store the request and reference it to report.
|
||
|
|
This identifier can be transmitted either as a GET argument or as an HTTP header whose name is X-Progress-ID.
|
||
|
|
|
||
|
|
upload_progress
|
||
|
|
+++++++++++++++
|
||
|
|
:Syntax: upload_progress <zone_name> <zone_size>
|
||
|
|
:Default: none
|
||
|
|
:Context: http
|
||
|
|
:Description:
|
||
|
|
This directive enables the upload progress module and reserve <zone_size> bytes to the <zone_name> which
|
||
|
|
will be used to store the per-connection tracking information.
|
||
|
|
|
||
|
|
track_uploads
|
||
|
|
+++++++++++++
|
||
|
|
:Syntax: track_uploads <zone_name> <timeout>
|
||
|
|
:Default: none
|
||
|
|
:Context: location
|
||
|
|
:Description:
|
||
|
|
This directive enables tracking uploads for the current location. Each POST landing in this location will register
|
||
|
|
the request in the <zone_name> upload progress tracker.
|
||
|
|
Since Nginx doesn't support yet RFC 1867 upload, the location must be a proxy_pass or fastcgi location.
|
||
|
|
The POST _must_ have a query parameter called X-Progress-ID (or an HTTP header of the same name) whose value is the
|
||
|
|
unique identifier used to get progress information. If the POST has no such information, the upload will not be tracked.
|
||
|
|
The tracked connections are kept at most <timeout> seconds after they have been finished to be able to serve
|
||
|
|
useful information to upload progress probes.
|
||
|
|
WARNING: this directive must be the last directive of the location. It must be in a proxy_pass or
|
||
|
|
fastcgi_pass location.
|
||
|
|
|
||
|
|
report_uploads
|
||
|
|
++++++++++++++
|
||
|
|
:Syntax: report_uploads <zone_name>
|
||
|
|
:Default: none
|
||
|
|
:Context: location
|
||
|
|
:Description:
|
||
|
|
This directive allows a location to report the upload progress that is tracked by track_uploads for <zone_name>.
|
||
|
|
The returned document is a Javascript text with the possible 4 results by default:
|
||
|
|
* the upload request hasn't been registered yet or is unknown:
|
||
|
|
new Object({ 'state' : 'starting' })
|
||
|
|
|
||
|
|
* the upload request has ended:
|
||
|
|
new Object({ 'state' : 'done' })
|
||
|
|
|
||
|
|
* the upload request generated an HTTP error
|
||
|
|
new Object({ 'state' : 'error', 'status' : <error code> })
|
||
|
|
one error code that can be of use to track for the client is 413 (request entity too large).
|
||
|
|
|
||
|
|
* the upload request is in progress:
|
||
|
|
new Object({ 'state' : 'uploading', 'received' : <size_received>, 'size' : <total_size>})
|
||
|
|
|
||
|
|
It is possible to return pure json instead of this javascript (see upload_progress_json_output).
|
||
|
|
It is also possible to configure completely the response format with the directive:
|
||
|
|
upload_progress_template
|
||
|
|
|
||
|
|
The HTTP request to this location must have a X-Progress-ID parameter or HTTP header containing a valid
|
||
|
|
unique identifier of an in progress upload.
|
||
|
|
|
||
|
|
upload_progress_content_type
|
||
|
|
++++++++++++++++++++++++++++
|
||
|
|
:Syntax: upload_progress_content_type <content_type>
|
||
|
|
:Default: text/javascript
|
||
|
|
:Context: location
|
||
|
|
:Description:
|
||
|
|
This directive allows to change the upload progress probe response content-type.
|
||
|
|
|
||
|
|
upload_progress_header
|
||
|
|
++++++++++++++++++++++
|
||
|
|
:Syntax: upload_progress_header <progress-id>
|
||
|
|
:Default: X-Progress-ID
|
||
|
|
:Context: location
|
||
|
|
:Description:
|
||
|
|
This directive allows to change the header name of the progress ID.
|
||
|
|
|
||
|
|
upload_progress_jsonp_parameter
|
||
|
|
++++++++++++++++++++++
|
||
|
|
:Syntax: upload_progress_jsonp_parameter <callback_parameter>
|
||
|
|
:Default: callback
|
||
|
|
:Context: location
|
||
|
|
:Description:
|
||
|
|
This directive allows to change the name of the GET parameter with the jsonp callback name.
|
||
|
|
|
||
|
|
upload_progress_java_output
|
||
|
|
+++++++++++++++++++++++++++
|
||
|
|
:Syntax: upload_progress_java_output
|
||
|
|
:Default: N/A
|
||
|
|
:Context: location
|
||
|
|
:Description:
|
||
|
|
This directive sets everything to output as eval() javascript compatible code.
|
||
|
|
|
||
|
|
upload_progress_json_output
|
||
|
|
+++++++++++++++++++++++++++
|
||
|
|
:Syntax: upload_progress_json_output
|
||
|
|
:Default: N/A
|
||
|
|
:Context: location
|
||
|
|
:Description:
|
||
|
|
This directive sets everything to output as pure json.
|
||
|
|
|
||
|
|
upload_progress_jsonp_output
|
||
|
|
++++++++++++++++++++++++++++
|
||
|
|
:Syntax: upload_progress_jsonp_output
|
||
|
|
:Default: N/A
|
||
|
|
:Context: location
|
||
|
|
:Description:
|
||
|
|
This directive sets everything to output as jsonp (like json output, but with callback).
|
||
|
|
|
||
|
|
upload_progress_template
|
||
|
|
++++++++++++++++++++++++
|
||
|
|
:Syntax: upload_progress_template <state> <template>
|
||
|
|
:Default: none
|
||
|
|
:Context: location
|
||
|
|
:Description:
|
||
|
|
This directive can be used to install a progress response template.
|
||
|
|
The available list of state is:
|
||
|
|
* starting
|
||
|
|
* uploading
|
||
|
|
* error
|
||
|
|
* done
|
||
|
|
|
||
|
|
Nginx will replace the value of the following variables with their respective
|
||
|
|
value for the upload:
|
||
|
|
* $uploadprogress_length: total size of the upload
|
||
|
|
* $uploadprogress_received: what the server has received so far
|
||
|
|
* $uploadprogress_status: error code in case of HTTP error
|
||
|
|
* $uploadprogress_callback: jsonp callback name if provided as a GET query parameter with name 'callback'
|
||
|
|
|
||
|
|
For instance to return XML (instead of the default Javascript or json):
|
||
|
|
|
||
|
|
upload_progress_content_type 'text/xml';
|
||
|
|
upload_progress_template starting '<upload><state>starting</state></upload>';
|
||
|
|
upload_progress_template uploading '<upload><state>uploading</state><size>$uploadprogress_length</size><uploaded>$uploadprogress_received</uploaded></upload>';
|
||
|
|
upload_progress_template done '<upload><state>done</state></upload>';
|
||
|
|
upload_progress_template error '<upload><state>error</state><code>$uploadprogress_status</code></upload>';
|
||
|
|
|
||
|
|
Example of jsonp response:
|
||
|
|
|
||
|
|
upload_progress_template starting "$uploadprogress_callback({ \"state\" : \"starting\"});";
|
||
|
|
upload_progress_template error "$uploadprogress_callback({ \"state\" : \"error\", \"status\" : $uploadprogress_status });";
|
||
|
|
upload_progress_template done "$uploadprogress_callback({ \"state\" : \"done\"});";
|
||
|
|
upload_progress_template uploading "$uploadprogress_callback({ \"state\" : \"uploading\", \"received\" : $uploadprogress_received, \"size\" : $uploadprogress_length });";
|
||
|
|
|
||
|
|
Configuration Example:
|
||
|
|
+++++++++++++++++++++
|
||
|
|
|
||
|
|
http {
|
||
|
|
|
||
|
|
# reserve 1MB under the name 'proxied' to track uploads
|
||
|
|
upload_progress proxied 1m;
|
||
|
|
|
||
|
|
server {
|
||
|
|
listen 127.0.0.1 default;
|
||
|
|
server_name _ *;
|
||
|
|
|
||
|
|
root /path/to/root;
|
||
|
|
|
||
|
|
location / {
|
||
|
|
# proxy to upstream server
|
||
|
|
proxy_pass http://127.0.0.1;
|
||
|
|
proxy_redirect default;
|
||
|
|
|
||
|
|
# track uploads in the 'proxied' zone
|
||
|
|
# remember connections for 30s after they finished
|
||
|
|
track_uploads proxied 30s;
|
||
|
|
}
|
||
|
|
|
||
|
|
location ^~ /progress {
|
||
|
|
# report uploads tracked in the 'proxied' zone
|
||
|
|
report_uploads proxied;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
Usage Example
|
||
|
|
=============
|
||
|
|
|
||
|
|
(based on Lighttd mod_uploadprogress module example):
|
||
|
|
|
||
|
|
First we need a upload form:
|
||
|
|
|
||
|
|
<form id="upload" enctype="multipart/form-data"
|
||
|
|
action="/upload.php" method="post"
|
||
|
|
onsubmit="openProgressBar(); return true;">
|
||
|
|
<input type="hidden" name="MAX_FILE_SIZE" value="30000000" />
|
||
|
|
<input name="userfile" type="file" label="fileupload" />
|
||
|
|
<input type="submit" value="Send File" />
|
||
|
|
</form>
|
||
|
|
|
||
|
|
And a progress bar to visualize the progress:
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<div id="progress" style="width: 400px; border: 1px solid black">
|
||
|
|
<div id="progressbar"
|
||
|
|
style="width: 1px; background-color: black; border: 1px solid white">
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div id="tp">(progress)</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
Then we need to generate the Unique Identifier and launch the upload on submit
|
||
|
|
action. This also will start the ajax progress report mechanism.
|
||
|
|
|
||
|
|
interval = null;
|
||
|
|
|
||
|
|
function openProgressBar() {
|
||
|
|
/* generate random progress-id */
|
||
|
|
uuid = "";
|
||
|
|
for (i = 0; i < 32; i++) {
|
||
|
|
uuid += Math.floor(Math.random() * 16).toString(16);
|
||
|
|
}
|
||
|
|
/* patch the form-action tag to include the progress-id */
|
||
|
|
document.getElementById("upload").action="/upload.php?X-Progress-ID=" + uuid;
|
||
|
|
|
||
|
|
/* call the progress-updater every 1000ms */
|
||
|
|
interval = window.setInterval(
|
||
|
|
function () {
|
||
|
|
fetch(uuid);
|
||
|
|
},
|
||
|
|
1000
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function fetch(uuid) {
|
||
|
|
req = new XMLHttpRequest();
|
||
|
|
req.open("GET", "/progress", 1);
|
||
|
|
req.setRequestHeader("X-Progress-ID", uuid);
|
||
|
|
req.onreadystatechange = function () {
|
||
|
|
if (req.readyState == 4) {
|
||
|
|
if (req.status == 200) {
|
||
|
|
/* poor-man JSON parser */
|
||
|
|
var upload = eval(req.responseText);
|
||
|
|
|
||
|
|
document.getElementById('tp').innerHTML = upload.state;
|
||
|
|
|
||
|
|
/* change the width if the inner progress-bar */
|
||
|
|
if (upload.state == 'done' || upload.state == 'uploading') {
|
||
|
|
bar = document.getElementById('progressbar');
|
||
|
|
w = 400 * upload.received / upload.size;
|
||
|
|
bar.style.width = w + 'px';
|
||
|
|
}
|
||
|
|
/* we are done, stop the interval */
|
||
|
|
if (upload.state == 'done') {
|
||
|
|
window.clearTimeout(interval);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
req.send(null);
|
||
|
|
}
|
||
|
|
|
||
|
|
Companion Software
|
||
|
|
==================
|
||
|
|
|
||
|
|
This software can also work with Valery Kholodkov' Nginx Upload Module:
|
||
|
|
http://www.grid.net.ru/nginx/upload.en.html
|
||
|
|
|
||
|
|
You can also use the following javascript libraries client side:
|
||
|
|
http://drogomir.com/blog/2008/6/30/upload-progress-script-with-safari-support
|
||
|
|
|
||
|
|
Note that when using jQuery AJAX for progress monitoring, such as:
|
||
|
|
https://github.com/drogus/jquery-upload-progress
|
||
|
|
you should be sure to set a upload_progress template parameter:
|
||
|
|
upload_progress_json_output
|
||
|
|
or
|
||
|
|
upload_progress_jsonp_output
|
||
|
|
depending on your jQuery AJAX dataType setting.
|