Всегда отправлять Content-Length в Apache?

Я загружаю особенно большую строку JSON, динамически генерируемую PHP. Чтобы предоставить пользователю обратную связь, я хочу показать прогресс загрузки.

Я понял код, и он отлично работает для статического содержимого, такого как изображения, файлы JS и т. Д. Однако, похоже, он не работает для динамических файлов.

Это имеет смысл, поскольку динамические файлы не имеют предсказуемой длины содержимого, но даже если я добавлю это в PHP:

ob_start(function($c) {
    header("Content-Length: ".strlen($c));
    return $c;
});

Он по-прежнему не отправляет заголовок (но если я добавлю какой-либо другой заголовок, он будет работать нормально).

Есть ли способ заставить Apache отправлять заголовок Content-Length? В настоящее время моя единственная альтернатива - сохранить вывод во временный файл и вместо этого перенаправить на него. Это сработает, но это немного беспорядочно, поэтому я бы предпочел избегать этого, если возможно.


person Niet the Dark Absol    schedule 22.01.2013    source источник
comment
... а точнее, примечание о кодировке передачи в ответе.   -  person David Ravetti    schedule 26.01.2013
comment
Спасибо за указатель. Не похоже, что он использует кодировку по частям (по крайней мере, Transfer-Encoding не отображается в заголовках ответов), и я не могу заставить его принудительно использовать HTTP / 1.0 ...   -  person Niet the Dark Absol    schedule 26.01.2013
comment
Вы пробовали с простым ob_start(); /* output json */ $content = ob_get_clean(); header('Content-Length: '.strlen($content)); print($content);? Хотя было бы неожиданно, что ваш обратный вызов ob_start не будет выполнен, вы определенно можете ожидать чего-нибудь неожиданного от PHP.   -  person lanzz    schedule 27.01.2013
comment
Что такое расширение динамического файла?   -  person Wojciech Bednarski    schedule 29.01.2013
comment
Запрос аналогичен /ajax/getdata, файл - getdata.php.   -  person Niet the Dark Absol    schedule 29.01.2013
comment
Есть ли какой-нибудь общедоступный URL-адрес, где можно было бы попробовать это? (То есть ob_start-with-callback?) То, что он отправит любой заголовок, но Content-Length действительно странно.   -  person Ry-♦    schedule 29.01.2013
comment
Итак, может быть, в Apache conf есть что-то, что переопределяет заголовки для файлов php?   -  person Wojciech Bednarski    schedule 29.01.2013
comment
Не могли бы вы опубликовать результат curl -I <url>?   -  person slm    schedule 30.01.2013
comment
Вы используете error_reporting (-1)? Вы уверены, что в этот момент все еще можете отправлять заголовки? Вы пробовали, что сказал Ланзз?   -  person Tiberiu-Ionuț Stan    schedule 02.02.2013


Ответы (5)


У меня была аналогичная проблема, но в моем случае заголовок Content-Length не был отправлен Apache, потому что ответ был сжат с помощью gzip. Когда я отключил сжатие, Content-Length была рассчитана и отправлена ​​правильно.

Ниже приведена настройка htaccess для отключения сжатия gzip только для файла swf.

<FilesMatch "\.swf$">
  SetEnv no-gzip 1
</FilesMatch>
person gerrytan    schedule 18.02.2014

Я предполагаю, что у вас есть некоторые изменения в modules/http/http_filters, потому что Apache по умолчанию отправляет Content-Length.

