Загрузка файла на сервер без использования формы

22.09.2010

Теги: CURLHTMLHTTPPHPPOSTWeb-разработкаСокетФайл

Для HTTP запроса типа POST существует два варианта передачи полей из HTML форм, а именно, используя алгоритм application/x-www-form-urlencoded и multipart/form-data. Алгоритм первого типа создавался давным-давно, когда в языке HTML еще не предусматривали возможность передачи файлов через HTML формы.

Со временем возникла необходимость через формы отсылать еще и файлы. Тогда консорциум W3C взялся за доработку формата POST запроса. К тому времени уже достаточно широко применялся формат MIME (Multipurpose Internet Mail Extensions — многоцелевые расширения протокола для формирования Mail сообщений), поэтому, чтобы не изобретать велосипед заново, решили использовать часть данного формата формирования сообщений для создания POST запросов в протоколе HTTP.

Главное отличие multipart/form-data от application/x-www-form-urlencoded в том, что тело запроса теперь можно поделить на разделы, которые разделяются границами. Каждый раздел может иметь свой собственный заголовок для описания данных, которые в нем хранятся, т.е. в одном запросе можно передавать данные различных типов (как в теле письма можно одновременно с текстом передавать файлы). Пример запроса:

Content-Type: multipart/form-data; boundary=ff4ed67396bc8e1d6dbf19d65b6c6348
Content-Length: 417339
 
--ff4ed67396bc8e1d6dbf19d65b6c6348
Content-Disposition: form-data; name="name"
 
Евгений
--ff4ed67396bc8e1d6dbf19d65b6c6348
Content-Disposition: form-data; name="message"
 
Какое-то сообщение от пользователя
--ff4ed67396bc8e1d6dbf19d65b6c6348
Content-Disposition: form-data; name="upload"; filename="image.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary

...содержимое файла image.jpg...
--ff4ed67396bc8e1d6dbf19d65b6c6348--

Boundary (граница) — это последовательность байтов, которая не должна встречаться внутри передаваемых данных. Content-Length — суммарный объём, включая дочерние заголовки. Само содержимое полей при этом оставляется «как есть».

CURL, multipart/form-data

$file = 'image.jpg';
$postdata = array( 'name' => 'Евгений',
                   'message' => 'Какое-то сообщение от пользователя',
                   'upload' => '@'.$file );

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://server.com/get.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_exec($ch);
curl_close($ch);

Файл get.php на сервере http://server.com:

print_r($_POST);
print_r($_FILES);
move_uploaded_file($_FILES['upload']['tmp_name'], 'image.jpg');

Результат работы:

Array
(
    [name] => Евгений
    [message] => Какое-то сообщение от пользователя
)
Array
(
    [upload] => Array
        (
            [name] => image.jpg
            [type] => application/octet-stream
            [tmp_name] => C:\Windows\Temp\php504D.tmp
            [error] => 0
            [size] => 416919
        )
)

Важный момент: на форуме PHPCLUB.RU встретил упоминание, что может потребоваться указание полного пути файла — иначе CURL выдает ошибку.

$file = 'C:/work/localhost/www/image.jpg';

CURL, application/x-www-form-urlencoded

$file = 'image.jpg';
// данные POST-запроса
$postdata = array( 'name' => 'Евгений',
                   'message' => 'Какое-то сообщение от пользователя',
                   'upload' => file_get_contents($file) );

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://server.com/get.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_exec($ch);
curl_close($ch);

Файл get.php на сервере http://server.com:

print_r($_POST);
file_put_contents('image.jpg', $_POST['upload']);

Результат работы:

Array
(
    [name] => Евгений
    [message] => Какое-то сообщение от пользователя
    [upload] => ...содержимое файла image.jpg...
)

Сокеты, multipart/form-data

// устанавливаем соединение с сервером
$fp = fsockopen('server.com', 80, $errno, $errstr, 30);
if (!$fp) die($errstr.' ('.$errno.')');

$name = 'Евгений';
$message = 'Какое-то сообщение от пользователя';
$file = 'image.jpg';
// содержимое файла
$content = file_get_contents($file);

// разделитель
$boundary = md5(uniqid(time()));

$body = '--'.$boundary."\r\n";
$body = $body.'Content-Disposition: form-data; name="name"'."\r\n\r\n";
$body = $body.$name."\r\n";

$body = $body.'--'.$boundary."\r\n";
$body = $body.'Content-Disposition: form-data; name="message"'."\r\n\r\n";
$body = $body.$message."\r\n";

$body = $body.'--'.$boundary."\r\n";
$body = $body.'Content-Disposition: form-data; name="upload"; filename="image.jpg"'."\r\n";
$body = $body.'Content-Type: image/jpeg'."\r\n";
$body = $body.'Content-Transfer-Encoding: binary'."\r\n\r\n";
$body = $body.$content."\r\n";

