Friday, July 03, 2009

 

Apache 2.2.x modules with FreePascal (Win32)

Continuing with this series of articles, I will start with the FreePascal version using plain command line FreePascal 2.2.4 for Win32, and later I'll try to create a module for Apache 2.2.x on Linux.

Before copying-pasting this example, I recommend you to read this Wiki to get a detailed knowledge of the problem.

The code

Open your favorite editor and type this code, then save it as "mod_helloworld.pp":


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

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

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

{$define Apache2_0}

uses SysUtils, httpd {$ifndef Apache1_3}, apr{$endif};

var
test_module: module; {$ifdef Unix} public name 'test_module'; {$endif}
default_module_ptr: Pmodule;

const
MODULE_NAME = 'mod_helloworld.so';
HANDLER = 'helloworld-handler';

{****************************************************
* 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(MODULE_NAME, 54, APLOG_NOERRNO or APLOG_NOTICE,
{$ifndef Apache1_3}0,{$endif} r^.server,
'mod_hello: %s', [PChar('Before content is output')]);

{ We set the content type before doing anything else }
{$ifdef Apache1_3}
r^.content_type := 'text/html';
// ap_send_http_header(r);
{$else}
ap_set_content_type(r, 'text/html');
{$endif}

{ 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 />' + LineEnding, []);
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
****************************************************}
{$ifdef apache1_3}

procedure hw_init(s: PServer_rec; p: PPool); cdecl;
begin
end;

var
hw_handlers: array[0..0] of handler_rec =
(
(content_type: 'hw_app'; handler: @DefaultHandler)
);

{$else}

procedure RegisterHooks(p: Papr_pool_t); cdecl;
begin
ap_hook_handler(@DefaultHandler, nil, nil, APR_HOOK_MIDDLE);
end;

{$endif}

{***************************************************
* Library initialization code
****************************************************}
begin
default_module_ptr := @test_module;
FillChar(default_module_ptr^, SizeOf(default_module_ptr^), 0);

{$ifdef apache1_3}
STANDARD_MODULE_STUFF(test_module);

with test_module do
begin
name := MODULE_NAME;
init := @hw_init;
handlers := hw_handlers;
end;
{$else}
STANDARD20_MODULE_STUFF(test_module);

with test_module do
begin
name := MODULE_NAME;
register_hooks := @RegisterHooks;
end;
{$endif}
end.


Then stop Apache 2 service and compile the file with this command:


fpc -WR -XX -Xs mod_helloworld.pp


Note: The -WR parameter tells the compiler to add relocatable code to the dll. Without this, you cannot load two dlls compiled with FPC with the same executable.

After compiling the program you will get the file mod_helloworld.dll, now you have to rename the file to mod_helloworld.so and copy to your Apache/modules directory, usually in C:\Program files\Apache Software Foundation\Apache2.2\modules.

Now add this to your httpd.conf file in C:\Program files\Apache Software Foundation\Apache2.2\conf:


LoadModule test_module modules/mod_helloworld.so
<Location /hello>
SetHandler helloworld-handler
</Location>


I don't have a Linux box here to test this (shhh, I'm at work), I'll try to compile it this weekend at home in Linux and FreeBSD.

Wednesday, July 01, 2009

 

Apache 2.2.x modules with Delphi II

After my "Apache 2.2.x modules with Delphi" article, I received many requests for an example, and here it is.


Creating the directory structure


Create a directory where your Delphi project will reside, for example C:\myModule, then copy the files HTTPD2.pas, ApacheTwoApp.pas and ApacheTwoHTTP.pas from your Delphi/Source directory to the newly created directory.

Patching HTTPD2.pas

Open the file c:\myModule\HTTPD2.pas and replace/fix the lines as shown in my previous post.

The code

This is a very small HelloWorld WebBroker example, it is composed of only three files, mod_helloworld.dpr, main.pas and main.dfm

mod_helloworld.dpr


library mod_helloworld;

uses
HTTPD2 in 'HTTPD2.pas',
ApacheTwoApp,
WebBroker,
main in 'main.pas' {WebModule1: TWebModule};

{$E so}

{$R *.res}

exports
apache_module name 'helloworld_module';

