当前位置: 首页 > 面试题库 >

通过PHP或Apache从服务器端中断HTTP文件上传

卫甫
2023-03-14
问题内容

当将大文件(> 100M)上传到服务器时,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,像这样(代码片段):

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;
    }

结果是…… 再次 失败。

因此,请帮助我找到解决此问题的正确路径,或者告诉我没有办法。


问题答案:

首先,您可以使用我为此创建的GitHub存储库自己尝试此代码。只需克隆存储库并运行node header

(扰流器,如果您正在阅读本文,并且在时间上压力很大,需要工作,而不愿意学习(:(),最后有一个更简单的解决方案)

总体思路

这是一个很好的问题。您所要求的是 很有可能的不需要客户端
,只需深入了解HTTP协议的工作原理,同时展示node.js的运行方式即可:)

如果我们更深入地了解底层TCP协议并针对这种特定情况自行处理HTTP请求,这将变得很容易。Node.js使您可以使用内置的net模块轻松地执行此操作。

HTTP协议

首先,让我们看一下HTTP请求的工作方式。

HTTP请求由以CRLF(\r\n)分隔的key:value对的常规格式的标头部分组成。我们知道,当到达双倍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但标题仍保持相似。让我们来探索一下这对我们的适用方式。

Node.js中的TCP

我们可以侦听TCP中的原始请求,并读取获得的数据包,直到我们读取了所讨论的double
crlf。然后,我们将检查我们已经拥有的短标头部分,以进行所需的任何验证。之后,如果验证没有通过(例如,仅通过结束TCP连接),我们可以结束请求,也可以通过。这使我们不接收或读取请求主体,而仅接收或读取较小的标头。

将其嵌入到现有应用程序中的一种简单方法是针对特定用例将请求从代理请求发送到实际的HTTP服务器。

实施细节

该解决方案是 裸露的骨头 ,因为它得到。这只是一个建议。

这是工作流程:

  1. 我们需要netnode.js中的模块,该模块允许我们在node.js中创建tcp服务器

  2. 使用net将监听数据的模块 创建一个TCP服务器var tcpServer = net.createServer(function (socket) {...。不要忘记告诉它收听正确的端口

    • 在该回调内部,侦听数据事件socket.on("data",function(data){,该事件将在数据包到达时触发。
    • 从’data’事件中读取传递的缓冲区的数据,并将其存储在变量中
    • 检查是否有双CRLF,以确保根据HTTP协议,请求HEADER部分已结束
    • 假设验证是标头(用您的话来说是令牌),请在 分析 标头 之后检查它(即,我们得到了双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);

这是基于以下事实:默认情况下,在发送标题(但未执行其他操作)之后,requestnodejs
http模块中的句柄实际上已挂接到钩子上。(这在服务器模块中,在解析器模块中)

igorw用户提出了使用100Continue标头的更干净的解决方案,假设您要定位的浏览器支持该标头。100Continue是一种状态代码,旨在完全执行您要执行的操作:

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:标头触发请求。



 类似资料:
  • 问题内容: 我想将一些文件上传到HTTP服务器。基本上,我需要的是对服务器的某种POST请求,其中包含一些参数和文件。我看到了仅上传文件的示例,但没有找到如何也传递其他参数的示例。 什么是最简单,免费的解决方案?有人有我可以学习的文件上传示例吗?我已经搜寻了几个小时,但是(也许只是那几天)找不到我真正需要的东西。最好的解决方案是不涉及任何第三方类或库的东西。 问题答案: 通常,你会用来触发HTTP

  • 我正在尝试通过REST API在服务器上上传文件。我的api请求FormData模型,但Im得到错误。 devtools中得“我得请求”得有效负载 ------WebKitFormBoundaryVpirgXMC3RU3AOFA内容-配置:表单-数据;name=“文件”;filename=“test.jpg”content-type:image/jpeg -----WebKitFormBounda

  • 我正试图从我的android设备上传文件到php服务器,但服务器没有收到任何文件。这是我的示例代码,我在网上找到的。php服务器,我正在通过chunk上传文件 我的Android代码 PHP Server文件上传中的代码,我是通过块完成文件上传的,请谁帮我发送正确的代码文件通过块上传和相关的android文件上传代码

  • 问题内容: 我正在尝试将文件从我的Android设备上传到php服务器,但服务器未收到任何文件。这是我的示例代码,我在线找到了。php服务器,我正在通过块上传文件 我的Android代码 PHP Server代码在文件上传中,我已经完成了通过块进行文件上传,请有人帮助我通过块和相关的android文件上传代码发送正确的代码文件 问题答案: 我不知道您的代码,但是为您提供了两个有效的代码:这适用于所

  • 问题内容: 我一直在寻找解决方案,但是已经找不到了。 基本上,我想从Android设备上将文件上传到http网站。但是,我不知道如何执行此操作。我在设备上使用Java,并且我想在服务器端使用PHP。我只想上传文件,而不想在服务器上做任何花哨的事情。 谁能提供代码和/或指向我所需的链接?我对此几乎没有经验,也很茫然。 谢谢,NS PS。我没有PHP编码经验。 问题答案: 是的,所以我找到了Java方

  • 问题内容: 是否有Node.js即用型工具(安装了),可以帮助我通过HTTP将文件夹内容作为文件服务器公开。 例如,如果我有 然后开始, 我可以通过访问文件 为什么我的节点静态文件服务器删除请求? 引用一些神秘的东西 标准的node.js静态文件服务器 如果没有这样的工具,我应该使用什么框架? 问题答案: 一个很好的“即用型工具”选项可以是http-server: 要使用它: 或者像这样: 签出: