Fast web serving and mod_lisp mini-HOWTO

Started by lithper, March 12, 2008, 10:31:36 AM

Previous topic - Next topic

lithper

1. Introduction

In serving web pages. nL can (a) work as a standalone server for smaller applications (b) can be spawned in scores of copies because it's so light and/or (c) be dropped into a cgi-bin directory.

So as it is it's already more versatile than many other languages.



newLisp, however, is unlike the "big" ones in the sense that there is no immediate way to embed it into a web server, in the manner of mod_perl or the php apache module.

This is desirable because it can considerably decrease load on the server and response times - the application will remain persistent rather than restart each time.



There are several ways to solve the problem of serving newLisp scripts from Apache, i.e. a serious server capable of many functions, faster than a regualr CGI can do.



One known alternative to embedding a language into Apache (the way mod_perl and php do) is that used by FastCGI:

(a) create an extension=module for Apache that opens a socket (network or unix socket) to an external process

(b) which will listen for Apache connections, and serve replies

(c) The application looks like a regular CGI script,, but after initialization etc. it starts a loop that reads from the socket, analyzes the CGI environment resent to it by Apache, and replies with headers and content.





With FastCGI there are two ways newLisp could talk to Apache:

(a) using imports from a fastcgi library in C supplied in the fastCGI distribution

(b) or reimplementine in script (a) function(s) that unpacks fastCGI packets and speaks the fastCGI protocol.



FastCGI packets are binary, and this is would be a more complex way to use it, although the distribution includes a perl module that can be compiled (a special options) to produce a script in pure perl - it could be used as a crib.



A simpler alternative to FastCGI, which uses the same principle is mod_lisp, whith a primitive protocol in ASCII. Scripting to this module is very easy.

Contradicting its name, mod_lisp is totally generic, and can be used from any program in any language that is capable of networking.



2. Getting and compiling mod_lisp



2.1 The URLs

The tiny mod_lisp.c can be downloaded directly from

http://www.fractalconcept.com:8000/public/">http://www.fractalconcept.com:8000/public/

open-source/mod_lisp/

Pages describing the module and its protocol are linked from here (bottom of the page):

http://www.fractalconcept.com">http://www.fractalconcept.com

(download links look broken).

The modules in the mod_lisp-current.tgz was last updated some time in either 2004 or 2006, it seems.



Secondly, the pages link to sample scripting in clisp, cmucl and LispWorks that show how it can serve primitive dynamic web pages.



One more useful example and howto/tutorial is in a link to a web site that went into an Internet black hole, but can be retrieved from the Internet history archive:

search for

http://lisp.t2100cdt.kippona.net/lispy/home">http://lisp.t2100cdt.kippona.net/lispy/home

on the Wayback machine site:

http://www.archive.org/web/web.php">http://www.archive.org/web/web.php



This tutorial describes a setup with MySQL backend (which in 2003 the author's 350 MHz machime pushed at 6 hits/sec) or for a simple dynamic page (36 hits/second)





2.2 Compilation

The downloaded distribution includes file mod_lisp2.c - this version is for Apache 2.0.x

Another file mod_lisp.c is for Apache 1.3.x



Apache Foundation currently maintains (with latest patches including security patches) 3 series of its server:

1.3.x, 2.0.x and the latest 2.2.x



Apache software relies on its APR library, which between versions 2.0 and 2.2 jumped from 0.9.x to 1.x, with changes in its API. Therefore modules written for Apache 2.0.x will most probably require some porting.



2.2.1 compilation for Apache 2.0.x

mod_lisp2.c was created for 2.0.x and compiles cleanly using the usual (an apache with enabled module support is presumed)
cd /dir/where/mod_lisp_source/is
apxs -c mod_lisp.c
cd ./.libs
cp mod_lisp.so /to/apachedir/modules

2.2.2 compilation for Apache 2.2.x

Compilation for Apache 2.2.x requires some porting. I did it comparing APR library header files between pre-1.0 and post-1.0 versions, and by using the Changes doc from the distribution.

After renaming 3-4 functions and excluding the APR_STATUS IS_SUCCESS(s) macro the module becomes usable on Apache 2.2.x

Briefly:

1. in mod_lisp.c (line 327):
(apr_socket_create ((&socket), AF_INET, SOCK_STREAM, socket_pool));

Now a new argument "APR_PROTO_TCP" needs to be added:
(apr_socket_create ((&socket), AF_INET, SOCK_STREAM, APR_PROTO_TCP, socket_pool));

2, Depreciated function was used in the module which is no longer valid: apr_send; needs to be found in the text and changed to apr_socket_send;
APR authors mentioned in their Changes doc compatibility of such change.
See header in APR sources; as defined in $apacheindcludedir/apr_network_io.h :
APR_DECLARE(apr_status_t) apr_socket_send(apr_socket_t *sock, const char *buf, apr_size_t *len);
/** @deprecated @see apr_socket_send */
APR_DECLARE(apr_status_t) apr_send(apr_socket_t *sock, const char *buf, apr_size_t *len);

3. same substitution with apr_connect --> apr_socket_connect
4. Same with apr_recv --> apr_socket_recv

5. (the macro APR_SATUS_IS_SUCCESS was obsoleted): Changed:
APR_STATUS_IS_SUCCESS(s)
to
((s) == APR_SUCCESS))


