Benjy's Guide for Constructing CD-Based Dynamic Web Sites


Additional notes on this technique
Notes on using Jetty-6 with this cookbook

Introduction

Running a static web site from a CD-ROM is an easy thing to do. All you need are the HTML files and a web browser. Static web sites on a CD are a good way to distribute information to those that do not have Internet access or have slow-speed lines. But, dynamic sites, those whose content changes based on user input, can provide features that static pages cannot. However, running a dynamic web site requires a web server.

It is possible to distribute dynamic web content and a server on a CD and require the user to install the web server on his machine. This document describes a less invasive approach using Java-only tools that requires no components to be installed on the target user's machine. In the approach described here, the CD can be configured to autorun the server and site directly from the CD. This description addresses the Windows operating system; however, this approach can be adapted to other platforms such as Linux and Mac OS/X. All the code is portable except for code to autorun the web server and the method I describe to start the default web browser automatically.

It should be noted that applets can also be used to deliver dynamic content; however, it might be necessary to update the end users' browsers with a Java plug-in. However, using the approach described here, no installations are required.

Motivation

What types of information can be distributed using the technique? Some examples are
In the example of marketing information, large amounts of product information are best organized in databases where pricing and other information can be easily changed. The product information in the site can be changed by updating a database and creating a new CD for salesmen or for customer mailings.

Teaching materials can benefit from having quizzes that guide a student's progress through a web site. Teaching aids, such as small simulations, can also be included in a dynamic site.

Whole scientific applications can be distributed using a dynamic web site on a CD. The power of this approach is to take advantage of the universal interface created by HTML and distribute the application in a platform independent manner.

Java Tools

I selected a Java approach for creating CD-based dynamic web sites server for several reasons. First, a Java servlet server with Java Server Pages (JSP) provides a powerful web server including scripting features, the ability to produce custom tags, and the power of servlets. JDBC (Java DataBase Connectivity) provides the ability to connect web applications to common databases, and a Java-only database is available.

Second, an all Java solution is portable and can thus be run on a number of platforms including Unix and Windows operating systems.

And finally, according to my understanding, the license agreements for the tools described here allow for them to be embedded in commercial products. The tools are Sun's Java Runtime Environment (JRE) (java.sun.com), the MortBay Jetty servlet server (www.mortbay.org), the JSP standard tag library JSTL (jakarta.apache.org/taglibs/), and the hsqldb database engine (hsqldb.sourceforge.net). I make no claims on the license agreements associated with these packages. You should consult the indivdual licenses for these tools to confirm that they can be freely redistributed and under what circumstances.


Figure 1 - a sample site, click to enlarge

There are other approaches and other tools that can be used to construct a CD-based web site, as well as variations on this approach. The reader can experiment and expand on the ideas presented here.

Dynamic Web Server CD Cookbook

This is a cookbook approach for creating a dynamic web server that runs from a CD. The CD includes the Jetty servlet server, a Java virtual machine (JVM), and autorun files. Optionally, a database package and a database can be added.

Here is a quick overview of the steps needed to create a CD-based dynamic web site. For complicated steps, a link is given for more details.
  1. Install Java: Install a Java Development Kit (JDK) from java.sun.com that also includes a JRE. The JDK will be used for developing the site, and the JRE will be placed on the CD to run the web server.
  2. Create a CD image directory: Create an image of your site on your hard drive. Begin by creating a subdirectory called CDRoot.
  3. Put a copy of the JRE in the CDRoot: Locate the JRE you installed in the first step, and copy it to CDRoot. I recently installed a Java 1.5 JDK under Windows, and the JRE was installed in C:\Program Files\Java\jre1.5.0_03. I copied this subdirectory to CDRoot.
  4. Install Jetty: Download the Jetty servlet server zip file from www.mortbay.org. Install Jetty by extracting the zip file into CDRoot.
  5. Configure Jetty: Copy the jetty.xml in the Jetty etc directory to cd.xml, change the HTTP port from 8080 to 80, and disable logging. (Logging is disabled so that no attempt is made to write log files to the CD when the server is run.) Also, remove all .war (web archive) files from the webapps subdirectory.
  6. Install the JSP JSTL standard taglib: (Optional). Download the JSTL taglib zip file from jakarta.apache.org/taglibs/. Follow the Standard-1.1 JSTL (2.0) link to the zip file, download it, extract it into a temporary location, and copy the lib/standard.jar and lib/jstl.jar files to the Jetty ext directory. You will only need these files if you plan to use JSP and use the standard taglibs.
  7. Install the HSQLDB database package: (Optional). Download the HSQLDB database package from hsqldb.sourceforge.net and extract it to some directory outside the CDRoot subtree. Copy the lib/hsqldb.jar library to the Jetty ext directory. This step is only required if you plan to use a database.
  8. Create autorun files: Create autorun files, run.bat and autorun.inf, in CDRoot so that the web server will be executed when the CD is read on a PC. Follow the link for examples of these files. CDRoot should look something like this


  9. Create utility servlets: Create servlets to
  10. Develop databases: (Optional). Use HSQLDB to create databases for your site. Create a database directory under Jetty and put your databases there. Edit the properties file for each database to make it read only.
  11. Develop your site: Create a web application using servlets and/or JSP pages. Test your site using the hard drive image of the CD.
  12. Precompile and install JSP pages: (Optional).If you use JSP, precompile the pages, copy the class files to your web application, and modify your web.xml to include entries for each page.
  13. Create a CD: Before burning a CD, make sure all JSP files are precompiled and that the web.xml file for your web app has entries for all of them. Make sure the read-only attribute is set to true in each databases .property file. Make sure the pages in your site work properly. Create a CD by copying the files in CDRoot to the top level of the CD.

