FPC and Apache Modules

From Free Pascal wiki
Jump to navigationJump to search

Documentation

The contents of the book "Writing Apache Modules with Perl and C" is available on-line:


http://162.105.203.19/apache-doc/1.htm


It´s a great reference. The preface and the first chapter are very good to understand what you are doing, and the C API is the same utilized by Free Pascal Apache modules.


In this section you will find a quick start guide, examples and technical details and other things to make the life of someone writting an apache module with pascal easier, but to understand the complete theory, take a look at the book above.


How the bindings work

A basic apache module created with Free Pascal will have a code similar to this:

library mod_hello;

{$ifndef win32}
  {$l hello_module.o}
{$endif}

uses httpd;

var
 hello_module: module; {$ifdef Unix}cvar; external; {$endif}
 default_module_ptr: Pmodule;

{$ifdef Win32}
exports
 hello_module name 'hello_module';
{$endif}

begin
  default_module_ptr := @hello_module;
  FillChar(default_module_ptr^, SizeOf(default_module_ptr^), 0);
  with default_module_ptr^ do
  begin
    version := MODULE_MAGIC_NUMBER_MAJOR;
    minor_version := MODULE_MAGIC_NUMBER_MINOR;
    module_index := -1;
    name := 'mod_hello.so';
    magic := MODULE_MAGIC_COOKIE;
  end;
end.

Apache uses a non-standard way to exchange information between the module and the server. Normally libraries export functions, but Apache instead expects a library that exports a variable. The variable is a structure with the module information. This variable needs to be filled with information as soon as the module is loaded.

Now, on Windows we can easely export a variable with Free Pascal. Just put it on the exports section. On Linux, Free Pascal doesn´t yet support exporting variables, so we need a alternative. To work around this we can create a assembler file that will export the variable and then link it into our code.

The file will look like this:

[SECTION .data]
global hello_module
hello_module dd 0

And the command line to compile this assembler code is: "nasm -f elf hello_module.asm"

There is also an easier way is to do it in Pascal, but it´s untested:

Var
  hello_module : module_type; public name 'hello_module';

How the bindings were created

The translated headers follow some simple guidelines. First all translated declarations remain at the exact same position as they were on the .h files, unless there is a incompatibility and they have to be moved. This is to make it easier to compare the .h files with the pascal translation and find possible mistakes on translation. Second, most files become include files, and some units are created to hold them. In particular, one unit was created for each apache library (httpd, apr, aprutil and apriconv).


The translation was all done manually with the help of "Replace All" from the Lazarus IDE. This is because automatic translators cannot deal with the heavy use of macros by apache headers, and passing the c preprocessor produces a code that is unrecognizably different from the original headers.


The Apache headers heavily rely on macros to work. In fact, almost every single declaration is a macro. Bellow are some of the most common macros and appropriate translations:


1 - AP_DECLARE

AP_DECLARE(void) ap_add_version_component(apr_pool_t *pconf, const char *component);


procedure ap_add_version_component(pconf: Papr_pool_t; const component: PChar);
 {$IFDEF WINDOWS} stdcall; {$ELSE} cdecl; {$ENDIF}
 external LibHTTPD name LibNamePrefix + 'ap_add_version_component' + LibSuff8;

The AP_DECLARE macro says that the calling convention for the function is stdcall on Windows and cdecl on other operating systems, so we need an IFDEF for this. It also says that the function name will have a prefix and a suffix on Windows. The prefix is "_" and the suffix is "@N", where N is a number multiple of 4. Tipically, the number is 4 times the number of parameters on the function, but there are some exceptions. To find out possible conflics on the function names, a software to list all exported functions of a dll was created, and later transformed into a Lazarus example called Libview.

2 - AP_DECLARE_NONSTD

AP_DECLARE_NONSTD(const char *) ap_set_string_slot(cmd_parms *cmd, 
                                                   void *struct_ptr,
                                                   const char *arg);


function ap_set_string_slot(cmd: Pcmd_parms; struct_ptr: Pointer; const arg: PChar): PChar;
 cdecl; external LibHTTPD name 'ap_set_string_slot';

This is the same as AP_DECLARE, but the calling convention is cdecl and no suffix or preffix is present on the function name.

3 - Other combinations

APR_DECLARE_NONSTD(int) apr_file_printf(apr_file_t *fptr, 
                                        const char *format, ...)


