Php передача бинарных данных
HTML-формы могут выгружать (upload) файлы сколь угодно больших размеров 39 . Файлы, как правило, передаются через HTTP-метод POST 40 .
39) Выгрузка файлов по HTTP описана в RFC-1867. Этот механизм позволяет выгружать большие файлы, используя бинарное кодирование передаваемых данных. Для этой цели используется тип кодировки «multipart/form-data».
40) HTTP-метод GET неэффективен для выгрузки файлов, так как у длины URL есть предел. Кроме того, URL-кодирование данных файлов значительно увеличивает длину URL.
По умолчанию, HTTP использует URL-кодирование для передачи данных форм. Вы могли видеть это кодирование в предыдущих главах. Однако, оно неэффективно для выгрузки больших файлов, так как такое кодирование бинарных данных сильно увеличивает длину HTTP-запроса. Для выгрузки данных лучше использовать так называемое бинарное транспортное кодирование (binary transfer encoding), рассматриваемое в следующем разделе.
10.1.1. Бинарное транспортное кодирование HTTP
Простая HTML-форма с возможностью выгрузки файлов показана в примере кода ниже. Бинарный тип кодирования устанавливается заданием атрибута enctype формы со значением «multipart/form-data»:
В строке 1 мы явно задаем кодирование «multipart/form-data» (атрибут `enctype) для использования эффективного бинарного кодирования передачи данных формы.
В строке 2 мы определяем поле ввода с типом «file» и именем «myfile». Это поле позволит посетителям сайта выбирать файл для выгрузки.
Если вы сохраните представленную выше разметку в файл .html и откроете его в своем браузере, вы увидите страницу как на рисунке 10.1.
Рисунок 10.1. Простая HTML-форма с возможностью выгрузки файлов
Элемент файла имеет кнопку поиска Browse. , которая позволяет выбрать файл для выгрузки. После того как пользователь сайта выбрал файл и нажал кнопку отправки формы, веб-браузер посылает веб-серверу HTTP-запрос, содержащий данные выгружаемого файла. Пример ниже показывает, как может выглядеть HTTP-запрос:
POST http://localhost/upload HTTP/1.1 Host: localhost Content-Length: 488 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) Content-Type: multipart/form-data; boundary=----j1bOrwgLvOC3dy7o Accept-Encoding: gzip,deflate,sdch ------j1bOrwgLvOC3dy7o Content-Disposition: form-data; name="myfile"; filename="Somefile.txt" Content-Type: text/html (двоичные данные файла) ------j1bOrwgLvOC3dy7o Content-Disposition: form-data; name="Submit" Отправка запроса ------j1bOrwgLvOC3dy7o--
Как видите, HTTP-запрос с типом кодирования «multipart/form-data» выглядит аналогично обычному HTTP-запросу (содержит строку статуса, заголовки и область содержимого), однако он имеет следующие важные отличия:
- Строка 5 устанавливает заголовок Content-Type» со значением «multipart/form-data». Форма собирается из полей, отмеченных «границами» — уникальными случайно сгенерированными последовательностями символов, отделяющими поля формы друг от друга.
- Строки 8-17 представляют собой содержимое HTTP-запроса. Поля формы разделены «границами» (строки 8, 13, 17). Данные выгружаемого файла передаются в двоичном формате (строка 12), что позволяет уменьшить до минимума размер содержимого.
По умолчанию, настройки PHP-движка не позволяют выгружать большие файлы (больше 2МБ). Для выгрузки таких файлов нужно изменить файл конфигурации php.ini, а также параметры post_max_size и upload_max_filesize . О том, как это сделать, можете прочитать в Приложение А. Организация среды веб-разработки.. Установив эти настройки на 100M позволит выгружать на сервер файлы размером до 100 Мб, и этого, как правило, будет достаточно. Если вы планируете выгрузку очень больших файлов вплоть до 1 ГБ, лучше задайте в настройках 1024М. Не забудьте перезапустить веб-сервер Apache после изменения файла конфигурации.
10.1.2. Суперглобальный массив $_FILES в PHP
При выгрузке посетителем сайта файлов на ваш веб-сервер Apache, эти файлы помещаются во временное хранилище (обычно в системный временный каталог — /tmp в Linux и C:\Windows\Temp в Windows). PHP-скрипт принимает информацию о файле в специальный суперглобальный массив $_FILES .
Массив $_FILES аналогичен суперглобальным $_GET и $_POST . Два последних используются для хранения переменных GET и POST соответственно, в то время как первый используется для хранения информации о выгружаемых на сервер файлах.
Например, для вышеупомянутой формы выгрузки суперглобальный массив $_FILES будет выглядеть следующим образом (выходные данные генерируются с помощью PHP-функции var_dump() ):
array (size=1) 'myfile' => array (size=5) 'name' => string 'somefile.txt' (length=12) 'type' => string 'text/plain' (length=10) 'tmp_name' => string '/tmp/phpDC66.tmp' (length=16) 'error' => int 0 'size' => int 18
Как видите из этого примера, массив $_FILES содержит запись для каждого выгружаемого на сервер файла. Для каждого файла он содержит следующую информацию:
- name — исходное имя файла (строка 4).
- type — MIME-тип 41 файла (строка 5).
- tmp_name — временное имя для выгружаемого файла (строка 6).
- error — код ошибки, сигнализирующий о статусе выгрузки (строка 7); нулевой код ошибки означает, что файл был корректно выгружен на сервер.
- size — размер файла в байтах (строка 8).
41) MIME-тип (Multipurpose Internet Mail Extension — Многоцелевое расширение почты Интернета), также известный как «тип содержимого» — стандартный идентификатор, используемый в Интернете для указания типа данных, которые содержит файл. Например, MIME-тип «text/plain» присваивается текстовому файлу, а MIME-тип «application/octet-stream» — бинарному.
PHP-движок хранит выгруженные файлы во временном хранилище, которое очищается, как только завершается выполнение PHP-скрипта. Таким образом, если вы хотите сохранить выгруженные на сервер файлы в какой-нибудь каталог для дальнейшего использования, нужно воспользоваться PHP-функцией move_uploaded_file() . Она принимает два аргумента: первый — это имя временного файла, а второй — имя файла назначения.
Вы, наверное, не понимаете, почему нельзя использовать обычную PHP-функцию rename() , чтобы переместить временный выгруженный файл в его путь назначения. Специальная функция для перемещения выгруженных на сервер файлов существует в PHP из соображений безопасности. Функция move_uploaded_file() аналогична функции rename() , за исключением того, что она делает дополнительные проверки для гарантии того, что файл на самом деле был передан через метод запроса POST и что процесс выгрузки на сервер завершился без ошибок.
Следующий фрагмент кода показывает, как перемещать файл, выгруженный на сервер с помощью простой формы, которую мы рассмотрели выше:
$destPath = '/path/to/your/upload/dir'; $result = move_uploaded_file($_FILES['myfile']['tmp_name'], $destPath); if(!$result) < // Возникла какая-то ошибка. >
В строке 1 этого примера мы задаем $destPath — имя каталога, куда нужно сохранить выгруженный файл.
В строке 2 мы вызываем функцию move_uploaded_file() и передаем ей два аргумента: путь к временному файлу и путь назначения.
Передача имени каталога в качестве второго аргумента функции move_uploaded_file() подходит для тех случаев, когда вы не хотите переименовывать файл. Если вам нужно сохранить выгруженный файл под другим именем, можете вместо имени директории указать полный путь к файлу.
В строке 3 мы проверяем возвращаемое значение функции. При успешной операции функция вернет true . Если произошли какие-либо ошибки (например, если прав доступа к каталогу недостаточно для сохранения файла), вернется булевое false .
Бинарный парсинг с PHP
От автора: бинарные операции в PHP – немного странные. Так как PHP с самого начала являлся шаблонным слоем для C-кода, в нем все еще много этих C-измов. Множество названий функции в точности отображают API C-уровня, даже если работают иногда немного по-разному. Например, PHP strlen напрямую устанавливают соответствие с STRLEN(3), и тому есть бесконечное множество примеров. Однако, как только дело доходит до работы с бинарными данными, все неожиданно сильно меняется.
Детали учебника
Сложность: продвинутая
Бинарные данные, говорите?
Что такое бинарные данные? Бинарный (двоичный) код на самом деле – всего лишь представление данных, а любые данные можно представить как нули и единицы. Говоря о бинарных данных, мы обычно подразумеваем представление данных, как последовательность битов. И обычно нам нужно закодировать некие данные для передачи в биты, а затем на другой стороне декодировать их. Бинарное представление – это просто эффективный формат передачи.
Чтобы кодировать и раскодировать, нам нужно как-то получить доступ к отдельным битам, а затем получить функции, способные конвертироваться из некоего существующего представления в упакованное и обратно. Один из способных к этому и обеспечиваемых языками программирования инструментов – побитовые операции.
Онлайн курс «PHP-разработчик»
Изучите курс и создайте полноценный проект — облачное хранилище файлов
С нуля освоите язык программирования PHP, структурируете имеющиеся знания, а эксперты помогут разобраться с трудными для понимания темами, попрактикуетесь на реальных задачах. Напишете первый проект для портфолио.
Способ C
До того, как рассмотреть его работу в PHP, я хотел бы сначала изнутри рассмотреть, как C управляется с ним.
Хотя C является языком высокого уровня, он все еще очень близок к «железу». Внутри CPU и RAM данные хранятся, как последовательность битов. Следовательно, целые числа внутри C – тоже последовательность битов. Символ – тоже последовательность битов, а строка – массив символов.
Давайте рассмотрим пример:
мы обращаемся к первому символу H и печатаем два его представления. Первое – это представление символа (%c), второе – представление целого числа (%i). Представление символа – это H, представление целого числа – 72. Почему 72, спросите вы? Потому что десятичное число 72 представляет букву H в таблице ascii (Американского стандартного кода обмена информацией), что определяет набор знаков, который назначает каждому числу от 0 до 128 отдельное значение. Некоторые из них – управляющие символы, некоторые представляют числа, а некоторые – буквы.
Все в порядке. Данные – это просто данные, которые где-то хранятся, и нам нужно решить, как их интерпретировать.
PHP: как бы то ни было, вам не следует этого делать в PHP
Одна из основных причин, почему в PHP это так отличается – тот факт, что строка – это совсем иной тип. Давайте разберемся, что делает PHP:
Чтобы получить код ascii символа в PHP, вам нужно вызвать ord к символу (который на самом деле не символ, а строка из одного знака, так как здесь нет типа символа). Ord возвращает значение ascii символа.
В отличие от примера в C, здесь у нас имеется более одного представления данных. В C есть только единичное представление, у которого могут оказаться разные интерпретации. Число 72 в одно и то же время может быть символом H. PHP требует от нас конвертирования строк и значений ascii, сохраняя их оба в различных переменных с разными типами.
И это является основной головной болью при выполнении бинарного парсинга в PHP. Так как данные могут представляться как строка или число, вам необходимо знать, с чем из них вы имеете дело. А в зависимости от этого можно применять различные инструменты.
Опускаемся до уровня битов
Пока что мы видели, как получить доступ к отдельным байтам и как получить их значение ascii. Но пока нам это не очень пригодно. Для парсинга бинарных протоколов нам нужно получить доступ к отдельным байтам.
В качестве примера я использую заголовок пакета DNS. Заголовок состоит из 12 байтов. Эти байты разделены на 6 полей по 2 байта в каждом. Вот формат, определенный RFC 1035: