⬆️ ⬇️

Developing a module for Apache 2.x

Foreword





Recently I had to face the need to develop my own small module for the Apache 2.2.x web server. After spending several hours in search of suitable information, I was confronted with the fact that very few people speak Russian about it. Therefore, the idea arose to write this article. Below, I will try to share my experience in as much detail as possible, step by step to describe the stages of creating a module, and provide various useful links on this topic.





')

Introduction





The first thing that a new developer encounters, who has never encountered writing modules for Apache, is the question “Where to start?”.



Start by recognizing the fact that, without any extra movements, you will have to deal with pure C without any ++. And maybe this is for the best.



Next, check whether your system has APXS installed, if not, install it. On Debian (Ubuntu) Linux systems, this can be done fairly simply by running the command:



  apt-get install apache2-threaded-dev 




or



  apt-get install apache2-prefork-dev 


depending on the corresponding version of apache you are using.



You need this to build and compile the module.



Now everything is ready, and you can proceed directly to the development of the module itself. We will study everything with an example, so let's create a small module that brings all the HTML tags that the web server gives to uppercase.



So, let's call our module without extra tricks - uptags. Create a folder with the same name and in it the source file mod_uptags.c, as well as the README and config.m4 files. This is how our file structure will look like:



  uptags /
     config.m4
     mod_uptags.c
     README 




You can immediately place these files in the source code of Apache, and you can do it after, act on your own.



In principle, for the successful creation of the module, we would only have enough of the C source file; nevertheless, the README would be at least a good tone, and config.m4 would be useful for automatically assembling the module when compiling the entire web server.



API to Apache modules involves the implementation of several functions and structures, which can logically be attributed to several groups, which are discussed below.



However, before we start the analysis of functions, you should connect the necessary libraries and declare the module itself in our source code 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;




* This source code was highlighted with Source Code Highlighter .


Configuration Management Features



As you know, Apache is a highly customizable web server.



From the point of view of the system administrator, there are several types of directives that can be defined in different scopes, such as the global server configuration, <VirtualHost>, <Directory> (as well as <Files> or <Location>) and .htaccess files.



Directive conflicts defined in different scopes, by default, are resolved by the server itself according to the following rules:







When writing a module, you can independently define directives and containers, determine their scope and change the rules for their redefinition.



To do this, first of all, you need to create appropriate structures in which configuration data will be stored in memory. It should be noted that in the general case, you can define separate structures for server configuration and directory configuration. This should be done if the sets of directives are different. Otherwise, you can use the same structure for storing settings.



Since the module that we create is quite simple and will have minimal settings, we will follow a simple path and combine both types of settings (server and for directories) into one data structure. Let's define a flag to enable and disable the operation of our module:



engine = On / Off - will allow you to enable / disable the work of the entire module.





That is, our structure will look like this:



typedef struct uptags_cfg {

int engine;

} uptags_cfg;




* This source code was highlighted with Source Code Highlighter .




Now for each separate type of configuration we need to write data retrieval functions:



/**

*

*

*/

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);

}



* This source code was highlighted with Source Code Highlighter .




Now is the time to take care of the default settings. To do this, we write the following two functions:



/**

* -

*/

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;

}




* This source code was highlighted with Source Code Highlighter .




Since in the web server structure there may actually be several configurations in different blocks (for example, an entry can be created for the directory configuration in the virtual host structure in the <Directory> </ Directory>, <Files> </ Files> or <Location > </ Location>, and, at the same time, a .htaccess file can be created), we may need to define our own rules for combining these configs. Thank God, the Apache API allows us to do this quite simply by defining the rules for server and directory settings. To do this, simply define the following functions:



/**

*

*

*

*/

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;

}




* This source code was highlighted with Source Code Highlighter .




However, keep in mind that merge rules should be applied only if the server rules do not suit you by default, i.e. you want to change them. Otherwise, just do not write these functions and everything will happen by itself.



Definition of configuration directives (commands)





After we have defined the structure of our configuration and the function for its filling, the processing union, it is not at all superfluous to define the directives themselves, so they will have to be specified in the configs:



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}

};




* This source code was highlighted with Source Code Highlighter .




We’ll stop here a bit to see the various features that the Apache web server API provides for handling configuration directives.



The definition of the directive structure always ends with a NULL structure, and the implementation macros are defined in the http_config.h file. AP_INIT_FLAG is one of many such macros, in general, the non-NULL structure contains the following elements:







Often the standard functions that implement the command provided by the Apache API are enough to solve the problem. These functions are also described in the http_config.h file. Here they are:



/**

*

*/

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);




* This source code was highlighted with Source Code Highlighter .




Macros defining the data directive type can be the following:







Macros defining the scope of a directive are:







Now that we have decided to configure our module, we can proceed directly to the development of its functionality.



Request Handlers





You can create your own Apache request handlers, which will issue the appropriate documents for the corresponding request using the SetHandler and AddHandler directives.



