From 73649a3d672d15622481e1400310f1d512d6a6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C5=82as=20Piotr?= Date: Wed, 26 Feb 2025 18:59:36 +0100 Subject: [PATCH 1/2] Add mod_mesi Apache module for ESI support Introduce the `mod_mesi` module to enable Edge Side Includes (ESI) processing for Apache servers. The module adds request handling, response filtering, and integrates with libgomesi for ESI parsing. Adjusted `.gitignore` to include related build artifacts and added a Makefile for compiling and cleaning the module. --- .gitignore | 4 ++ servers/apache/Makefile | 5 ++ servers/apache/mod_mesi.c | 101 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 servers/apache/Makefile create mode 100644 servers/apache/mod_mesi.c diff --git a/.gitignore b/.gitignore index a9af763..e036b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,7 @@ go.work.sum /debian/libgomesi/ /debian/libgomesi-dev/ /servers/frankenphp/frankenphp +/servers/apache/.libs/ +/servers/apache/mod_mesi.la +/servers/apache/mod_mesi.lo +/servers/apache/mod_mesi.slo diff --git a/servers/apache/Makefile b/servers/apache/Makefile new file mode 100644 index 0000000..b63018a --- /dev/null +++ b/servers/apache/Makefile @@ -0,0 +1,5 @@ +build: + apxs -c -I ../../libgomesi/ mod_mesi.c + clean: + rm -rf .libs + rm -f mod_mesi.la mod_mesi.lo mod_mesi.slo \ No newline at end of file diff --git a/servers/apache/mod_mesi.c b/servers/apache/mod_mesi.c new file mode 100644 index 0000000..a04ee5c --- /dev/null +++ b/servers/apache/mod_mesi.c @@ -0,0 +1,101 @@ +#include "httpd.h" +#include "http_config.h" +#include "http_protocol.h" +#include "http_request.h" +#include "apr_strings.h" + +#include "libgomesi.h" + +module AP_MODULE_DECLARE_DATA mod_mesi_module; + +typedef struct { + int enable_mesi; +} mesi_config; + +static void *create_server_config(apr_pool_t *p, server_rec *s) { + mesi_config *conf = apr_pcalloc(p, sizeof(*conf)); + conf->enable_mesi = 0; + return conf; +} + +static void *merge_server_config(apr_pool_t *p, void *basev, void *addv) { + mesi_config *base = (mesi_config *) basev; + mesi_config *add = (mesi_config *) addv; + mesi_config *conf = apr_pcalloc(p, sizeof(*conf)); + conf->enable_mesi = (add->enable_mesi != 0) ? add->enable_mesi : base->enable_mesi; + return conf; +} + +static void *create_dir_config(apr_pool_t *p, char *dir) { + return NULL; +} + +static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) { + return NULL; +} + +static const char *set_enable_mesi(cmd_parms *cmd, void *cfg, int flag) { + mesi_config *conf = (mesi_config *) ap_get_module_config(cmd->server->module_config, &mod_mesi_module); + conf->enable_mesi = flag; + return NULL; +} + +static int mesi_request_handler(request_rec *r) { + mesi_config *conf = (mesi_config *) ap_get_module_config(r->server->module_config, &mod_mesi_module); + if (conf->enable_mesi) { + apr_table_set(r->headers_in, "Surrogate-Capability", "ESI/1.0"); + } + return DECLINED; +} + +static int mesi_response_filter(ap_filter_t *f, apr_bucket_brigade *bb) { + mesi_config *conf = (mesi_config *) ap_get_module_config(f->r->server->module_config, &mod_mesi_module); + if (!conf->enable_mesi) { + return ap_pass_brigade(f->next, bb); + } + + if (!f->r->content_type || strncmp(f->r->content_type, "text/html", 9) != 0 || f->r->status > 400) { + return ap_pass_brigade(f->next, bb); + } + + apr_bucket *b; + for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) { + if (APR_BUCKET_IS_EOS(b)) { + continue; + } + + const char *data; + apr_size_t len; + if (apr_bucket_read(b, &data, &len, APR_BLOCK_READ) == APR_SUCCESS) { + char *html = strdup(data); + char *esi = Parse(html, 5, "http://127.0.0.1/"); + free(html); + + apr_bucket *new_bucket = apr_bucket_pool_create(esi, strlen(esi), f->r->pool, f->c->bucket_alloc); + APR_BUCKET_INSERT_BEFORE(b, new_bucket); + apr_bucket_delete(b); + free(esi); + } + } + return ap_pass_brigade(f->next, bb); +} + +static void register_hooks(apr_pool_t *p) { + ap_hook_post_read_request(mesi_request_handler, NULL, NULL, APR_HOOK_MIDDLE); + ap_register_output_filter("MESI_RESPONSE", mesi_response_filter, NULL, AP_FTYPE_RESOURCE); +} + +static const command_rec mesi_directives[] = { + AP_INIT_FLAG("EnableMesi", set_enable_mesi, NULL, RSRC_CONF, "Enable or disable the Mesi module"), + {NULL} +}; + +module AP_MODULE_DECLARE_DATA mod_mesi_module = { + STANDARD20_MODULE_STUFF, + create_dir_config, + merge_dir_config, + create_server_config, + merge_server_config, + mesi_directives, + register_hooks +}; \ No newline at end of file From a771b2933417add229a423d145477cd5b1e0a5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C5=82as=20Piotr?= Date: Wed, 26 Feb 2025 22:03:42 +0100 Subject: [PATCH 2/2] Add ESI response filter and dynamic parsing via Go library Introduced an output filter to enable ESI parsing for HTML content using a dynamically loaded Go library. Enhanced logging for missing or unsupported content types. Updated configuration with necessary defaults and added a minimal server setup for testing. --- .gitignore | 2 + servers/apache/Makefile | 7 ++- servers/apache/apache.conf.dist | 28 ++++++++++++ servers/apache/mime.types | 1 + servers/apache/mod_mesi.c | 77 ++++++++++++++++++++++++++------- 5 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 servers/apache/apache.conf.dist create mode 100644 servers/apache/mime.types diff --git a/.gitignore b/.gitignore index e036b4e..a2ef6ad 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ go.work.sum /servers/apache/mod_mesi.la /servers/apache/mod_mesi.lo /servers/apache/mod_mesi.slo +/servers/apache/minimal.conf +/servers/apache/apache2.pid diff --git a/servers/apache/Makefile b/servers/apache/Makefile index b63018a..c7453c8 100644 --- a/servers/apache/Makefile +++ b/servers/apache/Makefile @@ -1,5 +1,8 @@ build: - apxs -c -I ../../libgomesi/ mod_mesi.c + apxs -c -I ../../libgomesi/ mod_mesi.c -lgomesi clean: rm -rf .libs - rm -f mod_mesi.la mod_mesi.lo mod_mesi.slo \ No newline at end of file + rm -f mod_mesi.la mod_mesi.lo mod_mesi.slo +run: build + MY_DOC_ROOT=$$(realpath "$$(pwd)/../../examples") envsubst < apache.conf.dist > minimal.conf + apache2 -f $$(pwd)/minimal.conf -DFOREGROUND \ No newline at end of file diff --git a/servers/apache/apache.conf.dist b/servers/apache/apache.conf.dist new file mode 100644 index 0000000..e0491b9 --- /dev/null +++ b/servers/apache/apache.conf.dist @@ -0,0 +1,28 @@ +ServerRoot "." +PidFile ./apache2.pid + +LoadModule mod_mesi_module "./.libs/mod_mesi.so" +LoadModule mpm_worker_module "/usr/lib/apache2/modules/mod_mpm_worker.so" +LoadModule authz_core_module "/usr/lib/apache2/modules/mod_authz_core.so" +LoadModule authz_host_module "/usr/lib/apache2/modules/mod_authz_host.so" +LoadModule dir_module /usr/lib/apache2/modules/mod_dir.so +LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so +LoadModule mime_module /usr/lib/apache2/modules/mod_mime.so +Listen 8080 +ServerName localhost + + +DefaultType text/html + EnableMesi On + DocumentRoot "${MY_DOC_ROOT}" + + + DirectoryIndex index.html + AllowOverride None + Require all granted + + + + +ErrorLog /dev/stderr + diff --git a/servers/apache/mime.types b/servers/apache/mime.types new file mode 100644 index 0000000..fda4662 --- /dev/null +++ b/servers/apache/mime.types @@ -0,0 +1 @@ +text/html html htm \ No newline at end of file diff --git a/servers/apache/mod_mesi.c b/servers/apache/mod_mesi.c index a04ee5c..16c9c98 100644 --- a/servers/apache/mod_mesi.c +++ b/servers/apache/mod_mesi.c @@ -2,9 +2,24 @@ #include "http_config.h" #include "http_protocol.h" #include "http_request.h" +#include "http_core.h" +#include "http_log.h" +#include "util_filter.h" #include "apr_strings.h" +#include "http_log.h" #include "libgomesi.h" +#include "dlfcn.h" + +#ifndef LIB_GOMESI_PATH +#define LIB_GOMESI_PATH "/usr/lib/libgomesi.so" +#endif + +typedef char *(*ParseFunc)(char *, int, char *); + +typedef struct { + apr_bucket_brigade *bb; +} response_filter_ctx; module AP_MODULE_DECLARE_DATA mod_mesi_module; @@ -44,6 +59,7 @@ static int mesi_request_handler(request_rec *r) { mesi_config *conf = (mesi_config *) ap_get_module_config(r->server->module_config, &mod_mesi_module); if (conf->enable_mesi) { apr_table_set(r->headers_in, "Surrogate-Capability", "ESI/1.0"); + ap_add_output_filter("MESI_RESPONSE", NULL, r, r->connection); } return DECLINED; } @@ -55,34 +71,65 @@ static int mesi_response_filter(ap_filter_t *f, apr_bucket_brigade *bb) { } if (!f->r->content_type || strncmp(f->r->content_type, "text/html", 9) != 0 || f->r->status > 400) { + if (!f->r->content_type) { + ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, f->r, "No content type, %s", f->r->uri); + } else { + ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, f->r, "Content type: %s", f->r->content_type); + } + + return ap_pass_brigade(f->next, bb); } + apr_status_t rv; apr_bucket *b; - for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) { - if (APR_BUCKET_IS_EOS(b)) { - continue; - } + apr_size_t len; + char *html = NULL; + response_filter_ctx *ctx; + + ctx = f->ctx; + if (!ctx) { + ctx = apr_pcalloc(f->r->pool, sizeof(response_filter_ctx)); + f->ctx = ctx; + } + for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) { const char *data; - apr_size_t len; - if (apr_bucket_read(b, &data, &len, APR_BLOCK_READ) == APR_SUCCESS) { - char *html = strdup(data); - char *esi = Parse(html, 5, "http://127.0.0.1/"); - free(html); - - apr_bucket *new_bucket = apr_bucket_pool_create(esi, strlen(esi), f->r->pool, f->c->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(b, new_bucket); - apr_bucket_delete(b); - free(esi); + apr_size_t data_len; + + if (apr_bucket_read(b, &data, &data_len, APR_BLOCK_READ) == APR_SUCCESS) { + if (!html) { + html = apr_pstrndup(f->r->pool, data, data_len); + } else { + html = apr_pstrcat(f->r->pool, html, data, NULL); + } } } + + void *go_module = dlopen(LIB_GOMESI_PATH, RTLD_NOW); + + if (!go_module) { + dlerror(); + return ap_pass_brigade(f->next, bb); + } + + ParseFunc EsiParse = (ParseFunc)dlsym(go_module, "Parse"); + char *esi = EsiParse(html, 5, "http://127.0.0.1"); + + dlclose(go_module); + + apr_brigade_cleanup(bb); + b = apr_bucket_pool_create(esi, strlen(esi), f->r->pool, bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(bb->bucket_alloc)); + return ap_pass_brigade(f->next, bb); } + static void register_hooks(apr_pool_t *p) { ap_hook_post_read_request(mesi_request_handler, NULL, NULL, APR_HOOK_MIDDLE); - ap_register_output_filter("MESI_RESPONSE", mesi_response_filter, NULL, AP_FTYPE_RESOURCE); + ap_register_output_filter("MESI_RESPONSE", mesi_response_filter, NULL, AP_FTYPE_CONTENT_SET); } static const command_rec mesi_directives[] = {