So, maybe I've been slated by my Linux colleagues, but I wanted to see how simple that last example was in Linux.
My assumptions point towards a C (maybe even C++ if I'm lucky) compiled .so library with some handy headers which differ massively from version to version of apache. But the IIS module was a bit toooo easy, so I'm up for the challenge.
OK, so the last example wasn't an actual 'module', which I will demonstrate later, but a mere handler. So maybe I could quickly knock up a handler for CGI grabbing a Perl library for ImageMagick, but that again wouldn't be the same kind of performance shown by the .NET library I demonstrated in the last example.
Instead, this module will have:
* A proper HTTPD module, using the API
* Using the http://www.imagemagick.org/script/magick-core.php ImageMagick C API
* A comparable speed to the IIS module (probably much faster)
After a bit of surfing, we have a contender: http://threebit.net/tutorials/apache2_modules/tut1/tutorial1.html
So, we already had Apache 2.2 installed on this test (RHEL 5.1) machine, get rid of that:
[root@cpetest ~]# rpm -e httpd mod_perl mod_ssl mod_python php webalizer httpd-manual
Now, following the tutorial (roughly) from the beginning,
wget http://apache.rmplc.co.uk/httpd/httpd-2.0.63.tar.gz
tar xzf httpd-2.0.63.tar.gz
mkdir threebit-tutorials
cd threebit-tutorials/
export TUTORIAL_HOME=`pwd`
wget http://threebit.net/tutorials/tutorials.tar.gz
tar xzf tutorials.tar.gz
cd ~/httpd-2.0.63
./configure --prefix=$TUTORIAL_HOME/apache2 --enable-so
make && make install
cd ../threebit-tutorials/apache2
ls conf/
So here we have a seemingly working apache compile, put quite horribly in the root home directory (my fault for taking this tutorial a bit too literally)
[root@cpetest apache2]# bin/apachectl start
[root@cpetest apache2]# lsof -i tcp:80
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
httpd 32010 root 3u IPv6 763802 TCP *:http (LISTEN)
httpd 32011 nobody 3u IPv6 763802 TCP *:http (LISTEN)
httpd 32012 nobody 3u IPv6 763802 TCP *:http (LISTEN)
httpd 32013 nobody 3u IPv6 763802 TCP *:http (LISTEN)
httpd 32014 nobody 3u IPv6 763802 TCP *:http (LISTEN)
httpd 32015 nobody 3u IPv6 763802 TCP *:http (LISTEN)
So, yes it does work.
Right, so now we compile a quick test, looking down the tutorial and picking out my key points,
1. Edit a C file, put in a struct called 'module' to make it look like a natural part of the code, place in a callback (usual C module-ness) then fill in the guts of the callback.
2. Compile the code into a shared library which should build into the correct place (the configure, make scripts that the example has provided ensure that)
3. Change the httpd config to see this new super module that currently does nothing.
Looking at the callback, it takes a pointer to a request_rec struct, which after some simple digging, has these fields:
http://www.temme.net/sander/api/httpd/structrequest__rec.html
So, straightfoward enough, changing the first line in the callback to check everything works as it should,
// Send a message to stderr (apache redirects this to the error log)
fprintf(stderr,"apache2_mod_tut1: A request was made to %s.\n",r->the_request);
So, continuing down the tutorial, lets try and build:
[root@cpetest tut1]# autoconf
configure.in:5: error: possibly undefined macro: AM_INIT_AUTOMAKE
If this token and others are legitimate, please use m4_pattern_allow.
See the Autoconf documentation.
configure.in:9: error: possibly undefined macro: AM_PROG_LIBTOOL
[root@cpetest tut1]#
Oh, something about libtool, crap, is it installed at least?
[root@cpetest tut1]# libtool --help
Usage: libtool [OPTION]... [MODE-ARG]...
Yep, so more searching, blah blah deprecated blah shows something about aclocal, so I run
[root@cpetest tut1]# aclocal -I /usr/share/aclocal/
And then autoconf works, great. We have a configure script to build something to tell the compiler to compile, straightforward enough.
automake -a
[root@cpetest tut1]# automake -a
configure.in: installing `./install-sh'
configure.in: installing `./missing'
configure.in:9: installing `./config.guess'
configure.in:9: installing `./config.sub'
Makefile.am: installing `./depcomp'
configure.in:9: required file `./ltmain.sh' not found
Ok, missing shell script which is standard in most builds, find it somewhere,
find / | grep ltmain.sh
cp /usr/share/libtool/ltmain.sh .
automake -a
Nothing, hoorah that fixed that step.
./configure --with-apache=$TUTORIAL_HOME/apache2
make
$TUTORIAL_HOME/apache2/bin/apxs -i -a -n tut1 libmodtut1.la
vi /root/threebit-tutorials/apache2/conf/httpd.conf
That shows that it did (as the tutorial said, load the module into the config)
Then, after that, apache started but threw a 403, having the apache root as inside the 'root' user home directory didn't help. Changing the default to /var/www/, touching a index.html inside that directory and ahoy, we have a 200.
[root@cpetest apache2]# wget http://localhost:21000/index.html
[root@cpetest apache2]# tail -n 1 logs/error_log
apache2_mod_tut1: A request was made to GET /index.html HTTP/1.0.
So, yes! It works! One quick test,
[root@cpetest apache2]# wget http://localhost:21000/index.html?flip=x
apache2_mod_tut1: A request was made to GET /index.html?flip=x HTTP/1.0.
Right, now onto the actual coding, looking at that original callback method, we have the last line returning 'DECLINED', looking at httpd.h, there is another return 'DONE' which I assume makes it stop processing modules, which is what we want for this image filter.
YES, I realise that ideally the module should be a handler, but I don't really have time for that at the moment.
So, we change the callback to see if this is a request we are interested in, otherwise let apache get on with its business.
Next, we install ImageMagick,
wget ftp://ftp.imagemagick.org/pub/ImageMagick/ImageMagick.tar.gz
tar xzf ImageMagick.tar.gz
cd ImageMagick-6.4.1/
./configure
make && make install
Now, we edit Makefile.am so that it knows to look in the right place for the ImageMagick Libs,
[root@cpetest tut1]# cat Makefile.am
## This is the shared library to be built
lib_LTLIBRARIES = libmodtut1.la
## Define the source file for the module
libmodtut1_la_SOURCES = mod_tut1.c
## Define that an include directory is required.
INCLUDES = -I@apache_dir@/include -I/usr/local/include/ImageMagick
So now the module code looks like this:
[root@cpetest tut1]# cat mod_tut1.c
#include "httpd.h"
#include "http_config.h"
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <wand/MagickWand.h>
static int mod_tut1_method_handler (request_rec *r)
{
int is_image = 0;
// decide the content type, is it a jpeg?
is_image = strcmp ( r->content_type, "image/jpeg" ) ;
if (!is_image) // Get on with the show
return DECLINED;
else { // Now we take over
// image magick gubbins.
MagickBooleanType status;
// setup our wand
MagickWand *magick_wand;
// Something about initialisation
MagickWandGenesis();
magick_wand=NewMagickWand();
// Super, read the file that apache would have done otherwise
status=MagickReadImage(magick_wand,r->filename);
// Check it worked
if (status == MagickFalse)
fprintf ( stderr , "error reading image" ) ;
else {
// Great. Flip the image!
MagickFlipImage ( magick_wand ) ;
status=MagickWriteImages(magick_wand,stdout,MagickTrue);
if (status == MagickFalse)
fprintf( stderr , "error writing to output" ) ;
magick_wand=DestroyMagickWand(magick_wand);
MagickWandTerminus();
}
return DONE; // return the end of response
}
}
static void mod_tut1_register_hooks (apr_pool_t *p)
{
ap_hook_handler(mod_tut1_method_handler, NULL, NULL, APR_HOOK_LAST);
}
module AP_MODULE_DECLARE_DATA tut1_module =
{
// Only one callback function is provided. Real
// modules will need to declare callback functions for
// server/directory configuration, configuration merging
// and other tasks.
STANDARD20_MODULE_STUFF,
NULL,
NULL,
NULL,
NULL,
NULL,
mod_tut1_register_hooks, /* callback for registering hooks */
};
[root@cpetest tut1]# /root/threebit-tutorials/apache2/bin/apachectl stop
Syntax error on line 232 of /root/threebit-tutorials/apache2/conf/httpd.conf:
Cannot load /root/threebit-tutorials/apache2/modules/libmodtut1.so into server: /root/threebit-tutorials/apache2/modules/libmodtut1.so: undefined symbol: NewMagickWand
That would mean that Apache doesn't load the imagemagick libraries itself (no suprise there), so apache has the 'LoadFile' option, by copying the
libMagickCore.so and libMagickWand.so files into the lib folder for Apache then having:
LoadFile lib/libMagickCore.so
LoadFile lib/libMagickWand.so
Before we load the new funky module
Then, it does manage to start!
Surfing for the same Winter.jpg I used in the other test, it gives the image the right way up, shame. But the logs show this:
sh: html2ps: command not found
So I installed that (no idea why)
Then we had this issue:
mod_tut1.c unknown 48 no decode delegate for this image format `/var/www/Winter.jpg'
convert -list format
Showed that ImageMagick wasn't even installed with JPEG support!! So I've quickly changed all this to work for BMP format.
Then we have the error 'error writing to output', which means my assumption of stdout being the output was a bit obvious!
Summary:
Well, aside from my naivety of libtool, automake and autoconf, the apache module API is very similar to IIS, you have the same sort of input (a callback is pretty much the same as a interface implement), the same sort of information (the correct info passed as a parameter) and some tools to compile and load the module as you need. All in all, both seem easy enough to setup, next time I'll develop something useful.