function apr_file_printf(fptr: Papr_file_t; const format: PChar;
 othres: array of const): Integer;
 cdecl; external LibAPR name 'apr_file_printf';

Another possible combination are macros starting with APR. AP means libhttpd, APR is libapr, APU is libaprutil and API is for libapriconv. Also notice that this function has a variable number of parameters, that in pascal is represented by an array of const.

4 - AP_DECLARE_HOOK

AP_DECLARE_HOOK(int,pre_connection,(conn_rec *c, void *csd))

type
  ap_HOOK_pre_connection_t = function (c: Pconn_rec; csd: Pointer): Integer; cdecl;

procedure ap_hook_pre_connection(pf: ap_HOOK_pre_connection_t; const aszPre: PPChar;
 const aszSucc: PPChar; nOrder: Integer);
 {$IFDEF WINDOWS} stdcall; {$ELSE} cdecl; {$ENDIF}
 external LibHTTPD name LibNamePrefix + 'ap_hook_pre_connection' + LibSuff16;

The hooks use one of the most strange declarations possible. All hook are represent a function like ap_hook_pre_connection, with the same 4 parameters, and no return. The difference between them is the first parameter, that has a different function type in each hook function.

AP_DECLARE_HOOK macro receives 3 parameters. The first is the return type of the hook function type. The second plus the prefix ap_hook_ form the name of the function, and the third are the parameters of the hook function.

Possible problems

1 - Apache version number

Apache expects that the module is compiled specifically for the exact same version as the server. Because we have a single translation for all 2.0.x versions, you can just change the MODULE_MAGIC_NUMBER_MAJOR to what Apache expects and it should work. On the file ap_mmn.inc you can find a list of apache magic numbers for almost all 2.0.x Apache versions.

2 - Using string functions

note := apr_pstrcat(p, [PChar('x_child_init('), sname, PChar(')'), nil]);

Be careful when using apr_pstrcat and other string function. Note that the unlike c, you have to enclose the extra parameters with a [ ]. Also note that the extra parameters are untyped, so your code may compile, but crash the server if you use pascal strings. To avoid this, make sure to cast all strings to PChar when using c string functions.

3 - Loading 2 or more pascal Apache modules

By default, Free Pascal (actually, the linker used by Free Pascal) creates libraries that are not relocatable. This means that if two Free Pascal generated libraries are loaded by a program, there will be a conflict, and the second library will fail to be loaded.

To fix this you must pass the -WR option to the compiler. On Lazarus you can add this option on "Compiler Options" -> "Other" -> "Custom Options". Using Lazarus 0.9.16 and FPC 2.0.2 that comes with it on Windows XP, even passing -WR option the libraries will fail to load at the same time. Using Free Pascal 2.0.2 installed separately from Lazarus, and compiling from the command line, the libraries will work correctly. This problem may be solved with the release of 2.0.4, or not.

There is more detailed information here.


Debugging a module

If there are problems on your module that make Apache crash, or fail to start, it won´t produce any error.log. To debug this problems, run apache from the command line using the Gnu Debugger (gdb).

First, start gdb pointing to the executable. The command is "gdb Apache.exe" on Windows. And then type the command "run" on gdb console. Now Apache will show error messages on the terminal.

Screenshot

Authors

Felipe Monteiro de Carvalho


Special thanks to:


Giovanni Premuda for making this possible thought the Lazarus Bounties


Moacir Schmidt for helping me understand how a apache module works.

License

Download

http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=197409

Status: Beta

Installation

Hello World Module

This is an example apache module that will write an internet page when the user enters in a specific directory on the server. Follow the instructions bellow to compile and setup the module.


1 - To set the build environment for this module you can either create a new library or open the example project that comes with the bindings. If it´s a new project, don´t forget to to add all apache folders to your "Other Unit Files" and "Other sources" on the "Compiler Options" dialog on the Lazarus IDE, or add them on the command line. The folders are:

httpd_2_0/
httpd_2_0/apr/
httpd_2_0/apriconv/
httpd_2_0/aprutil/

Also, on the "Project Options" dialog, set the target file name to mod_hello.so


2 - Next compile the module and copy mod_hello.so file to the modules folder of your Apache installation.


3 - Open httpd.conf file of your server and add the following lines on a suitable place:


LoadModule test_module modules/mod_hello.so