After these several changes the module seems to compile (same procedure as for 2.0.x) and run on 2.2.x (I checked the latest 2.2.8)







3. Configuring and using mod_lisp



3.1 The minimal Apache 2.x.x configuration

consists of:



(a) adding to httpd.conf line (adjust if your modules are in a different dir):
LoadModule lisp_module modules/mod_lisp.so

(b) and adding (minimally)


LispServer 127.0.0.1 3000 "somenameforit"
# stupid forum sfw eats up slash-dirname; it must be
# Location slash-lisp  here:
<Location>
SetHandler lisp-handler
</Location>


This means that your apache will knock at port 3000 of your local machine to speak with your newLisp server application every time a user browses into http://www.your.server.name.com/lisp/xxx.html">www.your.server.name.com/lisp/xxx.html directory



The newLisp hadndling server can be installed on a different machine in the backend network, I believe, so creating a flexible architecture. In contrast to FastCGI this module does not seem to be able to communicate through local Unix sockets.



3.2 Scripting on newLisp side



The module sends the regular CGI header data one item on a line, splitting it into "key-value" pairs, i.e. in exchanges it should look like
"Content-Typen"
"text/html; charset=utf-8n"


Note that "text/html; charset=..." are on the same line, however

Header information must end with "endn"



You can see what Apache sends to you by starting newLisp to listen on a configured port as a server with "-L filename" option, which logs this information.

It's a dangerous exercise, but because request headers and vars are not lisp commands, you'll see the information simply logged but interspersed with newLisp "nnil" responses or some echoes:
server-protocol
           ......this new line
nil        ......and this nil come from newLisp response  
HTTP/1.1

nil
method

nil
GET

nil
url

nil
/lisp/index.html

nil

and so on. This is for a first look only. When your script is working, you'll inspect it from there.





So the minimal newLisp script to talk to mod_lisp will look like this:


#!/usr/bin/newlisp

;.....initialize, do things that need to be done before serving; then