person Wojciech Bednarski    schedule 30.01.2013
comment
Интересно, что я просто написал быстрый PHP-скрипт для извлечения файла и там есть заголовок Content-Length. Но в браузере xhr.getResponseHeader("Content-Length") возвращает null, и это тоже не отображается в консоли. - person Niet the Dark Absol; 30.01.2013
comment
@Kolink Какие заголовки у вас есть в Google Chrome на вкладке «Сеть»? (когда вы проверяете этот звонок). И обязательно смотрите на ответы, а не на заголовки запросов. - person Wojciech Bednarski; 30.01.2013
comment
Конечно, заголовок есть. Итак, почему IE10 не перечисляет и не подтверждает заголовки Content-Length в некоторых вызовах, но есть в других ??? - person Niet the Dark Absol; 30.01.2013
comment
@Kolink Похоже, сейчас другая проблема. Что вы используете для обработки вызовов XHR, jQuery? Кстати, я думаю, что IE по-прежнему действительно непредсказуемый браузер (как и все от Microsoft), PHP опасен. И Apache, ну ... Это было хорошо много лет назад, может, пора попробовать что-то более быстрое, например, основанное на цикле событий Nginx? - person Wojciech Bednarski; 30.01.2013
comment
Никакого jQuery, просто XMLHttpRequest. Он отлично работает со статическими файлами, что очень странно ... - person Niet the Dark Absol; 30.01.2013
comment
@Kolink IE странный, и вы смелы, используя простой JS против IE: -) Можете ли вы попробовать то же самое, используя jQuery? (или любая кроссбраузерная библиотека) - person Wojciech Bednarski; 30.01.2013
comment
Я категорически отказываюсь использовать любую библиотеку. Они вызывают более непредсказуемое поведение, чем решают. См. Также это изображение, которое я сделал. - person Niet the Dark Absol; 30.01.2013
comment
@Kolink Я не согласен, но это долгое обсуждение. Не могли бы вы использовать jQuery, и если он работает, мы можем посмотреть, как jQuery решает эту проблему, и вы можете извлечь из него ванильный код JavaScript. - person Wojciech Bednarski; 30.01.2013
comment
Не происходит. Прости. Даже если бы это сработало, как бы это помогло? - person Niet the Dark Absol; 30.01.2013
comment
@Kolink Если jQuery работает, мы можем взглянуть на его внутреннее устройство и увидеть, как он обрабатывает ошибки в IE. Просто чтобы убедиться, что мы находимся на одной странице, CL в заголовках отправляется, но только IE не может его увидеть? - person Wojciech Bednarski; 30.01.2013

Протоколу HTTP нужен способ определить, когда закончился ответ. В соответствии с

RFC2616, раздел 4

4.4 Длина сообщения

Длина передачи сообщения - это длина тела сообщения в том виде, в котором оно отображается в сообщении; то есть после применения любых кодировок передачи. Когда тело сообщения включается в сообщение, длина передачи этого тела определяется одним из следующих условий (в порядке приоритета):

1. Любое ответное сообщение, которое «НЕ ДОЛЖНО» включать тело сообщения (например, ответы 1xx, 204 и 304 и любой ответ на запрос HEAD) всегда заканчивается первой пустой строкой после полей заголовка, независимо от Поля заголовка объекта, присутствующие в сообщении.

2. Если поле заголовка Transfer-Encoding (раздел 14.41) присутствует и имеет любое значение, отличное от «identity», то длина передачи определяется с использованием «фрагментированного» кодирования передачи (раздел 3.6), если только сообщение завершается закрытием соединения.

3. Если поле заголовка Content-Length (раздел 14.13) присутствует, его десятичное значение в OCTET представляет как длину объекта, так и длину передачи. Поле заголовка Content-Length НЕ ДОЛЖНО отправляться, если эти две длины различаются (например, если Transfer-Encoding

 header field is present). If a message is received with both a
 Transfer-Encoding header field and a Content-Length header field,
 the latter MUST be ignored.

4. Если в сообщении используется тип мультимедиа «multipart / byteranges» и длина передачи не указана иначе, то этот саморазграничивающийся тип мультимедиа определяет длину передачи. Этот тип носителя НЕ ДОЛЖЕН использоваться, если отправитель не знает, что получатель может его проанализировать; присутствие в запросе заголовка Range с несколькими спецификаторами диапазона байтов от клиента 1.1 означает, что клиент может анализировать ответы multipart / byteranges.

   A range header might be forwarded by a 1.0 proxy that does not
   understand multipart/byteranges; in this case the server MUST
   delimit the message using methods defined in items 1,3 or 5 of
   this section.