Notes

Perhaps the biggest problem with this approach is the potential delay between running a CD that starts Jetty and when the dynamic site becomes available. An improvement over the browser launch servlet I've provided would be to start a small GUI control program in one thread and start Jetty in another. The GUI process would give the user quicker feedback and give a more application-like control panel over my shutdown applet.

Some systems are configured not to autorun a CD, so the users of your CD site should be told to double click on run.bat if the CD doesn't automatically load Jetty. You can also give them the URL of your site, e.g., http://localhost/CDServlet, in case there are problems launching the default browser. If your users typically use your site often, you could include instructions for copying the entire CD to their hard drive to avoid CD-ROM delays each time the site is run.

I would appreciate receiving questions and suggestions about this document at the e-mail address at the bottom of the page. If there is enough interest, I'll create an FAQ.

More information for selected steps

Configure Jetty

Copy <JETTY>/etc/jetty.xml to <JETTY>/etc/cd.xml where "<JETTY>" is the Jetty directory under CDRoot. If Jetty 5.1.3 were installed, <JETTY> would be Jetty-5.1.3. Edit cd.xml and make two changes. The first disables logging. Find the request log section of the configuration file:

<!--=============================================================== -->
<!-- Configure the Request Log-->
<!--=============================================================== -->
<Set name="RequestLog">
    [snip]
</Set>


Add HTML comment lines to disable the entire <Set>....</Set> section:
      
<!--=============================================================== -->
<!-- Configure the Request Log-->
<!--=============================================================== -->
<!--
<Set name="RequestLog">
    [snip]
</Set>
-->

The second change is optional and involves changing the default port for Jetty from 8080 to 80. Locate the configuration line that sets the "jetty.port" system property to 8080 and change "default=8080" to "default=80". Or, you can change the run.bat autorun file so that the javaw command contains "-Djetty.port=80", which overrides the default port specified in the config file.

I recommend removing all the files in the <Jetty>/webapps directory. War files, *.war, are unpacked each time Jetty is run. This delays startup and won't work without extra work on a CD-ROM-based servlet server.

Autorun files

If you want the CD to autorun under Windows, create run.bat and autorun.info. Run.bat looks something like

ECHO OFF
cd jetty-5.1.3
start ..\jre1.5.0_03\bin\javaw -jar start.jar etc/cd.xml

And the autorun.inf to invoke run.bat follows:
      
[autorun]
open=run.bat

Note that javaw is used to run the server without a shell window. See the updated version of run.bat that works with the Jetty 6 version of the stop servlet.

Utility servlets

I find it useful to have the default browser automatically started after Jetty is up and running. (The Jetty startup time depends on the speed of the machine being used and the speed of its CD drive. Starting the browser prematurely with a URL served by the servlet server will give a "connection refused" message.) One way to have Jetty start the browser is to create a subclass of ServletContextListener that is run when your servlet context is up and can be used to start the default browser with a URL. Here is a version that works under Windows 2000 and should work under NT and XP:

package com.benjysbrain.servlet ;

import javax.servlet.* ;
import javax.servlet.http.* ;


// Copyright (c) 2005 by Ben E. Cline. All rights reserved.

/**
  Launch a browser when the servlet server is up.
*/

public class AppInit implements ServletContextListener {

   public void contextInitialized(ServletContextEvent sce) {
      ServletContext application = sce.getServletContext() ;

      String startURL = application.getInitParameter("startURL") ;

      // Do a "start <startURL>" command.

      try {
         Runtime runtime = Runtime.getRuntime() ;
         runtime.exec("cmd.exe /c start " + startURL) ;
      }
      catch(Exception e) {
         System.out.println(e) ;
      }
   }

   public void contextDestroyed(ServletContextEvent sce) {
   }
}



