Slyweb
На разработку сайта! Скидки 50%!

Ajax uploader with nginx, php and jQuery, javascript!

Ajax uploader with nginx, php and jQuery, javascript!

This is post about configure nginx with module «HttpUploadProgressModule» for people who want create ajax uploader with progress bar.

Before I use ajax uploader with progress bar which was builded on APC module. But after upgrade server and php until 5.4 APC module was stoped work.

May be this post help someone else.

Standart installation of nginx not including module «nginx-upload-progress-module», therefore you should compiling nginx 1.4.1 with option --add-module=/etc/nginx/nginx-1.4.1/nginx-upload-progress-module-master where «/etc/nginx/nginx-1.4.1/nginx-upload-progress-module-master » path to folder with module. Change it for you own path.

You can find and download nginx upload module use next link http://wiki.nginx.org/HttpUploadProgressModule (v0.9.0). Documentation of options that you can use with this module you can find use next link http://wiki.nginx.org/HttpUploadProgressModule

Of course before you will compile remove older version of nginx.

This is command that I use to compile nginx 1.4.1:

  • Код
  • Чистый код
  • Копировать в буфер
  1../configure --add-module=/etc/nginx/nginx-1.4.1/nginx-upload-progress-module-master --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-cc-opt='-O2 -g -pipe -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m32 -march=i386 -mtune=generic -fasynchronous-unwind-tables'
  2.make
  3.make install

        

You can change «--conf-path» option but I recommended use default option --conf-path=/etc/nginx/nginx.conf

nginx.conf

  • Код
  • Чистый код
  • Копировать в буфер
  1.
  2.user nginx;
  3.worker_rlimit_nofile 100000;
  4.worker_processes 1;
  5.
  6.error_log /var/log/nginx/error.log warn;
  7.pid        /var/run/nginx.pid;
  8.
  9.
