Apache. Документация на русском


Разделы:   1    2    3    4    5    6    7    8    9    10      11      12    13    14    15    16  

Раздел 11. Документация для разработчиков

Пункты:   214    215    216    217    218    219    220    221      222      223  

 <         > 
  RU            EN  

Пункт 222. Рекомендации по выходным фильтрам в версии 2.x

При написании выходных фильтров встречается ряд распространенных ошибок; эта страница предназначена для документирования лучших практик для авторов новых или существующих фильтров.

Этот документ применим как к версии 2.0, так и к версии 2.2 HTTP-сервера Apache; он специально нацелен на фильтры RESOURCE -level или CONTENT_SET -level, хотя некоторые советы являются общими для всех типов фильтров.

Фильтровальные и ковшовые бригады

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

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

Фильтр может определить, представляет ли сегмент данные или метаданные с помощью APR_BUCKET_IS_METADATA макроса. Как правило, все сегменты метаданных должны передаваться по цепочке фильтров выходным фильтром. Фильтры могут преобразовывать, удалять и вставлять сегменты данных по мере необходимости.

Существует два типа корзины метаданных, на которые должны обращать внимание все фильтры: EOS тип корзины и FLUSH тип корзины. Сегмент EOS указывает на то, что достигнут конец ответа и дальнейшая обработка сегментов не требуется. Сегмент FLUSH указывает, что фильтр должен немедленно сбрасывать все буферизованные сегменты (если применимо) вниз по цепочке фильтров.

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

Фильтры могут создавать FLUSH сегменты и при необходимости передавать их по цепочке фильтров. Создание FLUSH сегментов без необходимости или слишком часто может нанести ущерб использованию сети, поскольку может привести к отправке большого количества небольших пакетов, а не небольшого количества более крупных пакетов. Раздел о неблокирующих чтениях бакетов описывает случай, когда фильтрам рекомендуется генерировать FLUSH бакеты.

Пример ковшовой бригады

HEAP FLUSH FILE EOS

Здесь показана бригада ведер, которую можно передать фильтру; он содержит два сегмента метаданных ( FLUSH и EOS ) и два сегмента данных ( HEAP и FILE ).

Вызов фильтра

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

Выходной фильтр, который выделяет долгоживущую память каждый раз, когда он вызывается, может потреблять память, пропорциональную размеру ответа. Выходные фильтры, которым необходимо выделять память, должны делать это один раз для каждого ответа; см. Поддержание состояния ниже.

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

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

Как справиться с пустой бригадой

 apr_status_t dummy_filter (ap_filter_t *f, apr_bucket_brigade *bb)
{
 если (APR_BRIGADE_EMPTY(bb)) {
 вернуть APR_SUCCESS;
 }
 ... 

Структура бригады

Группа ведер — это двусвязный список ведер. Список завершается (с обоих концов) дозорным , который можно отличить от обычного сегмента, сравнив его с указателем, возвращаемым функцией APR_BRIGADE_SENTINEL . Страж списка на самом деле не является допустимой структурой сегмента; любая попытка вызвать нормальные функции корзины (такие как apr_bucket_read ) на часовом будет иметь неопределенное поведение (т.е. приведет к сбою процесса).

Имеется множество функций и макросов для перемещения и манипулирования ковшовыми бригадами; полный охват см. в заголовке apr_buckets.h. К часто используемым макросам относятся:

APR_BRIGADE_FIRST(bb)
возвращает первое ведро в бригаде бб
APR_BRIGADE_LAST(bb)
возвращает последнее ведро в бригаде бб
APR_BUCKET_NEXT(e)
дает следующее ведро после ведра e
APR_BUCKET_PREV(e)
дает ведро перед ведром e

Сама структура apr_bucket_brigade выделяется из пула, поэтому, если фильтр создает новую бригаду, он должен убедиться, что использование памяти правильно ограничено. Например , фильтр, который выделяет новую бригаду из пула запросов ( r->pool ) при каждом вызове, не соответствует приведенному выше предупреждению об использовании памяти. Вместо этого такой фильтр должен создавать бригаду при первом вызове каждого запроса и сохранять эту бригаду в своей структуре состояний.

Как правило, никогда не рекомендуется использовать apr_brigade_destroy для «уничтожения» бригады, если вы точно не знаете, что бригада никогда не будет использоваться снова, даже в этом случае ее следует использовать редко. Память, используемая структурой бригады, не будет освобождена при вызове этой функции (поскольку она исходит из пула), но соответствующая очистка пула не зарегистрирована. Использование apr_brigade_destroy фактически может вызвать утечку памяти; если «уничтоженная» бригада содержит ведра при уничтожении содержащего ее пула, эти ведра не будут немедленно уничтожены.

Как правило, фильтры следует использовать apr_brigade_cleanup вместо файлов apr_brigade_destroy .

Ковши для обработки

При работе с сегментами без метаданных важно понимать, что apr_bucket * объект " " является абстрактным представлением данных:

  1. Количество данных, представленных ведром, может иметь или не иметь определенную длину; для сегмента, который представляет данные неопределенной длины, в ->length поле устанавливается значение (apr_size_t)-1 . Например, ковши ковшового PIPE типа имеют неопределенную длину; они представляют вывод из канала.
  2. Данные, представленные сегментом, могут отображаться или не отображаться в памяти. Тип FILE сегмента, например, представляет данные, хранящиеся в файле на диске.

Фильтры считывают данные из корзины с помощью apr_bucket_read функции. При вызове этой функции ведро может трансформироваться в ведро другого типа, а также может вставить новое ведро в группу ведер. Это должно происходить для сегментов, которые представляют данные, не отображаемые в память.

Чтобы привести пример; рассмотрим группу сегментов, содержащую один FILE сегмент, представляющий целый файл размером 24 килобайта:

FILE(0K-24K)

Когда это ведро будет прочитано, оно прочитает блок данных из файла, превратится в ведро HEAP для представления этих данных и вернет данные вызывающей стороне. Он также вставляет новую FILE корзину, представляющую оставшуюся часть файла; после apr_bucket_read вызова бригада выглядит так:

HEAP(8K) FILE(8K-24K)

Фильтровальные бригады

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

Возьмем пример, который проходит через всю бригаду следующим образом:

Плохой выходной фильтр - не имитируйте!

 apr_bucket *e = APR_BRIGADE_FIRST(bb);
константный символ *данные;
длина apr_size_t;
в то время как (e != APR_BRIGADE_SENTINEL (bb)) {
 apr_bucket_read(e, &data, &length, APR_BLOCK_READ);
 e = APR_BUCKET_NEXT (e);
}
вернуть ap_pass_brigade(bb); 

Приведенная выше реализация будет потреблять память, пропорциональную размеру содержимого. Например, при передаче FILE ведра все содержимое файла будет считано в память, поскольку каждый apr_bucket_read вызов превращает FILE ведро в HEAP ведро.

Напротив, приведенная ниже реализация будет потреблять фиксированный объем памяти для фильтрации любой бригады; требуется временная бригада, и она должна выделяться только один раз для каждого ответа, см. раздел «Поддержание состояния».

Лучший выходной фильтр

 апрель_ведро *e;
константный символ *данные;
длина apr_size_t;
в то время как ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) {
 rv = apr_bucket_read(e, &data, &length, APR_BLOCK_READ);
 если (рв)...;
 /* Удалить ведро e из bb. */
 APR_BUCKET_REMOVE(д);
 /* Вставляем во временную бригаду. */
 APR_BRIGADE_INSERT_HEAD(tmpbb, e);
 /* Передать бригаду вниз по течению. */
 rv = ap_pass_brigade(f->следующий, tmpbb);
 если (рв)...;
 апрель_brigade_cleanup(tmpbb);
} 

Поддержание состояния

Фильтр, который должен поддерживать состояние при нескольких вызовах на ответ, может использовать ->ctx поле своей ap_filter_t структуры. Обычно в такой структуре хранится временная бригада, чтобы не выделять новую бригаду для каждого вызова, как описано в разделе «Структура бригады».