The "start" command is used to run the web browser in a new process. Because it is a built-in command in Windows 2000, "cmd.exe" must be run instead of running "start" directly. The URL that the browser is started on is taken from the web.xml file for the web application. An excerpt of a web.xml file that relates to this servlet follows. See the sample web.xml for a complete web app web.xml file. The "<context-param>" and "<listener>" tags configure the ServletContextListener.


    <context-param>
      <param-name>startURL</param-name>
      <param-value>http://localhost/CDServlet/</param-value>
    </context-param>

    <listener>
      <listener-class>com.benjysbrain.servlet.AppInit</listener-class>
    </listener>

Because Jetty is run in the background, I created a servlet, called stop, that can be called from a web page to stop the server. When the server starts, I use the AppInit servlet to invoke index.html in the top-level directory of my web app. This page contains a form to run the stop servlet:

 <TITLE>Welcome</TITLE>
<script language="JavaScript">
window.open("/CDServlet/demo", "Welcome", '') ;
self.resizeTo(200, 200) ;
</script>
<p>
<form action=/CDServlet/stop method=post>
<input type=submit value="Stop Server">
</form>

This page first opens a new browser window to run my primary servlet and then resizes itself to a small window with a "Stop Server" button. Pressing the button stops Jetty. See Figure 1.

This approach does have some drawbacks. For example, if the user closes the larger browser window first, his default browser size will, in most cases, become the size of the smaller window with the stop button. Also, browsers that prevent popup windows will prevent the larger window from opening.

The source code for the stop servlet follows. See my notes on a version of the stop servlet that should work for both Jetty 5 and Jetty 6.

package com.benjysbrain.servlet ;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.* ;
import java.util.* ;
import org.mortbay.http.* ;

// Copyright (c) 2005 by Ben E. Cline. All rights reserved.

/**
  Shutdown Jetty.
*/

public class stop extends HttpServlet {
   public void doPost(HttpServletRequest request,
              HttpServletResponse response) throws ServletException,
                               IOException {
      response.setContentType("text/html") ;
      PrintWriter out = response.getWriter() ;
      out.println("<HTML><HEAD><TITLE>Stop</TITLE></HEAD><BODY>") ;
      out.println("<h2>Stopping Jetty</h2>") ;
      out.println("</body></html>") ;
      out.close() ;

      Collection servers = HttpServer.getHttpServers() ;
      Iterator sList = servers.iterator() ;
      while(sList.hasNext()) {
       HttpServer s = (HttpServer) sList.next() ;
       try {
            s.stop() ;
       }
       catch(Exception e) {
            System.out.println("Stop:  " + e) ;
            out.println("Problem stopping:  " + e) ;
       }
      }
   }
}

