The OpenNET Project / Index page

[ новости/++ | форум | wiki | теги ]

Разработка модуля для Apache 2.x (module apache gcc)


<< Предыдущая ИНДЕКС Исправить src / Печать Следующая >>
Ключевые слова: module, apache, gcc,  (найти похожие документы)
From: Михаил Стадник Date: Mon, 3 Aug 2010 17:02:14 +0000 (UTC) Subject: Разработка модуля для Apache 2.x Оригинал: http://mikhailstadnik.com/developing-apache-module Предисловие Недавно мне пришлось столкнуться с необходимостью разработать собственный небольшой модуль для веб-сервера Apache 2.2.x. Проведя несколько часов в поисках подходящей информации, я столкнулся с тем фактом, что по-русски об этом мало кто рассказывает. Поэтому и возникла идея написать эту статью. Ниже я постараюсь как можно подробнее поделиться накопленным опытом, пошагово описать этапы создания модуля и приведу различные полезные ссылки по данной теме. Введение Первое, с чем сталкивается разработчик-новичок, никогда не сталкивавшийся с написанием модулей для Apache, это вопрос -- "С чего же начать?". Начните с осознания того факта, что, без лишних телодвижений, вам придется иметь дело с чистым языком C безо всяких ++. И, возможно, это и к лучшему. Далее проверьте, установлен ли в вашей системе APXS, если нет -- установите. В системах Debian (Ubuntu) Linux это можно сделать достаточно просто, достаточно запустить команду: apt-get install apache2-threaded-dev или apt-get install apache2-prefork-dev в зависимости от соответствующей версии Apache, которую вы используете. Это вам понадобиться для сборки и компиляции модуля. Теперь все готово, и можно приступить непосредственно к разработке самого модуля. Мы будем изучать все на примере, поэтому давайте создадим небольшой модуль, который приводит все тэги HTML-страниц, которые отдает веб-сервер, к верхнему регистру. Итак, назовем наш модуль без лишних хитройстей -- uptags. Создадим одноименную папку и в ней исходный файл mod_uptags.c, а также файлы README и config.m4. Вот так будет выглядеть наша структура файлов: uptags/ config.m4 mod_uptags.c README Можете сразу расположить данные файлы в исходниках Apache, а можете сделать это и после, действуйте по своему усмотрению. В принципе, для успешного создания модуля нам бы хватило лишь файла с исходным кодом C, тем не менее README будет, как минимум, хорошим тоном, а config.m4 пригодится для автоматической сборки модуля при компиляции всего веб-сервера. API к модулям Apache предполагает реализацию нескольких функций и структур, которые логически можно отнести к нескольким группам, которые рассмотрены ниже. Однако прежде, чем мы приступим к разбору функций, следует подключить необходимые библиотеки и объявить сам модуль в нашем исходнике mod_uppertags.c: #include "apr_general.h" #include "apr_lib.h" #include "apr_buckets.h" #include "apr_strings.h" #include "ap_config.h" #include "util_filter.h" #include "httpd.h" #include "http_config.h" #include "http_request.h" #include "http_core.h" #include "http_protocol.h" #include "http_log.h" #include "http_main.h" #include "util_script.h" #include "http_core.h" #include <string.h> #include <stdio.h> #include <ctype.h> #include <sys/stat.h> module AP_MODULE_DECLARE_DATA uptags_module; Функции управления конфигурацией Как вы знаете, Apache -- гибко настраиваемый веб-сервер. С точки зрения системного администратора существуют несколько типов директив, которые могут быть определены в различных областях видимости, таких как глобальный конфиг сервера, <VirtualHost>, <Directory> (а также <Files> или <Location>) и файлы .htaccess. Конфликты директив, определенные в различных областях видимости, по-умолчанию разбираются самим сервером по таким правилам: * Основной конфиг Директивы в данном файле (за исключением определенных в некоторых контейнерах) применяются глобально. Большинство директив могут быть использованы здесь. * Виртуальный хост <VirtualHost> Каждый виртуальный хост может иметь свой (виртуальный) серверный конфиг внутри контейнера <VirtualHost>. Директивы, который могут быть использованы в глобальном конфиге, могут быть использованы и здесь и наоборот. * Директория Контейнеры <Directory>, <Files> и <Location> определяют на разных уровнях как могут быть переопределены глобальные настройки. В данной статье именно на них мы будем ссылаться как на конфиги директорий. * Файлы .htaccess Также относятся к настройкам директорий. В данных файлах пользователи могут определять настройки директив самостоятельно, на основе значения директивы AllowOverride, установленной для каждого из них администратором сервера. При написании модуля, вам под силу самостоятельно определять директивы и контейнеры, определять области их видимости и изменять правила их переопределения. Для этого, в первую очередь, вам необходимо создать соответствующие структуры, в которых данные конфигурации будут храниться в памяти. Следует отметить, что в общем случае, вы можете задавать отдельные структуры для серверной конфигурации и конфигурации директорий. Делать это следует в том случае, если наборы директив отличаются. В противном случае, вы можете воспользоваться одной и той же структурой для хранения настроек. Поскольку модуль, который мы создаем достаточно прост и будет обладать минимальными настройками, мы пойдем по простому пути, и объединим оба типа настроек (серверную и для директорий) в одну структуру данных. Давайте определим флаг, позволяющий включать и выключать работу нашего модуля: engine = On/Off -- будет позволять включать/выключать работу всего модуля. Т.е., наша структура будет выглядеть следубщим образом: typedef struct uptags_cfg { int engine; } uptags_cfg; Теперь для каждого отдельного типа конфигурации нам нужно написать функции получения данных: /** * Данная функция возвращет структуру конфигурации директории * данного модуля для текущего запроса */ static uptags_cfg *uptags_dconfig( const request_rec *r) { return (uptags_cfg *) ap_get_module_config( r->per_dir_config, &uptags_module); } /** * Данная функция возвращет структуру конфигурации сервера * данного модуля */ static uptags_cfg *uptags_sconfig( const server_rec *s) { return (uptags_cfg *) ap_get_module_config( s->module_config, &uptags_module); } Теперь самое время позаботиться о настройках по-умолчанию. Для этого напишем две следующие функции: /** * Инициализация настроек по-умолчанию для директории */ static void *uptags_create_dir_config( apr_pool_t *p, char *dirspec) { uptags_cfg *cfg; /** * Выделим память в пуле соединения для структуры настроек */ cfg = (uptags_cfg *) apr_pcalloc( p, sizeof( uptags_cfg)); /** * Теперь определим значения по умолчанию для наших настроек */ cfg->engine = 1; return (void *) cfg; } /** * Для серверных настроек определим аналогичную функцию */ static void *uptags_create_server_config( apr_pool_t *p, server_rec *s) { uptags_cfg *cfg; cfg = (uptags_cfg *) apr_pcalloc( p, sizeof( uptags_cfg)); cfg->engine = 1; return (void *) cfg; } Поскольку в структуре веб-сервера может существовать, фактически, несколько конфигураций в разных блоках (например, для конфигурации директории в структуре виртуального хоста может быть создана запись в блоке <Directory></Directory>, <Files></Files> или <Location></Location>, и, одновременно, может быть создан файл .htaccess), нам может понадобиться определить собственные правила объединения данных конфигов. Слава богу, API Apache позволяет нам это сделать достаточно просто, определив правила мерджинга серверных настроек и настроек директорий. Для этого достаточно определить следующие функции: /** * Данная функция будет вызвана для объединения конфигураций директории * Именно с помощью этой функции вы можете определить свои собственные * правила объединения */ static void *uptags_merge_dir_config( apr_pool_t *p, void *parent_conf, void *newloc_conf) { uptags_cfg *megred_conf = (uptags_cfg *) apr_pcalloc( p, sizeof( uptags_cfg)); uptags_cfg *pconf = (uptags_cfg *) parent_conf; uptags_cfg *nconf = (uptags_cfg *) newloc_conf; /** * ... * Здесь следует определить правила объединения конфигов */ return (void *) merged_conf; } /** * Аналогично для серверной конфигурации */ static void *uptags_merge_server_config( apr_pool_t *p, void *srv1conf, void *srv2conf) { uptags_cfg *merged_config = (uptags_cfg *) apr_pcalloc( p, sizeof( uptags_cfg)); uptags_cfg *s1conf = (uptags_cfg *) srv1conf; uptags_cfg *s2conf = (uptags_cfg *) srv2conf; /** * ... * Здесь следует определить правила объединения конфигов */ return (void *) merged_config; } Тем не менее, учтите, что правила объединения следует применять только в том случае, если правила сервера по-умолчанию вас не устраивают, т.е. вы хотите их изменить. В противном случае, просто не пишите данные функции и все произойдет само собой. Определение директив (команд) конфигурации После того, как мы определили структуру нашей конфигурации и функции для ее наполнения, объединения о обработки, совсем не лишним будет определить сами директивы так, так они должны будут задаваться в конфигах: static command_rec uptags_directives[] = { AP_INIT_FLAG( "UptagsEngine", ap_set_flag_slot, (void *) APR_OFFSETOF( uptags_cfg, engine), OR_OPTIONS, "uptags module switcher" ), {NULL} }; Здесь мы немного остановимся для того, чтобы рассмотреть различные возможности, которые предоставляет API веб-сервера Apache для обработки директив конфигурации. Определение структуры директив всегда заканчивается NULL-структурой, а макросы реализации определены в файле http_config.h. AP_INIT_FLAG -- один из многих таких макросов, в целом же не-NULL структура содержит следующие элементы: * Имя директивы -- это то, как директива будет задаваться в конфигах (по сути маппинг строк из конфигов и созданной нами ранее структуры в памяти -- это именно это место) * Функция, реализующая директиву. ap_set_flag_slot -- стандартная функция Apache API, которая разбирает параметр-выключатель или флаг (как раз то, что нам подходит -- On/Off) * Указатель на структуру данных (с помощью APR_OFFSETOF мы маппим данные в определенную нами структуру) * Макрос, определяющий, где разрешена директива (OR_OPTIONS -- директива разрешена для директорий и файлов .htaccess, если значение директивы AllowOverride установлено администратором с тегом Options) * Описание директивы для функций помощи. Зачастую стандартных функций, реализующих команду, которые предоставляет Apache API вполне достаточно для решения задачи. Данные функции также описаны в файле http_config.h. Вот они: /** * Возвращает значение установленного строкового параметра директивы */ AP_DECLARE_NONSTD(const char *) ap_set_string_slot( cmd_parms *cmd, void *struct_ptr, const char *arg); /** * Возвращает значение установленного целочисленного параметра директивы */ AP_DECLARE_NONSTD(const char *) ap_set_int_slot( cmd_parms *cmd, void *struct_ptr, const char *arg); /** * Возвращает значение установленного строкового параметра директивы, приведенного к нижнему регистру */ AP_DECLARE_NONSTD(const char *) ap_set_string_slot_lower( cmd_parms *cmd, void *struct_ptr, const char *arg); /** * Возвращает значение установленного опционального параметра директивы (флаг On/Off) */ AP_DECLARE_NONSTD(const char *) ap_set_flag_slot( cmd_parms *cmd, void *struct_ptr, const char *arg); /** * Возвращает значение установленного строкового параметра директивы, где строка - путь к файлу */ AP_DECLARE_NONSTD(const char *) ap_set_file_slot( cmd_parms *cmd, void *struct_ptr, const char *arg); /** * Обрабатывает директиву как запрещенную к использованию, например: * AP_INIT_RAW_ARGS("Foo", ap_set_deprecated, NULL, OR_ALL, * "Директива Foo больше не поддерживается, используйте директиву Bar") */ AP_DECLARE_NONSTD(const char *) ap_set_deprecated( cmd_parms *cmd, void *struct_ptr, const char *arg); Макросы, определяющие тип данных директивы могут быть следующими: * AP_INIT_NO_ARGS -- директива без аргументов * AP_INIT_FLAG -- директива выключатель (On/Off) * AP_INIT_TAKE1 -- один аргумент -- строка * AP_INIT_TAKE2, AP_INIT_TAKE3, AP_INIT_TAKE12 -- директивы принимают несколько различных аргументов * AP_INIT_ITERATE -- к каждому аргументу диретивы будет применена функция * AP_INIT_ITERATE2 -- функция будет вызвана с передачей в нее двух аргументов * AP_INIT_RAW_ARGS -- будет вызвана функция с передачей в нее всех аргументов директивы Макросы, определяющие область видимости директивы такие: * OR_NONE -- недоступно нигде в конфиге * OR_LIMIT -- в конфигах сервера внутри контейнеров <Directory> или <Location> и внутри .htaccess-файлов, если директива AllowOverride имеет ключевое слово Limit * OR_OPTIONS -- где угодно, включая .htaccess, если директива AllowOverride имеет ключевое слово Options * OR_FILEINFO -- где угодно, включая .htaccess, если директива AllowOverride имеет ключевое слово FileInfo * OR_AUTHCFG -- в конфигах сервера внутри контейнеров <Directory> или <Location> и в файлах .htaccess, если диретива AllowOverride имеет ключевое слово AuthConfig * OR_INDEXES -- где угодно, включая файлы .htaccess, если директива AllowOverride имеет ключевое слово Indexes * OR_UNSET -- удаление директивы в Allow * ACCESS_CONF -- в серверных конфигах внутри контейнеров <Directory> или <Location> * RSRC_CONF -- в серверных конфигах за пределами контейнеров <Directory> или <Location> * EXEC_ON_READ -- принуждает директиву выполнять внешнюю команду, которая изменит конфигурацию (как подключение внешнего файла или IFModule) * OR_ALL -- директива разрешена где угодно Теперь, когда мы определились с конфигурированием нашего модуля можно приступать непосредственно к разработке его функциональности. Обработчики запросов Вы можете создавать собственные обработчики запросов к Apache, которые будут выдавать соответствующие документы на соответствующий запрос используя директивы SetHandler и AddHandler. Поскольку данные обработчики отсылают данные непосредственно в вывод, вам необходимо позаботиться о том, чтобы заголовки ответа были отправлены прежде, чем будет осуществлен вывод содержимого. Это может быть осуществлено с помощью функции send_http_header(). Также вы можете задавать собственные заголовки. Любая функция-обработчик принимает, как аргумент, указатель на структуру запроса. Возвращаемыми значениями могут быть: * OK -- все в порядке, мы успешно обработали запрос * DECLINED -- запрос отклонен * HTTP_[код_ошибки] -- рапортуем об ошибке HTTP-протокола, например HTTP_401 -- Требуется авторизация static int uptags_handler( request_rec *r) { if (strcmp( r->handler, "uptags-handler")) { return DECLINED; } r->content_type = "text/html"; /** * Если мы отправляем заголовок - это как раз то место */ if (r->header_only) { return OK; } /** * Заголовки отправлены, начинаем выводить содержимое: */ ap_rputs( DOCTYPE_HTML_4_0_STRICT, r); ap_rputs( "<HTML>\n", r); ap_rputs( " <HEAD>\n", r); ap_rputs( " <TITLE>Example Title</TITLE>\n", r); ap_rputs( " </HEAD>\n", r); ap_rputs( " <BODY>\n", r); ap_rputs( " <H1>Example content</H1>\n", r); ap_rputs( " <P>Example text</P>\n", r); ap_rputs( " </BODY>\n", r); ap_rputs( "</HTML>\n", r); return OK; } Теперь мы можем в конфиг веб-сервера добавить наш обработчик: <Location /uptags-handler-example> SetHandler uptags-handler </Location> По сути, к модулю, который мы пишем это не имеет никакого отношения, поэтому данная функциональность нам не нужна. Мы лишь рассмотрели пример написания своего обработчика. Данная задача, возможно, возникнет, если вы захотите, например, написать свою систему управления содержимым веб-сайта, как модуль к веб-серверу. Или же будете встраивать свой интерпретируемый язык. Для решения же поставленной нами задачи больше подходит работа с фильтрами, что мы и рассмотрим в следующем разделе. Фильтры Фильтры позволяют производить манипуляции над текущим содержимым, которое Apache отдает по запросу. Как раз подходящее место для решения нашей задачи. Здесь следует немного отклониться от самого решения и поговорить немного о принципах функционирования веб-сервера и о том, как он обрабатывает и отдает данные. Архитектура фильтров Apache 2 -- основная инновация данного веб-сервера, которая наряду со своей мощностью и универсальностью имеет отрицательный оттенок в вопросе изучения и понимания концепции "корзин" и "бригад" (buckets and brigades). Низкоуровневое API Apache позволяет манипулировать "корзинами" и "бригадами" напрямую. Ниже мы рассмотрим принципы того, как это делается. Что же такое "корзины" и "бригады"? Корзина -- это контейнер данных, который может содержать данные любого типа. Тем не менее наиболее распространенным типом является блок памяти, который уже может содержать как файл на диске так и поток данных передаваемых от одного источника к другому, к такому, например, как отдельная программа. В принципе, существуют несколько различных типов "корзин" -- с различными типами данных, а также "корзины" с метаданными, такими как признак конца данных (EOS) или FLUSH-"корзина", которая сбрасывает буфер данных в выходной поток. Бригада -- это контейнер, объединяющий цикл "корзин", и подразделение, которое передается от фильтра к фильтру. "Корзины" и "бригады" обеспечивают эффективное манипулирование блоками памяти, типичных для приложений-фильтров. Для манипулирования "корзинами" и "бригадами" Apache 2 предоставляет нам набор типов данных, структур, макросов и функций API, описанных в файле apr_buckets.h. Их описание можно найти [64]здесь. Следует учесть, что для решения поставленной задачи, нам необходимо создать два фильтра -- входной (in) и выходной (out): /** * Входной фильтр */ static void uptags_in_filter( request_rec *r) { /** * На входе нам не нужно ничего делать, просто добавляем выходной фильтр к стеку фильтров */ ap_add_output_filter( "Uptags", NULL, r, r->connection); } /** * Выходной фильтр - именно он будет делать все полезную работу */ static apr_status_t uptags_out_filter( ap_filter_t *f, apr_bucket_brigade *pbbIn) { request_rec *r = f->r; conn_rec *c = r->connection; apr_bucket *pbktIn; apr_bucket_brigade *pbbOut; uptags_cfg *cfg = uptags_dconfig( f->r); /** * Проверяем, включена ли работа фильтра в настройках. Если нет - ничего неделаем с исходными данными * Также не следует ничего делать, если данный контент не является HTML */ if (!cfg->engine || strcmp( r->content_type, "text/html") != 0) { return ap_pass_brigade( f->next, pbbIn); } /* инициализируем пустую выходную бригаду */ pbbOut = apr_brigade_create( r->pool, c->bucket_alloc); /** * Читаем корзины данных из входящей бригады */ for (pbktIn = APR_BRIGADE_FIRST( pbbIn); pbktIn != APR_BRIGADE_SENTINEL( pbbIn); pbktIn = APR_BUCKET_NEXT( pbktIn)) { const char *data; apr_size_t len; char *buf; apr_bucket *pbktOut; /* если текущая "корзина" - это метаданные-признак конца - просто перемещаем корзину в конец бригады и продолжаем обход данных */ if(APR_BUCKET_IS_EOS( pbktIn)) { apr_bucket *pbktEOS = apr_bucket_eos_create( c->bucket_alloc); APR_BRIGADE_INSERT_TAIL( pbbOut,pbktEOS); continue; } /* читаем данные */ apr_bucket_read( pbktIn, &data, &len, APR_NONBLOCK_READ); /** * производим полезную работу над данными * ВАЖНО! Не меняйте исходные данные напрямую - это может привести к неожиданным результатам. * Действуйте через промежуточный буфер. */ buf = apr_bucket_alloc( len, c->bucket_alloc); memset( buf, 0, sizeof( buf)); uptags_tags_to_uppercase( data, buf); /* записываем новые данные */ pbktOut = apr_bucket_heap_create( buf, len, apr_bucket_free, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL( pbbOut, pbktOut); } apr_brigade_cleanup( pbbIn); return ap_pass_brigade( f->next, pbbOut); } Естественно. нам необходимо реализовать также нашу функцию uptags_tags_to_uppercase(), которая, собственно, и проделывает все необходимые манипуляции над данными, и поместить ее объявление перед объявлением фильтров: /** * Функция преобразует тэги в строке к верхнему регистру */ void uptags_tags_to_uppercase( const char *data, char *str) { int i, s = strlen( data), tag_opened = 0; for (i = 0; i < s; i++) { str[i] = data[i]; if (str[i] == '<') { tag_opened = 1; } else if (str[i] == '>') { tag_opened = 0; } if (tag_opened && str[i] != '\0') { str[i] = apr_toupper( str[i]); } } } Мы написали достаточно простую функцию, которая в реальной жизни не всегда будет вести себя так как нужно. Но наша задача достаточно проста, поэтому в целях тестирования она нам вполне подойдет. На этом разработку фильтров для нашего модуля можно считать завершенной и мы можем перейти к заключительному этапу. Определение модуля После того, как мы определили все функции нашего модуля, нам необходимо "объяснить" веб-серверу, как их следует использовать. Для этого достаточно перечислить их в соответствующих ячейках структуры модуля Apache: /** * Описываем структуру модуля Apache */ module AP_MODULE_DECLARE_DATA uptags_module = { STANDARD20_MODULE_STUFF, uptags_create_dir_config, /* создание конфигурации директории */ NULL, /* объединение конфигураций директории */ uptags_create_server_config, /* создание серверной конфигурации */ NULL, /* объединение серверной конфигурации */ uptags_directives, /* маппинг директив конфигурации */ uptags_register_hooks /* регистрируем обработчики */ }; Как мы видим, нам не хватает лишь небольшой функции uptags_register_hooks, которая возмет на себя работу по регистрации всех нужных нам обработчиков для данного модуля. Давайте ее напишем: /** * Регистрируем фильтры модуля в обработчиках */ static void uptags_register_hooks( apr_pool_t *p) { ap_hook_insert_filter( uptags_in_filter, NULL, NULL, APR_HOOK_MIDDLE); ap_register_output_filter( "Uptags", uptags_out_filter, NULL, AP_FTYPE_RESOURCE); } Вы можете скачать полный исходник модуля [69]отсюда. Компиляция и сборка модуля Самый простой способ скомпилировать и подключить модуль -- это собрать его как DSO (Dynamically Shared Object). Чтобы сделать это запустите команду в директории с исходником: apxs2 -c mod_uppertags.c Если компиляция прошла успешно, должна появиться папка .libs, а в ней файл mod_uptegs.so. Следует скопировать данный файл в директорию с подгружаемыми модулями Apache. В Debian (Ubuntu) Linux это, как правило, директория /usr/lib/apache2/modules/ Теперь в основной конфиг Apache вам нужно вписать директивы: LoadModule uptags_module /usr/lib/apache2/modules/mod_uptags.so <IfModule mod_uptags.c> UptagsEngine On </IfModule> Далее следует перезагрузить веб-сервер: /etc/init.d/apache2 restart Теперь можете проверить результат, просмотрев любую страницу, которую выдает ваш веб-сервер. Также нелишним будет написать содержимое файла config.m4: APACHE_MODPATH_INIT(uptags) APACHE_MODULE(uptags, reformatting all HTML tags to upercase, , , no) APACHE_MODPATH_FINISH Это позволит автоматически скомпилировать модуль при общей сборке Apache. Для того, чтобы собрать модуль статически при сборке всего веб-сервера теперь достаточно указать опцию --enable-uptags при запуске команды ./configure: ./configure --enable-uptags И, конечно же, не забудьте дать другим людям необходимые инструкции, описав их в файле README. Ссылки (на английском) Configuration for Modules http://www.apachetutor.org/dev/config Introduction to Buckets and Brigades http://www.apachetutor.org/dev/brigades Basic Resource Management in Apache: the APR Pools http://www.apachetutor.org/dev/pools Request Processing in Apache http://www.apachetutor.org/dev/request Apache Portable Runtime (APR) Documentation http://apr.apache.org/docs/apr/1.3/ Заключение Как мы увидели, разработка собственных модулей для веб-сервера Apache не такое уж и сложное дело. Достаточно вооружиться терпением и ссылками на соответствующую документацию, и задача становиться вполне разрешимой. Я же надеюсь, что данное руководство поможет читателю ближе познакомиться с Apache API для написания модулей и станет начальной отправной точкой в этом интересном деле.

<< Предыдущая ИНДЕКС Исправить src / Печать Следующая >>

Ваш комментарий
Имя:         
E-Mail:      
Заголовок:
Текст:





  Закладки на сайте
  Проследить за страницей
Created 1996-2017 by Maxim Chirkov  
ДобавитьРекламаВебмастеруГИД  
Hosting by Ihor