Содержание


Apache 2

Часть 9. Hello World модуль

Comments

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

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

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

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

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

Сегодня мы разработаем простой генератор контента. Он демонстрирует базовые концепции модульного программирования, включая полную структуру модуля, использование callback и request_rec. Будут показаны детализированно заголовки запросов и ответов, переменные среды.

1. Скелет простого модуля

В каждом модуле Apache есть базовая экспортируемая структура, которая во второй версии в общем виде имеет следующую форму:

module AP_MODULE_DECLARE_DATA some_module = {
    STANDARD20_MODULE_STUFF,
    some_dir_cfg,     /* локальная конфиг-структура */
    some_dir_merge,   /* обьединенная локальная конфиг-структура */
    some_svr_cfg,     /* базовая конфиг-структура */
    some_svr_merge,   /* обьединенная базовая конфиг-структура */
    some_cmds,        /* конфигурационные директивы */
    some_hooks        /* хуки */
};

Макрос STANDARD20_MODULE_STUFF обеспечивает бинарную совместимость на уровне версий, остальные поля имеют отношение к конфигурации. Последнее нам не нужно, поэтому структура нашего модуля имеет вид:

module AP_MODULE_DECLARE_DATA helloworld_module = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    helloworld_hooks
};

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

static void helloworld_hooks(apr_pool_t *pool)
{
    ap_hook_handler(helloworld_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

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

static int helloworld_handler(request_rec *r)
{
    if (!r->handler || (strcmp(r->handler, "helloworld") != 0)) {
        return DECLINED;
    }
    if (r->method_number != M_GET) {
        return HTTP_METHOD_NOT_ALLOWED;
    }
    ap_set_content_type(r, "text/html;charset=ascii");
    ap_rputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n",
             r);
    ap_rputs("<html><head><title>Apache HelloWorld "    "Module</title></head>", r);
    ap_rputs("<body><h1>Hello World!</h1>", r);
    ap_rputs("<p>This is the Apache HelloWorld module!</p>", r);
    ap_rputs("</body></html>", r);
    return OK;
}

Эта функция сначала проверяет валидность r->handler, т. е. предназначен ли этот запрос нам или кому-то еще. Если это не наш запрос, его обработка игнорируется, и он передается следующему обработчику. Наша функция обрабатывает только 2 типа запросов, GET и HEAD, в противном случае она возвращает ошибку, и клиент уже увидит ошибку у себя на странице. HEAD отличается от GET только тем, что у него есть заголовок, но нет тела. В Apache и HEAD, и GET интерпретируются как один тип - M_GET.

В противном случае наш модуль генерирует простую html-страницу. Функция возвращает OK - сигнал к тому, что данный запрос полностью обработан. Модуль может вернуть DECLINED - это говорит о том, что наш модуль просто проигнорировал данный запрос. Модуль также может вернуть ошибку, в этом случае Apache делает внутренний редирект на документ с ошибкой.

Проверка r->handler необходима для любого генератора. Apache устроен таким образом, что он будет вызывать подряд все генераторы, зарегистрированные во всех модулях, до тех пор, пока не получит OK или ошибку. Один генератор контента берет всю ответственность за обработку любого запроса. Эта особенность отличает генераторы контента в Apache от других хуков.

2. Полный текст

Исходный файл mod_helloworld.c:

#include <httpd.h>
#include <http_protocol.h>
#include <http_config.h>
static int helloworld_handler(request_rec *r)
{
    if (!r->handler || strcmp(r->handler, "helloworld")) {
        return DECLINED;
    }
    if (r->method_number != M_GET) {
        return HTTP_METHOD_NOT_ALLOWED;
    }
    ap_set_content_type(r, "text/html;charset=ascii");
    ap_rputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n",
             r);
    ap_rputs("<html><head><title>Apache HelloWorld "
             "Module</title></head>", r);
    ap_rputs("<body><h1>Hello World!</h1>", r);
    ap_rputs("<p>This is the Apache HelloWorld module!</p>", r);
    ap_rputs("</body></html>", r);
    return OK;
}
static void helloworld_hooks(apr_pool_t *pool)
{
    ap_hook_handler(helloworld_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
module AP_MODULE_DECLARE_DATA helloworld_module = {
        STANDARD20_MODULE_STUFF,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        helloworld_hooks
};

Теперь можно собирать модуль. Для этого есть утилита apxs. Компиляция модуля:

	# apxs -c mod_helloworld.c

Инсталляция:

  # apxs -i mod_helloworld.la

Нужно сконфигурировать наш модуль в файле httpd.conf:

LoadModule    helloworld_module modules/mod_helloworld.so
<Location /helloworld>
    SetHandler helloworld
</Location>

Когда пользователь наберет в адресной строке браузера /helloworld, будет вызван наш модуль.

И хук helloworld_hooks, и сам обработчик helloworld_handler продекларированы как static.

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

3. Вариант с request_rec

Единственным аргументом нашего обработчика является обьект request_rec. Это обширная структура данных, которая представляет запрос и предоставляет доступ к данным, вовлеченным в обработку этого запроса. В следующем примере вместо генерации простого html-файла мы загрузим локальный файл. Для этого нам понадобится r->filename. Вместо чтения файла и отправки его содержимого с помощью ap_rwrite, мы просто отправим сам файл:

static int helloworld_handler(request_rec *r)
{
    apr_file_t *fd;
    apr_size_t sz;
    apr_status_t rv;

    /* Проверка r->filename и finfo   */
if (r->filename == NULL) {
    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
            "Incomplete request_rec!") ;
    return HTTP_INTERNAL_SERVER_ERROR;
}

/* установим дополнительные заголовки:
 * (1) Content-Length
 * (2) Last-Modified
 */
ap_set_content_length(r, r->finfo.size);
if (r->finfo.mtime) {
    char *datestring = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
    apr_rfc822_date(datestring, r->finfo.mtime);
    apr_table_setn(r->headers_out, "Last-Modified", datestring);
}

  rv = apr_file_open(&fd, r->filename,
               APR_READ|APR_SHARELOCK|APR_SENDFILE_ENABLED,
               APR_OS_DEFAULT, r->pool);
  if (rv != APR_SUCCESS) {
      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
              "can't open %s", r->filename);
      return HTTP_NOT_FOUND;
  }
  ap_send_fd(fd, r, 0, r->finfo.size, &sz);
  apr_file_close(fd);
  return OK;
}