$body = $body.'--'.$boundary.'--';

// пишем в сокет метод, URI и протокол
fwrite($fp, 'POST /get.php HTTP/1.1'."\r\n");
// а также имя хоста
fwrite($fp, 'Host: server.com'."\r\n");
// отправляем заголовки
fwrite($fp, 'Content-Type: multipart/form-data; boundary='.$boundary."\r\n");
fwrite($fp, 'Content-Length: '.strlen($body)."\r\n\r\n");
// теперь передаем данные
fwrite($fp, $body);

// получаем ответ
$result = '';
while ( !feof($fp) ) $result .= fgets($fp, 1024);
// закрываем соединение
fclose($fp);
// выводим ответ в браузер
echo $result;

Файл get.php на сервере http://server.com:

print_r( $_POST );
print_r( $_FILES );
move_uploaded_file($_FILES['upload']['tmp_name'], 'image.jpg')

Результат:

HTTP/1.1 200 OK
Server: Apache/2.0 (Win32) PHP/5.1
X-Powered-By: PHP/5.1
Content-Length: 310

Array
(
    [name] => Евгений
    [message] => Какое-то сообщение от пользователя
)
Array
(
    [upload] => Array
        (
            [name] => image.jpg
            [type] => image/jpeg
            [tmp_name] => C:\Windows\Temp\phpA457.tmp
            [error] => 0
            [size] => 416919
        )
)

Сокеты, application/x-www-form-urlencoded

// устанавливаем соединение с сервером
$fp = fsockopen('server.com', 80, $errno, $errstr, 30);
if (!$fp) die($errstr.' ('.$errno.')');

$file = 'image.jpg';
// содержимое файла
$content = file_get_contents($file);
// данные POST-запроса
$data = 'name=' . urlencode('Евгений') . '&message=' . urlencode('Какое-то сообщение от пользователя') . '&upload='.urlencode($content);
// заголовоки запроса
$headers = 'POST /get.php HTTP/1.1'."\r\n";
$headers .= 'Host: server.com'."\r\n";
$headers .= 'Content-type: application/x-www-form-urlencoded'."\r\n";
$headers .= 'Content-Length: '.strlen($data)."\r\n\r\n";
// отправляем запрос серверу
fwrite($fp, $headers.$data);
// получаем ответ
$result = '';
while ( !feof($fp) ) $result .= fgets($fp, 1024);
// закрываем соединение
fclose($fp);
// выводим ответ в браузер
echo $result;

Файл get.php на сервере http://server.com:

print_r($_POST);
file_put_contents('image.jpg', $_POST['upload']);

Результат работы:

HTTP/1.1 200 OK
Server: Apache/2.0 (Win32) PHP/5.1
X-Powered-By: PHP/5.1
Transfer-Encoding: chunked

Array
(
    [name] => Евгений
    [message] => Какое-то сообщение от пользователя
    [upload] => ...содержимое файла image.jpg...
)

Метод PUT

Описанные выше способы работают для относительно небольших файлов (примерно до 2-х мегабайт, для получения более точного значения необходимо смотреть в настройках PHP максимальный объем принимаемых данных методом POST). Чтобы обойти это ограничение, будем передавать файл методом PUT:

$url = 'http://server.com/get.php';
$file = 'image.jpg';
// Открываем передаваемый файл на чтение для дальнейшей его передачи
$fp = fopen($file, 'r');
// Инициализируем сеанс CURL
$ch = curl_init();
// Указываем URL скрипта, который примет наш запрос. К имени скрипта
// добавляем еще две переменные, передаваемые методом GET
curl_setopt($ch, CURLOPT_URL, $url . '?name=' . urlencode('Евгений') . '&message=' . urlencode('Какое-то сообщение от пользователя'));
// Дескриптор файла, который собираемся передать
curl_setopt($ch, CURLOPT_INFILE, $fp);
// Указываем размер отправляемого файла
curl_setopt($ch, CURLOPT_INFILESIZE, filesize($file));
// Указываем, что файл передается методом PUT
curl_setopt($ch, CURLOPT_PUT, 1);
// Указываем, что будет производиться закачка на удаленный сервер
curl_setopt($ch, CURLOPT_UPLOAD, 1);
// Выполняем запрос CURL
curl_exec($ch);
// Завершаем сеанс CURL
curl_close($ch);

Файл get.php на сервере http://server.com:

print_r($_GET);
file_put_contents ('image.jpg', file_get_contents('php://input'));

Результат работы:

Array
(
    [name] => Евгений
    [message] => Какое-то сообщение от пользователя
)

Поиск: $_FILES • $_POST • CURL • HTML • HTTP • PHP • POST • PUT • Web-разработка • application/x-www-form-urlencoded • boundary • move_uploaded_file • multipart/form-data • socket • Файл • сокет

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.