Opened 16 years ago

Last modified 12 years ago

#2565 assigned enhancement

Apache module support for WMS

Reported by: aeichner Owned by: tbonfort
Priority: normal Milestone: FUTURE
Component: WMS Server Version: unspecified
Severity: normal Keywords:
Cc: mdsmith, dmorissette, springmeyer, assefa, jmckenna

Description

I mentioned in the thread "Hiding overviews" on the mapserver-users list that I've modified MapServer to run as Apache module. Due to several requests of interested people I file that first approach here. So before anyone goes patching, compiling and testing it some things should be noted.

Running directly as module has some advantages. For some people the most important reason is speed. Since you stay in one process there's no reason for forking and starting a new program and throwing away all the work you've done (all the things that could be cached) for every single request like the good old CGI does. You also don't need to move your data through pipes or the like to a dispatcher process which itself sends it back to Apache like the FastCGI does. Because the module should be bound to a specific URI serving exactly one map file (you may serve another map file by another URI) you need to load and parse the map file only once on startup which speeds up things a lot especially if you have large map files and use the INCLUDE directive excessively. Some other things have been mentioned in the "ModMapServer" thread on the mapserver-users mailing list like simplified configuration.

I've created a module source which mostly consists of the loadMap() function from mapserv.c and a simple query string decoder (which does what cgiutil.c is normally responsible for). But it ignores the map= parameter and moves the map file loading into the startup code. A separate patch deals with the upcoming HTTP header problem. In CGI the headers, especially the "Content-Type" are send directly to the output stream while Apache wants it to be set in a field of it's request data structure. Basically I moved that in a macro and substituted the msIO_fprintf calls. So may be the macro should be moved to mapio.h and something like msIO_setContentType should be defined as that macro or simply the old msIO_fprintf depending whether we build as CGI or as module. The macro looks like happy hacking since we need access to the request_rec data structure and currently this is stored in the output handlers private context. That patch also modifies the configure.in and Makefile.in. And now it's time for a big fat warning: ONLY RUN IT WITH THE MPM PREFORK MODELL - MPM WORKER WILL CRASH DUE TO A RACE CONDITION Also, only the WMS portion has been modified and tested and can be used as an example. The other OGC parts may be done in much the same way, I believe. Ok, now you can go and try your luck. It has been successfully built and tested on the current 'testing' release of Debian GNU/Linux and on RedHat Enterprise Linux 5 with additional packages from Fedora Core 6 (gdal, geos for example). Please also see the module source file. It is heavily commented and will probably help you understand what's done.

Attachments (5)

mod_wms.c (12.8 KB ) - added by aeichner 16 years ago.
the Apache module
mod_wms.patch (29.3 KB ) - added by aeichner 16 years ago.
the HTTP headers patch
cgi.png (216.7 KB ) - added by aeichner 16 years ago.
The result from the CGI
module.png (252.4 KB ) - added by aeichner 16 years ago.
The result from the Apache module
mapserver-6.0.0-beta3-modwms.patch.gz (12.7 KB ) - added by aeichner 13 years ago.
cumulative patch against MS 6.0.0-beta3 that supports WMS and WFS

Download all attachments as: .zip

Change History (19)

by aeichner, 16 years ago

Attachment: mod_wms.c added

the Apache module

by aeichner, 16 years ago

Attachment: mod_wms.patch added

the HTTP headers patch

comment:1 by aeichner, 16 years ago

Probably I should mention how I build it (under Debian 'testing') just for completeness:

Get required packages:
$ sudo apt-get build-dep cgi-mapserver
$ sudo apt-get install apache2-mpm-prefork apache2-prefork-dev libapr1-dev libaprutil1-dev

now you should have everything you need and can go with the build:
$ tar xzf mapserver-5.0.2.tar.gz
$ cd mapserver-5.0.2/
$ patch -p1 < ../mod_wms.patch
$ cp ../mod_wms.c .
$ autoconf
$ ./configure --with-apr=/usr/bin/apr-1-config --with-apxs=/usr/bin/apxs2 --with-proj --with-gdal --with-tiff --with-postgis --with-ogr --with-geos --with-agg --with-freetype

if you don't have apxs but apxs2 simply edit the Makefile and change 'apxs' to 'apxs2' in the mod_wms.la and install targets.
$ make

this uses apxs/apxs2 -i to install the module for your setup
$ sudo make install

Now you have to configure Apache to load it and insert into your configuration something like...

<Location "/wms">

SetHandler "wms"
WMS_Map "/srv/maps/itasca.map"

</Location>

..and you're (hopefully) done. Restart Apache and hope for the best:
$ sudo /etc/init.d/apache2 restart

Now you can go and check it out:
$ wget -O /dev/stdout 'http://localhost/wms?service=WMS&request=GetCapabilities'

comment:2 by dmorissette, 16 years ago

Cc: dmorissette added

in reply to:  2 ; comment:3 by ctweedie, 16 years ago

aeichner, i know its early days, but do you have any kind of benchmarks on the wms server? I'd be very interested in the results, with the possibility of throwing funding behind it to get it over the line if needed

in reply to:  3 comment:4 by aeichner, 16 years ago

Well, we've ran a benchmark, but IMHO it's a little bit incomplete since we've only compared the CGI with the module mainly because the FastCGI crashed under heavy load for some unknown reasons. I've attached the results to the ticket. The benchmark was done with Compuware's QALoad and ran against a rather old Compaq Proliant DL-380 (Dual Xeon 733MHz / 1Gig RAM) delivering Itasca. The results look very similiar but take a look at the scale on the left. They differ a little bit. This benchmark should be treated as a hint what Apache integration could give us. But the most important thing is that it only loads the map file at startup and so gets independent of the map file's size which can be really important if you support many layers and/or make heavy use of the INCLUDE directive.

