当上传大文件(> 100米)到服务器,PHP总是先接受来自浏览器的整个数据POST。 我们不能注入上传的过程。
例如,检查“的值, token
”之前整个数据发送到服务器,在我的PHP代码不可能的 :
<form enctype="multipart/form-data" action="upload.php?token=XXXXXX" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="3000000" />
Send this file: <input name="userfile" type="file" />
<input type="submit" value="Send File" />
</form>
所以,我尝试使用mod_rewrite
是这样的:
RewriteEngine On
RewriteMap mymap prg:/tmp/map.php
RewriteCond %{QUERY_STRING} ^token=(.*)$ [NC]
RewriteRule ^/upload/fake.php$ ${mymap:%1} [L]
map.php
#!/usr/bin/php
<?php
define("REAL_TARGET", "/upload/real.php\n");
define("FORBIDDEN", "/upload/forbidden.html\n");
$handle = fopen ("php://stdin","r");
while($token = trim(fgets($handle))) {
file_put_contents("/tmp/map.log", $token."\n", FILE_APPEND);
if (check_token($token)) {
echo REAL_TARGET;
} else {
echo FORBIDDEN;
}
}
function check_token ($token) {//do your own security check
return substr($token,0,4) === 'alix';
}
但是......它再次失败。 mod_rewrite
看起来在这种情况下也工作到很晚。 数据还完全转移。
然后我试图Node.js
,像这样的(代码SNIP):
var stream = new multipart.Stream(req);
stream.addListener('part', function(part) {
sys.print(req.uri.params.token+"\n");
if (req.uri.params.token != "xxxx") {//check token
res.sendHeader(200, {'Content-Type': 'text/plain'});
res.sendBody('Incorrect token!');
res.finish();
sys.puts("\n=> Block");
return false;
}
结果...... 再次失败。
所以,请帮我找到解决这个问题,或者告诉我没有办法正确的路径。
相关的问题:
PHP(与Apache或Nginx的)POST请求前检查HTTP标头可以完成?
可一些告诉我如何使输入密码这个脚本检查它开始上传过程,而不是文件上传后的前?
首先, 你可以使用GitHub库我为此创建自己试试这个代码 。 只是克隆库和运行node header
。
(扰流板,如果你正在读这篇文章,是在时间压力下得到的东西的工作,并没有心情学习(:(),有一个简单的解决方案在最后)
总体思路
这是一个很好的问题。 你所要求的是非常可能的 , 不需要客户方 ,只是一个HTTP协议是如何工作的更深层次的理解,同时展示了如何Node.js的岩石:)
这可以变得容易,如果我们去更深一层底层TCP协议和处理HTTP请求自己的这种特殊情况下。 Node.js的可以让你做到这一点很容易地使用内置的网络模块 。
HTTP协议
首先,让我们来看看在HTTP请求中是如何工作的。
一个HTTP请求包括在关键的一般格式的标头部分的:由CRLF(分隔值对\r\n
)。 我们知道,当我们到达一个双CRLF结束了头部分(即\r\n\r\n
)。
一个典型的HTTP GET请求可能会是这个样子:
GET /resource HTTP/1.1
Cache-Control: no-cache
User-Agent: Mozilla/5.0
Hello=World&stuff=other
的“空”线之前的顶部部分是标头部分和底部部分是请求的主体。 您的请求将看起来有点不同的身体部分,因为它是与编码multipart/form-data
,但头仍然similarLet的探讨如何适用于我们。
在TCP的NodeJS
我们可以听在TCP的原始请求和阅读我们得到的,直到我们看到双CRLF我们谈到的数据包。 然后,我们会检查我们已经为我们所需要的任何验证的短头部分。 我们这样做了之后,我们可以终止该请求,如果验证未通过(例如,通过简单地结束TCP连接),或让它通过。 这让我们不接收或读取请求主体,而只是头这小得多。
一个简单的方法嵌入到现有的应用程序,这是由它请求代理到实际的HTTP服务器的具体使用情况。
实施细节
该解决方案是因为它得到裸露的骨头 。 这仅仅是一个建议。
这里是工作流程:
我们要求net
在Node.js的模块,它允许我们创建在node.js中的TCP服务器
创建一个使用TCP服务器net
模块,它会听数据: var tcpServer = net.createServer(function (socket) {...
不要忘记告诉它来听正确的端口
- 内部的回调,听数据事件
socket.on("data",function(data){
,每当一个数据包到达将触发。 - 从“数据”事件读出的传递缓冲的数据,并存储在一个变量
- 检查双CRLF,这确保了请求HEADER部分已经结束根据HTTP协议
- 假设验证头(令牌,你的话)检查解析只是头后,(也就是,我们得到了双CRLF)。 为Content-Length头检查时,这也适用。
- 如果您发现该头查不出来,叫
socket.end()
将关闭连接。
这里有一些事情,我们将使用
一种用于读取头的方法:
function readHeaders(headers) {
var parsedHeaders = {};
var previous = "";
headers.forEach(function (val) {
// check if the next line is actually continuing a header from previous line
if (isContinuation(val)) {
if (previous !== "") {
parsedHeaders[previous] += decodeURIComponent(val.trimLeft());
return;
} else {
throw new Exception("continuation, but no previous header");
}
}
// parse a header that looks like : "name: SP value".
var index = val.indexOf(":");
if (index === -1) {
throw new Exception("bad header structure: ");
}
var head = val.substr(0, index).toLowerCase();
var value = val.substr(index + 1).trimLeft();
previous = head;
if (value !== "") {
parsedHeaders[head] = decodeURIComponent(value);
} else {
parsedHeaders[head] = null;
}
});
return parsedHeaders;
};
一种用于缓冲你会得到一个数据事件,如果它在一个对象存在返回其位置检查双CRLF方法:
function checkForCRLF(data) {
if (!Buffer.isBuffer(data)) {
data = new Buffer(data,"utf-8");
}
for (var i = 0; i < data.length - 1; i++) {
if (data[i] === 13) { //\r
if (data[i + 1] === 10) { //\n
if (i + 3 < data.length && data[i + 2] === 13 && data[i + 3] === 10) {
return { loc: i, after: i + 4 };
}
}
} else if (data[i] === 10) { //\n
if (data[i + 1] === 10) { //\n
return { loc: i, after: i + 2 };
}
}
}
return { loc: -1, after: -1337 };
};
而这个小工具的方法:
function isContinuation(str) {
return str.charAt(0) === " " || str.charAt(0) === "\t";
}
履行
var net = require("net"); // To use the node net module for TCP server. Node has equivalent modules for secure communication if you'd like to use HTTPS
//Create the server
var server = net.createServer(function(socket){ // Create a TCP server
var req = []; //buffers so far, to save the data in case the headers don't arrive in a single packet
socket.on("data",function(data){
req.push(data); // add the new buffer
var check = checkForCRLF(data);
if(check.loc !== -1){ // This means we got to the end of the headers!
var dataUpToHeaders= req.map(function(x){
return x.toString();//get buffer strings
}).join("");
//get data up to /r/n
dataUpToHeaders = dataUpToHeaders.substring(0,check.after);
//split by line
var headerList = dataUpToHeaders.trim().split("\r\n");
headerList.shift() ;// remove the request line itself, eg GET / HTTP1.1
console.log("Got headers!");
//Read the headers
var headerObject = readHeaders(headerList);
//Get the header with your token
console.log(headerObject["your-header-name"]);
// Now perform all checks you need for it
/*
if(!yourHeaderValueValid){
socket.end();
}else{
//continue reading request body, and pass control to whatever logic you want!
}
*/
}
});
}).listen(8080); // listen to port 8080 for the sake of the example
如果您有任何问题随时问 :)
好吧,我撒谎了,还有一个更简单的方式!
但是,什么是在的乐趣? 如果您最初跳过这里,你不会学习如何HTTP工程:)
Node.js的有一个内置的http
模块。 由于请求是由性质node.js的分块,特别长的请求,就可以实现同样的事情,如果没有该协议的更深入的了解。
这一次,让我们使用http
模块创建HTTP服务器
server = http.createServer( function(req, res) { //create an HTTP server
// The parameters are request/response objects
// check if method is post, and the headers contain your value.
// The connection was established but the body wasn't sent yet,
// More information on how this works is in the above solution
var specialRequest = (req.method == "POST") && req.headers["YourHeader"] === "YourTokenValue";
if(specialRequest ){ // detect requests for special treatment
// same as TCP direct solution add chunks
req.on('data',function(chunkOfBody){
//handle a chunk of the message body
});
}else{
res.end(); // abort the underlying TCP connection, since the request and response use the same TCP connection this will work
//req.destroy() // destroy the request in a non-clean matter, probably not what you want.
}
}).listen(8080);
这是基于以下事实的request
一中的NodeJS手柄http
模块实际上是挂钩的头部被默认发出后(但执行没有别的)。 (此服务器模块中 , 该解析器模块中)
用户igorw建议使用稍微清洁的解决方案100 Continue
头假设浏览器你的目标支持它。 100继续是一个状态代码设计做你尝试什么:
的100(继续)状态的目的(参见10.1.1节)是允许客户端在发送请求消息,请求主体,以确定源服务器愿意接受请求(基于请求头)客户端之前将请求发送体。 在某些情况下,可能要么是不恰当或高度低效客户端发送的身体,如果服务器将拒绝该邮件不看身体。
这里是 :
var http = require('http');
function handle(req, rep) {
req.pipe(process.stdout); // pipe the request to the output stream for further handling
req.on('end', function () {
rep.end();
console.log('');
});
}
var server = new http.Server();
server.on('checkContinue', function (req, rep) {
if (!req.headers['x-foo']) {
console.log('did not have foo');
rep.writeHead(400);
rep.end();
return;
}
rep.writeContinue();
handle(req, rep);
});
server.listen(8080);
你可以看到样品的输入/输出这里 。 这需要你的要求在火中相应的Expect:
头。
使用JavaScript。 通过AJAX提交预成型时,用户点击提交,等待Ajax响应,那么当它回来了成功与否,提交实际的表单。 您也可以回退到你不想这是聊胜于无的方法。
<script type="text/javascript">
function doAjaxTokenCheck() {
//do ajax request for tokencheck.php?token=asdlkjflgkjs
//if token is good return true
//else return false and display error
}
</script>
<form enctype="multipart/form-data" action="upload.php?token=XXXXXX" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="3000000" />
Send this file: <input name="userfile" type="file" />
<input type="submit" value="Send File" onclick="return doAjaxTokenCheck()"/>
</form>
这听起来像你想流的上传和需要处理之前验证:这是否帮助? http://debuggable.com/posts/streaming-file-uploads-with-node-js:4ac094b2-b6c8-4a7f-bd07-28accbdd56cb
http://www.componentix.com/blog/13/file-uploads-using-nodejs-once-again
我建议你使用一些客户端插件上传文件。 你可以使用
http://www.plupload.com/
要么
https://github.com/blueimp/jQuery-File-Upload/
这两个插件有规定上传前检查文件的大小。
如果你想使用自己的脚本,检查。 这可能会帮助你
function readfile()
{
var files = document.getElementById("fileForUpload").files;
var output = [];
for (var i = 0, f; f = files[i]; i++)
{
if(f.size < 100000) // Check file size of file
{
// Your code for upload
}
else
{
alert('File size exceeds upload size limit');
}
}
}
一个版本是有些模糊。 所以我重写了代码来显示路线处理和中间件之间的差异。 中间件是为每个请求执行。 他们是在他们给出的顺序执行。 express.bodyParser()
是处理文件的上传,你应该跳过,不正确的令牌中间件。 mymiddleware
简单地检查令牌和终止无效的请求。 这必须前完成express.bodyParser()
被执行。
var express = require('express'),
app = express();
app.use(express.logger('dev'));
app.use(mymiddleware); //This will work for you.
app.use(express.bodyParser()); //You want to avoid this
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname+'/public'));
app.listen(8080, "127.0.0.1");
app.post('/upload',uploadhandler); //Too late. File already uploaded
function mymiddleware(req,res,next){ //Middleware
//console.log(req.method);
//console.log(req.query.token);
if (req.method === 'GET')
next();
else if (req.method === 'POST' && req.query.token === 'XXXXXX')
next();
else
req.destroy();
}
function uploadhandler(req,res){ //Route handler
if (req.query.token === 'XXXXXX')
res.end('Done');
else
req.destroy();
}
uploadhandler
因为它已被处理,另一方面不能中断上传express.bodyParser()
了。 它只是处理POST请求。 希望这可以帮助。
绕过PHP的后处理的方法之一是借道PHP-CLI的请求。 创建下面的CGI脚本并尝试上传大文件到它。 Web服务器应通过杀死连接响应。 如果确实如此,那么它只是一个打开内部套接字连接,并发送数据到实际位置的事情 - 只要条件满足,当然。
#!/usr/bin/php
<?php
echo "Status: 500 Internal Server Error\r\n";
echo "\r\n";
die();
?>
你为什么不只是使用APC文件上传进度,因此在这种情况下,提交表单设置进度密钥,作为APC文件上传密钥和上传进度将开始,但随后开始在第一进度检查你将验证密钥如果它不正确,你将中断一切:
http://www.johnboy.com/blog/a-useful-php-file-upload-progress-meter http://www.ultramegatech.com/2008/12/creating-upload-progress-bar-php/
这是做的更天然的方法。 大致相同,只是改变了隐藏输入的关键是你的令牌和验证,并中断了错误的情况下连接。 也许这就是更好的。 http://php.net/manual/en/session.upload-progress.php