日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

nginx delete form表单 收不到参数_HTTP 文件上传的一个后端完善方案(NginX)

發(fā)布時(shí)間:2023/12/3 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 nginx delete form表单 收不到参数_HTTP 文件上传的一个后端完善方案(NginX) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

(給PHP開(kāi)發(fā)者加星標(biāo),提升PHP技能)

轉(zhuǎn)自:林伯格

https://breeze2.github.io/blog/scheme-nginx-php-js-upload-process

前言

很多網(wǎng)站都會(huì)有上傳文件的功能,比如上傳用戶頭像,上傳個(gè)人簡(jiǎn)歷等等,除非是網(wǎng)盤(pán)類(lèi)的網(wǎng)站,一般上傳文件不會(huì)作為網(wǎng)站的主要功能;而且,如今大眾的網(wǎng)速已經(jīng)是足夠的快,上傳幾百KB的文件,幾乎可以秒內(nèi)完成。

但是,隨著文件大小和類(lèi)型越來(lái)越龐大,文件上傳也就越值得我們重視。

大多數(shù)網(wǎng)站,對(duì)于上傳文件的處理,都是簡(jiǎn)單的前端POST上傳,后端驗(yàn)證存放然后返回訪問(wèn)地址。畢竟,文件小,網(wǎng)速快,一瞬間的事情誰(shuí)會(huì)多在意呢?

存在問(wèn)題

假設(shè)我們有一個(gè)網(wǎng)站,基于NginX+PHP+JS構(gòu)架,網(wǎng)站允許用戶上傳一些小視頻、音樂(lè)或者PPT等文件在線上展示,單個(gè)文件大小限制不超過(guò)30MB,那么我們要怎樣實(shí)現(xiàn)這個(gè)上傳功能呢?

限制上傳文件的大小

首先,NginX要能接受最大32MB的請(qǐng)求(除了最大文件本身30MB,再預(yù)留一些給其他請(qǐng)求參數(shù)),我們會(huì)修改網(wǎng)站的虛擬主機(jī)配置:

# website.conf server { client_max_body_size 32M; ...}

然后,PHP也要修改配置,接受最大30MB的文件上傳和最大32MB的POST請(qǐng)求:

# php.iniupload_max_filesize = 30M;post_max_size = 32M;

其實(shí),單憑client_max_body_size,NginX是不能真正限制上傳文件大小的,因?yàn)镹ginX會(huì)先讓客戶端(一般是瀏覽器)開(kāi)始上傳請(qǐng)求,直到上傳的內(nèi)容大小超過(guò)了限制,NginX才會(huì)中止上傳,報(bào)413 Request Entity Too Large錯(cuò)誤,沒(méi)超過(guò)限制則交給PHP處理。

于是,PHP的upload_max_filesize和post_max_size就更沒(méi)用了,因?yàn)镻HP獲取到文件信息的時(shí)候,上傳過(guò)程已經(jīng)結(jié)束了(這時(shí)當(dāng)然是上傳成功,NginX中止請(qǐng)求的話PHP不會(huì)進(jìn)場(chǎng))。在NginX傳遞請(qǐng)求結(jié)果前,PHP什么(比如驗(yàn)證用戶,驗(yàn)證權(quán)限等等)都做不了。

如果用戶上傳了一個(gè)大于32MB的時(shí)候,直到上傳到32MB的時(shí)候才能告訴用戶文件過(guò)大了,那么前面的時(shí)間用戶不就白等了嗎?而且服務(wù)器的帶寬還是一樣被消耗了。

我們更希望在上傳開(kāi)始前就能告訴用戶文件過(guò)大了。很多網(wǎng)站開(kāi)發(fā),都會(huì)把這一步交給JS處理,在新型瀏覽器(支持HTML5)里,JS的確可以獲取input文件的大小;在舊的IE里,也可以通過(guò)ActiveX來(lái)實(shí)現(xiàn)。但是JS的限制處理很容易被繞過(guò)去,只要知道上傳地址,一個(gè)form標(biāo)簽就能把文件傳過(guò)去:

<form id="upload_form" action="/path/to/upload" enctype="multipart/form-data" method="post"> <input type="file" name="upload_file" value="/path/of/big/big/file" /> <input type="submit" value="Upload" />form>

正常的用戶當(dāng)然不會(huì)這樣做,但是有意攻擊網(wǎng)站的人會(huì)。

限制上傳文件的速度

如果服務(wù)器的入口帶寬是100mbps,用戶的上行帶寬是10mbps,用戶上傳一個(gè)30MB的文件至少需要30秒,那么在30秒內(nèi),服務(wù)器的帶寬只能滿足10個(gè)用戶上傳文件,帶寬被占滿后,服務(wù)器就很難再處理其他請(qǐng)求了。

所以,限制用戶上傳文件的速度就很有必要。目前,JS做不到限制上傳文件的速度,PHP也做不到。

上傳文件的進(jìn)度

用戶上傳一個(gè)30MB的文件至少需要30秒,那么30秒內(nèi)應(yīng)該告知用戶上傳的進(jìn)度,不能讓用戶無(wú)感知的等待。HTML5改進(jìn)了XMLHttpRequest對(duì)象,在支持HTML5的新型瀏覽器里,JS可以獲取XMLHttpRequest上傳文件的進(jìn)度;在舊的瀏覽器的也可以通過(guò)Flash與JS結(jié)合(比如SWFUpload),從而獲取上傳文件的進(jìn)度。

但是新型瀏覽器里,Flash已經(jīng)被摒棄了,因而要支持新舊瀏覽器,JS就要寫(xiě)成兩套代碼。在這里PHP也是幫不上忙,因?yàn)镻HP拿到傳文件信息的時(shí)候,上傳已經(jīng)結(jié)束了。

解決方案

網(wǎng)站是NginX+PHP+JS構(gòu)架的,PHP和JS解決不了的問(wèn)題,那應(yīng)該在NginX上解決它。NginX雖然是一個(gè)現(xiàn)成的軟件,但是它還是可以繼續(xù)擴(kuò)展和修改的。

NginX本身沒(méi)有提供上傳文件的復(fù)雜處理功能,而在NginX官方認(rèn)可的第三方擴(kuò)展模塊里,有兩個(gè)模塊可以幫助我們實(shí)現(xiàn)復(fù)雜的上傳文件功能,分別是nginx-upload-module和nginx-upload-progress-module。

要將nginx-upload-module和nginx-upload-progress-module編譯進(jìn)NginX,首先要下載NginX源碼和nginx-upload-module、nginx-upload-progress-module這兩個(gè)模塊的源碼,然后在NginX源碼目錄中,在configure參數(shù)中加入這兩個(gè)這兩個(gè)模塊,最后make install,大概的執(zhí)行命令:

$ cd ~$ mkdir tmp$ cd tmp$ wget http://nginx.org/download/nginx-1.11.3.tar.gz$ tar -xvzf nginx-1.11.3.tar.gz$ git clone https://github.com/vkholodkov/nginx-upload-module.git$ git clont https://github.com/masterzen/nginx-upload-progress-module.git$ cd nginx-1.11.3$ ./configure --add-module=~/tmp/nginx-upload-module --add-module~/tmp/nginx-upload-progress-module ...$ make$ make install

如果系統(tǒng)上已經(jīng)安裝過(guò)NginX并且所安裝NginX版本支持動(dòng)態(tài)模塊,那么可以考慮將nginx-upload-module和nginx-upload-progress-module編譯成動(dòng)態(tài)模塊,這樣就不需要重新安裝NginX。

nginx-module-libs上有Ubuntu系統(tǒng)上主線NginX版本的一些動(dòng)態(tài)模塊,可以上面下載適配你的nginx-upload-module和nginx-upload-progress-module。

