Модуль Apache в C: как записать данные в клиентский сокет?
Я создал простой модуль mod_perl, который записывает строку с нулем в конце во Flash-клиентов, подключающихся к порту 843. Он работает нормально, но использует 20 м на httpd-child на моем компьютере с ОС CentOS 5 Linux.
Поэтому я пытаюсь переписать свой модуль на C, но я не уверен, как получить доступ к клиентскому сокету через структуру conn_rec, которую получает мой обработчик протокола.
Я спросил в списке рассылки и попытался добавить #define CORE_PRIVATE и использовать ap_get_module_config(conn->conn_config, &core_module), но это ломает мой веб-сервер: строка подается как на порт 843 (что нормально), так и на порт 80 (что не в порядке).
Кто-нибудь, пожалуйста, есть предложение здесь?
Вот мой SocketPolicy.pm (работает нормально, но требует много памяти):
package SocketPolicy;
# Listen 843
# <VirtualHost _default_:843>
# PerlModule SocketPolicy
# PerlProcessConnectionHandler SocketPolicy
# </VirtualHost>
use strict;
use warnings FATAL => 'all';
use APR::Const(-compile => 'SO_NONBLOCK');
use APR::Socket();
use Apache2::ServerRec();
use Apache2::Connection();
use Apache2::Const(-compile => qw(OK DECLINED));
use constant POLICY =>
qq{<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM
"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" to-ports="8080"/>
</cross-domain-policy>
\0};
sub handler {
my $conn = shift;
my $socket = $conn->client_socket();
my $offset = 0;
# set the socket to the blocking mode
$socket->opt_set(APR::Const::SO_NONBLOCK => 0);
do {
my $nbytes = $socket->send(substr(POLICY, $offset), length(POLICY) - $offset);
# client connection closed or interrupted
return Apache2::Const::DECLINED unless $nbytes;
$offset += $nbytes;
} while ($offset < length(POLICY));
my $slog = $conn->base_server()->log();
$slog->warn('served socket policy to: ', $conn->remote_ip());
return Apache2::Const::OK;
}
1;
Вот мой сломанный mod_socket_policy.c (захватывает порт 80):
/*
LoadModule socket_policy_module modules/mod_socket_policy.so
Listen 843
<VirtualHost _default_:843>
SetHandler socket_policy
</VirtualHost>
*/
#include <httpd.h>
#include <http_protocol.h>
#include <http_connection.h>
#include <http_config.h>
#include <http_log.h>
#define CORE_PRIVATE
#include <http_core.h>
#define POLICY "<?xml version=\"1.0\"?>\n" \
"<!DOCTYPE cross-domain-policy SYSTEM\n" \
"\"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\">\n" \
"<cross-domain-policy>\n" \
"<allow-access-from domain=\"*\" to-ports=\"8080\"/>\n" \
"</cross-domain-policy>\0"
static int socket_policy_handler(conn_rec *conn) {
apr_socket_t *socket = ap_get_module_config(conn->conn_config, &core_module);
apr_size_t len = strlen(POLICY);
apr_socket_send(socket, POLICY, &len);
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, conn->base_server,
"served socket policy to %s", conn->remote_ip);
return OK;
}
static void register_hooks(apr_pool_t *pool) {
ap_hook_process_connection(socket_policy_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
module AP_MODULE_DECLARE_DATA socket_policy_module = {
STANDARD20_MODULE_STUFF,
NULL,
NULL,
NULL,
NULL,
NULL,
register_hooks
};
Кроме того, я посмотрел на источник mod_perl, и они, похоже, используют один и тот же метод для доступа к клиентскому сокету:
static MP_INLINE
apr_socket_t *mpxs_Apache2__Connection_client_socket(pTHX_ conn_rec *c,
apr_socket_t *s)
{
apr_socket_t *socket =
ap_get_module_config(c->conn_config, &core_module);
if (s) {
ap_set_module_config(c->conn_config, &core_module, s);
}
return socket;
}
Так почему мой модуль Perl работает, а C - нет?
1 ответ
Для обработчиков контента принято использовать SetHandler XXX в httpd.conf, а затем во время выполнения они проверяют эту строку с помощью
if (!r->handler || (strcmp(r->handler, "XXX") != 0))
return DECLINED;
Но для обработчиков протоколов нет такого соглашения. Вы должны ввести ключевое слово для httpd.conf и проверить его. Например, mod_echo (исходный код, включенный в Apache) вводит ProtocolEcho On. Или в моем случае вы можете просто проверить порт:
if (conn->base_server->port != 843)
return DECLINED;
Также ниже приведен рабочий модуль для той же цели, но с использованием бригад ведра, что лучше, чем запись непосредственно в сокет клиента:
/*
LoadModule socket_policy_module modules/mod_socket_policy.so
Listen 843
<VirtualHost _default_:843>
</VirtualHost>
*/
#include <httpd.h>
#include <http_protocol.h>
#include <http_connection.h>
#include <http_config.h>
#include <http_log.h>
#define POLICY "<?xml version=\"1.0\"?>\n" \
"<!DOCTYPE cross-domain-policy SYSTEM\n" \
"\"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\">\n" \
"<cross-domain-policy>\n" \
"<allow-access-from domain=\"*\" to-ports=\"8080\"/>\n" \
"</cross-domain-policy>\n"
static int socket_policy_handler(conn_rec *conn) {
apr_bucket_brigade *bb;
apr_bucket *b;
apr_status_t rv;
if (conn->base_server->port != 843)
return DECLINED;
bb = apr_brigade_create(conn->pool, conn->bucket_alloc);
/* this will send the terminating 0 as well */
b = apr_bucket_immortal_create(POLICY, sizeof(POLICY), bb->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
b = apr_bucket_eos_create(bb->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
rv = ap_pass_brigade(conn->output_filters, bb);
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, conn->base_server, "output error");
return DECLINED;
}
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, conn->base_server,
"served socket policy to %s", conn->remote_ip);
return OK;
}
static void register_hooks(apr_pool_t *pool) {
ap_hook_process_connection(socket_policy_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
module AP_MODULE_DECLARE_DATA socket_policy_module = {
STANDARD20_MODULE_STUFF,
NULL,
NULL,
NULL,
NULL,
NULL,
register_hooks
};