The web.xml configuration for stop is

    <servlet>
        <servlet-name>stop</servlet-name>
        <servlet-class>com.benjysbrain.servlet.stop</servlet-class>
    </servlet>

    <servlet-mapping>
      <servlet-name>stop</servlet-name>
      <url-pattern>/stop/*</url-pattern>
    </servlet-mapping>

Develop database

Instructions on creating databases is beyond the scope of this page, but I've included a few notes to help you get started. The HSQLDB package comes with an extensive manual, which you should find useful.

I first create a development directory outside the CDRoot subtree where I keep the databases and a shell script to run the database server. I create a file of SQL commands called db.sql to build the database. Next, I run HSQLDB in server mode using a command similar to

  java -cp ..\lib\hsqldb.jar org.hsqldb.Server -database.0 BenjysProjects

This will create a new database called BenjysProjects or open an existing database by the same name using a proprietary protocol.

Next I make a copy of the demo/org/hsqldb/sample/sqltool.rc file in my development directory. I add the following lines to indicate that id "cdsite" refers to the server on localhost using id "sa" with no password.

urlid cdsite
url jdbc:hsqldb:hsql://localhost
username sa
password

This command runs sqltool and creates the database by executing the SQL statements in sql.db.

  java -jar ..\lib\hsqldb.jar --rcfile sqltool.rc cdsite sql.db

You can use

  java -jar ..\lib\hsqldb.jar --rcfile sqltool.rc cdsite 

to query and update the database. When I am finished creating the database, I copy all the database files to the Jetty databases directory, which I created, and edit the database properties file, setting readonly=true.

Here is sample Java to open the database from a Jetty servlet. Once the database connection is open, standard JDBC calls can be made to query the database.

       Connection c = null ;
       try {
         Class.forName("org.hsqldb.jdbcDriver" );
         c = DriverManager.getConnection("jdbc:hsqldb:file:databases/BenjysProjects", "sa", "");
       }
       catch(Exception e) {
         System.out.println("DB Open:  " + e) ;
       }

If you are using the standard JSP taglib, you can connect to the database using

<sql:setDataSource
  var="db"
  driver="org.hsqldb.jdbcDriver"
  url="jdbc:hsqldb:file:databases/BenjysProjects"
/>

Developing your site

You need to decide if you're going to use servlets, JSP, or a combination of both and if your application requires a database. I suggest you create your site as a web app. A web app consists of a directory in the <Jetty>/webapps directory. The subdirectory contains HTML files and/or JSP files, e.g., index.html, and a WEB-INF subdirectory. This subdirectory contains a web.xml file, a classes subdirectory for class files, and a lib directory for library files. The web.xml file contains configuration information for the web app. I will address JSP pages in a later section. For servlets, the web.xml file maps URL's to servlets and specifies the class files of the servlets. See the example web.xml file.

During development, I use the JDK by placing the JDK bin directory in my path. I then use it to develop servlets and JSP files. The JDK is required to compile servlets and JSP files. To run Jetty with the JDK in the path, go the Jetty directory and run

java -jar start.jar etc/cd.xml

You can test your site by then running Jetty with the JRE using the run.bat file in the CDRoot directory.

Precompiling JSP

JSP pages are a combination of Java code, html, standard tag calls, and custom tag calls. During development, the programmer creates a text file with a .jsp extension. When the page is visited by a browser, Jetty and Jasper, a JSP engine, decides if there is up to date executable code to generate the page for the browser. If not, Jasper first converts the .jsp program into a .java file. It then compiles this program into a .class file and runs it as a servlet. The generated .java and .class files are kept in the Jetty temporary subdirectory in the Jetty home directory. It is work/<webapp-port>/org/apache/jsp/ by default where <webapp-port> is the string "Jetty__<n>__<appname>", with <n> being the port number of the server, e.g., 80, and <appname> being the name of the web app. Under Jetty-6 and XP, temporary disk space is used, so you do not need to precompile to test a CD. But you should precompile JSPs for production CDs for better performance.

Before creating a CD image, all the JSP files must be visited so that they will be translated and compiled. Unfortunately, copying the "work" directory to CD with all the JSP files translated won't work because Jetty will not use a read-only temporary directory. There are at least three ways to precompile the JSP files. All three methods require that several lines be added to the web app's web.xml file for each JSP file. The methods are

If you only have a few JSP files, the simple method should work fine:

    <servlet>
        <servlet-name>db</servlet-name>
        <servlet-class>org.apache.jsp.db_jsp</servlet-class>
    </servlet>

    <servlet-mapping>
      <servlet-name>db</servlet-name>
      <url-pattern>/db.jsp/*</url-pattern>
    </servlet-mapping>

The second method invokes org.apache.jasper.JspC directly. JspC will translate and compile all the .jsp files into .class files for servlets and optionally generate the lines needed for your web.xml file. In order to run JspC directly, you need the following files in your class path:

Under Jetty-6, the jar files you need are different. My Jetty-6 page contains a list of Jetty 6 jar files needed for precompiling JSP files.

I create a development directory and copy all the .jsp files there. I then run JspC in the development directory:

java org.apache.jasper.JspC -d . -l -s -uriroot <webapps-dir> -compile -webxml webfrag.xml *.jsp

where <webapps-dir> is the full path to the directory for your web app under the Jetty webapps directory. "-d ." indicates the files are in the current directory. "-l and -s" output each filename as it succeeds or fails. "-uniroot" (or -webapp) is the webapp directory for your web app. "-webxml" gives the filename for a file to receive the web.xml fragment for the .jsp files that are processed.

Once you run JspC successfully, copy the "org" subdirectory to your WEB-INF/classes directory in your web app or jar this subdirectory and place it in the WEB-INF/lib subdirectory. Insert the lines in the -webxml file into the web.xml file.

The final method for precompiling your JSP files is to use "ant", the Java-based replacement for the "make" utility. Do a web search for details.

The sample web.xml file


<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
         http://java.sun.cm/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">

  <display-name>Welcome to Benjy's Brain Demo Servlet</display-name>
  <description>
     Demo Servlet for CD-based Web Server.
  </description>
    <servlet>
        <servlet-name>DemoServlet</servlet-name>
        <servlet-class>com.benjysbrain.servlet.DemoServlet</servlet-class>
    </servlet>

    <servlet-mapping>
      <servlet-name>DemoServlet</servlet-name>
      <url-pattern>/demo/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>stop</servlet-name>
        <servlet-class>com.benjysbrain.servlet.stop</servlet-class>
    </servlet>

    <servlet-mapping>
      <servlet-name>stop</servlet-name>
      <url-pattern>/stop/*</url-pattern>
    </servlet-mapping>

    <context-param>
      <param-name>startURL</param-name>
      <param-value>http://localhost/CDServlet/</param-value>
    </context-param>

    <listener>
      <listener-class>com.benjysbrain.servlet.AppInit</listener-class>
    </listener>

</web-app>



This site © copyright 2005 by Ben E. Cline.  E-Mail: