Сервер возвращает поврежденный файл

#php #nginx #export #attachment

#php #nginx #экспорт #вложение

Вопрос:

У меня есть скрипт, который берет данные из базы данных и собирает их в кэш вывода PHP (ob_start), распечатывая его. После его очистки и я получаю запрос на сохранение файла. Но файл не всегда нужной длины. Почти всегда он имеет длину 160 Кб, но иногда имеет полную длину 15 Мб. Я только что запустил этот скрипт 20 раз, и 3 раза из 20 я получил нормальный размер файла, в других случаях он имеет тот же размер (160 538 байт).

Почему это может произойти?

Короткий вариант моего кода:

 ob_start();
$offset = 0;
$count  = 20000;
while (true) {
    $dataFromMysql = GetMySqlData($count, $offset);
    foreach($dataFromMysql as $data) {
        print implode(';', $data);
    }

    if (!$dataFromMysql || count($dataFromMysql) < $count) {
        break;
    }

    $offset  = $count;
}

header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
header('Content-Type: application/csv');
header('Content-Length: ' . ob_get_length());
header('Content-Disposition: attachment; filename="export.csv"');
ob_end_flush();
exit;
  

Сайт стоит на двух физических серверах. Но я попросил администратора направить поток на главный сервер для этого скрипта.

Комментарии:

1. сначала переместите заголовки в начало

2. Проверьте максимальное время выполнения попробуйте отправить фрагментированный файл (используя заголовок HTTP / 1.1)

3. Теперь заголовки в верхней части скрипта и добавлен заголовок (‘Content-Transfer-Encoding: chunked’); но все та же проблема…

Ответ №1:

Вместо использования функций ob_ * вы могли бы просто записать в строковую переменную, затем вы можете использовать strlen() для получения длины содержимого, а затем распечатать полную строку в конце.

Редактировать: что может быть более полезным, так это fputcsv() , вы можете использовать его для создания файла csv на стороне сервера и отправки его в конце с помощью readfile() .

Комментарии:

1. Я попытался сохранить данные в строку, php:// output, реальный файл, ob_ *, но ничего из этого не работает должным образом… вот почему я здесь)

Ответ №2:

Я победил!

Вот краткий вариант рабочего кода:

 header('Content-Description: File Transfer');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Transfer-Encoding: chunked');
header('Pragma: public');
header('Expires: 0');
header('Content-type: application/csv');
header('Content-Disposition: attachment; filename="export.csv"');
header('Cache-Control: max-age=0');

$outputString = '';
$offset = 0;
$count  = 20000;
while (true) {
    $dataFromMysql = GetMySqlData($count, $offset);
    foreach($dataFromMysql as $data) {
        $outputString .= implode(';', $data);
    }

    if (!$dataFromMysql || count($dataFromMysql) < $count) {
        break;
    }

    $offset  = $count;
}

$contentLength = strlen($outputString);
header('Content-Length: ' . $contentLength);

$hFile = fopen('php://output', 'wb ');
if ($hFile) {
    for ($i=0; $i<$contentLength; $i  ) {
        fwrite($hFile, $this->outputString[$i]);
    }
    fclose($hFile);
}

exit;
  

Я не знаю почему, но теперь это работает! Просто потому, что я вывожу полную собранную строку на один символ (1 байт). Я попытался вывести его на 1 КБ (1024 байта), но это не сработало : (

 $hFile = fopen('php://output', 'wb ');
if ($hFile) {
    for ($i=0; $i<$contentLength; $i  ) {
        fwrite($hFile, $this->outputString[$i]);
    }
    fclose($hFile);
}
  

Я надеюсь, что это кому-то поможет 😉