Since data handlers send data directly to the output, you need to ensure that the response headers are sent before the content is rendered. This can be done using the send_http_header () function. You can also set your own headers.



Any handler function accepts, as an argument, a pointer to the request structure. Return values ​​can be:







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;

}




* This source code was highlighted with Source Code Highlighter .




Now we can add our handler to the web server config:



 <Location / uptags-handler-example>
         SetHandler uptags-handler
 </ Location>




In fact, this module has nothing to do with the module that we write, so we do not need this functionality. We just looked at an example of writing our handler. This task may arise if you want, for example, to write your website content management system as a module to a web server. Or you will embed your interpreted language.



To solve the problem we have set, the work with filters is more suitable, which we will consider in the next section.



Filters





Filters allow you to manipulate the current content, which Apache gives on request. Just the right place to solve our problem.



Here you should deviate a little from the solution itself and talk a little about the principles of functioning of the web server and how it processes and sends the data.



The Apache 2 filter architecture is the main innovation of this web server, which, along with its power and versatility, has a negative connotation in the matter of learning and understanding the concept of baskets and brigades (buckets and brigades).



Apache's low-level API allows you to manipulate “baskets” and “brigades” directly. Below we will look at the principles of how this is done.



What is the "basket" and "brigade"?





A recycle bin is a data container that can contain data of any type. Nevertheless, the most common type is a block of memory, which may already contain both a file on disk and a stream of data transmitted from one source to another, such as a separate program.



In principle, there are several different types of “baskets” - with different types of data, as well as “baskets” with metadata, such as an end-of-day sign (EOS) or FLUSH- “basket”, which flushes the data buffer to the output stream.



A brigade is a container that combines a cycle of "baskets" and a subdivision that is passed from filter to filter.



Baskets and brigades provide efficient manipulation of memory blocks typical of filter applications.



To manipulate the baskets and brigades, Apache 2 provides us with a set of data types, structures, macros, and API functions described in the apr_buckets.h file. Their description can be found here .



It should be noted that in order to solve the task, we need to create two filters - input (in) and output (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);

}



* This source code was highlighted with Source Code Highlighter .




Naturally. we also need to implement our uptags_tags_to_uppercase () function, which, in fact, does all the necessary data manipulations, and place its declaration before declaring filters:



/**

*

*/

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]);

}

}

}



* This source code was highlighted with Source Code Highlighter .




We have written a fairly simple function that in real life will not always behave as it should. But our task is quite simple, so for testing purposes, it is quite suitable for us.



At this, the development of filters for our module can be considered complete and we can proceed to the final stage.



Module definition





After we have defined all the functions of our module, we need to “explain” to the web server how to use them.



To do this, it is enough to list them in the corresponding cells of the Apache module structure:



/**

* 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 /* */

};



* This source code was highlighted with Source Code Highlighter .




As we can see, we lack only a small function uptags_register_hooks, which will take over the work of registering all the handlers we need for this module.



Let's write it:



/**

*

*/

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);

}



* This source code was highlighted with Source Code Highlighter .




You can download the full source of the module from here .



Compiling and building a module





The easiest way to compile and connect a module is to assemble it as a DSO (Dynamically Shared Object). To do this, run the command in the source directory:



  apxs2 -c mod_uppertags.c 




If the compilation was successful, the .libs folder should appear, and in it the file mod_uptegs.so. This file should be copied to the directory with Apache loadable modules. On Debian (Ubuntu) Linux, this is usually the / usr / lib / apache2 / modules / directory



Now in the main Apache config you need to enter directives:



 LoadModule uptags_module /usr/lib/apache2/modules/mod_uptags.so

 <IfModule mod_uptags.c>
     Uptagsengine on
 </ IfModule>




Next, restart the web server:



 /etc/init.d/apache2 restart




Now you can check the result by viewing any page that your web server gives out.



It is also useful to write the contents of the config.m4 file:



 APACHE_MODPATH_INIT (uptags)
 APACHE_MODULE (uptags, reformatting all HTML tags to upercase,,, no)
 APACHE_MODPATH_FINISH




This will allow you to automatically compile the module with a common Apache build. In order to build a module statically when building an entire web server, it is now sufficient to specify the option --enable-uptags when running the command ./configure:



 ./configure --enable-uptags




And, of course, do not forget to give other people the necessary instructions, describing them in the README file.



Links (in English)





Configuration for Modules

Introduction to Buckets and Brigades

Basic Resource Management in Apache: the APR Pools

Request Processing in Apache

Apache Portable Runtime (APR) Documentation



Conclusion





As we have seen, developing your own modules for the Apache web server is not so difficult. It is enough to arm ourselves with patience and references to the relevant documentation, and the task becomes quite solvable.



I hope that this guide will help the reader become more familiar with the Apache API for writing modules and become the starting point for this interesting matter.



Good luck in development!



PS This is a cross-posting of the original article from my blog.

Source: https://habr.com/ru/post/50909/



All Articles