10.events {
11.    worker_connections 1024;
12.}
13.
14.
15.http {
16.    include     /etc/nginx/mime.types;
17.    default_type application/octet-stream;
18.    client_body_buffer_size 128K;
19.    client_max_body_size 50m;
20.    upload_progress proxied 1m;
21.
22.    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
23.                     '$status $body_bytes_sent "$http_referer" '
24.                     '"$http_user_agent" "$http_x_forwarded_for"';
25.
26.    access_log /var/log/nginx/access.log main;
27.
28.    output_buffers 1 32k;
29.    postpone_output 1460;
30.
31.    sendfile        on;
32.    tcp_nopush     on;
33.    tcp_nodelay     on;
34.
35.    keepalive_timeout 15;
36.    charset utf-8;
37.    gzip on;
38.    gzip_types text/plain application/xml;
39.    include /etc/nginx/conf.d/*.conf;
40.
41.}

        

This is standart configuration file that will appear after installing nginx. Description of all directives this module you can find on http://wiki.nginx.org/HttpUploadProgressModule#upload_progress. I add only «charset utf-8»:

  • Код
  • Чистый код
  • Копировать в буфер
  1.gzip on;
  2.gzip_types text/plain application/xml;

        

And for ajax upload:

  • Код
  • Чистый код
  • Копировать в буфер
  1.upload_progress proxied 1m;

        

Use your own charset and if you want compress html page.

Last line more important for explanation

  • Код
  • Чистый код
  • Копировать в буфер
  1.include /etc/nginx/conf.d/*.conf;

        

Path «/etc/nginx/conf.d/» containing all configuration files (all virtual host that you can use with nginx).

For example, I have file «joomla.conf» for my joomla site:

  • Код
  • Чистый код
  • Копировать в буфер
  1.
  2.server {
  3.        listen     80;
  4.        server_name ip;
  5.
  6.        location / {
  7.
  8.            root /var/www/joomla/data/www/ip;
  9.            index index.php index.html index.htm;
10.
11.             if (!-e $request_filename)
12.             {
13.             rewrite ^/(.*)$ /index.php?q=$1 last;
14.             }
15.
16.        }
17.
18.        error_page 404             /404.html;
19.        location = /404.html {
20.            root /usr/share/nginx/html;
21.        }
22.
23.        error_page 500 502 503 504 /50x.html;
24.        location = /50x.html {
25.            root /var/www/joomla/data/www/ip;
26.        }
27.
28.        location ~ \.php$ {
29.            root         /var/www/joomla/data/www/ip;
30.            fastcgi_pass 127.0.0.1:9000;
31.            fastcgi_index index.php;
32.            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
33.            include        fastcgi_params;
34.            track_uploads proxied 1m;
35.     }
36.
37.location ^~ /progress {
38.        # report uploads tracked in the 'proxied' zone
39.        report_uploads proxied;
40.}
41.location ~* \.(jpg|jpeg|gif)$ {
42.            root         /var/www/joomla/data/www/ip;
43.            access_log off;
44.            expires     30d;
45.        }
46.
47.
48.}

        

Change root for you own and ip address too.

And for ajax upload tracking I add directive:

  • Код
  • Чистый код
  • Копировать в буфер
  1.track_uploads proxied 1m;

        
  • Код
  • Чистый код
  • Копировать в буфер
  1.location ^~ /progress {
  2.        # where is track
  3.        report_uploads proxied;
  4.}

        

Now you can use url «http://yousite/progress» as url to get information about uploading file. Url request will like this:

  • Код
  • Чистый код
  • Копировать в буфер
  1.http://youipordomain/progress?X-Progress-ID=0.0029315509280958274

        

Remember that each individual request should contain a progress unique identifier (X-Progress-ID).

For example I use next small jQuery script to upload files:

  • Код
  • Чистый код
  • Копировать в буфер
  1.    var loader;
  2.    var fileField;
  3.
  4.    fileField = document.getElementById("uploadfile");
  5.     if(fileField) {
  6.            fileField.onchange = function () {
  7.                $('.myvideo').show().text('('+this.value+')');
  8.            }
  9.        }
10.        
11.    $("#uploadImage").submit(function(e){
12.        $("#tabs-2 .progress").show();
13.
14.        
15.     if (fileField) {
16.     if($('#videoname').val() == '') {
17.            $('#videoname_error').show();
18.         return false;
19.        }
20.        else if($('textarea[name="videodescription"]').val() == '') {
21.            $('#videodescription_error').show();
22.         return false;
23.        }
24.        var isIE = !!document.all && document.uniqueID;
25.
26.         if(!isIE) {
27.                var fileToUpload = fileField.files[0];
28.                
29.             if(!fileToUpload) {
30.                    $('#file_name_error').show();
31.                 return false;
32.                }
33.            
34.                var fs = fileToUpload.size/1024;
35.             if(fn == '') {
36.                    $('#file_name_error').show();
37.                 return false;
38.                }
39.                typeimg = fileToUpload.type.split("/");
40.                
41.                switch(typeimg[1]) {
42.                        case "jpg":
43.                        break;
44.                        case "jpeg":
45.                        break;
46.                        case "gif":
47.                        break;
48.                        case "png":
49.                        break;
50.                        case "zip":
51.                        break;
52.                        default:
53.                        $('#tabs-3 .file_type_error').show();
54.                     return false;
55.                        break;
56.                }
57.                
58.             if(fs > 20000) {
59.                    $('#file_size_error').show();
60.                 return false;
61.                }
62.                var fn = fileToUpload.name;
63.                
64.            }
65.        }
66.        $('#file_size_error,#file_name_error,#videodescription_error,#videoname_error').hide();
67.        
68.        var randIDS = Math.random();
69.        var p = $(this);
70.
71.        p.attr('target','tmpForm');
72.        p.attr('action','/album/loadimage?X-Progress-ID='+randIDS);
73.
74.    // first uploading
75. if($("#tmpForm").length == 0){
76.    
77.            var hidden = $("<input>");
78.            hidden.attr({
79.                name:"APC_UPLOAD_PROGRESS",
80.                id:"progress_key",
81.                type:"hidden",
82.                value:randIDS
83.            });
84.            $("#uploadImage").prepend(hidden);
85.            
86.            var album = $('#tabs').attr('data-globalmetka');
87.            var albumhidden = $("<input>");
88.            albumhidden.attr({
89.                name:"album",
90.                id:"album",
91.                type:"hidden",
92.                value:album
93.            });
94.            $("#uploadImage").prepend(albumhidden);
95.            
96.            var frame = $("<iframe>");
97.            frame.attr({
98.            name:'tmpForm',
99.            id:'tmpForm',
100.            action:'about:blank',
101.            border:0
102.            }).css('display','none');
103.            p.after(frame);
104.        } else {
105.            $('#progress_key').attr({value:randIDS});
106.        
107.        }
108.        
109.        $('.avatar-buttons').hide('slow');
110.        $('.myvideo').show().text(fn);
111.        
112.        
113.        $.get("/progress", {"X-Progress-ID":randIDS, rand:Math.random()},
114.        function(data){
115.            
116.            var uploaded = eval(data);
117.            console.log(uploaded);
118.         if(!upload.received) { w = 0;} else {
119.                w = Math.ceil(100 * upload.received / upload.size);
120.            }
121.            
122.         if(w == 100 || uploaded.state == "done"){
123.                $('.avatar-buttons').show('slow');
124.    
125.                
126.                $(".progress, .badge").hide();
127.                clearInterval(loadLoader);
128.            }
129.            else if(w < 100)
130.            {
131.                $(".progress, .badge").show();
132.                $(".badge").text(w+"%");
133.                var cWidth = $(".badge").animate({'width' : w+"%"},500);
134.            }
135.         if(w < 100 || w.state == "starting")
136.            // start interval for check upload status
137.                loader = setInterval(loadLoader,2000);
138.        });
139.
140.        var loadLoader = function(){
141.        $.get("/progress", {"X-Progress-ID":randIDS, rand:Math.random()}, function(data)
142.        {
143.            
144.            var uploaded = eval(data);
145.         if(!uploaded.received) { w = 0;} else {
146.                w = Math.ceil(100 * uploaded.received / uploaded.size);
147.            }
148.            
149.         if(w == 100 || uploaded.state == "done"){
150.        
151.
152.                $(".progress, .badge").hide();
153.                $('#status').empty().text(File was upload!');
154.                
155.
156.                
157.                clearInterval(loader);
158.            }
159.            else if(w < 100)
160.            {
161.
162.                $(".progress, .badge").show();
163.                $(".badge").text(w+"%");
164.                var cWidth = $(".badge").animate({'width' : w+"%"},500);
165.            }
166.        });
167.        }
168.    });
169.


        

When user submit from with id "#uploadImage" jQuery will trigger submit handler, which can use for creating timeout function for tracking upload process as you can see in listing 10.

And this PHP script that I use (for example when I need upload images):

  • Код
  • Чистый код
  • Копировать в буфер
  1. public function loadPersonPicture($uid)
  2.    {
  3.    
  4.        /* validate form */
  5.        require_once ' /www/data/lib/WideImage.php';
  6.        $firstfilenm = $_FILES['userImage']['name'];
  7.        $albumhidden = $_POST['album'];
  8.        
  9.     if($user) {
10.            $album = $albumhidden;
11.        } else {
12.            $album = $albumhidden;
13.        }
14.        $uniqid = uniqid();
15.        
16.        /**
17.         *
18.         * create item
19.         */
20.        $scriptProperties['active'] = !empty($scriptProperties['active']) ? 1 : 0;
21.
22.        $arr = explode('.',$firstfilenm);
23.
24.        $filenm = $firstfilenm;
25.
26.
27.        /* Upload */
28.        $albumDir = $album.'/';
29.        $targetDir = $_SERVER['DOCUMENT_ROOT'].'/content/avatar/'.$uid.'/'.$albumDir;
30.
31.        
32.        /* if directory doesnt exist, create it */
33.     if (!file_exists($targetDir) || !is_dir($targetDir)) {
34.            
35.            mkdir($targetDir);
36.            
37.        }
38.        
39.        /* make sure directory is readable/writable */
40.     if (!is_readable($targetDir) || !is_writable($targetDir)) {
41.            var_dump($filenm);
42.        }
43.
44.        /* upload the file */
45.        $extension = end(explode('.', $filenm));
46.        $filename = $filenm;
47.        $relativePath = $albumDir.$filename;
48.        $absolutePath = $targetDir.$filename;
49.
50.     if (@file_exists($absolutePath)) {
51.            @unlink($absolutePath);
52.        }
53.        $image_folder = $_SERVER['DOCUMENT_ROOT']."/ajaxupload/uploads/";
54.        
55.     if (!empty($_FILES['userImage'])) {
56.         if(filesize($_FILES['userImage']['tmp_name'])/1024 > 100000) {
57.                echo '<script type="text/javascript">window.top.document.getElementById("file_size_error").style.display="block";</script>';
58.                die();
59.            }
60.
61.            
62.            
63.            
64.         if (!move_uploaded_file($_FILES['userImage']['tmp_name'],$targetDir.$uniqid.'.'.$extension)) {
65.                
66.             return false;    
67.                
68.            }
69.            
70.         if(is_file($targetDir.$uniqid.'.'.$extension)) {
71.            $sf = $uniqid.'_s.'.$extension;
72.         if($extension == 'png') {$compress = 6;} else {$compress = 90;}
73.            \WideImage::load($targetDir.$uniqid.'.'.$extension)->resize(200,200,'inside')->crop('center', 'center', 100,100)->saveToFile($targetDir.$sf,$compress);
74.                        
75.
76.            }
77.            
78.        } else {
79.
80.        }
81.
82.
83.
84.        $img = array('smallfile' => $sf, 'album' => $album,'type' => 'img');
85.        
86.     return $img;
87.    
88.    
89.    }
90.
91.

        