下面主要介紹一下兩個(gè)模塊的用法:

nginx-upload-module

當(dāng)上傳文件的體積小于client_max_body_size時(shí), nginx-upload-module可以幫助我們限制上傳速度,使用方法見(jiàn)下。

NginX的站點(diǎn)配置:

# website.confserver { ... client_max_body_size 32m; # 限制上傳速度最大2Mbps upload_limit_rate 256k; location /upload { # 限制上傳文件最大30MB upload_max_file_size 30m; # 后續(xù)交給 upload.php 處理 upload_pass /upload.php; # 指定上傳文件存放目錄,1表示按1位散列,將上傳文件隨機(jī)存到指定目錄下的0、1、2、...、8、9目錄中(這些目錄要手動(dòng)建立) upload_store /tmp 1; # 上傳文件的訪問(wèn)權(quán)限,user:r表示用戶只讀 upload_store_access user:r; # 設(shè)置請(qǐng)求體的字段 upload_set_form_field "${upload_field_name}_name" "$upload_file_name"; upload_set_form_field "${upload_field_name}_content_type" "$upload_content_type"; upload_set_form_field "${upload_field_name}_path" "$upload_tmp_path"; # 指示后端關(guān)于上傳文件的md5值和文件大小 upload_aggregate_form_field "${upload_field_name}_md5" "$upload_file_md5"; upload_aggregate_form_field "${upload_field_name}_size" "$upload_file_size"; upload_pass_form_field "^submit$|^description$"; # 若出現(xiàn)如下錯(cuò)誤碼則刪除上傳的文件 upload_cleanup 400 404 499 500-505; }}

上傳文件的頁(yè)面:

<form id="upload" enctype="multipart/form-data" action="/upload" method="post" > <input name="upload_file" type="file" label="fileupload" /> <input type="submit" value="Upload File" />form>

處理上傳結(jié)果的腳本:

 php// upload.phpprint_r($_REQUEST);

如果對(duì)PHP解析使用了優(yōu)雅鏈接,比如Laravel,那么應(yīng)該這樣使用:

NginX的站點(diǎn)配置:

# website.confserver { ... client_max_body_size 32m; # 限制上傳速度最大2Mbps upload_limit_rate 256k; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_param HTTP_PROXY ""; fastcgi_pass unix:/run/php/php-fpm.sock; } location @upload_handle { rewrite ^ /index.php last; } location /upload { # 限制上傳文件最大30MB upload_max_file_size 30m; # 后續(xù)交給 index.php 處理 upload_pass @upload_handle; # 指定上傳文件存放目錄,1表示按1位散列,將上傳文件隨機(jī)存到指定目錄下的0、1、2、...、8、9目錄中(這些目錄要手動(dòng)建立) upload_store /tmp 1; # 上傳文件的訪問(wèn)權(quán)限,user:r表示用戶只讀 upload_store_access user:r; # 設(shè)置請(qǐng)求體的字段 upload_set_form_field "${upload_field_name}_name" "$upload_file_name"; upload_set_form_field "${upload_field_name}_content_type" "$upload_content_type"; upload_set_form_field "${upload_field_name}_path" "$upload_tmp_path"; # 指示后端關(guān)于上傳文件的md5值和文件大小 upload_aggregate_form_field "${upload_field_name}_md5" "$upload_file_md5"; upload_aggregate_form_field "${upload_field_name}_size" "$upload_file_size"; upload_pass_form_field "^submit$|^description$"; # 若出現(xiàn)如下錯(cuò)誤碼則刪除上傳的文件 upload_cleanup 400 404 499 500-505; }}

上傳文件的頁(yè)面:

<form id="upload?_token={{csrf_token()}}" enctype="multipart/form-data" action="/upload" method="post" > <input name="upload_file" type="file" label="fileupload" /> <input type="submit" value="Upload File" />form>

