Содержание


Apache 1. Часть 6: Обзор API

Comments

Серия контента:

Этот контент является частью # из серии # статей:

Следите за выходом новых статей этой серии.

Этот контент является частью серии:

Следите за выходом новых статей этой серии.

В этом документе мы рассмотрим API первого Apache и базовые структуры.

1. Базовые концепции

Обработка запроса в Apache разбита на несколько частей:

  1. URL транслируется в имя файла.
  2. Аутентификационная проверка ID пользователя.
  3. Проверка авторизационного доступа (логин/пароль).
  4. Проверка локального доступа.
  5. Определяется MIME-тип запрашиваемого объекта.
  6. Отсылка ответа клиенту.
  7. Логирование.

Сначала в списке модулей ищется нужный обработчик, который потом вызывается. Обычно каждый обработчик делает одну из 3 вещей:

  1. В случае обработки запроса возвращает OK.
  2. Возвращает константу DECLINED в противном случае.
  3. Возвращает ошибку, при этом обработка запроса останавливается.

На отсылку ответа может накладываться множество обработчиков в зависимости от MIME-типа. Обработчик ответа может быть помечен как */*, в этом случае он может быть вызван, если не найден соответствующий MIME-тип. Обработчик представляет из себя функцию с одним аргументом — request_rec, и, как правило, возвращает целое число.

CGI модуль работает с двумя типами объектов — с CGI скриптами и с конфигурационной директивой ScriptAlias. Чтобы управлять CGI скриптами, модуль должен объявить для них обработчик. Структура модуля также включает указатели на функции, которые строят структуру на основе директивы ScriptAliases. В модуле есть код, который управляет командами ScriptAliases. В командной таблице модуля описано, где и когда вызывается внешний CGI скрипт.

Пул (Pool) представляет из себя указатель на пул ресурсов, структура cmd_parms включает информацию о конфигурационном файле. Модуль может выглядеть следующим образом:

  /* декларация обработчиков */

int translate_scriptalias (request_rec *);
int type_scriptalias (request_rec *);
int cgi_handler (request_rec *);

/* таблица обработчиков для генерации ответа */

handler_rec cgi_handlers[] = {
{ "application/x-httpd-cgi", cgi_handler },
{ NULL }
};

/* процедуры для манипуляции информацией из конфигов
   возвращают и передают тип void *
 */

void *make_cgi_server_config (pool *);
void *merge_cgi_server_config (pool *, void *, void *);

/* процедуры для управления команд из конфига  */

extern char *script_alias(cmd_parms *, void *per_dir_config, char *fake, char *real);

command_rec cgi_cmds[] = {
{ "ScriptAlias", script_alias, NULL, RSRC_CONF, TAKE2,
"a fakename and a realname"},
{ NULL }
};

module cgi_module = {

  STANDARD_MODULE_STUFF,
  NULL,                     /* initializer */
  NULL,                     /* dir config creator */
  NULL,                     /* dir merger */
  make_cgi_server_config,   /* server config */
  merge_cgi_server_config,  /* merge server config */
  cgi_cmds,                 /* command table */
  cgi_handlers,             /* handlers */
  translate_scriptalias,    /* filename translation */
  NULL,                     /* check_user_id */
  NULL,                     /* check auth */
  NULL,                     /* check access */
  type_scriptalias,         /* type_checker */
  NULL,                     /* fixups */
  NULL,                     /* logger */
  NULL                      /* header parser */
};

2. Структура request_rec

Аргументом обработчиков является структура request_rec. Структура описывает конкретный клиентский запрос. Как правило, один клиентский коннект генерирует один клиентский запрос. В этой структуре есть указатель на пул, который будет очищен после обработки запроса, а также информация о коннекте и о самом запросе. Структура включает набор строк, описывающих атрибуты запроса — URL, имя файла, тип контента, кодировка, таблица MIME-заголовков, которые будут посланы клиенту, переменные среды для запуска фоновых процессов. Имеются также 2 указателя на конфигурационные структуры, которые содержат информацию, полученную в результате обработки .htaccess либо директивы <Directory>. В структуре также имеется дополнительный массив данных, хранящий виртуальную конфигурационную информацию.

Декларация структуры request_rec:

struct request_rec {

pool *pool;
conn_rec *connection;
server_rec *server;

/* атрибуты запроса */

char *uri;
char *filename;
char *path_info;

char *args;           /* QUERY_ARGS */
struct stat finfo;    /* инициализируется ядром, st_mode устанавливается в
						 ноль при отсутствии файла */

char *content_type;
char *content_encoding;

/* MIME заголовки, массив переменных окружения
 *
 * err_headers_out сохраняются при последующих редиректах в ErrorDocument
 */

table *headers_in;
table *headers_out;
table *err_headers_out;
table *subprocess_env;

/* информация о запросе */

int header_only;         /* заголовок запроса */
char *protocol;           /* протокол */
char *method;            /* GET, HEAD, POST, etc. */
int method_number;  /* M_GET, M_POST, etc. */


/* информация для логирования */

char *the_request;
int bytes_sent;

/* клиент не просит кешировать документ  */

int no_cache;

/* информация, взятая из .htaccess 
 * 
 * массив, имеющий тип void*
*/

void *per_dir_config;   /* опции конфига */
void *request_config;   /* информация о текущем запросе */

};

Как правило, структура request_rec создается и инициализируется в момент чтения запроса от клиента, но есть несколько исключений:

  1. Структура может быть создана, если запрос направлен на извлечение картинки или вызывается CGI скрипт. В этом случае организуется так называемый внутренний редирект.
  2. В случае ошибки вызывается ErrorDocument.
  3. Под-запросы на базе SSI.

3. Обработка запросов

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

  • OK — успешная обработка результата;
  • DECLINED — запрос отклонен;
  • HTTP error.

Ошибка может вернуть, например, REDIRECT, в этом случае в headers_out должно быть указано, куда.

Как правило, работа обработчика заключается в модификации полей request_rec. Это не относится к обработчикам, которые посылают ответ клиентам. Сначала клиенту посылается HTTP заголовок, это делает ap_send_http_header. Если запрос промаркирован как header_only, то на этом отсылка заканчивается. В противном случае формируется тело ответа на основе ap_rputc и ap_rprintf.

В следующем куске кода показано, как обрабатывается GET запрос, где, в частности, показано, были ли сделаны изменения в истории запросов для данного клиента — ap_set_last_modified:

int default_handler (request_rec *r)
{
int errstatus;
FILE *f;

if (r->method_number != M_GET) return DECLINED;
if (r->finfo.st_mode == 0) return NOT_FOUND;

if ((errstatus = ap_set_content_length (r, r->finfo.st_size))
    || (errstatus = ap_set_last_modified (r, r->finfo.st_mtime)))
return errstatus;

f = fopen (r->filename, "r");

if (f == NULL) {
log_reason("file permissions deny server access", r->filename, r);
return FORBIDDEN;
}

register_timeout ("send", r);
ap_send_http_header (r);

if (!r->header_only) send_fd (f, r);
ap_pfclose (r->pool, f);
return OK;
}

Этот код еще не генерирует тело ответа, он посылает клиенту заголовок и возвращает код, на основе которого может быть послан ответ.

Обработчики аутентификации вызываются в случае, если соответствующая директория сконфигурирована для этого. Базовая аутентификация хранится в главном конфиге, она читается и хранится в объектах ap_auth_type, ap_auth_name, ap_requires. Естественно, во всех случаях работает базовая HTTP-аутентификация, в этом случае ap_get_basic_auth_pw инициализирует структуру connection->user, а также note_basic_auth_failure, заполняющие аутентификационный заголовок, отсылаемый клиенту.

Когда запрос проходит стадию внутреннего редиректа, встает вопрос о логировании. Редиректы добавляются в специальный связный список в рамках структуры request_rec, а сама эта структура передается в качестве аргумента обработчику, отвечающему за логирование.

4. Управление памятью

Одна из основных проблем, которая встает при разработке веб-серверов — это проблема утечки памяти, проблема с закрытием дескрипторов и т. д. Пул ресурсов разрешает эту проблему, выделяя ресурсы таким образом, что они автоматически очищаются после использования. Делается это следующим образом: выделяемая память, открываемые дескрипторы привязываются к конкретному запросу, который помещается в пул запросов.

Пул — это структура данных в обычном понимании. После обработки запроса пул очищается, и гарантированно не остается незакрытой памяти или незакрытых дескрипторов.

При перезапуске сервера управление ресурсами проходит аналогично. Для этого есть конфигурационный пул, который очищается при ре-старте. Например, функция ap_pfopen закрывает дескрипторы, которые могут быть открыты при вызове CGI скрипта.