by aeichner, 16 years ago

Attachment: cgi.png added

The result from the CGI

by aeichner, 16 years ago

Attachment: module.png added

The result from the Apache module

comment:5 by springmeyer, 15 years ago

Cc: springmeyer added

comment:6 by assefa, 15 years ago

Cc: assefa added

comment:7 by jmckenna, 13 years ago

Cc: jmckenna added

comment:8 by tbonfort, 13 years ago

Cc: mdsmith added
Owner: changed from mapserverbugs to tbonfort
Status: newassigned

comment:9 by aeichner, 13 years ago

Since the ticket has been picket up, I want to add some

Pros:

  • config is read and parsed once, keeping nested/large configs fast
  • Database connections can be kept open
  • Projection objects can be prepared/cached
  • small objects like symbols and fonts with significant load time can be cached

and Cons:

  • config changes need special handling (i.e. server reload)
  • every webserver process uses it's own connection pool to connect to DB
  • MS code base currently does not distinguish between configuration and processing data (WMS-requests SRS/BBOX parameter set MapObj's extent/srs).

Also, when using a more complex styling the additional startup time introduced by the CGI gets non-fatal and with more modern hardware with plenty of RAM the effect seems to be negligible.

comment:10 by tbonfort, 13 years ago

I've tested and updated the patch for 6.0, and am getting relatively encouraging results. thanks aiechner for providing the initial implementation.

compared to the original patch, I think that the mapfile should be private to each apache process, and not shared between processes as is the case when using in the module per_dir_config, so that changes to e.g. extent are not propagated to all apache processes. The mapfile would thus be reparsed or copied from the master one each time a child process is created.

another point to account for is that the runtime substitution updates the map object itself , and thus would currently impact all the following requests handled by the same apache worker. this must be modified to make a copy of the mapObj in case substitution is applied.

the build process will have to be reworked to make it more general. apxs heavily relies on libtool, which we currently do not use in mapserver. also, should we link with the static or shared version of libmapserver (I don't know how the global connection pool would react in case we're using the shared version of the library)

there is still a non trivial amount of work to allow the use of other mapserver modes, i.e. not only ows ones.

adressing aeichner's cons:

  • the mapfile could be reloaded by the apache workers in case it's timestamp has changed since the last parse. I don't think this would be too difficult to implement.
  • I don't think we would want to share the connection pool, as we would usually need one connection per process. It should be documented that the database should be configured to allow at least as many connections as there are apache maximum number of workers
  • see above. if msCopyMap/msFreeMap is not too expensive, it might be feasible to have each request work on its own mapObj instead of reusing it between requests.

by aeichner, 13 years ago

cumulative patch against MS 6.0.0-beta3 that supports WMS and WFS

comment:11 by aeichner, 13 years ago

I've attached an updated version that includes WMS and WFS support and automatically reloads the MAP file if it's mtime changed. It still uses a per-dir-config and uses msCopyMap/msFreeMap to derive an object that can safely be modified. This patch introduces msIO_setHeader and msIO_sendHeaders in msio.c/.h so you can easily switch at compile time. To make the module use a shared libmapserver.so this must become runtime switchable. This version also no longer uses apxs. It's not fully integrated into the build process so you might need to change the Makefile to suit your needs. SetHandler is no longer required in the config.

Currently this is something that might be tried by interested people and might help Thomas to bring it on. To compile use (works at least on Debian 5 and 6 incl. oraclespatial):

$ tar xzf mapserver-6.0.0-beta3.tar.gz
$ cd mapserver-6.0.0-beta3/
$ gunzip -c ../mapserver-6.0.0-beta3-modwms.patch.gz |patch -p1
$ ./configure --prefix=/usr --with-wmsclient --with-oraclespatial=/opt/instantclient_10_2/ --with-postgis --with-ogr --with-gdal --with-proj --with-cairo --with-geos --with-xml2-config=/usr/bin/xml2-config --with-wfs
$ make mod_wms.so
$ install -m 0644 -t /usr/lib/apache2/modules/ mod_wms.so

comment:12 by aeichner, 13 years ago

It seems this also solved the multithreading issues. So I've compiled it --with-threads and tested it with apache-mpm-worker rendering a point layer from an Oracle spatial source. So far it works quite nicely: using Debian's default configuration Apache creates 1 master and 3 slave processes with a total of 56 threads establishing 29 database connections. Tested with two OpenLayers clients requesting the layer in tiles.

comment:13 by tbonfort, 13 years ago

With the release of 6.0, it is now too early to include this in the trunk. Once we've branched off 6.x, we can start moving forward with a tighter integration, given that the changes to the mapserver core files is minimal.

As for mapfile storing, I think I would go with a global apr_hash_t in mod_wms.c, the key being the mapfile path, and the content a mapObj*. the module->per_dir_config would thus only contain a char* mapfilepath. This means that each child process will parse the mapfile and have its own private copy of the mapObj.

For each request, we would then have to look at the passed parameters: if they contain an sld parameter or some CGI/runtime substitution directives, we would use a copy of the mapObj stored in the hashtable, otherwise we can use the original mapObj* as it will not be modified.

thoughts?

comment:14 by tbonfort, 12 years ago

I have committed a slightly modified version to trunk in r12907:

  • the module is called mod_mapserver
  • the httpd.conf parameter is called Mapfile
  • the mapserver module should support the full CGI params, not only the WMS ones

The mapObj is reused from one request to the next, this must be clarified. Usage of this module is considered experimental.

Note: See TracTickets for help on using tickets.