#; ------server simple---------
(define (server_simple str_content_to_send)

                ; maximum bytes to receive
        (constant 'max-bytes 1024)
        (set 'cnt 0)

        (if (not (set 'listen (net-listen 3000)))
                (print "ERR opening socket: " (net-error)))
        (set 'c_connection (net-accept listen))

        (while true  ; -- forever;  handle better in a real script
                (if (net-error)
                        (set 'c_connection (net-accept listen)) ) ;; blocking here
                       
                ; get request info and do sth with it
                (net-receive c_connection 'message-from-client max-bytes)
               
                ; send the generated response back to apache
                (net-send c_connection str_content_to_send)
                     
                ;(silent
                ;       (inc 'cnt 1)
                ;       (print (string cnt) "-" (net-error) "n")
                ;); /silent/

        );/end of inner read-write while/
); /end of server_simple/

Uncomment the "silent" block to see counter and resets on the console from which you test the driver (more precise reasons for resets can be glimpsed with "net-select"  if needed.

This is not a realistic script - in reality you would pick up lines (arguments) from "message_from_client" (print it to see) and form a response in the main part of you application, which you will send in place of my constant "str_content_to_send".  Note how it is formed in "main".

But this is all it takes to work with mod_lisp, not much to talk about.



Note that the connection gets reset in case of errors (e.g. inability to send to an already closed socket).



And "main" will set up some test page to send:
;-------MAIN-------------
;       sets args

; content first to calculate its length
(set 'str_second (read-file "/path/name/to/some/file/to/send.html") )
; header written according to mod_lisp protocol
(set 'str_head (append
        "Content-Typen"
        "text/html; charset=utf-8n"
        "Content-Lengthn"
        (string (length str_second))
        "n"
        "Keep-Socketn"
        "0n"
        "endn"
        ) )

; run the app
(server_simple (append str_head str_second))


The simple mod_lisp exchange protocol requires

(a) splitting header info into key-value pairs and sending them on separate line each. Output "endn" to finish sending key-value pairs.

(b) announcing Content_Length and sending after the end of the header a chunk of that size. This parameter is important for the correct work of the protocol.

(c) supplying "Keep-Socketn" "1n" if you wish to keep the connection open for a subsequent transfer, or setting it to "0" as in the example to release the connection for next requests from apache.





4. Conclusion, speed, and comparison with CGI



This setup allowed to drop load on the web server - actually, it became quite negligible with NewLisp alone sending pages from the filesystem or generating it programmatically.

Secondly, it sped up one page delivery from a persistent backend server in newLisp from roughly 50/sec as CGI for printing out one 9.5kB+(html styling pages) -- to roughly between 200 and 250/second with mod_lisp (depends on the general load level, concurrency etc) with subsecond delays.



It's interesting to note, that this mod_lisp speed is 5-7 times higher than reported with a "big lisp" setup in 2003 on a machine that is probably roughly 1.3 times slower than mine.







FastCGI (which I have not tested with newLlisp) may be preferrable because it's been implemented on majority of web servers, big and small, and because much more engineering went into it, which might result in better performance or stability or God knows what else



Results of testing FastCGI on my machine were not that great (roughly twice slower than the major mode of mod_perl for a particular perl script) and depended on the web server and used FastCGI implementation. I have not tested it enough, however.



mod_lisp is in fact not for Lisp only, but is a generic solution implementing the same idea as FastCGI with a very simple ASCII protocol, and so available for any language with networking capabilities. ASCII protocol is very  easy for scripting.



mod_lisp  currently runs on Apache 2.0.x and 2.2.x (and supposedly also on 1.3.x, which I have not tested); the speedups are about the same for the simplest one-page tests.

Correction I wrote "does not run on web servers besides Apache" -- There is a "mod_lisp.c" rewritten for the popular lighttpd web server (i.e. it implements the same protocol, the author says), and the only message about it plus source code I found dates from 2007, i.e. it's recent. I have not tried to compile or use it, however.



One interesting question remains, however

Is it possible to put into apache httpd.conf (in different subsections, possibly) not one, but a number of mod_lisp references to several servers running on different ports?

Yes, they can be tucked into different virtual hosts, at least:


NameVirtualHost 127.0.0.1:8000

<VirtualHost>
ServerName "localhost"

LispServer  127.0.0.1 3000 "fractal"
#forum sfw eat up Location slash, lisp - the dir name
<Location>
SetHandler lisp-handler
</Location>
</VirtualHost>

<VirtualHost>
ServerName "localtwo"
DocumentRoot "/usr/local/apache2.0.63/htdocs2"

LispServer  127.0.0.1 5000 "fraction"
#forum script eat up Location slash, lisptwo - the dir name
<Location>
SetHandler lisp-handler
</Location>
</VirtualHost>


How soon mod_lisp will become a bottleneck with this setup is unclear, however.

On my machine the module simply "multiplexes" between the two streams, feeding a hundred into one newLisp script instance, then switching and feeding a hundred or so into the other one for a second or two -- while the sum of two speeds remains roughly equal to the speed of a single stream from one instance.

.

newdep

#1
Nice job... i like these progressings ;-)





http://www.fractalconcept.com/asp/At1/sdataQ0WR9unT$sSYDM==/asdataQuMh-N3qe0jUC1B=">//
-- (define? (Cornflakes))

lithper

#2
Quote from: "newdep"
Aaaa you already listed that Url ;-) the latest version is v 2.43..


Well, I already ported it to Apache 2.2.x series (tried on 2.2.8 (latest))  ;)))