Self-configuring web page scripts

Kawa makes it easy to set up a web site without configuration files. Instead, the mapping from request URL to web page script matches the layout of files in the application directory.

Many web servers make it easy to execute a script using a script processor which is selected depending on the extension of the requested URL. That is why you see lots of URLs that end in .cgi, .php, or .jsp. This is bad, because it exposes the server-side implementation to the user: Not only are such URLs ugly, but they make it difficult to change the server without breaking people’s bookmarks and search engines. A server will usually provide a mechanism to use prettier URLs, but doing so requires extra effort, so many web-masters don’t.

If you want a script to be executed in response to a URL http://host/app/foo/bar you give the script the name app/foo/bar, in the appropriate server “application” directory (as explained below). You get to pick the name bar. Or you can use the name bar.html, even though the file named bar.html isn’t actually an html file - rather it produces html when evaluated. Or better: just use a name without an extension at all. Kawa figures out what kind of script it is based on the content of the file, rather than the file name. Once Kawa has found a script, it looks at the first line to see if it can recognize the kind (language) of the script. Normally this would be a comment that contains the name of a programming language that Kawa knows about. For example:

;; Hello world page script written in -*- scheme -*- 
#<p>Hello, <b>&(request-remote-host)</b>!</p>

(Using the funny-looking string -*- scheme -*- has the bonus is that it recognized by the Emacs text editor.)

A script named +default+ is run if there isn’t a matching script. For example assume the following is a file named +default.

;; This is -*- scheme -*-
(make-element 'p "servlet-path: " (request-servlet-path))

This becomes the default script for HTTP requests that aren’t handled by a more specific script. The request-servlet-path function returns the "servlet path", which is the part of the requested URL that is relative to the current web application. Thus a request for http://host:port/app/this/is/a/test will return:

servlet-path: /this/is/a/test

You can use the feature variable in-http-server in a cond-expand to test if the code is executing in a web server.

Using the OpenJDK built-in web server

The easiest way to run a Kawa web server is to use the web server built in to JDK 6 or later.

kawa --http-auto-handler context-path appdir --http-start port

This starts a web server that listens on the given port, using the files in directory appdir to handle requests that start with the given context-path. The context-path must start with a "/" (one is added if needed), and it is recommended that it also end with a "/" (otherwise you might get some surprising behavior).

You can specify multiple --http-auto-handler options.

For example use the files in the current directory to handle all requests on the standard port 80 do:

kawa --http-auto-handler / . --http-start 80

There are some examples in the testsuite/webtest directory the Kawa source distribution. You can start the server thus:

bin/kawa --http-auto-handler / testsuite/webtest/ --http-start 8888

and then for example browse to http://localhost:8888/adder.scm.

For lots of information about the HTTP request, browse to http://localhost:8888/info/anything.

Using a servlet container

You can also can use a “servlet container” such as Tomcat or Glassfish with self-configuring script. See Servlets for information on how to install these servers, and the concept of web applications. Once you have these server installed, you create a web application with the following in the appdir/WEB-INF/web.xml configuration file:

<web-app>
  <display-name>Kawa auto-servlet</display-name>
  <servlet>
    <servlet-name>KawaPageServlet</servlet-name>
    <servlet-class>gnu.kawa.servlet.KawaPageServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>KawaPageServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

This creates a web application where all URLs are handled by the gnu.kawa.servlet.KawaPageServlet servlet class, which is included in the Kawa jar file. The KawaPageServlet class handles the searching and compiling described in this page.

Finding a matching script

When Kawa receives a request for:

http://host:port/appname/a/b/anything

it will look for a file:

appdir/a/b/anything

If such a file exists, the script will be executed, as described below. If not, it will look for a file name +default+ in the same directory. If that desn’t exist either, it will look for +default+ in the parent directory, then the grand-parent directory, and so on until it gets to the appname web application root directory. So the default script is this: appdir/+default.

If that doesn’t exist then Kawa returns a 404 "page not found" error.

Determining script language

Once Kawa has found a script file corresponding to a request URL, it needs to determine if this is a data file or a web page script, and in the latter case, what language it is written in.

Kawa recognizes the following "magic strings" in the first line of a script:

kawa:scheme

The Scheme language.

kawa:xquery

The XQuery language.

kawa:language

Some other language known to Kawa.

Kawa also recognizes Emacs-style "mode specifiers":

-*- scheme -*-

The Scheme language.

-*- xquery -*-

The XQuery language (though Emacs doesn’t know about XQuery).

-*- emacs-lisp -*-
-*- elisp -*-

The Emacs Lisp extension language.

-*- common-lisp -*-
-*- lisp -*-

The Common Lisp language.

Also, it also recognizes comments in the first two columns of the line:

;;

A Scheme or Lisp comment - assumed to be in the Scheme language.

(:

Start of an XQuery comment, so assumed to be in the XQuery language.

If Kawa doesn’t recognize the language of a script (and it isn’t named +default+) then it assumes the file is a data file. It asks the servlet engine to figure out the content type (using the getMimeType method of ServletContext), and just copies the file into the response.

Compilation and caching

Kawa automatically compiles a script into a class. The class is internal to the server, and is not written out to disk. (There is an unsupported option to write the compiled file to a class file, but there is no support to use previously-compiled classes.) The server then creates a module instance to handle the actual request, and runs the body (the run method) of the script class. On subsequence requests for the same script, the same class and instance are reused; only the run is re-executed.

If the script is changed, then it is re-compiled and a new module instance created. This makes it very easy to develop and modify a script. (Kawa for performance reasons doesn’t check more than once a second whether a script has been modified.)