4. Вариант с переменными среды

Наш модуль может предоставить информацию о переменных среды, о заголовках запроса, о заголовках ответа. Следующая версия нашего модуля будет распечатывать эту информацию клиенту. В структуре request_rec есть специальная APR-таблица, которую можно распечатать с помощью apr_table_do. Следующая функция распечатывает строку из этой таблицы в формате html:

static int printitem(void *rec, const char *key, const char *value)
{
    /* rec - указатель на данные пользователя */
    request_rec *r = rec;
    ap_rprintf(r, "<tr><th scope=\"row\">%s</th><td>%s</td></tr>\n",
           ap_escape_html(r->pool, key),
           ap_escape_html(r->pool, value));
    return 1;
}

Цикл для распечатки всей таблицы:

static void printtable(request_rec *r, apr_table_t *t,
                 const char *caption, const char *keyhead,
                 const char *valhead)
{
    /* заголовок таблицы */
    ap_rprintf(r, "<table><caption>%s</caption><thead>"
           "<tr><th scope=\"col\">%s</th><th scope=\"col\">%s"
           "</th></tr></thead><tbody>", caption, keyhead, valhead);
    /* печать данных: apr_table_do делает итерацию */
    apr_table_do(printitem, r, t, NULL);
    /* Finish the table */
    ap_rputs("</tbody></table>\n", r);
}

И сам обработчик:

static int helloworld_handler(request_rec *r)
{
   if (!r->handler || (strcmp(r->handler, "helloworld") != 0)) {
       return DECLINED;
   }
   if (r->method_number != M_GET) {
       return HTTP_METHOD_NOT_ALLOWED;
   }
  ap_set_content_type(r, "text/html;charset=ascii");
  ap_rputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n"
         "<html><head><title>Apache HelloWorld Module</title></head>"
         "<body><h1>Hello World!</h1>"
         "<p>This is the Apache HelloWorld module!</p>", r);
  /* Print the tables */
  printtable(r, r->headers_in, "Request Headers", "Header", "Value");
  printtable(r, r->headers_out, "Response Headers", "Header", "Value");
  printtable(r, r->subprocess_env, "Environment", "Variable", "Value");
  ap_rputs("</body></html>", r);
  return OK;
}

5. bucket brigade

Теперь рассмотрим механизм использования фильтров в генерации контента. Для этого нужно:

  1. Создать bucket brigade - кольцевую структуру, в основе которой лежит связный список.
  2. Заполнить список данными.
  3. Передать этот список в качестве параметра фильтру, который представлен r->output_filters.

При необходимости можно создать несколько brigade. Если вывод велик по объему, его можно передавать малыми порциями между фильтрами и самому клиенту. Мы будем использовать stdio-like API, прописанные в заголовке util_filter.h: ap_fflush, ap_fwrite, ap_fputs, ap_fputc, ap_fputstrs, ap_fprintf. В качестве параметров этим функциям передается фильтр и brigade. Обработчик генерации контента:

static int helloworld_handler(request_rec *r)
{
    static const char *const helloworld =
        "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n"
        "<html><head><title>Apache HelloWorld Module</title></head>"
        "<body><h1>Hello World!</h1>"
        "<p>This is the Apache HelloWorld module!</p>"
        "</body></html>";
    apr_status_t rv;
    apr_bucket_brigade *bb;
    apr_bucket *b;
    if (!r->handler || strcmp(r->handler, "helloworld")) {
        return DECLINED;
    }
    if (r->method_number != M_GET) {
        return HTTP_METHOD_NOT_ALLOWED;
    }
    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    ap_set_content_type(r, "text/html;charset=ascii");
  b = apr_bucket_immortal_create(helloworld, strlen(helloworld),
                       bb->bucket_alloc);
  APR_BRIGADE_INSERT_TAIL(bb, b);
  APR_BRIGADE_INSERT_TAIL(bb,
                  apr_bucket_eos_create(bb->bucket_alloc));
  rv = ap_pass_brigade(r->filters_out, bb);
  if (rv != APR_SUCCESS) {
      ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Output Error");
      return HTTP_INTERNAL_SERVER_ERROR;
  }
  return OK;
}

Это был пример output-фильтра. Входящий фильтр создается аналогично:

  1. Создаем bucket brigade.
  2. Заполняем список данными.
  3. Читаем данные из этого списка.

В нашем примере входящий фильтр будет проверять данные, приходящие из поста - POST.

Алгоритм таков: сначала в клиентском запросе проверяем Content-Length и Transfer-Encoding. Затем создаем brigade, и кладем в нее порциями данные, пришедшие от клиента. В конце подсчитываем общее количество байт:

