Как base64-декодировать большие файлы в PHP

Мое веб-приложение PHP имеет API, который может получать достаточно большие файлы (до 32 МБ), закодированные в base64. Цель состоит в том, чтобы записать эти файлы где-нибудь в моей файловой системе. Расшифровано конечно. Каким будет наименее ресурсоемкий способ сделать это?

Редактировать: получение файлов через API означает, что в моем PHP-приложении есть строка размером 32 МБ, а не исходный файл размером 32 МБ где-то на диске. Мне нужно, чтобы эта строка была декодирована в файловой системе.

Использование собственной функции PHP base64_decode() не помогает, потому что она использует много памяти, поэтому я продолжаю сталкиваться с ограничением памяти PHP (я знаю, что мог бы увеличить этот предел, но мне не нравится позволяя PHP использовать 256 МБ или около того на процесс).

Любые другие варианты? Могу ли я сделать это вручную? Или записать закодированный файл на диск и вызвать какую-то внешнюю команду? Любая мысль?


person Sander Marechal    schedule 20.05.2009    source источник


Ответы (3)


Несмотря на то, что у этого есть принятый ответ, у меня есть другое предложение.

Если вы извлекаете данные из API, вам не следует хранить всю полезную нагрузку в переменной. Используя curl или другие сборщики HTTP, вы можете автоматически сохранять свои данные в файл.

Предполагая, что вы извлекаете данные через простой URL-адрес GET:

$url = 'http://www.example.com/myfile.base64';
$target = 'localfile.data';

$rhandle = fopen($url,'r');
stream_filter_append($rhandle, 'convert.base64-decode');

$whandle = fopen($target,'w');

stream_copy_to_stream($rhandle,$whandle);
fclose($rhandle);
fclose($whandle);

Выгоды:

  • Должно быть быстрее (меньше копирования огромных переменных)
  • Очень мало памяти

Если вам нужно получить данные из временной переменной, я могу предложить такой подход:

$data = 'your base64 data';
$target = 'localfile.data';

$whandle = fopen($target,'w');
stream_filter_append($whandle, 'convert.base64-decode',STREAM_FILTER_WRITE);

fwrite($whandle,$data);

fclose($whandle);
person Evert    schedule 21.05.2009
comment
Хорошая идея, но не то, что я ищу. В моем случае клиентские приложения передают большие файлы через XML-RPC (HTTP POST) на мой сервер (вместе с парой других параметров). Клиенты могут находиться за NAT и брандмауэрами, поэтому получение данных от клиента с помощью GET невозможно. - person Sander Marechal; 22.05.2009
comment
Если структура ответа xml rpc несколько статична, вы можете вручную просмотреть тело ответа, чтобы полностью избежать использования памяти. Если вам необходимо поместить данные во временную переменную, вы можете немного изменить настройку. (Я обновляю пример сразу после примера;)) - person Evert; 22.05.2009
comment
Спасибо за обновления. Я считаю, что это лучше, чем ответ, который я изначально принял. - person Sander Marechal; 22.05.2009
comment
Что касается вашего второго примера, будет ли это отличаться, если вам придется принять загрузку файла через POST и отправить его с вашего сервера на мыльный сервер? - person Chris; 10.08.2010
comment
file_put_contents() ожидает, что первым параметром будет строка, в этом случае передается ресурс. Я получаю такую ​​​​ошибку в приведенном выше коде - person detj; 24.01.2011
comment
Извините, последний файл file_put_contents должен был быть просто fwrite() - person Evert; 24.01.2011

Декодируйте данные небольшими порциями. Четыре символа данных Base64 равны трем байтам данных «Base256».

Таким образом, вы можете сгруппировать каждые 1024 символа и декодировать их до 768 октетов двоичных данных:

$chunkSize = 1024;
$src = fopen('base64.data', 'rb');
$dst = fopen('binary.data', 'wb');
while (!feof($src)) {
    fwrite($dst, base64_decode(fread($src, $chunkSize)));
}
fclose($dst);
fclose($src);
person Gumbo    schedule 20.05.2009
comment
Спасибо. Одна вещь, прежде чем я отмечу это как принятое: в моем исходном вопросе я упоминаю, что исходный файл поступает через API. Итак, это переменная (строка размером 32 МБ) в PHP, а не файл, из которого вы читаете. Есть ли что-то, что я могу использовать вместо вашего fread(), что эффективно возвращает мне фрагменты строки? т.е. не делая слишком много копий-дубликатов, которые занимают память? - person Sander Marechal; 20.05.2009
comment
Вы можете читать из ввода через php://input. См. docs.php.net/manual/en/wrappers.php.php - person Gumbo; 21.05.2009

Не рекомендуется передавать строку размером 32 Мб. Но у меня есть решение для моей задачи, которое может принимать файлы любого размера из браузера на сервер приложений. Алгоритм: Клиент

  1. Javascript: чтение файла из INPUT с помощью FileReader и readAsDataURL() в FILE var.
  2. Вырежьте все данные в ФАЙЛЕ от начала до первого, разделите их с помощью array_chunks на max_upload_size/max_post_size php var.
  3. Отправьте чанк с UID, номером чанка и количеством чанков и дождитесь ответа, затем отправьте еще один чанк один за другим.

На стороне сервера Записывайте каждый фрагмент до последнего. Затем выполните base64 с потоками:

$src = fopen($source, 'r');
$trg = fopen($target, 'w');
stream_filter_append($src, 'convert.base64-decode');
stream_copy_to_stream($src, $trg);
fclose($src);
fclose($trg);

... теперь у вас есть декодированный файл base64 в локальном пути $ target. Примечание! Вы не можете читать и записывать один и тот же файл, поэтому $source и $target должны быть разными.

person Ross Alex    schedule 05.01.2021