Для выделения памяти в пул вызывается ap_palloc, которая имеет 2 аргумента: первый указывает на сам пул, второй — на размер выделяемой памяти. Для получения доступа к самому пулу нужно получить доступ к полю структуры request_rec:

int my_handler(request_rec *r)
{
struct my_structure *foo;
...

foo = (foo *)ap_palloc (r->pool, sizeof(my_structure));
}

Обратите внимание, что мы создали объект структуры foo и не позаботились об очистке — этого не нужно делать, это, так сказать, входит в пакет услуг — объект будет удален позже автоматически вместе с пулом данного конкретного запроса. Если вы не хотите использовать стандартный пул, вы можете организовать свой собственный так называемый sub-pool, но тогда вам самим придется заниматься его очисткой.

Есть несколько специальных функций, которые выделяют память. Функция ap_pcalloc аналогична ap_palloc, но очищает память после возвращения. ap_pstrdup выделяет память под символьную строку, возвращая указатель на нее. ap_pstrcat выделяет память для массива строк, в конце аргументов нужно поставить NULL:

	ap_pstrcat (r->pool, "foo", "/", "bar", NULL);

Она вернет указатель на память, в которой будет суммарная строка — "foo/bar".

Есть несколько базовых пулов, указатели на которые передаются из http_main остальным функциям в качестве аргумента:

  1. permanent_pool — это базовый предок всех остальных пулов.
  2. pconf — потомок предыдущего пула, создается в момент чтения конфигурации, существует на протяжении всей жизни сервера, передается во все конфигурационные процедуры; когда инициализируется какой-то модуль, передается в функцию инициализации init().
  3. ptemp — временный пул, существует во время разбора конфигурационного файла.
  4. pchild — пул дочернего процесса или потока.
  5. ptrans — используется для коннектов.
  6. r->pool — это вложенный пул для коннект-пула, используется для подзапросов, живет столько же, сколько и данный запрос.

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

Иногда встает дилемма выбора между r->pool и r->main->pool. При выборе второго варианта очистка пула будет выполнена после того, как ответ окончательно будет отослан клиенту и будет выполнено логирование.

Для открытия файлов используется функция ap_pfopen:

		FILE *f = ap_pfopen (r->pool, r->filename, "r");

Есть также аналог в виде ap_popenf. В обоих случаях дескриптор закрывается при очистке пула. Есть специальные функции для закрытия файлов — ap_pfclose и ap_pclosef. Это может пригодиться в том случае, когда операционная система имеет ограничения на количество одновременно открываемых файлов.

Пул очищается с помощью clear_pool(), которая в свою очередь рекурсивно вызывает destroy_pool() для пулов-потомков. При этом важно понимать, что не происходит удаления пула, он очищается и становится доступен для повторного использования и инициализации.

Для создания вложенных пулов — sub-pools — используется функция ap_make_sub_pool, которой в качестве аргумента нужно передать родительский пул. Очищен такой пул будет во время очистки родителя.

Очистить его можно также в произвольный момент с помощью ap_clear_pool / ap_destroy_pool.

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

5. Конфигурация

Первый Apache с самого начала разрабатывался как совместимый со своим непосредственным предшественником — сервером NCSA 1.3. Apache аналогичным образом читает конфигурацию, также интерпретирует директивы. Другой целью было переместить весь функционал из ядра в модули. Для этого каждый модуль имеет собственную таблицу команд. Ядро должно знать порядок выполнения этих команд. Этот порядок во многом зависит от конфигурации каждой конкретной директории. В наследство от NCSA также пришла обработка файлов .htaccess. Когда URL оттранслирован в конкретный путь, сервер должен пройтись по всему этому пути иерархически, учитывая все .htaccess, которые могут попасться на каждом уровне этого пути. Прочитанная информация складывается с генеральной конфигурацией. После этого надо освободить все ресурсы, задействованные под парсинг и анализ.

В качестве примера рассмотрим код из mod_mime.c, в котором определяется тип файла по его расширению. .htaccess может включать в себя команды AddType и AddEncoding, под которые отведены две специальные таблицы:

typedef struct {
    table *forced_types;        /* Additional AddTyped stuff */
    table *encoding_types;    /* Added with AddEncoding... */
} mime_dir_config;