static int check_postdata_new_method(request_rec *r)
{
    apr_status_t status;
    int end = 0;
    apr_size_t bytes, count = 0;
    const char *buf;
    apr_bucket *b;
    apr_bucket_brigade *bb;
    /* проверяем Content-Length и Transfer-Encoding. */
    int has_input = 0;
    const char *hdr = apr_table_get(r->headers_in, "Content-Length");
    if (hdr) {
         has_input = 1;
    }
    hdr = apr_table_get(r->headers_in, "Transfer-Encoding");
    if (hdr) {
         if (strcasecmp(hdr, "chunked") == 0) {
             has_input = 1;
         }
         else {
             ap_rprintf(r, "<p>Unsupported Transfer Encoding: %s</p>",
                  ap_escape_html(r->pool, hdr));
             return OK; /* we allow this, but just refuse to handle it */
         }
    }
    if (!has_input) {
         ap_rputs("<p>No request body.</p>\n", r);
         return OK;
    }
    /*  создаем brigade. */
    bb  = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    /*  читаем клиентские данные в цикле до тех пор, пока не получим EOS */
    do  {
         /* копируем порцию данных в bb */
         status = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
                       APR_BLOCK_READ, BUFLEN);
      if ( status == APR_SUCCESS ) {
          for (b = APR_BRIGADE_FIRST(bb);
             b != APR_BRIGADE_SENTINEL(bb);
               b = APR_BUCKET_NEXT(b) ) {
              /* Проверяем EOS */
            if (APR_BUCKET_IS_EOS(b)) {
                    end = 1;
                    break;
            }
              /* игнорируем metadata */
          else if (APR_BUCKET_IS_METADATA(b)) {
            continue;
              }
          /* получаем сумму */
              bytes = BUFLEN;
            status = apr_bucket_read(b, &buf, &bytes,
                           APR_BLOCK_READ);
            count += bytes;
          }
      }
      apr_brigade_cleanup(bb);
  } while (!end && (status == APR_SUCCESS));
  if (status == APR_SUCCESS) {
      ap_rprintf(r, "<p>Got %d bytes of request body data.</p>\n",
           count);
      return OK;
  }
  else {
      ap_rputs("<p>Error reading request body.</p>", r);
      return OK; /* Just send the above message and ignore the data */
  }
}

6. default handler

Теперь осталось представить собственно обработчик Apache, который работает по умолчанию:

static int default_handler(request_rec *r)
{
    conn_rec *c = r->connection;
    apr_bucket_brigade *bb;
    apr_bucket *e;
    core_dir_config *d;
    int errstatus;
    apr_file_t *fd = NULL;
    apr_status_t status;
    int bld_content_md5;

Конфигурацию получаем с помощью ap_get_module_config:

	d = (core_dir_config *)ap_get_module_config(r->per_dir_config,&core_module);

Вычисляем MD5 hash:

	bld_content_md5 = (d->content_md5 & 1) && r->output_filters->frec->ftype
	 != AP_FTYPE_RESOURCE;

Назначаем методы:

	ap_allow_standard_methods(r, MERGE_ALLOW,    M_GET, M_OPTIONS, M_POST, -1);

Обслуживаем файлы, а не каталоги:

if (r->finfo.filetype == APR_DIR) {
    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                 "Attempt to serve directory: %s", r->filename);
    return HTTP_NOT_FOUND;
}
if ((r->used_path_info != AP_REQ_ACCEPT_PATH_INFO) &&
    r->path_info && *r->path_info)
{
    /* default to reject */
    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                  "File does not exist: %s",
                  apr_pstrcat(r->pool, r->filename,
                    r->path_info, NULL));
    return HTTP_NOT_FOUND;
}
        if (r->method_number != M_GET) {
            core_request_config *req_cfg;
            req_cfg = ap_get_module_config(r->request_config,
                            &core_module);
            if (!req_cfg->deliver_script) {
                /* The flag hasn't been set for this request. Punt. */
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                         "This resource does not accept the %s method.",
                         r->method);
                return HTTP_METHOD_NOT_ALLOWED;
            }
        }
        if ((status = apr_file_open(&fd, r->filename,APR_READ|APR_BINARY
#if APR_HAS_SENDFILE
                          | ((d->enable_sendfile == ENABLE_SENDFILE_OFF)
                                            ? 0 : APR_SENDFILE_ENABLED)
#endif
                                    , 0, r->pool)) != APR_SUCCESS) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
                "file permissions deny server access: %s", r->filename);
            return HTTP_FORBIDDEN;
        }

Устанавливаем заголовки:

ap_update_mtime(r, r->finfo.mtime);
ap_set_last_modified(r);
ap_set_etag(r);
apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
ap_set_content_length(r, r->finfo.size);
bb = apr_brigade_create(r->pool, c->bucket_alloc);

Заключение

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


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


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Linux
ArticleID=832826
ArticleTitle=Apache 2: Часть 9. Hello World модуль
publish-date=08312012