Laravel路由配置:

<?php // routes/web.phpRoute::post('/upload', 'Web\IndexController@upload')->name('upload');

Laravel控制器中處理上傳的方法:

<?php // Web/IndexController.phpfunction upload() { dump(request());}

nginx-upload-progress-module

nginx-upload-progress-module可以幫助我們跟蹤上傳的進(jìn)度,使用方法見(jiàn)下。

NginX的站點(diǎn)配置:

# website.confserver { ... client_max_body_size 32m; # 開(kāi)辟一個(gè)空間proxied來(lái)存儲(chǔ)跟蹤上傳的信息1MB upload_progress proxied 1m; location ^~ /progress { # 報(bào)告上傳的信息 report_uploads proxied; } location /upload { ... # 上傳完成后,仍然保存上傳信息5s track_uploads proxied 5s; }}

上傳文件的頁(yè)面和每隔一秒查詢一下上傳進(jìn)度的腳本:

<form id="upload" enctype="multipart/form-data" action="/upload" method="post" onsubmit="openProgressBar(); return true;"> <input name="userfile" type="file" label="fileupload" /> <input type="submit" value="Upload File" />form><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><script type="text/javascript"> var interval = null; var uuid = ""; function openProgressBar() { for (var i = 0; i < 32; i++) { uuid += Math.floor(Math.random() * 16).toString(16); } document.getElementById("upload").action = "/upload?X-Progress-ID=" + uuid; /* 每隔一秒查詢一下上傳進(jìn)度 */ interval = window.setInterval(function () { fetch(uuid); }, 1000); } function fetch(uuid) { var 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) { var upload = eval(req.responseText); document.getElementById('tp').innerHTML = upload.state; /* 更新進(jìn)度條 */ if (upload.state == 'done' || upload.state == 'uploading') { var bar = document.getElementById('progressbar'); var w = 400 * upload.received / upload.size; bar.style.width = w + 'px'; } /* 上傳完成,不再查詢進(jìn)度 */ if (upload.state == 'done') { window.clearTimeout(interval); } if (upload.state == 'error') { window.clearTimeout(interval); alert('something wrong'); } } } } req.send(null); }script>

當(dāng)上傳文件的體積大于client_max_body_size時(shí), nginx-upload-module未能幫我們立刻中斷上傳,并且不能限制上傳速度,但是nginx-upload-progress-module可以向前端報(bào)告文件過(guò)大的錯(cuò)誤,前端可以這樣子來(lái)中斷上傳:

<form id="upload" enctype="multipart/form-data" action="/upload" method="post" onsubmit="openProgressBar(); return false;"> <input name="userfile" type="file" label="fileupload" id="userfile" /> <input type="submit" value="Upload File" />form><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><script type="text/javascript"> var interval = null; var uuid = ""; var uploadxhr = null; function openProgressBar() { for (var i = 0; i < 32; i++) { uuid += Math.floor(Math.random() * 16).toString(16); } var action = "/upload?X-Progress-ID=" + uuid; var file = document.getElementById('userfile').files[0]; uploadxhr = new XMLHttpRequest(); // uploadxhr.file = file; uploadxhr.open('post', action, true); uploadxhr.setRequestHeader("Content-Type","multipart/form-data"); uploadxhr.send(file); /* 每隔一秒查詢一下上傳進(jìn)度 */ interval = window.setInterval(function () { fetch(uuid); }, 1000); } function fetch(uuid) { var 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) { var upload = eval(req.responseText); document.getElementById('tp').innerHTML = upload.state; /* 更新進(jìn)度條 */ if (upload.state == 'done' || upload.state == 'uploading') { var bar = document.getElementById('progressbar'); var w = 400 * upload.received / upload.size; bar.style.width = w + 'px'; } /* 上傳完成,不再查詢進(jìn)度 */ if (upload.state == 'done') { window.clearTimeout(interval); } if (upload.state == 'error') { window.clearTimeout(interval); uploadxhr.abort(); alert('something wrong'); } } } } req.send(null); }script>

另外

nginx-upload-module和nginx-upload-progress-module還提供了更多的指令,幫忙我們實(shí)現(xiàn)更復(fù)雜的上傳文件功能,比如斷點(diǎn)續(xù)傳等,有興趣可以閱讀兩個(gè)模塊的官方文檔,了解更多。

另外,因?yàn)閚ginx-upload-module未能及時(shí)攔下體積過(guò)大的文件上傳,所以,盡管保障了用戶的正常使用,可是依然不能防范惡意的流量攻擊。

