利用Nginx的上传模块和上传进度模块实现网页上传文件

来源:互联网 发布:极限挑战网络更新时间 编辑:程序博客网 时间:2024/06/05 12:44
    最近做一个产品,需要实现从网页上传文件给服务器。一般情况下都是采用Ajax异步方式,创建一个iframe,在iframe里面把数据以form方式提交给后端的服务器脚本,由服务器脚本(比如PHP)来负责接收上传的数据。这种方式存在性能和效率的问题。所以,决定采用Nginx的上传模块来完成接收数据的功能,接收完数据后,再去转给后端脚本语言进行后续处理(比如:移动文件、插入文件的信息到数据库中)。同时,由于需要在前端展现上传的进度,因此可以利用Nginx一个uploadprogress模块来获取。
     整个处理框图如下:

     实现步骤:
     1、查看Nginx是否安装了这两个模块(nginx_upload_module和nginx_uploadprogress_module),命令nginx -V (注意是大写),可以查看Nginx当时编译时候的参数,如果发现有上述两个模块,说明Nginx已经安装了这两个模块。如果没有的话,就需要安装这两个Nginx模块。由于这两个模块不在Nginx源代码中,需要重新编译Nginx,在编译选项中加上
    --add-module=/模块源代码路径/nginx_upload_module-2.2.0 --add-module=/模块源代码路径/nginx_uploadprogress_module-0.8.2 。
      2、由于产品的前端使用的是jQuery框架,所以,找了一个现成的jQuery下的上传文件插件(ajaxfileupload)。该代码基本原理就是动态创建一个iframe,在iframe中再增加一个form,最后数据放到这个form中提交给服务器,代码量比较小也就200来行代码。前端的代码如下:
<script type="text/javascript" src="http://192.168.1.203:7100/js/libs/ajaxfileupload.js" ></script><script type="text/javascript">    function uploadfile(){                $.ajaxFileUpload({            url:'http://192.168.1.203:7100/upload/‘,//上传的地址            sercureuri:false,            fileElementId:'fileToUpload',            dataType:'json',            success:function(data,status){                if(typeof(data.error) != 'undefined'){                    if(data.error != '')                        alert(data.error);                }                else{                    alert(data.msg);                }            },            error:function(data,status, e){                alert(e);            }        });        return false;    }   </script><div><input type="file" name="addfile" size="50" id="fileToUpload" /><input type="button" value="上传" onclick="return uploadfile();"/></div>


其中,success的回调函数参数是服务器返给浏览器的结果。
3、配置Nginx,实现上传模块来接收页面上传的文件。把下面配置添加到Nginx的配置文件中,注意是加在server的上下文中。
        location = /upload {
                upload_pass     /service.php?path=uploadfile&a=upload_server;//表示Nginx接收完上传的文件后,然后交给后端处理的地址
                upload_cleanup 400 404 499 500-505; //表示当发生这些http status代码的情况下,会把上传的文件删除
                upload_store    /tmp/upload_tmp 1;//上传模块接收到的文件临时存放的路径, 1 表示方式,该方式是需要在/tmp/upload_tmp下创建以0到9为目录名称的目录,上传时候会进行一个散列处理。
                upload_store_access user:r; //指定访问模式
                upload_limit_rate 128k; //设定上传速度上限
                upload_set_form_field "${upload_field_name}_name" $upload_file_name; //设定后续脚本语言访问的变量,其中${upload_field_name}对照本例子就是addfile。比如后台PHP就可以通过$_POST['addfile_name']来获取上传文件的名称。
                upload_set_form_field "${upload_field_name}_content_type" $upload_content_type;//同上
                upload_set_form_field "${upload_field_name}_path" $upload_tmp_path;//由于在upload_store设置了临时文件存放根路径,该路径就是经过散裂后上传文件存在真实路径,比如后续处理可以根据这值把上传文件拷贝或者移动到指定的目录下。
                upload_pass_form_field "^.*$";//
                upload_pass_args on;// 打开开关,意思就是把前端脚本请求的参数会传给后端的脚本语言,比如:http://192.168.1.203:7100/upload/?k=23.PHP脚本可以通过$_POST['k']来访问。
        }
4、上述配置完了,就可以实现上传的功能了。但是,要获取上传的进度,那还是需要配置另外一个模块nginx_uploadprogress_module。其实,获取当前进度原理比较简单,就是通过javascript以异步方式定时给特定地址发送请求,这个模块会以json格式返回上传的进度。配置比较简单。
          1)、首先打开这个模块功能,在Nginx配置文件中http上下文里面,增加upload_progress proxied 5m;其中,proxied表示名称(zone_name官方文档),5m表示每次链接存放跟踪信息的大小。另外,再设置返回格式为json,upload_progress_json_output;
          2)、在上述的location = /upload中增加一个配置项track_uploads proxied 30s; 其中,proxied就是刚才在第一步设置的名字,30s表示每次链接处理完毕后,链接会保持30s。
          3)、设置一个location来处理javascript发送请求。
          location ^~ /progress {

  report_uploads proxied;    #GET此地址得到上传进度
}
           4)、还有一个参数考虑设置upload_progress_header ,这个值缺省是X-Progress-ID。有点类似SessionID,主要用在前台需要在上传文件的时候需要设置这个参数值,比如设置为uuid值。这样javascript每次发送请求要获取上传进度时候,都需要带上这个参数,这样上传进度跟踪模块才知道是返回那个链接的进度。
           经过这三步骤,就把上传进度跟踪模块配置好了。现在就需要对前台脚本就行修改
