Раздел 11. Документация для разработчиков RU EN Пункт 216. Разработка модулей для Apache HTTPD 2.4 В этом документе объясняется, как разрабатывать модули для Apache HTTP Server 2.4. ВведениеЧто мы будем обсуждать в этом документе
В этом документе обсуждается, как вы можете создавать модули для Apache HTTP Server 2.4, исследуя пример модуля с именем
Во второй части этого документа, которая имеет дело с директивой конфигурации и пониманием контекста, мы рассмотрим модуль, который просто записывает свою конфигурацию клиенту. ПредпосылкиПрежде всего, вы должны иметь базовые знания о том, как работает язык программирования C. В большинстве случаев мы постараемся быть максимально педагогичными и давать ссылки на документы, описывающие функции, используемые в примерах, но также есть много случаев, когда необходимо либо просто предположить, что «это работает», либо немного покопаться в себе. как и почему различные вызовы функций. Наконец, вам необходимо иметь общее представление о том, как модули загружаются и настраиваются на HTTP-сервере Apache, а также о том, как получить заголовки для Apache, если у вас их еще нет, поскольку они необходимы для компиляции новых модулей. Компиляция вашего модуляЧтобы скомпилировать исходный код, который мы создаем в этом документе, мы будем использовать APXS. Предполагая, что ваш исходный файл называется mod_example.c, скомпилировать, установить и активировать модуль так же просто, как: apxs -i -a -c mod_example.c Определение модуля
модуль AP_MODULE_DECLARE_DATA пример_модуля = { STANDARD20_MODULE_STUFF, create_dir_conf, /* Обработчик конфигурации для каждого каталога */ merge_dir_conf, /* Обработчик слияния для конфигураций для каждого каталога */ create_svr_conf, /* Обработчик конфигурации для каждого сервера */ merge_svr_conf, /* Обработчик слияния для серверных конфигураций */ директивы, /* Любые директивы, которые могут быть у нас для httpd */ register_hooks /* Наша функция регистрации хука */ };
Этот фрагмент кода сообщает серверу, что мы зарегистрировали новый модуль в системе и что его имя
На данный момент нас интересует только первая цель имени модуля, которая вступает в игру, когда нам нужно загрузить модуль: LoadModule example_module modules/mod_example.so
По сути, это говорит серверу открыть В этом нашем теге имени также есть куча ссылок на то, как мы хотели бы обрабатывать вещи: на какие директивы мы отвечаем в файле конфигурации или .htaccess, как мы работаем в определенных контекстах и какие обработчики нас интересуют. регистрация в HTTP-сервисе Apache. Мы вернемся ко всем этим элементам позже в этом документе. Начало работы: подключение к серверуЗнакомство с хуками
При обработке запросов в Apache HTTP Server 2.4 первое, что вам нужно сделать, — это создать хук в процессе обработки запросов. Хук — это, по сути, сообщение, сообщающее серверу, что вы готовы либо обслужить, либо, по крайней мере, взглянуть на определенные запросы, данные клиентами. Все обработчики, будь то mod_rewrite, mod_authn_*, mod_proxy и так далее, подключаются к определенным частям процесса запроса. Как вы, наверное, знаете, модули служат разным целям; Некоторые из них являются обработчиками аутентификации/авторизации, другие — обработчиками файлов или сценариев, а третьи модули переписывают URI или прокси-контент. Кроме того, в конце концов, от пользователя сервера зависит, как и когда каждый модуль будет установлен на свое место. Таким образом, сам сервер не предполагает знать, какой модуль отвечает за обработку конкретного запроса, и будет спрашивать каждый модуль, заинтересованы ли они в данном запросе или нет. Затем каждый модуль должен либо мягко отклонить запрос, либо принять его, либо категорически отказать в обслуживании запроса, как это делают модули аутентификации/авторизации AddHandler пример-обработчик .sum
Это сообщает серверу следующее: всякий раз, когда мы получаем запрос на URI, оканчивающийся на .sum, мы должны сообщить всем модулям, что мы ищем того, кто идет под именем «example-handler» . Таким образом, когда обслуживается запрос, оканчивающийся на .sum, сервер сообщает всем модулям, что этот запрос должен обслуживаться "примером-обработчиком". Как вы увидите позже, когда мы начнем собирать mod_example, мы проверим этот тег обработчика, переданный сервером, Подключение к httpdДля начала мы хотим создать простой обработчик, который отвечает клиентскому браузеру при запросе определенного URL-адреса, поэтому мы пока не будем настраивать обработчики конфигурации и директивы. Наше начальное определение модуля будет выглядеть так: модуль AP_MODULE_DECLARE_DATA пример_модуля = { STANDARD20_MODULE_STUFF, НУЛЕВОЙ, НУЛЕВОЙ, НУЛЕВОЙ, НУЛЕВОЙ, НУЛЕВОЙ, register_hooks /* Наша функция регистрации хука */ }; Это позволяет серверу понять, что нас не интересует ничего необычного, мы просто хотим перехватить запросы и, возможно, обработать некоторые из них. Ссылка в объявлении нашего примера — статическая пустота register_hooks (apr_pool_t *pool) { /* Создаем хук в обработчике запроса, чтобы нас вызывали, когда приходит запрос */ ap_hook_handler (example_handler, NULL, NULL, APR_HOOK_LAST); }
Ссылка Другие полезные крючкиПодключение к фазе обработки запросов — это лишь одна из многих ловушек, которые вы можете создать. Некоторые другие способы подключения:
Создание обработчикаОбработчик — это, по сути, функция, которая получает обратный вызов при выполнении запроса к серверу. Ему передается запись о текущем запросе (как он был сделан, какие заголовки и запросы были переданы, кто отправляет запрос и т. д.), и он отвечает либо за сообщение серверу, что он не заинтересован в запросе, либо обрабатывать запрос с помощью предоставленных инструментов. Простое «Привет, мир!» обработчикДавайте начнем с создания очень простого обработчика запросов, который делает следующее:
В коде C наш пример обработчика теперь будет выглядеть так: статический интервал example_handler (request_rec * r) { /* Во-первых, нам нужно проверить, не является ли это вызовом обработчика «example-handler». * Если есть - принимаем и делаем свои дела, если нет - просто возвращаем DECLINED, * и сервер попробует в другом месте. */ if (!r->handler || strcmp(r->handler, "пример-обработчик")) return (DECLINED); /* Теперь, когда мы обработали этот запрос, мы напишем «Hello, world!» клиенту. * Для этого мы должны сначала установить соответствующий тип контента, а затем наш вывод. */ ap_set_content_type(r, "текст/html"); ap_rprintf(r, "Привет, мир!"); /* Наконец, мы должны сообщить серверу, что мы позаботились об этом запросе и все прошло нормально. * Мы делаем это, просто возвращая серверу значение OK. */ вернуть ОК; } Теперь мы объединим все, что узнали, и в итоге получим программу, которая выглядит как mod_example_1.c. Функции, используемые в этом примере, будут объяснены позже в разделе «Некоторые полезные функции, которые вам следует знать». Структура request_recНаиболее важной частью любого запроса является запись запроса
. При вызове функции-обработчика это представляется структурой, Некоторые ключевые элементы структуры
Полный список всех значений, содержащихся в
Давайте попробуем некоторые из этих переменных в другом примере обработчика: статический интервал example_handler (request_rec * r) { /* Установите соответствующий тип содержимого */ ap_set_content_type(r, "текст/html"); /* Выводим IP-адрес клиента, который к нам подключается: */ ap_rprintf(r, "<h2>Здравствуйте, %s!</h2>", r->useragent_ip); /* Если нас связали с помощью запроса GET или POST, радуйтесь, иначе грустите. */ if ( !strcmp(r->метод, "POST") || !strcmp(r->метод, "GET") ) { ap_rputs("Вы использовали метод GET или POST, это нас очень радует!<br/>", r); } еще { ap_rputs("Вы не использовали POST или GET, это нас огорчает :(<br/>", r); } /* Наконец, если была строка запроса, давайте напечатаем и ее! */ если (г->аргументы) { ap_rprintf(r, "Ваша строка запроса: %s", r->args); } вернуть ОК; } Возвращаемые значения
Apache полагается на возвращаемые обработчиками значения, чтобы указать, был ли обработан запрос или нет, и если да, то успешно ли он прошел. Если модуль не заинтересован в обработке конкретного запроса, он всегда должен возвращать значение статический интервал example_handler (request_rec * r) { /* Возврат 404: Не найдено */ вернуть HTTP_NOT_FOUND; }
Возврат
Специальные коды возврата HTTP (выдержка):
Некоторые полезные функции, которые вы должны знать
Управление памятьюУправлять вашими ресурсами в Apache HTTP Server 2.4 довольно просто благодаря системе пула памяти. По сути, каждый сервер, соединение и запрос имеют свой собственный пул памяти, который очищается, когда его область действия заканчивается, например, когда выполняется запрос или когда серверный процесс завершает работу. Все, что нужно сделать вашему модулю, — это зафиксироваться в этом пуле памяти, и вам не придется беспокоиться о необходимости убирать за собой — довольно аккуратно, да?
В нашем модуле мы в первую очередь будем выделять память для каждого запроса, поэтому уместно использовать ссылку
Давайте поместим эти функции в пример обработчика: статический интервал example_handler (request_rec * r) { const char *original = "Вы не можете редактировать это!"; символ *копировать; int *целые числа; /* Выделить место для 10 целочисленных значений и установить их все равными нулю. */ целые числа = apr_pcalloc (r-> pool, sizeof (int) * 10); /* Создаем копию «исходной» переменной, которую мы можем редактировать. */ копия = apr_pstrdup(r->pool, original); вернуть ОК; }
Это все хорошо для нашего модуля, которому не потребуются предварительно инициализированные переменные или структуры. Однако, если мы хотим инициализировать что-то на ранней стадии, до того, как начнут поступать запросы, мы можем просто добавить вызов функции в нашу статическая пустота register_hooks (apr_pool_t *pool) { /* Вызов функции, которая инициализирует некоторые вещи */ example_init_function (пул); /* Создаем хук в обработчике запроса, чтобы нас вызывали, когда приходит запрос */ ap_hook_handler (example_handler, NULL, NULL, APR_HOOK_LAST); } В этой функции инициализации перед запросом мы не будем использовать тот же пул, что и при выделении ресурсов для функций на основе запросов. Вместо этого мы будем использовать пул, предоставленный нам сервером, для выделения памяти на уровне каждого процесса. Парсинг данных запроса
В нашем примере модуля мы хотели бы добавить функцию, которая проверяет, какой тип дайджеста, MD5 или SHA1, хотел бы видеть клиент. Эту проблему можно решить, добавив в запрос строку запроса. Строка запроса обычно состоит из нескольких ключей и значений, объединенных в строку, например
С момента появления Apache HTTP Server 2.4 синтаксический анализ данных запросов GET и POST никогда не был таким простым. Все, что нам нужно для анализа данных GET и POST, — это четыре простые строки: апр_таблица_t *GET; апрель_array_header_t*POST; ap_args_to_table(r, &GET); ap_parse_form_data(r, NULL, &POST, -1, 8192);
В нашем конкретном примере модуля мы ищем /* Получить ключ "дайджеста" из строки запроса, если он есть. */ const char *digestType = apr_table_get(GET, "дайджест"); /* Если ключ не был возвращен, вместо него мы установим значение по умолчанию. */ если (!digestType) дайджестТип = "sha1"; Структуры, используемые для данных POST и GET, не совсем одинаковы, поэтому, если бы нам нужно было получить значение из данных POST вместо строки запроса, нам пришлось бы прибегнуть к еще нескольким строкам, как показано в этом примере в последнюю главу этого документа. Создание расширенного обработчикаТеперь, когда мы научились анализировать данные формы и управлять нашими ресурсами, мы можем перейти к созданию расширенной версии нашего модуля, который выдает дайджест файлов MD5 или SHA1: статический интервал example_handler (request_rec * r) { int rc, существует; апрель_финфо_т инфо; апрель_файл_т *файл; символ *имя файла; символьный буфер[256]; апрель_размер_t байтов чтения; инт н; апр_таблица_t *GET; апрель_array_header_t *POST; const char *digestType; /* Проверяем, вызывается ли обработчик "example-handler". */ if (!r->handler || strcmp(r->handler, "пример-обработчик")) return (DECLINED); /* Выясните, какой файл запрашивается, удалив из него .sum */ имя_файла = apr_pstrdup(r->пул, r->имя файла); имя_файла[strlen(имя_файла)-4] = 0; /* Отрезать последние 4 символа. */ /* Выясняем, существует ли файл, для которого мы запрашиваем сумму, и не является ли он каталогом */ rc = apr_stat(&finfo, имя файла, APR_FINFO_MIN, r->pool); если (rc == APR_SUCCESS) { существует = ( (finfo.filetype != APR_NOFILE) && !(finfo.filetype и APR_DIR) ); если (! существует) вернуть HTTP_NOT_FOUND; /* Возвращаем 404, если не найдено. */ } /* Если apr_stat не удалось, нам, вероятно, не разрешено проверять этот файл. */ иначе вернуть HTTP_FORBIDDEN; /* Разбираем данные GET и, возможно, POST, отправленные нам */ ap_args_to_table(r, &GET); ap_parse_form_data(r, NULL, &POST, -1, 8192); /* Установите соответствующий тип содержимого */ ap_set_content_type(r, "текст/html"); /* Вывести название и некоторую общую информацию */ ap_rprintf(r, "<h2>Информация о %s:</h2>", имя файла); ap_rprintf(r, "<b>Размер:</b> %u байт<br/>", finfo.size); /* Получить тип дайджеста, который хочет видеть клиент */ дайджестТип = apr_table_get(GET, "дайджест"); если (!digestType) дайджестТип = "MD5"; rc = apr_file_open(&file, имя файла, APR_READ, APR_OS_DEFAULT, r->пул); если (rc == APR_SUCCESS) { /* Пытаемся ли мы вычислить дайджест MD5 или SHA1? */ если (!strcasecmp(digestType, "md5")) { /* Вычислить сумму MD5 файла */ союз { символ хр[16]; uint32_t число[4]; } дайджест; апр_md5_ctx_t md5; апр_md5_init(&md5); прочитанные байты = 256; в то время как ( apr_file_read (файл, буфер и байты чтения) == APR_SUCCESS ) { apr_md5_update(&md5, буфер, readBytes); } apr_md5_final(digest.chr, &md5); /* Распечатайте дайджест MD5 */ ap_rputs("<b>MD5: </b><code>", r); для (n = 0; n < APR_MD5_DIGESTSIZE/4; n++) { ap_rprintf(r, "%08x", дайджест.num[n]); } ap_rputs("</code>", г); /* Вывести ссылку на версию SHA1 */ ap_rputs("<br/><a href='?digest=sha1'>Просмотреть вместо этого хэш SHA1</a>", r); } еще { /* Вычислить сумму SHA1 файла */ союз { символ хр[20]; uint32_t число[5]; } дайджест; апр_ша1_ctx_t ша1; апрель_sha1_init(&sha1); прочитанные байты = 256; в то время как ( apr_file_read (файл, буфер и байты чтения) == APR_SUCCESS ) { apr_sha1_update(&sha1, буфер, readBytes); } apr_sha1_final(digest.chr, &sha1); /* Распечатываем дайджест SHA1 */ ap_rputs("<b>SHA1: </b><code>", r); для (n = 0; n < APR_SHA1_DIGESTSIZE/4; n++) { ap_rprintf(r, "%08x", дайджест.num[n]); } ap_rputs("</code>", г); /* Вывести ссылку на версию MD5 */ ap_rputs("<br/><a href='?digest=md5'>Просмотреть вместо этого хэш MD5</a>", r); } апр_файл_закрыть (файл); } /* Сообщаем серверу, что мы ответили на этот запрос. */ вернуть ОК; } Полностью эту версию можно найти здесь: mod_example_2.c. Добавление параметров конфигурацииВ следующем сегменте этого документа мы отвлечемся от модуля дайджеста и создадим новый примерный модуль, единственной функцией которого является запись его собственной конфигурации. Целью этого является изучение того, как сервер работает с конфигурацией, и что происходит, когда вы начинаете писать расширенные конфигурации для своих модулей. Введение в директивы конфигурации
Если вы читаете это, то вы, вероятно, уже знаете, что такое директива конфигурации. Проще говоря, директива — это способ сообщить отдельному модулю (или набору модулей), как себя вести, например, эти директивы управляют тем, как RewriteEngine включен RewriteCond "%{REQUEST_URI}" "^/foo/bar" RewriteRule "^/foo/bar/(.*)$" "/foobar?page=$1" Каждая из этих директив конфигурации обрабатывается отдельной функцией, которая анализирует заданные параметры и соответствующим образом настраивает конфигурацию. Делаем пример конфигурацииДля начала создадим базовую конфигурацию в C-space: структура typedef { инт включен; /* Включаем или отключаем наш модуль */ константный символ *путь; /* Какой-то путь к... чему-то */ интервал типа действия; /* 1 означает действие A, 2 означает действие B и так далее */ } пример_конфигурации;
Теперь давайте представим это в перспективе, создав очень маленький модуль, который просто распечатывает жестко закодированную конфигурацию. Вы заметите, что мы используем
структура typedef { инт включен; /* Включаем или отключаем наш модуль */ константный символ *путь; /* Какой-то путь к... чему-то */ интервал типа действия; /* 1 означает действие A, 2 означает действие B и так далее */ } пример_конфигурации; статический конфиг example_config; статический интервал example_handler (request_rec * r) { if (!r->handler || strcmp(r->handler, "пример-обработчик")) return(DECLINED); ap_set_content_type(r, "текст/обычный"); ap_rprintf(r, "Включено: %u\n", config.enabled); ap_rprintf(r, "Путь: %s\n", config.path); ap_rprintf(r, "TypeOfAction: %x\n", config.typeOfAction); вернуть ОК; } статическая пустота register_hooks (apr_pool_t *pool) { конфиг.включено = 1; config.path = "/foo/bar"; config.typeOfAction = 0x00; ap_hook_handler (example_handler, NULL, NULL, APR_HOOK_LAST); } /* Определяем наш модуль как сущность и назначаем функцию для регистрации хуков */ модуль AP_MODULE_DECLARE_DATA пример_модуля = { STANDARD20_MODULE_STUFF, NULL, /* Обработчик конфигурации для каждого каталога */ NULL, /* Обработчик слияния для конфигураций для каждого каталога */ NULL, /* Обработчик конфигурации для каждого сервера */ NULL, /* Обработчик слияния для серверных конфигураций */ NULL, /* Любые директивы, которые могут быть у нас для httpd */ register_hooks /* Наша функция регистрации хука */ }; Все идет нормально. Чтобы получить доступ к нашему новому обработчику, мы можем добавить в нашу конфигурацию следующее: <Расположение "/пример"> SetHandler пример-обработчик </местоположение> При посещении мы увидим нашу текущую конфигурацию, выдаваемую нашим модулем. Регистрация директив на сервереЧто, если мы захотим изменить нашу конфигурацию не путем жесткого ввода новых значений в модуль, а с помощью файла apache2.conf или, возможно, файла .htaccess? Пришло время сообщить серверу, что мы хотим, чтобы это было возможно. Для этого мы должны сначала изменить наш тег имени , чтобы включить ссылку на директивы конфигурации, которые мы хотим зарегистрировать на сервере: модуль AP_MODULE_DECLARE_DATA пример_модуля = { STANDARD20_MODULE_STUFF, NULL, /* Обработчик конфигурации для каждого каталога */ NULL, /* Обработчик слияния для конфигураций для каждого каталога */ NULL, /* Обработчик конфигурации для каждого сервера */ NULL, /* Обработчик слияния для серверных конфигураций */ example_directives, /* Любые директивы, которые могут быть у нас для httpd */ register_hooks /* Наша функция регистрации хука */ };
Это сообщит серверу, что теперь мы принимаем директивы из файлов конфигурации и что вызываемая структура статическая константа command_rec example_directives[] = { AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Включить или отключить mod_example"), AP_INIT_TAKE1("examplePath", example_set_path, NULL, RSRC_CONF, "Путь к чему бы то ни было"), AP_INIT_TAKE2("exampleAction", example_set_action, NULL, RSRC_CONF, "Специальное значение действия!"), { НУЛЕВОЙ } };
( Параметр «отсутствует» в нашем определении, который обычно имеет значение
Функция обработчика директив
Теперь, когда мы сказали серверу ожидать некоторые директивы для нашего модуля, пришло время создать несколько функций для их обработки. То, что сервер читает в файле (файлах) конфигурации, является текстом, и поэтому, естественно, то, что он передает нашему обработчику директив, — это одна или несколько строк, которые нам самим нужно распознать и действовать. Вы заметите, что, поскольку мы настроили нашу /* Обработчик директивы "exampleEnabled" */ const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg) { if(!strcasecmp(arg, "on")) config.enabled = 1; иначе config.enabled = 0; вернуть НУЛЬ; } /* Обработчик директивы "examplePath" */ const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg) { config.path = аргумент; вернуть НУЛЬ; } /* Обработчик директивы "exampleAction" */ /* Предположим, что этот аргумент принимает один аргумент (файл или БД) и второй (запретить или разрешить), */ /* и мы храним его побитно. */ const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2) { if(!strcasecmp(arg1, "файл")) config.typeOfAction = 0x01; иначе config.typeOfAction = 0x02; if(!strcasecmp(arg2, "deny")) config.typeOfAction += 0x10; иначе config.typeOfAction += 0x20; вернуть НУЛЬ; } Собираем все вместеТеперь, когда у нас настроены наши директивы и настроены для них обработчики, мы можем собрать наш модуль в один большой файл: /* mod_example_config_simple.c: */ #include <stdio.h> #include "apr_hash.h" #include "ap_config.h" #include "ap_provider.h" #include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" /* ================================================== ============================= Наш прототип конфигурации и объявление: ================================================== ============================= */ структура typedef { инт включен; /* Включаем или отключаем наш модуль */ константный символ *путь; /* Какой-то путь к... чему-то */ интервал типа действия; /* 1 означает действие A, 2 означает действие B и так далее */ } пример_конфигурации; статический конфиг example_config; /* ================================================== ============================= Наши обработчики директив: ================================================== ============================= */ /* Обработчик директивы "exampleEnabled" */ const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg) { if(!strcasecmp(arg, "on")) config.enabled = 1; иначе config.enabled = 0; вернуть НУЛЬ; } /* Обработчик директивы "examplePath" */ const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg) { config.path = аргумент; вернуть НУЛЬ; } /* Обработчик директивы "exampleAction" */ /* Предположим, что этот аргумент принимает один аргумент (файл или БД) и второй (запретить или разрешить), */ /* и мы храним его побитно. */ const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2) { if(!strcasecmp(arg1, "файл")) config.typeOfAction = 0x01; иначе config.typeOfAction = 0x02; if(!strcasecmp(arg2, "deny")) config.typeOfAction += 0x10; иначе config.typeOfAction += 0x20; вернуть НУЛЬ; } /* ================================================== ============================= Структура директив для нашего тега имени: ================================================== ============================= */ статическая константа command_rec example_directives[] = { AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Включить или отключить mod_example"), AP_INIT_TAKE1("examplePath", example_set_path, NULL, RSRC_CONF, "Путь к чему бы то ни было"), AP_INIT_TAKE2("exampleAction", example_set_action, NULL, RSRC_CONF, "Специальное значение действия!"), { НУЛЕВОЙ } }; /* ================================================== ============================= Обработчик нашего модуля: ================================================== ============================= */ статический интервал example_handler (request_rec * r) { if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED); ap_set_content_type(r, "текст/обычный"); ap_rprintf(r, "Включено: %u\n", config.enabled); ap_rprintf(r, "Путь: %s\n", config.path); ap_rprintf(r, "TypeOfAction: %x\n", config.typeOfAction); вернуть ОК; } /* ================================================== ============================= Функция регистрации хука (также инициализирует значения конфигурации по умолчанию): ================================================== ============================= */ статическая пустота register_hooks (apr_pool_t *pool) { конфиг.включено = 1; config.path = "/foo/bar"; config.typeOfAction = 3; ap_hook_handler (example_handler, NULL, NULL, APR_HOOK_LAST); } /* ================================================== ============================= Тег имени нашего модуля: ================================================== ============================= */ модуль AP_MODULE_DECLARE_DATA пример_модуля = { STANDARD20_MODULE_STUFF, NULL, /* Обработчик конфигурации для каждого каталога */ NULL, /* Обработчик слияния для конфигураций для каждого каталога */ NULL, /* Обработчик конфигурации для каждого сервера */ NULL, /* Обработчик слияния для серверных конфигураций */ example_directives, /* Любые директивы, которые могут быть у нас для httpd */ register_hooks /* Наша функция регистрации хука */ }; В нашем файле apache2.conf теперь мы можем изменить жестко закодированную конфигурацию, добавив несколько строк: ПримерВключен ПримерПуть "/usr/bin/foo" Пример файла действия разрешить
Таким образом, мы применяем конфигурацию, заходим Контекстно-зависимые конфигурацииВведение в контекстно-зависимые конфигурацииВ Apache HTTP Server 2.4 разные URL-адреса, виртуальные хосты, каталоги и т. д. могут иметь очень разное значение для пользователя сервера и, следовательно, разные контексты, в которых должны работать модули. Например, предположим, что у вас настроена эта конфигурация для mod_rewrite: <Каталог "/var/www"> RewriteCond "%{HTTP_HOST}" "^example.com$" RewriteRule "(.*)" "http://www.example.com/$1" </Каталог> <Каталог "/var/www/sub"> RewriteRule "^foobar$" "index.php?foobar=true" </Каталог> В этом примере вы настроите два разных контекста для mod_rewrite:
Если бы mod_rewrite (или весь сервер в этом отношении) не был контекстно-зависимым, то эти правила перезаписи просто применялись бы к каждому и любому сделанному запросу, независимо от того, где и как они были сделаны, но поскольку модуль может получить контекстно-зависимую конфигурацию прямо с сервера, ему не нужно самому знать, какие из директив действительны в данном контексте, так как об этом позаботится сервер. Так как же модуль получает конкретную конфигурацию для рассматриваемого сервера, каталога или местоположения? Это делается с помощью одного простого вызова: example_config *config = (example_config*) ap_get_module_config(r->per_dir_config, &example_module); Вот и все! Конечно, многое происходит за кулисами, которые мы обсудим в этой главе, начиная с того, как сервер узнал, как выглядит наша конфигурация, и как она была настроена так, как она есть в конкретном контексте. Наша базовая настройка конфигурацииВ этой главе мы будем работать с немного измененной версией нашей предыдущей структуры контекста. Мы установим структура typedef { контекст char[256]; путь символа[256]; интервал типа действия; инт включен; } пример_конфигурации; Наш обработчик запросов также будет изменен, но все еще очень прост: статический интервал example_handler (request_rec * r) { if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED); example_config *config = (example_config*) ap_get_module_config(r->per_dir_config, &example_module); ap_set_content_type(r, "текст/обычный"); ap_rprintf("Включено: %u\n", config->включено); ap_rprintf("Путь: %s\n", config->путь); ap_rprintf("TypeOfAction: %x\n", config->typeOfAction); ap_rprintf("Контекст: %s\n", config->context); вернуть ОК; } Выбор контекстаПрежде чем мы сможем начать делать наш модуль контекстно-зависимым, мы должны сначала определить, какие контексты мы будем принимать. Как мы видели в предыдущей главе, для определения директивы необходимо установить пять элементов: AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Включить или отключить mod_example"), Определение
Использование сервера для выделения слотов конфигурацииГораздо разумнее управлять своими конфигурациями, позволяя серверу помогать вам создавать их. Для этого мы должны сначала изменить наш тег имени , чтобы сообщить серверу, что он должен помочь нам в создании и управлении нашими конфигурациями. Поскольку мы выбрали контекст для каждого каталога (или для каждого местоположения) для наших конфигураций модулей, мы добавим в наш тег ссылку на функцию создания и слияния для каждого каталога: модуль AP_MODULE_DECLARE_DATA пример_модуля = { STANDARD20_MODULE_STUFF, create_dir_conf, /* Обработчик конфигурации для каждого каталога */ merge_dir_conf, /* Обработчик слияния для конфигураций для каждого каталога */ NULL, /* Обработчик конфигурации для каждого сервера */ NULL, /* Обработчик слияния для серверных конфигураций */ директивы, /* Любые директивы, которые могут быть у нас для httpd */ register_hooks /* Наша функция регистрации хука */ }; Создание новых конфигураций контекстаТеперь, когда мы сказали серверу помочь нам создавать конфигурации и управлять ими, наш первый шаг — создать функцию для создания новых пустых конфигураций. Мы делаем это, создавая функцию, на которую мы только что ссылались в нашем теге имени, как обработчик конфигурации для каждого каталога: void *create_dir_conf(apr_pool_t *pool, char *context) { контекст = контекст? контекст : "(неопределенный контекст)"; example_config *cfg = apr_pcalloc(pool, sizeof(example_config)); если (конфиг.) { /* Установить некоторые значения по умолчанию */ strcpy(cfg->контекст, контекст); cfg->включено = 0; cfg->path = "/foo/bar"; cfg->typeOfAction = 0x11; } вернуть конфиг; } Объединение конфигурацийНаш следующий шаг в создании контекстно-зависимой конфигурации — объединение конфигураций. Эта часть процесса особенно применима к сценариям, в которых у вас есть родительская конфигурация и дочерняя, например: <Каталог "/var/www"> ПримерВключен ПримерПуть "/foo/bar" Пример файла действия разрешить </Каталог> <Каталог "/var/www/subdir"> Пример действия по отказу в доступе к файлу </Каталог>
В этом примере естественно предположить, что каталог
Это предложение обрабатывается функцией, на void *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD) { example_config *base = (example_config *) BASE ; /* Это то, что было установлено в родительском контексте */ example_config *add = (example_config *) ADD ; /* Это то, что установлено в новом контексте */ example_config *conf = (example_config *) create_dir_conf(pool, "Объединенная конфигурация"); /* Это будет объединенная конфигурация */ /* Объединение конфигураций */ conf->enabled = (add->enabled == 0)? base->enabled : add->enabled ; conf->typeOfAction = add->typeOfAction ? add->typeOfAction : base->typeOfAction; strcpy(conf->path, strlen(add->path) ? add->path : base->path); вернуть конфиг; } Пробуем наши новые контекстно-зависимые конфигурацииТеперь давайте попробуем собрать все это вместе, чтобы создать новый модуль, учитывающий контекст. Во-первых, мы создадим конфигурацию, которая позволит нам проверить, как работает модуль: <Расположение "/а"> SetHandler пример-обработчик ПримерВключено ПримерПуть "/foo/bar" Пример файла действия разрешить </местоположение> <Расположение "/a/b"> Пример действия по отказу в доступе к файлу ПримерВключено выключено </местоположение> <Расположение "/a/b/c"> Пример действия db deny ПримерПуть "/foo/bar/baz" ПримерВключено </местоположение> Затем мы соберем код нашего модуля. Обратите внимание: поскольку теперь мы используем наш тег имени в качестве ссылки при выборке конфигураций в нашем обработчике, я добавил несколько прототипов, чтобы компилятор был доволен: /*$6 ++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++ * mod_example_config.c ++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++ */ #include <stdio.h> #include "apr_hash.h" #include "ap_config.h" #include "ap_provider.h" #include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" /*$1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~ Структура конфигурации ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~ */ структура typedef { контекст char[256]; путь символа[256]; интервал типа действия; инт включен; } пример_конфигурации; /*$1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~ Прототипы ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~ */ статический интервал example_handler (request_rec * r); const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg); const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg); const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2); void *create_dir_conf(apr_pool_t *pool, char *context); void *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD); статическая пустота register_hooks (apr_pool_t *pool); /*$1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~ Директивы конфигурации ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~ */ директивы static const command_rec[] = { AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, ACCESS_CONF, "Включить или отключить mod_example"), AP_INIT_TAKE1("examplePath", example_set_path, NULL, ACCESS_CONF, "Путь к чему бы то ни было"), AP_INIT_TAKE2("exampleAction", example_set_action, NULL, ACCESS_CONF, "Специальное значение действия!"), { НУЛЕВОЙ } }; /*$1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~ Наша табличка с именем ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~ */ модуль AP_MODULE_DECLARE_DATA пример_модуля = { STANDARD20_MODULE_STUFF, create_dir_conf, /* Обработчик конфигурации для каждого каталога */ merge_dir_conf, /* Обработчик слияния для конфигураций для каждого каталога */ NULL, /* Обработчик конфигурации для каждого сервера */ NULL, /* Обработчик слияния для серверных конфигураций */ директивы, /* Любые директивы, которые могут быть у нас для httpd */ register_hooks /* Наша функция регистрации хука */ }; /* ================================================== ================================================== ==================== Функция регистрации хука ================================================== ================================================== ==================== */ статическая пустота register_hooks (apr_pool_t *pool) { ap_hook_handler (example_handler, NULL, NULL, APR_HOOK_LAST); } /* ================================================== ================================================== ==================== Наш пример обработчика веб-сервиса ================================================== ================================================== ==================== */ статический интервал example_handler (request_rec * r) { if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ example_config *config = (example_config *) ap_get_module_config(r->per_dir_config, &example_module); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ ap_set_content_type(r, "текст/обычный"); ap_rprintf(r, "Включено: %u\n", config->включено); ap_rprintf(r, "Путь: %s\n", config->путь); ap_rprintf(r, "TypeOfAction: %x\n", config->typeOfAction); ap_rprintf(r, "Контекст: %s\n", config->context); вернуть ОК; } /* ================================================== ================================================== ==================== Обработчик директивы «exampleEnabled» ================================================== ================================================== ==================== */ const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ example_config *conf = (example_config *) cfg; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ если (конф.) { если(!strcasecmp(аргумент, "вкл")) конф->включено = 1; еще конф->включено = 0; } вернуть НУЛЬ; } /* ================================================== ================================================== ==================== Обработчик директивы "examplePath" ================================================== ================================================== ==================== */ const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ example_config *conf = (example_config *) cfg; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ если (конф.) { strcpy(conf->путь, аргумент); } вернуть НУЛЬ; } /* ================================================== ================================================== ==================== Обработчик директивы "exampleAction" ; Предположим, что этот аргумент принимает один аргумент (файл или БД) и второй (запретить или разрешить), ; и мы храним его побитовым способом. ================================================== ================================================== ==================== */ const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ example_config *conf = (example_config *) cfg; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ если (конф.) { { если(!strcasecmp(arg1, "файл")) conf->typeOfAction = 0x01; еще conf->typeOfAction = 0x02; если(!strcasecmp(arg2, "запретить")) conf->typeOfAction += 0x10; еще conf->typeOfAction += 0x20; } } вернуть НУЛЬ; } /* ================================================== ================================================== ==================== Функция для создания новых конфигураций для контекстов каталогов ================================================== ================================================== ==================== */ void *create_dir_conf(apr_pool_t *pool, char *context) { контекст = контекст? context : "Вновь созданная конфигурация"; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~*/ example_config *cfg = apr_pcalloc(pool, sizeof(example_config)); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~*/ если (конфиг.) { { /* Установить некоторые значения по умолчанию */ strcpy(cfg->контекст, контекст); cfg->включено = 0; memset(cfg->путь, 0, 256); cfg->typeOfAction = 0x00; } } вернуть конфиг; } /* ================================================== ================================================== ==================== Функция объединения конфигураций ================================================== ================================================== ==================== */ void *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~*/ example_config *base = (example_config *) BASE; example_config *add = (example_config *) ADD; example_config *conf = (example_config *) create_dir_conf(pool, "Объединенная конфигурация"); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~*/ conf->enabled = (добавить->enabled == 0)? base->enabled : add->enabled; conf->typeOfAction = add->typeOfAction ? add->typeOfAction : base->typeOfAction; strcpy(conf->path, strlen(add->path) ? add->path : base->path); вернуть конфиг; } Подведение итоговТеперь мы рассмотрели, как создавать простые модули для Apache HTTP Server 2.4 и настраивать их. Что вы будете делать дальше, полностью зависит от вас, но я надеюсь, что чтение этой документации принесло вам что-то ценное. Если у вас есть вопросы о дальнейшей разработке модулей, присоединяйтесь к нашим спискам рассылки или ознакомьтесь с остальной частью нашей документации для получения дополнительных советов. Некоторые полезные фрагменты кодаПолучить переменные из данных формы POSTструктура typedef { константный символ *ключ; константный символ *значение; } пара значений ключей; keyValuePair *readPost(request_rec *r) { apr_array_header_t *pairs = NULL; апр_офф_т лен; размер apr_size_t; целое разрешение; интервал я = 0; символ *буфер; keyValuePair *kvp; res = ap_parse_form_data(r, NULL, &pairs, -1, HUGE_STRING_LEN); if (res != OK || !pairs) вернуть NULL; /* Возвращаем NULL, если мы потерпели неудачу или если нет данных POST */ kvp = apr_pcalloc(r->pool, sizeof(keyValuePair) * (pairs->nelts + 1)); в то время как (пары && !apr_is_empty_array(пары)) { ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs); apr_brigade_length(пара->значение, 1, &len); размер = (apr_size_t) длина; буфер = apr_palloc (r-> пул, размер + 1); apr_brigade_flatten (пара-> значение, буфер и размер); буфер[длина] = 0; kvp[i].key = apr_pstrdup(r->пул, пара->имя); kvp[i].value = буфер; я++; } возврат квп; } статический интервал example_handler (request_rec * r) { /*~~~~~~~~~~~~~~~~~~~~~~*/ keyValuePair *formData; /*~~~~~~~~~~~~~~~~~~~~~~*/ данные формы = чтение сообщения (г); если (данные формы) { инт я; для (i = 0; &formData[i]; i++) { если (formData[i].key && formData[i].value) { ap_rprintf(r, "%s = %s\n", formData[i].key, formData[i].value); } иначе если (formData[i].key) { ap_rprintf(r, "%s\n", formData[i].key); } иначе если (formData[i].value) { ap_rprintf(r, "= %s\n", formData[i].value); } еще { перерыв; } } } вернуть ОК; } Распечатка каждого полученного заголовка HTTPстатический интервал example_handler (request_rec * r) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ const apr_array_header_t *поля; инт я; apr_table_entry_t *e = 0; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ fields = apr_table_elts(r->headers_in); e = (apr_table_entry_t *) fields->elts; for(i = 0; i < fields->nelts; i++) { ap_rprintf(r, "%s: %s\n", e[i].key, e[i].val); } вернуть ОК; } Чтение тела запроса в памятьstatic int util_read (request_rec *r, const char **rbuf, apr_off_t *size) { /*~~~~~~~~*/ интервал rc = ОК; /*~~~~~~~~*/ if((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) { возврат (рс); } если (ap_should_client_block (r)) { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ char argsbuffer[HUGE_STRING_LEN]; apr_off_t rsize, len_read, rpos = 0; длина apr_off_t = r->оставшаяся; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ *rbuf = (const char *) apr_pcalloc(r->pool, (apr_size_t) (длина + 1)); *размер = длина; while((len_read = ap_get_client_block(r, argsbuffer, sizeof(argsbuffer))) > 0) { если ((rpos + len_read) > длина) { rsize = длина - rpos; } еще { rsize = len_read; } memcpy((char *) *rbuf + rpos, argsbuffer, (size_t) rsize); rpos += rsize; } } возврат (рс); } статический интервал example_handler (request_rec * r) { /*~~~~~~~~~~~~~~~~*/ размер апр_офф_т; константный символ *буфер; /*~~~~~~~~~~~~~~~~*/ if(util_read(r, &buffer, &size) == OK) { ap_rprintf(r, "Мы прочитали тело запроса длиной %" APR_OFF_T_FMT " байт", размер); } вернуть ОК; } |