Monday, 9 June 2008

Easier in Windows?

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.

Friday, 6 June 2008

First IIS 7 module

Hi,

To see what I can do with the example they gave here:

http://blogs.iis.net/rickjames/archive/2007/02/26/iis-7-hello-world-module-walkthrough.aspx

I want to see if I can create an IIS 7 module for JPEG files, to allow image flipping, then maybe some other things later on, eg:
http://mywebsite.com/picture.jpg?flip=true

What I will need to establish (I'm guessing) is:
* How we can read the GET string into the module
* How we can send the headers and mime types back in the response
* How to establish what directory to stream the image from
* How to load the .NET image libraries


Then download a starter pack for IIS 7 modules (should give me a head start)
http://www.iis.net/downloads/default.aspx?tabid=34&g=6&i=1302

This contains a vsi file, which (assuming you have Visual Studio installed), adds a template for a IIS 7 module.

Opening up the template it explains where to start:

A managed module class derives from the System.Web.IHttpHandler interface, and implements the following methods:

Init. This method is called when the module instance is initialized. It typically performs any instance-level initialization (such as reading configuration), and registers one or more of the module's methods for the desired request processing events.
Dispose. This method is called when the module instance is disposed. It typically does no work, unless the module needs to clean up any un-managed resources, or wants to do an early cleanup of its state before it is garbage collected.
ProcessRequest:
IsReusable:
Now for the development work

1. Create a project using the IIS7 Module template
2. Change the namespace and class names
3. Add a project reference for System. Drawing so that we have some imaging tools to play with.


And the code:

Firstly, setup our includes.


using System;
using System.Collections.Generic;
using System.Text;
using System.Web;

// Extra libs for this module
using System.IO;
using System.Drawing;

Start our class, implementing the IHttpHandler Interface

namespace ashaw.modules.iis7
{
public class AntsIIS7ImageFilter : IHttpHandler
{
#region IHttpHandler implementation - key
public AntsIIS7ImageFilter() { }
public String ModuleName { get { return "AntsII7ImageFilter"; } }
public void Init(HttpApplication application) { }
public void Dispose() { }
public bool IsReusable { get { return false; } }
#endregion

#region the actual request processing.
public void ProcessRequest (HttpContext context )
{
// Get the physical path that the image will be stored in
string path = context.Request.PhysicalPath;

// Get the file path
string filePath = context.Request.FilePath;

if (!File.Exists(path))
{
context.Response.AppendToLog("Can't find file:"+path);
context.Response.StatusCode = 404; // file does not exist
return;
}
try
{
// Setup the headers so that it knows its an image
context.Response.Headers["Content-Type"] = "image/jpeg";

// Create an image class for use!
Image myImage = new Bitmap(path);

// Check for flips
if (!String.IsNullOrEmpty(context.Request.Params["flip"]))
{
// Choose what kind of flip to do.
switch (context.Request.Params["flip"].ToLower())
{
case "x":
myImage.RotateFlip(RotateFlipType.RotateNoneFlipX);
break;
case "xy":
myImage.RotateFlip(RotateFlipType.RotateNoneFlipXY);
break;
case "y":
myImage.RotateFlip(RotateFlipType.RotateNoneFlipY);
break;
}
}

// write the image to the output stream.
myImage.Save( context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg ) ;

}
catch (Exception ex)
{
// Write a message in the IIS logs
context.Response.AppendToLog(ex.Message);

// Return an error to the client
context.Response.StatusCode = 500;
context.Response.Write(ex.Message + ex.StackTrace );
}
}

#endregion


Next step: Build the solution, which generates a DLL, then setup your web.config to look similar to this:


<configuration>
<system.webserver>
<handlers>
<add name="ants" path="*.jpg" verb="*" type="ashaw.modules.iis7.AntsIIS7ImageFilter" resourcetype="Unspecified" requireaccess="Script" precondition="integratedMode">
</add>
</handlers>
</system.webserver>
<configuration>


Create a /bin directory in your web application and copy across a jpg file to test with.

Then try:
yourapp.com/mypicture.jpg

















yourapp.com/mypicture.jpg?flip=x

















yourapp.com/mypicture.jpg?flip=xy

















Then give yourself a pat on the back!

It's not hard to see how to change this to work for png images, or detect what kind you're requesting, just change the image type for the Image.Save () and the Content-Type for the MIME header.

First Post

Hi!

I thought I'd set some standards here, no Windows XP installations, no crying about my personal misdemeanours. Instead here's some obscene class for having an audio player (for Mac OSX libs) in the background for a 3d engine, I'll post bits and pieces for the engine on from time to time.

implementation would be :


SlxAudioPlayer * player1 = new SlxAudioPlayer () ;
player1->PlayFile ( "/home/anthony/classic/dazed_and_confused.mp3" ) ;


/*
* slxaudioplayer.cpp
* slx
*
* Created by Anthony Shaw on 15/08/2006.
*
*/


#include "slxaudioplayer.h"

#include <AudioToolbox/AudioToolbox.h>

void SlxAudioPlayer::PlayFile (char * const fileName)
{
AudioFileID audioFile;

const char* inputFile = fileName;

FSRef theRef;
//xit
FSPathMakeRef ((const UInt8 *)inputFile, &theRef, NULL);
// xit
AudioFileOpen (&theRef, fsRdPerm, 0, &audioFile);

// get the number of channels of the file
AudioStreamBasicDescription fileFormat;
UInt32 propsize = sizeof(AudioStreamBasicDescription);
//xit
AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &propsize, &fileFormat);

printf ("playing file: %s\n", inputFile);

// lets set up our playing state now
AUGraph theGraph;
AudioUnit fileAU;

// this makes the graph, the file AU and sets it all up for playing
MakeSimpleGraph (theGraph, fileAU, fileFormat, audioFile);


// now we load the file contents up for playback before we start playing
// this has to be done the AU is initialized and anytime it is reset or uninitialized
Float64 fileDuration = PrepareFileAU (fileAU, fileFormat, audioFile);
printf ("file duration: %f secs\n", fileDuration);

// start playing
AUGraphStart (theGraph);

// lets clean up
AUGraphStop (theGraph);
AUGraphUninitialize (theGraph);
AudioFileClose (audioFile);
AUGraphClose (theGraph);
}

double SlxAudioPlayer::PrepareFileAU (AudioUnit &au, AudioStreamBasicDescription &fileFormat, AudioFileID audioFile)
{
//
// calculate the duration
UInt64 nPackets;
UInt32 propsize = sizeof(nPackets);
AudioFileGetProperty(audioFile, kAudioFilePropertyAudioDataPacketCount, &propsize, &nPackets);

Float64 fileDuration = (nPackets * fileFormat.mFramesPerPacket) / fileFormat.mSampleRate;

ScheduledAudioFileRegion rgn;
memset (&rgn.mTimeStamp, 0, sizeof(rgn.mTimeStamp));
rgn.mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
rgn.mTimeStamp.mSampleTime = 0;
rgn.mCompletionProc = NULL;
rgn.mCompletionProcUserData = NULL;
rgn.mAudioFile = audioFile;
rgn.mLoopCount = 1;
rgn.mStartFrame = 0;
rgn.mFramesToPlay = UInt32(nPackets * fileFormat.mFramesPerPacket);

// prime the fp AU with default values
UInt32 defaultVal = 0;

// tell the fp AU when to start playing (this ts is in the AU's render time stamps; -1 means next render cycle)
AudioTimeStamp startTime;
memset (&startTime, 0, sizeof(startTime));
startTime.mFlags = kAudioTimeStampSampleTimeValid;
startTime.mSampleTime = -1;
//au.SetProperty(kAudioUnitProperty_ScheduleStartTimeStamp,
// kAudioUnitScope_Global, 0, &startTime, sizeof(startTime));

return fileDuration;
}



void SlxAudioPlayer::MakeSimpleGraph (AUGraph &theGraph, AudioUnit &fileAU, AudioStreamBasicDescription &fileFormat, AudioFileID audioFile)
{
NewAUGraph (&theGraph);

ComponentDescription cd;

// output node
cd.componentType = kAudioUnitType_Output;
cd.componentSubType = kAudioUnitSubType_DefaultOutput;
cd.componentManufacturer = kAudioUnitManufacturer_Apple;

AUNode outputNode;
AUGraphNewNode (theGraph, &cd, 0, NULL, &outputNode);

// file AU node
AUNode fileNode;
cd.componentType = kAudioUnitType_Generator;
cd.componentSubType = kAudioUnitSubType_AudioFilePlayer;

AUGraphNewNode (theGraph, &cd, 0, NULL, &fileNode);

// connect & setup
AUGraphOpen (theGraph);

// install overload listener to detect when something is wrong
AudioUnit anAU;
AUGraphGetNodeInfo(theGraph, fileNode, NULL, NULL, NULL, &anAU);

AUGraphConnectNodeInput (theGraph, fileNode, 0, outputNode, 0);
AUGraphInitialize (theGraph);

}


And slxaudioplayer.h:

#ifndef SLXAUDIOPLAYERH
#define SLXAUDIOPLAYERH

#include <AudioToolbox/AudioToolbox.h>

class SlxAudioPlayer {
private:
double PrepareFileAU (AudioUnit &au, AudioStreamBasicDescription &fileFormat, AudioFileID audioFile);
void MakeSimpleGraph (AUGraph &theGraph, AudioUnit &fileAU, AudioStreamBasicDescription &fileFormat, AudioFileID audioFile);
public :
SlxAudioPlayer () {} ;
void PlayFile ( char * fileName ) ;
};
#endif