Running Drupal, Wordpress and Zend Framework on YAWS

YAWS is an elegant, high performance webserver written in Erlang that can also run PHP via FastCGI. (Actually, it can run anything that has fcgi, but this article is about PHP.)

The only problem is that PHP frameworks (like Zend Framework, Symfony, Kohana, CodeIgniter, CakePHP) and complex PHP applications (like Drupal, WordPress, Joomla, osCommerce, etc) use Apache's mod_rewrite from a .htaccess file to direct all requests to their index.php entry point and handle the URL routing ("SEO friendly URLs") themselves -- but at the same time they still rely on Apache to serve static files like images, JavaScript and CSS files.

The logic goes "see if the URL points to a file. If yes, let apache serve it; if not, call index.php".

In lighttpd, the same can be achieved with the url.rewrite-if-not-file directive.

As for YAWS, I've coded a quick solution to emulate this kind of redirect using an "appmod" (an Erlang plugin). It's this one file, fileorfcgi.erl:

Solution #1 (more flexible):

% File: fileorfcgi.erl % Compile inside YAWS with: c(fileorfcgi). -module(fileorfcgi). -author(erldev@kkovacs.eu). -export([out/1]). -include("../../include/yaws_api.hrl"). out(A) -> FullPathFileName = A#arg.docroot ++ A#arg.pathinfo, IsRegularFile = filelib:is_regular(FullPathFileName), if IsRegularFile == true -> [ {page, A#arg.server_path} % Let Yaws serve static files ]; true -> yaws_api:call_fcgi_responder(A, [ {extra_env, [ {"SCRIPT_FILENAME", A#arg.docroot ++ "/index.php"} ]} ]) end.

Here's an example yaws.conf to use the appmod:

# The path where fileorfcgi.beam is located: ebin_dir = /Users/kkovacs/dev/erlang/yaws <server localhost> # HTTP server parameters for YAWS listen = 0.0.0.0 port = 8080 # Where to find the fcgi server fcgi_app_server = 127.0.0.1:9000 # From YAWS 1.89. To call PHP the right way if a file is called directly: phpfcgi=127.0.0.1:9000 # document root for the server (where "index.php" resides) docroot = /Users/kkovacs/Sites/kkeu2/public # Finally, turn on our appmod: appmods = </, fileorfcgi> </server>

This is the original solution that I used. It's advantage is that it can be used not only for "/", but one could configure it only to URLs starting with "/foo/...", by using a different "appmod" command in yaws.conf.

Also, one could integrate Erlang code very easily by adding new clauses to the out/1 function, like this: "out(A#arg.pathinfo = "foo") -> ...;" .

In the YAWS console (the erlang shell), you need to compile the .erl file to a .beam file using the command: c(fileorfcgi). (The ending "." is important!)

Solution #2 (simpler & more efficient):

Some time later I realized we could do even better, if we utilize yaws's 404 error handler to call the PHP entry point when no file was found in the filesystem:

% File: fileorfcgi.erl % Compile inside YAWS with: c(fileorfcgi). -module(fileorfcgi). -author(erldev@kkovacs.eu). -export([out404/3]). -include("../../include/yaws_api.hrl"). out404(Arg, _GC, _SC) -> yaws_api:call_fcgi_responder(Arg, [ {extra_env, [ {"SCRIPT_FILENAME", Arg#arg.docroot ++ "/index.php"} ]} ]).

The matching yaws.conf differs only in one line, the "errormod_404":

# The path where fileorfcgi.beam is located: ebin_dir = /Users/kkovacs/dev/erlang/yaws <server localhost> # HTTP server parameters for YAWS listen = 0.0.0.0 port = 8080 # Where to find the fcgi server fcgi_app_server = 127.0.0.1:9000 # From YAWS 1.89. To call PHP the right way if a file is called directly: phpfcgi=127.0.0.1:9000 # document root for the server (where "index.php" resides) docroot = /Users/kkovacs/Sites/kkeu2/public # Finally, hook our appmod on the "file not found" handler: errormod_404 = fileorfcgi </server>

And that's all!

I hope it helps somebody out, since I believe that mixing Erlang and PHP is a very, very potent combination.

One can do the "usual" web stuff comfortably from PHP, while having (possibly distributed) Erlang code deal with the "persistent" stuff (for example, an in-memory session database), handle the interprocess communications, and also the "comet" functionality (keeping track of thousands of dormant connections that are waiting for an event to happen). Erlang is very good in these.

One more thing

Unix doesn't let Yaws bind to ports lower than 1024 unless it's running as root (which one shouldn't). So, one should use the following iptables command to forward port 80 to 8080 on the firewall:

iptables -A PREROUTING -t nat -p tcp --dport 80 -j REDIRECT --to-port 8080

Shameless plug: I'm a freelance software architect (resume), have a look at my services!