<Location /pmod>
    SetHandler testapache-handler
    Allow from all
</Location>


4 - Restart your Apache server, and use a web browser to access the location: myserver/pmod


In the case of a local server this will be: localhost/pmod

If you see a webpage on this location, then the module is working perfectly.

{*******************************************************************
*  Test library of the Apache Pascal Headers
*******************************************************************}
library mod_hello;

{*******************************************************************
*  The mode must be objfpc on this unit because the unix code uses
* some extensions introduced on Free Pascal
*******************************************************************}
{$mode objfpc}{$H+}

{$IFDEF WIN32}
  {$DEFINE WINDOWS}
{$ENDIF}

{*******************************************************************
*  Assembler code to export variables on UNIXes
*******************************************************************}
{$IFNDEF WINDOWS}
  {$l apache_module.o}
{$ENDIF}

uses SysUtils, httpd, apr;

var
 test_module: module; {$ifdef Unix}cvar; external; {$endif}
 default_module_ptr: Pmodule;

{*******************************************************************
*  Free Pascal only supports exporting variables on Windows
*******************************************************************}
{$ifdef WINDOWS}
exports
 test_module name 'test_module';
{$endif}

{*******************************************************************
*  Handles apache requests
*******************************************************************}
function DefaultHandler(r: Prequest_rec): Integer; cdecl;
var
  RequestedHandler: string;
begin
  RequestedHandler := r^.handler;

  { We decline to handle a request if hello-handler is not the value of r->handler }
  if not SameText(RequestedHandler, 'testapache-handler') then
  begin
    Result := DECLINED;
    Exit;
  end;

  { The following line just prints a message to the errorlog }
  ap_log_error(APLOG_MARK, APLOG_NOERRNO or APLOG_NOTICE, 0, r^.server,
   'mod_hello: %s', 'Before content is output');

  { We set the content type before doing anything else }
  ap_set_content_type(r, 'text/html');

  { If the request is for a header only, and not a request for
   the whole content, then return OK now. We don't have to do
   anything else. }
  if (r^.header_only <> 0) then
  begin
    Result := OK;
    Exit;
  end;

  { Now we just print the contents of the document using the
   ap_rputs and ap_rprintf functions. More information about
   the use of these can be found in http_protocol.inc }
  ap_rputs('<HTML>' + LineEnding, r);
  ap_rputs('<HEAD>' + LineEnding, r);
  ap_rputs('<TITLE>Hello There</TITLE>' + LineEnding, r);
  ap_rputs('</HEAD>' + LineEnding, r);
  ap_rputs('<BODY BGCOLOR="#FFFFFF">' + LineEnding ,r);
  ap_rputs('<H1>Hello world</H1>' + LineEnding, r);
  ap_rputs('This is the first Apache Module working with the new binding from Free Pascal' + LineEnding, r);
  ap_rprintf(r, '<br>A sample line generated by ap_rprintf<br>\n', []);
  ap_rputs('</BODY></HTML>' + LineEnding, r);

  { We can either return OK or DECLINED at this point. If we return
         * OK, then no other modules will attempt to process this request }
  Result := OK;
end;

{*******************************************************************
*  Registers the hooks
*******************************************************************}
procedure RegisterHooks(p: Papr_pool_t); cdecl;
begin
  ap_hook_handler(DefaultHandler, nil, nil, APR_HOOK_MIDDLE);
end;

{*******************************************************************
*  Library initialization code
*******************************************************************}
begin
  default_module_ptr := @test_module;
  FillChar(default_module_ptr^, SizeOf(default_module_ptr^), 0);
  with default_module_ptr^ do begin
    version := MODULE_MAGIC_NUMBER_MAJOR;
    minor_version := MODULE_MAGIC_NUMBER_MINOR;
    module_index := -1;
    name := 'mod_hello.so';
    magic := MODULE_MAGIC_COOKIE;
    register_hooks := @RegisterHooks;
  end;
end.

CVS

Not yet available.


Bug Reporting

Tests are necessary to verify if all functions and structures are declared properly. Tests were done on Windows and Linux operating systems. It is necessary to test if the modules work correctly in other operating system.

You can post Bug Reports here:

Change Log

  • 16.07.06 Apache headers version 0.1 released
  1. httpd 2.0.58 almost fully translated.
  2. mod_example module translated to pascal

Help

Please send help requests to the Free Pascal mailling list.

External Links