5、修改第2步的前台脚本
<script type="text/javascript" src="http://192.168.1.203:7100/js/libs/ajaxfileupload.js" ></script><script type="text/javascript">    var interval = undefined;    function uploadfile(){          var uuid = "";        for (var i = 0; i < 32; i++) {            uuid += Math.floor(Math.random() *16).toString(16);        }              $.ajaxFileUpload({            url:'http://192.168.1.203:7100/upload/?X-Progress-ID=' + uuid,//上传的地址            sercureuri:false,            fileElementId:'fileToUpload',            dataType:'json',            success:function(data,status){                if(typeof(data.error) != 'undefined'){                    if(data.error != '')                        alert(data.error);                }                else{                    alert(data.msg);                }            },            error:function(data,status, e){                alert(e);            }        });        interval = window.setInterval(           function () {             getUploadProgress(uuid);           },           2000         );        return false;    }       function getUploadProgress(uuid){      etajax.sendRequest('http://192.168.1.203:7100/progress/',"GET","X-Progress-ID=" + uuid,getUploadProgressCallback);    }    function getUploadProgressCallback(type, json, http){        if(type == "load"){            var bar = document.getElementById('tp');            if(json.state == "uploading"){                                  var w =  Math.floor(json.received * 100.0 / json.size) ;                 bar.innerHTML = w + "%";            }                /* we are done, stop the interval */             if (json.state == 'done') {                bar.innerHTML = "100%";                 window.clearTimeout(interval);            }        }    }     </script><div><input type="file" name="addfile" size="50" id="fileToUpload" /><input type="button" value="上传" onclick="return uploadfile();"/></div><div>    <div id="tp">0%</div></div>
上述黑体就是增加的代码,其中,有些函数是调用产品封装好的函数,所以,不要全部照搬。主要是抓住以下几个要点就可以了:
    1、在上传文件时候需要增加一个uuid,对应的参数就是upload_progress_header设置的,缺省是X-Progress-ID。
    2、在请求获取进度的时候,都要带上这个uuid。
    3、设定一个定时期,定时发送异步的GET方式请求,获取进度数据。
    4、返回的json格式是{"state":"uploading", "size":3232,"received":34},其中上传完毕state值为done,如果发生错误会,state就是error,并且会返回status,错误编码。
    
    步骤就介绍在这里了。另外,javascript方式上传文件,在用户上传文件时候,最好能获取上传文件大小,这样可以提前告诉用户是否超出允许上传的大小值。但是,目前javascript方式获取文件大小要兼容所有浏览器还是存在问题。我打算还是写个flash,通过flash方式来获取,比较保险。