Automatic servlet mapping

Introduction

The KawaPageServlet facility makes it trivial it set up a web server so any specific URL causes a module (or script) in any Kawa language (Scheme, XQuery, or BRL/KRL) to be automatically executed, and the result sent back as the HTTP response.

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.

With KawaPageServlet, 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 directory (as explained below). You choose the name bar. You can use the name bar.html, even though the file named bar.html isn't actually an HTML - rather it produces html when evaluated. Or better: just use a name without an extension at all. The KawaPageServlet will figure out what kind of script it is based on the content of the file, by looking at the first line, as detailed below. A script named +default+ is run if there isn't a matching script.

XQuery Example

Create a file with the path info/uri containing the following:

(: this is a script using kawa:xquery :)
let $uri := qexo:request-uri() return
  <p>The request-uri is: {$uri}.</p>

Then this script will be excuted in response to a request http://host:port/app/info/uri. The first line is a comment that identifies the language as XQuery. The response back will be the request URI (as returned by the getRequestURI method of HttpServletRequest). This is the server-specific part of the requested URL, so the response in this example would be:

The request-uri is: /utils/foo/info.

Scheme Example

Create a file named +default+ containing the following:

;; This is -*- scheme -*-
(require 'http)
(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 first line is a comment that identifies this as a Scheme module. The second line imports various useful definitions, including the request-servlet-path function, which 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

Setting up KawaPageServlet

You need a servlet-capable web-server, such as Tomcat, and you need to install the Kawa .jar library. You can read these instructions for installing Tomcat and Kawa.

A server like Tomcat can run one or more web applications, where each application has its own top-level name and configuration. You can choose any name you like for your web application, as long as it doesn't have funny characters or conflicts with other web application on the same server. In the following example we assume you've chosen appname as the name if your web application. We also assume $CATALINA_HOME is the name of the directory on your server that contains the Tomcat files.

First create a directory for the web application, and a WEB-INF directory within it:

mkdir $CATALINA_HOME/webapps/appname
mkdir $CATALINA_HOME/webapps/appname/WEB-INF

Create the file $CATALINA_HOME/webapps/appname/WEB-INF/web.xml containing the following:

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app>
  <display-name>My KawaPageServer application</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>

The important part is the servlet-mapping section. The url-mapping / specifies that the KawaPageServlet class is the default mapping: It will handle all HTTP requests for the appname application, unless you add another servlet-mapping with a more specific url-pattern. I.e. if the server is on host and listening to port then all requests that start with:

http://host:port/appname
will be handled by KawaPageServlet.

Finding a matching script

When KawaPageServlet receives a request for:

http://host:port/appname/anything
it will look for a file:
$CATALINA_HOME/webapps/appname/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 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:
$CATALINA_HOME/webapps/appname/+default+
If that doesn't exist then KawaPageServlet returns a 404 page not found error.

Executing a script

Once KawaPageServlet 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 KawaPageServlet knows about.

KawaPageServlet 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.

KawaPageServlet 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 KawaPageServlet 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

KawaPageServlet automatically compiles a script into a Java 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 a run method of the object. 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. (KawaPageServlet for performance reasons doesn't check more than once a second whether a script has been modified.)