nginx-upload-progress-module能夠在一開(kāi)始就檢測(cè)到上傳文件的體積是否過(guò)大(HTTP請(qǐng)求頭里的Content-Length存有文件的體積大小),這時(shí)候就應(yīng)該中斷上傳(可能是NginX限制,擴(kuò)展模塊無(wú)法中斷HTTP請(qǐng)求),大家有興趣的話可以研究一下NginX源碼和擴(kuò)展開(kāi)發(fā)。

思考

NginX的client_max_body_size設(shè)為32m,攻擊者可以上傳1GB的文件,直到上傳到32MB的時(shí)候,NginX才會(huì)中斷上傳,服務(wù)器被消耗了32MB的流量。細(xì)想一下:

  • 即使NginX在一開(kāi)始就攔下了體積大于32MB的文件,可是攻擊者依然可以直接上傳30MB大小的文件,服務(wù)器還是會(huì)被消耗了30MB的流量,所以在一開(kāi)始就攔截的意義并不大;

  • 可是上傳文件的體積大于client_max_body_size時(shí),nginx-upload-module的限速功能不起作用,這就成問(wèn)題了;

  • NginX沒(méi)有直接信任請(qǐng)求頭的Content-Length,應(yīng)該有他的依據(jù),不過(guò)正常用戶不會(huì)虛報(bào)吧(即使報(bào)小也不報(bào)大啊);

  • 看來(lái)這個(gè)方案還需繼續(xù)完善,或者借助現(xiàn)成的云存儲(chǔ)服務(wù)來(lái)實(shí)現(xiàn)文件上傳功能(可參考騰訊云COS的一次實(shí)踐)。

  • 最后

    如果一個(gè)網(wǎng)站,允許用戶全速上傳文件,并持續(xù)數(shù)十秒,那么這個(gè)網(wǎng)站一定存在被流量攻擊的風(fēng)險(xiǎn),有可能是大量用戶同時(shí)使用造成的,也有可能是惡意的DDoS攻擊(??好像所有網(wǎng)站都會(huì)有這個(gè)風(fēng)險(xiǎn))。

    要是服務(wù)器帶寬被占滿,服務(wù)器對(duì)于一些用戶就像是掉線了,所以上傳文件的問(wèn)題必須重視。另外,開(kāi)發(fā)者不應(yīng)該局限于一種編程語(yǔ)言或者一個(gè)知識(shí)領(lǐng)域上去思考解決問(wèn)題,應(yīng)該涉覽更多的知識(shí)領(lǐng)域,從更多角度、更多方位去解決問(wèn)題。

    - EOF -

    推薦閱讀??點(diǎn)擊標(biāo)題可跳轉(zhuǎn)

    1、Nginx 一個(gè)牛X的功能,流量拷貝!

    2、PHP下kafka的實(shí)踐

    3、Nginx 正向代理與反向代理

    看完本文有收獲?請(qǐng)分享給更多人

    推薦關(guān)注「PHP開(kāi)發(fā)者」,提升PHP技能

    點(diǎn)贊和在看就是最大的支持??

    總結(jié)

    以上是生活随笔為你收集整理的nginx delete form表单 收不到参数_HTTP 文件上传的一个后端完善方案(NginX)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。