Когда сервер читает в конфигурационном файле директиву <Directory>, он находит там команду для MIME и создает структуру mime_dir_config. В модуле есть специальная функция, которая имеет два аргумента — имя директории, к которой она применяется, и пул. В модуле mod_mime с помощью ap_pallocs выделяется память и создается копия этих двух таблиц, которая потом модифицированная и возвращается:

void *create_mime_dir_config (pool *p, char *dummy)
{
	mime_dir_config *new = (mime_dir_config *) ap_palloc (p, 
	sizeof(mime_dir_config));

	new->forced_types = ap_make_table (p, 4);
	new->encoding_types = ap_make_table (p, 4);

	return new;
}

Когда сервер проходит иерархически весь путь до файла, он объединяет информацию, взятую локально в .htaccess. Следующая функция имеет три аргумента — две описанных таблицы и пул, выделенный под результат.

void *merge_mime_dir_configs (pool *p, void *parent_dirv, void *subdirv)
{
mime_dir_config *parent_dir = (mime_dir_config *)parent_dirv;
mime_dir_config *subdir = (mime_dir_config *)subdirv;
mime_dir_config *new =
(mime_dir_config *)ap_palloc (p, sizeof(mime_dir_config));

new->forced_types = ap_overlay_tables (p, subdir->forced_types,
parent_dir->forced_types);
new->encoding_types = ap_overlay_tables (p, subdir->encoding_types,
parent_dir->encoding_types);

return new;
}

После получения результата, ядро должно интерпретировать команды AddType и AddEncoding. Для этого ядро просматривает таблицу команд модуля, которая включает информацию о том, сколько аргументов может иметь команда, в каком формате и т. д. Обработчик AddType:

char *add_type(cmd_parms *cmd, mime_dir_config *m, char *ct, char *ext)
{
if (*ext == '.') ++ext;
ap_table_set (m->forced_types, ext, ct);
return NULL;
}

Обработчик имеет 4 аргумента: первые два — это результат иерархического парсинга, третий — это структура главного конфигурационного файла, четвертый — указатель на структуру cmd_parms.

Командная таблица модуля MIME может иметь следующий вид:

command_rec mime_cmds[] = 
{
	{ "AddType", add_type, NULL, OR_FILEINFO, TAKE2,
	"a mime type followed by a file extension" },
	{ "AddEncoding", add_encoding, NULL, OR_FILEINFO, TAKE2,
	"an encoding (e.g., gzip), followed by a file extension" },
	{ NULL }
};

Она включает: имя команды, функцию, которая ею управляет, указатель типа void(*), который передает cmd_parms, битовую маску, указывающую на то, где может появиться команда, флаг, указывающий на число аргументов обработчика. После этого из структуры request_rec извлекается главная конфигурация и определяется тип файла:

int find_ct(request_rec *r)
{
int i;
char *fn = ap_pstrdup (r->pool, r->filename);
mime_dir_config *conf = (mime_dir_config *)
ap_get_module_config(r->per_dir_config, &mime_module);
char *type;

if (S_ISDIR(r->finfo.st_mode)) {
r->content_type = DIR_MAGIC_TYPE;
return OK;
}

if((i=ap_rind(fn,'.')) < 0) return DECLINED;
++i;

if ((type = ap_table_get (conf->encoding_types, &fn[i])))
{
r->content_encoding = type;

/* go back to previous extension to try to use it as a type */
fn[i-1] = '\0';
if((i=ap_rind(fn,'.')) < 0) return OK;
++i;
}

if ((type = ap_table_get (conf->forced_types, &fn[i])))
{
r->content_type = type;
}

return OK; 
}

Заключение

Архитектура первого Apache построена на основе своего непосредственного предшественника — сервера NCSA. Обработка клиентского запроса проходит несколько стадий, каждую из которых, как правило, обрабатывает отдельный модуль. Базовая структура для обработки — request_rec — создается на каждый такой запрос и передается в качестве параметра обработчикам. Все такие обработчики возвращают целочисленный ответ, могут модифицировать поля этой структуры. Управление ресурсами в первом Apache построено на основе пулов, что решает все проблемы с утечкой памяти и незакрытыми дескрипторами. Пулы привязываются к базовым объектам, и время жизни пула становится равным времени жизни такого объекта.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Linux
ArticleID=620090
ArticleTitle=Apache 1. Часть 6: Обзор API
publish-date=01272011