This code only example that you should modify for you purpose.

Result of progress you can get from location «/progress». See listing 9.

  • Код
  • Чистый код
  • Копировать в буфер
  1.location ^~ /progress {
  2.        # report uploads tracked in the 'proxied' zone
  3.        report_uploads proxied;
  4.}
  5.

        

Upload request will return javascript object, for example if request starting:

  • Код
  • Чистый код
  • Копировать в буфер
  1.new Object({ 'state' : 'starting' })

        

if upload request is ended:

  • Код
  • Чистый код
  • Копировать в буфер
  1.new Object({ 'state' : 'done' })

        

the upload request is in progress:

  • Код
  • Чистый код
  • Копировать в буфер
  1.new Object({ 'state' : 'uploading', 'received' : <size_received>, 'size' : <total_size>})

        

For parse response as javascript use this code (listing 9):

  • Код
  • Чистый код
  • Копировать в буфер
  1.var uploaded = eval(data);
  2.console.log(uploaded);

        

By default upload request will return javascript object, but we can change it use directive upload_progress_json_output, for get pure JSON. If you need other format use option upload_progress_template, for xml add this code to server section of nginx configuration file:

  • Код
  • Чистый код
  • Копировать в буфер
  1.location ^~ /progress {
  2.        # report uploads tracked in the 'proxied' zone
  3.        report_uploads proxied;
  4.
  5.        upload_progress_content_type 'text/xml';
  6.        upload_progress_template starting '<upload><state>starting</state></upload>';
  7.        upload_progress_template uploading '<upload><state>uploading</state>
  8.        <size>$uploadprogress_length</size><uploaded>$uploadprogress_received</uploaded></upload>';
  9.        upload_progress_template done '<upload><state>done</state></upload>';
10.        upload_progress_template error '<upload><state>error</state>
11.        <code>$uploadprogress_status</code></upload>';
12.
13.}
14.

        

Don`t forget, additional information about all directives for this module (upload_store, upload_set_form_field, upload_cleanup and other) described in http://wiki.nginx.org/HttpUploadProgressModule.


Александр Ермаков