Пример кода для сохранения состояния фильтра

 структура фиктивное_состояние {
 апрель_бакет_бригада *tmpbb;
 интервал_состояние_фильтра;
 ...
};
apr_status_t dummy_filter (ap_filter_t *f, apr_bucket_brigade *bb)
{
 структура фиктивное_состояние *состояние;
 
 состояние = f->ctx;
 если (состояние == NULL) {
 
 /* Первый вызов для этого ответа: инициализировать структуру состояния.
 */
 f->ctx = state = apr_palloc(f->r->pool, sizeof *state);
 состояние->tmpbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
 состояние->filter_state = ...;
 }
 ... 

Буферные ведра

Если фильтр решает хранить сегменты после продолжительности одного вызова функции фильтра (например, сохраняет их в своей ->ctx структуре состояния), эти сегменты должны быть отложены . Это необходимо, потому что некоторые типы сегментов предоставляют сегменты, представляющие временные ресурсы (например, память стека), которые выпадают из области действия, как только цепочка фильтров завершает обработку бригады.

Чтобы отложить ведро, apr_bucket_setaside можно вызвать функцию. Не все типы корзин могут быть отложены, но в случае успеха корзина будет преобразована, чтобы гарантировать, что ее время жизни не меньше, чем пул, указанный в качестве аргумента функции apr_bucket_setaside .

В качестве альтернативы ap_save_brigade можно использовать функцию, которая переместит все корзины в отдельную бригаду, содержащую корзины со временем жизни, равным заданному аргументу пула. Эту функцию следует использовать с осторожностью, принимая во внимание следующие моменты:

  1. По возвращении ap_save_brigade гарантирует, что все сегменты в возвращаемой бригаде будут представлять данные, отображаемые в память. Если задана входная группа, содержащая, например, PIPE ведро, ap_save_brigade она будет потреблять произвольный объем памяти для хранения всего вывода канала.
  2. При ap_save_brigade чтении из сегментов, которые нельзя отложить, он всегда будет выполнять чтение с блокировкой, исключая возможность использования неблокирующих операций чтения с сегментами.
  3. Если ap_save_brigade используется без передачи saveto параметра бригады, отличного от NULL " " (назначение), функция создаст новую бригаду, что может привести к тому, что использование памяти будет пропорционально размеру содержимого, как описано в разделе "Структура бригады".
Фильтры должны гарантировать, что любые буферизованные данные обрабатываются и передаются по цепочке фильтров во время последнего вызова для данного ответа (бригада, содержащая корзину EOS). В противном случае такие данные будут потеряны.

Неблокирующие чтения сегментов

Функция apr_bucket_read принимает apr_read_type_e аргумент, который определяет, будет ли выполняться блокирующее или неблокирующее чтение из источника данных. Хороший фильтр сначала попытается прочитать из каждого блока данных, используя неблокирующее чтение; если это не удается с APR_EAGAIN , то отправьте FLUSH корзину вниз по цепочке фильтров и повторите попытку, используя чтение с блокировкой.

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

CGI-скрипт является примером медленного источника контента, реализованного в виде ведра. mod_cgi отправит PIPE сегменты, представляющие выходные данные CGI-скрипта; чтение из такой корзины будет заблокировано, пока сценарий CGI выдаст больше вывода.

Пример кода с использованием неблокирующего чтения сегмента

 апрель_ведро *e;
режим apr_read_type_e = APR_NONBLOCK_READ;
в то время как ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) {
 апр_статус_т рв;
 rv = apr_bucket_read(e, &data, &length, mode);
 если (rv == APR_EAGAIN && режим == APR_NONBLOCK_READ) {
 /* Передать бригаду, содержащую флеш-базу: */
 APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_flush_create(...));
 rv = ap_pass_brigade(f->следующий, tmpbb);
 апрель_brigade_cleanup(tmpbb);
 если (rv != APR_SUCCESS) вернуть rv;
 /* Повторите попытку, используя блокирующее чтение. */
 режим = APR_BLOCK_READ;
 продолжать;
 }
 иначе если (rv != APR_SUCCESS) {
 /* обработка ошибок */
 }
 /* В следующий раз попробуйте сначала неблокирующее чтение. */
 режим = APR_NONBLOCK_READ;
 ...
} 

Десять правил для выходных фильтров

Таким образом, вот набор правил, которым должны следовать все выходные фильтры:

  1. Выходные фильтры не должны пропускать пустые бригады по цепочке фильтров, но должны быть терпимы к передаче пустых бригад.
  2. Выходные фильтры должны пропускать все сегменты метаданных по цепочке фильтров; FLUSH сегменты следует учитывать, передавая любые ожидающие или буферизованные сегменты по цепочке фильтров.
  3. Выходные фильтры должны игнорировать любые сегменты, следующие за EOS сегментом.
  4. Выходные фильтры должны обрабатывать фиксированный объем данных за раз, чтобы потребление памяти не было пропорционально размеру фильтруемого содержимого.
  5. Выходные фильтры не должны зависеть от типов корзин и должны иметь возможность обрабатывать корзины незнакомого типа.
  6. После вызова ap_pass_brigade для прохождения бригады по цепочке фильтров выходные фильтры должны вызывать apr_brigade_cleanup , чтобы гарантировать, что бригада пуста, прежде чем повторно использовать эту структуру бригады; выходные фильтры никогда не должны использоваться apr_brigade_destroy для «уничтожения» бригад.
  7. Выходные фильтры должны отбрасывать любые сегменты, которые сохраняются после окончания действия функции фильтра.
  8. Выходные фильтры не должны игнорировать возвращаемое значение ap_pass_brigade и должны возвращать соответствующие ошибки обратно по цепочке фильтров.
  9. Выходные фильтры должны создавать только фиксированное количество групп сегментов для каждого ответа, а не по одной на каждый вызов.
  10. Выходные фильтры должны сначала попытаться выполнить неблокирующее чтение из каждого блока данных и отправить блок FLUSH вниз по цепочке фильтров, если чтение блокируется, прежде чем повторить попытку блокирующего чтения.


 <         > 

Пункты:   214    215    216    217    218    219    220    221      222      223  

Рейтинг@Mail.ru