Intereting Posts
PHP Рекурсивно отключает ключи массива, если они совпадают Doctrine – вставить несколько строк только с одним сохранением () CodeIgniter – Загрузка изображения через форму, сохранение местоположения изображения в базе данных PHP PDO MySQL прокручиваемый курсор не работает Лучшая практика для вывода PHP Запрещено У вас нет разрешения на доступ к этому серверу. Сервер не может читать файл htaccess, запрещающий доступ к безопасности Простота внедрения зависимостей Безопасность PHP $ _GET, передовая практика безопасности $ _POST Закрытие соединения MySQL во время параллельных задач Cron Шифрование AES с использованием Java и PHP Каков наилучший способ реализации двухстороннего шифрования с помощью PHP? PHP fwrite новая строка Удаленное изображение Dompdf не отображается в pdf Альтернатива SSL – «Ручное» шифрование? CoinKite new / receive – передача правильных параметров

Перерыв загрузки HTTP-файла с сервера с помощью PHP или Apache

При загрузке большого файла (> 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 , как это (code 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) проверить HTTP-заголовок до завершения запроса POST?

Может ли кто-нибудь сказать мне, как сделать этот скрипт проверять пароль до того, как он начнет процесс загрузки, а не после загрузки файла?

Прежде всего, вы можете попробовать этот код самостоятельно, используя репозиторий 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 но заголовок останется подобным. Посмотрим, как это относится к нам.

TCP в узлах

Мы можем прослушивать необработанный запрос в TCP и читать пакеты, которые мы получаем до тех пор, пока не прочитаем этот двойной crlf, о котором мы говорили. Затем мы проверим короткую секцию заголовка, которую мы уже имеем, для любой необходимой проверки. После этого мы можем либо завершить запрос, если проверка не прошла (например, просто закончив TCP-соединение), либо пропустите его. Это позволяет нам не получать или читать тело запроса, а только заголовки, которые намного меньше.

Один простой способ встроить его в уже существующее приложение – это прокси-запросы от него к фактическому HTTP-серверу для конкретного варианта использования.

Детали реализации

Это решение – как голые кости, так и есть. Это просто предложение.

Вот работа:

  1. Нам нужен net модуль в node.js, который позволяет нам создавать tcp-серверы в node.js

  2. Создайте TCP-сервер, используя net модуль, который будет прослушивать данные: var tcpServer = net.createServer(function (socket) {... Не забудьте сказать ему, чтобы прослушать правильный порт

    • Внутри этого обратного вызова прислушайтесь к событиям данных socket.on("data",function(data){ , которые будут срабатывать всякий раз, когда приходит пакет.
    • прочитайте данные переданного буфера из события «данные» и сохраните это в переменной
    • проверьте наличие двойного CRLF, это гарантирует, что раздел HEADER запроса завершился в соответствии с протоколом HTTP
    • Предполагая, что проверка является заголовком (токеном в ваших словах), проверьте его после разбора только заголовков (т. Е. Мы получили двойной CRLF). Это также работает при проверке заголовка длины содержимого.
    • Если вы заметили, что заголовки не проверяются, вызовите 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 в http узле nodejs фактически подключается после отправки заголовков (но ничего не было выполнено) по умолчанию. (это в модуле сервера , это в модуле анализатора)

Пользователь igorw предложил несколько более чистое решение, используя заголовок 100 Continue предполагая, что браузеры, на которые настроен таргетинг, поддерживают его. 100 Continue – это код состояния, предназначенный для выполнения именно того, что вы пытаетесь сделать:

Цель состояния 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, когда пользователь нажимает кнопку submit, ждет ответа 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'); } } } 

Предыдущая версия несколько расплывчата. Поэтому я переписал код, чтобы показать разницу между обработкой маршрута и промежуточным программным обеспечением. Middlewares выполняются для каждого запроса. Они выполняются в том порядке, в котором они указаны. 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 и попробуйте загрузить в него большой файл. Веб-сервер должен ответить, убив соединение. Если это так, то это просто вопрос открытия внутреннего соединения сокета и отправки данных в фактическое местоположение – при условии, что условия выполнены, конечно.

 #!/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