5. При закрытии соединения сервером. (Закрытие соединения не может использоваться для обозначения конца тела запроса, поскольку это не оставит возможности для сервера отправить ответ.)

Для совместимости с приложениями HTTP / 1.0 запросы HTTP / 1.1, содержащие тело сообщения, ДОЛЖНЫ включать допустимое поле заголовка Content-Length, если только сервер не является совместимым с HTTP / 1.1. Если запрос содержит тело сообщения, а Content-Length не задано, серверу СЛЕДУЕТ ответить 400 (неверный запрос), если он не может определить длину сообщения, или 411 (требуется длина), если он хочет настоять на получение действительного Content-Length.

Все приложения HTTP / 1.1, которые получают объекты, ДОЛЖНЫ принимать "фрагментированное" кодирование передачи (раздел 3.6), что позволяет использовать этот механизм для сообщений, когда длина сообщения не может быть определена заранее.

Сообщения НЕ ДОЛЖНЫ включать в себя как поле заголовка Content-Length, так и неидентификационное кодирование передачи. Если сообщение действительно включает неидентификационное кодирование передачи, Content-Length ДОЛЖЕН игнорироваться.

Когда Content-Length дается в сообщении, где разрешено тело сообщения, значение его поля ДОЛЖНО точно соответствовать количеству OCTET в теле сообщения. Пользовательские агенты HTTP / 1.1 ДОЛЖНЫ уведомлять пользователя о получении и обнаружении недопустимой длины.

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

Согласно этому вопросу (Как заставить PHP генерировать фрагментированный ответ) Хороший способ принудительно установить "Chunked" - установить Transfer-Encoding и flush. Может, эти двое и не нужны. Будут ли какие-либо посторонние сбросы где-нибудь до того, как вы начнете буферизацию?

person JayC    schedule 01.02.2013

Из ob_start документации:

This function will turn output buffering on. While output buffering is active no 
output is sent from the script (other than headers), instead the output is stored 
in an internal buffer.

Обратите внимание на бит «кроме заголовков» - обратный вызов ob_start() не вызывается до тех пор, пока буфер не будет очищен (или выброшен), в этот момент уже слишком поздно использовать header(). Я предполагаю, что у вас не включен журнал ошибок, я вижу это в моем журнале ошибок:

PHP Warning:  Cannot modify header information - headers already sent 
  in /usr/local/apache2/apps/testing/test2.php on line 6

Обратный вызов позволяет вам изменить буфер перед отправкой, но, поскольку он содержит только данные тела, вы также не можете добавить новый заголовок (он появится в содержимом, если вы попытаетесь, один раз для каждого фрагмента для фрагментированных передач).

Ваш код должен вызывать вместо этого header("Content-Length: ..."), как только вы знаете размер, перед любым другим выводом. Когда обнаружен заголовок Content-Length:, передача по частям не выполняется.

Если вам действительно нужно принудительно использовать HTTP / 1.0 (а вы этого не делаете), вы можете сделать это в httpd.conf с помощью специальных переменных:

SetEnv downgrade-1.0 1
SetEnv force-response-1.0 1

Вы можете поместить их, например, в <Location> или <LocationMatch> или использовать флаг mod_rewrite «env» для большего контроля.

person mr.spuratic    schedule 01.02.2013

У меня было несколько файлов JS размером около 1 МБ, которые не получали заголовок Content-Length (в отличие от других файлов JS меньшего размера). Тот факт, что заголовок Content-Length не является обязательным для HTTP / 2, а наличие нестабильного Интернет-соединения приводило к очень сложной для отладки ошибке, когда файлы иногда доставлялись только частично. Решением было увеличить DeflateBufferSize https://httpd.apache.org/docs/2.4/mod/mod_deflate.html#DeflateBufferSize

person Raidok    schedule 03.01.2020