begin
Application.Initialize;
Application.CreateForm(TWebModule1, WebModule1);
Application.Run;
end.


main.pas


unit main;

interface

uses
SysUtils, Classes, HTTPApp,
HTTPProd;

type
TWebModule1 = class(TWebModule)
procedure WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
public
{ Public declarations }
end;

var
WebModule1: TWebModule1;

implementation

{$R *.dfm}

procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
Response.Content := 'Hello from Apache Module!';
end;

end.


main.dfm


object WebModule1: TWebModule1
OldCreateOrder = False
Actions = < default =" True" name =" 'WebActionItem1'" pathinfo =" '/test'" onaction =" WebModule1WebActionItem1Action">
Left = 679
Top = 385
Height = 150
Width = 215
end


Compilling the module

Save the three files in c:\myModule and open the .dpr with Delphi, then compile to your Apache2 modules directory, usually in C:\Program files\Apache Software Foundation\Apache2.2\modules.

Configuring Apache2

If the Apache 2.2 service it's running, please stop it, then open your C:\Program files\Apache Software Foundation\Apache2.2\conf\httpd.conf file and look for the "LoadModule" entries, add this after the last entry:


LoadModule helloworld_module modules/mod_helloworld.so


And below this line add this:


<Location /test>
SetHandler mod_helloworld-handler
</Location>


Testing the module

Restart your Apache 2.2 service and open a web browser, then type http://localhost:8080/test, it should show a page with the text "Hello from Apache Module!".

That's it.

Wednesday, May 20, 2009

 

FindFirst...FindClose in Linux

Last week a customer asked for one application we sell for sending a work list from a Radiology Information System to different equipment called Modalities in medical terminology, they are CT, CR, MR and so on.

The application is called Modality WorkList Server, and works as a Windows Service. It's function is reading XML files from a shared directory, convert to Dicom format and place in a specific directory. Also it opens a socket port for listening incoming requests from modalities, matches the Dicom files with the request and send the files to the origin. The final product of this process is a list containing patient data a radiology technician can see in the modality screen.

All went well, until our customer told us they use Linux!. Fortunately, the application was written taking care it should compile with FPC in the future, so, after a couple of IfDefs it compiled and started running on Linux.

The sad news, were, after ten minutes it stopped working.

What went wrong?

The first think I did was to add logs everywhere trying to catch the focus of the problem. And found it was stopping in a procedure similar to this:


if FindFirst(Edit1.Text, FileAttrs, sr) = 0 then
begin
repeat
...
...
until FindNext(sr) <> 0;
FindClose(sr);
end;


If you look at the FindFirst function in Delphi help, it has an example very similar to this, if FindFirst finds files that matches what I'm seeking, then a loop traverses the file list, and at the end, a FindClose must be issued, but only if FindFirst returned 0.

In Linux, on the other hand, FindClose must be called every time a FindFile is executed, it doesn't matter if it found anything or not. This is the code:


if FindFirst(Edit1.Text, FileAttrs, sr) = 0 then
begin
repeat
...
...
until FindNext(sr) <> 0;
end;
(* In Linux, FindClose must be called always after FindFirst *)
FindClose(sr);


Internals

To those who want to know why the FreePascal Linux version is different than Delphi/FPC windows counterpart, I'll try to explain here.

To interact with the Linux file system, FreePascal used to use an unit named OldLinux, which contains the function Glob(), in charge of finding files. This function returns a pointer to a glob structure (the file list buffer), it doesn't matter if there are found files or not, this pointer is always allocated. After calling Glob, always must be called GlobFree, to free the result of Glob call.

The Glob function of FPC, is a wrapper to the Glob function of GLibC's Glob function, that's why it is implemented this way, and it's correct.

In Linux, if you want to search for files, you use the "find" command, again, a wrapper to the GlibC Glob function. To implement FindFirst/FindNext/FindClose in FPC's RTL, the developers created them as wrappers to Glob, and again, this was a wise decision, at least to me. The only problem, is that it's usage is different in Linux than Windows, so, if you have to use this functions in a program that must run in both environments, just take care to write the call to FindClose in the right place for each operating system.

This page is powered by Blogger. Isn't yours?