Иногда при разработке проектов возникает задача отправки данных на другие серверы. В случае текстовых данных или небольших объемов бинарных данных можно ограничиться POST- или GET-запросами. Этот способ никаких сложностей не представляет и здесь описываться не будет. А как быть, если на сторонний сервер требуется передать не только данные, но и файлы? Например, вы загружаете картинки через форму на своем сервере, но фактически храните их на каком-нибудь другом. В этом случае нам надо полностью проэмулировать работу браузера, а именно его обмен данными с удаленным сервером, как будто бы пользователь заполнил и отправил форму с web-страницы. Предположим, что для загрузки файлов на сервер используется следующая форма:
<form action="/uploader.php" method="post" enctype="multipart/form-data">
Выберите файл: <input type="file" name="my_file"><br>
Описание: <input type="text" name="ext_field_1"><br>
<input type="submit" value="Загрузить">
</form>
C HTML все понятно, а вот чтобы понять как передаются данные после нажатия в браузере кнопки "Загрузить", рекомендую прочитать
официальную документацию. В заголовках HTTP-запроса, отправляемого на сервер, обязательно должен присутствовать заголовок "Content-Type: multipart/form-data; boundary=NNNNN". Значение параметра boundary - это уникальная строка, используемая в качестве разделителя полей в теле запроса. Она не должна встречаться ни в одном значении поля формы. Обычно для этого достаточно сгенерировать хэш от случайного числа и использовать его в качестве искомого значения.
Каждое текстовое поле в теле запросе кодируется следующим образом:
--boundary
Content-Disposition: form-data; name="имя_поля_в_форме"
значение поля
Файлы при передаче кодируются несколько иначе:
--boundary
Content-Disposition: form-data; name="имя_поля" filename="имя_файла"
Content-Type: mime-тип файла
двоичное содержимое файла
В заголовках запроса также обязательно должны присутствовать данные об общем размере тела запроса, а именно длина всех кодированных полей формы. Эта информация передается заголовком "Content-Length: NNNN". То есть сперва в вашем обработчике формируется тело запроса, а только после него заголовок.
Теперь мы знаем какие данные и в каком виде надо передавать, осталось сделать рабочий скрипт. Сперва, как я говорил выше, формируем тело запроса.
// Данные для формирования запроса
$boundary=md5(rand());
$crlf = "\r\n";
// Название поля в форме отправки файла
$field_name='my_file';
// Имя файла
$file_name='my_file.bin';
// Тип файла
$file_type='application/octet-stream';
// Содержимое файла
$file_data=str_repeat('x',100);
// Подготовить тело запроса с файлом
$body = '--'.$boundary.$crlf;
$body .= 'Content-Disposition: form-data; ';
$body .= 'name="'.$field_name.'"; filename="'.$file_name.'"'.$crlf;
$body .= 'Content-Type: '.$file_type.$crlf;
$body .= $crlf;
$body .= $file_data;
$body .= $crlf;
// Дополнительные поля формы (если надо)
$body .= '--'.$boundary.$crlf;
$body .= 'Content-Disposition: form-data; name="ext_field_1"'.$crlf;
$body .= $crlf;
$body .= 'Some text data....'.$crlf;
// Конец тела запроса
$body .= '--'.$boundary.$crlf;
$body .= $crlf;
Тело запроса собрано, переходим к формированию заголовка запроса. Здесь приведен минимальный пример, в реальной ситуации может потребоваться передавать в заголовке cookies для авторизации или идентификации сессии, адрес referer или какие-нибудь иные данные, если удаленный сервер требует их наличия. Отследить это все можно при помощи расширения LiveHTTPheaders для браузера Firefox.
$host='www.example.ru'; // Хост для приема файла
$url='/uploader.php'; // URL скрипта для приема файла
// Подготовить заголовок POST-запроса
$header = 'POST '.$url.' HTTP/1.1'.$crlf;
$header .= 'Host: '. $host . $crlf;
$header .= 'User-Agent: Uploader/1.0.0'.$crlf;
$header .= 'Content-Type: multipart/form-data; boundary='.$boundary.$crlf;
$header .= 'Content-Length: '.strlen($body).$crlf;
$header .= 'Connection: keep-alive'.$crlf;
$header .= 'Keep-Alive: 300'.$crlf;
$header .= $crlf;
Последний шаг - отправка запроса удаленному серверу. Для этого воспользуемся функцией PHP fsockopen.
Code (PHP) : Убрать нумерацию
$port=80;
$timeout=30;
// Открыть соединение с сервером
$f=fsockopen($host, $port, $errno, $errstr, $timeout);
// Отправить файл на сервер данные
fputs($f, $header.$body);
// Получить ответ от удаленного сервера
$response='';
while(!feof($f)) {
$line=fgets($f,1000);
if ($line=='') { break; }
$response.=$line;
}
echo $response;
// Закрыть соединение с сервером
fclose($f);
Как проверить корректность работы скрипта? Сделаем свой скрипт uploader.php следующего содержания и пропишем его в качестве приемника отправляемых данных:
echo 'Данные $_POST:<br>';
print_r($_POST);
echo 'Данные $_FILES:<br>';
print_r($_FILES);
Результат его работы:
Данные $_POST:
Array (
[ext_field_1] => Some text data....
)
Данные $_FILES:
Array (
[my_file] => Array (
[name] => my_file.bin
[type] => application/octet-stream
[tmp_name] => /tmp/php1P8imr
[error] => 0
[size] => 100
)
)
Как видите, данные из несуществующей формы отправляются и принимаются успешно, в том числе и файлы. Обратите внимание, что в качестве $file_data могут быть любые данные, то есть мы можем отправлять на удаленные серверы "виртуальные" файлы, которые сгенерированы динамически и существуют только в памяти нашего процесса. Для больших объемов данных информацию на сервер можно отправлять по частям, однако заголовок Content-Length в любом случае должен содержать правильное значение.
Это не единственный способ передачи файлов, можно также воспользоваться
cURL с методом PUT или функциями PHP
file_put_contents и
ftp_put, все зависит от поставленной задачи. Ну и конечно же не забывайте о безопасности ваших проектов и всегда проверяйте все полученные данные.