Advanced Web Topics

by Andrew Taylor <andy@benow.ca>
This tutorial covers some of the more advanced features and use the BeNOW Web API.
Contents
  1. Customizing the Web Service
  2. Starting on Boot
  3. Integrating with Apache
  4. Themes and User Agent Specific Content
  5. Bundled Web Content
  6. Controlling Rendering with the Render Object
  7. Web Configuration Pages
Customizing the Web Service
In addition to command line arguments, further customization or the web service can be done by creating a custom WebService descendant. WebService customization allows for more advanced things to be done, such as starting other servlets, fine grained jetty configuration, etc. A sample web service is provided in src/java/test/org/benow/web/SampleWebService.java. See the code documention for details of service customization.
Starting on Boot
The web application can be configured to start on boot.

For Windows (XP, Vista, 7, etc)...

For Un*x (Linux, Solaris, etc)...
Integrating with Apache
The web service can be run standalone, or integrated with apache web server for use within a larger site or as a vhost.

As it is currently implemented, the web application requires binding to a specific host and port. If a dedicated port is not an issue (ie connecting to http://myhost:8080/ is not a problem), then running standalone is preferred. However, in vhost situations, where there are many hostnames referring to the same ip, standalone function may not be possible. By integrating the standalone applications with apache, it is possible for apache to serve many hosts on the same ip and to quietly pass requests to the standalone applications. In such a way, multiple applications can be served on the same host or multiple hosts on the same ip and port.

There are two main methods for apache integration, mod_proxy_ajp or mod_proxy_http with mod_rewrite. The mod_proxy_ajp method is preferred, as it is more transparent by not clobbering 5XX error codes. The jetty guide recommends mod_proxy_http over mod_proxy_ajp, but I have found mod_proxy_ajp to work better.

Integrating with Apache via mod_proxy_ajp
When using mod_proxy_ajp, you'll want to make sure that the modules enabled. Enabling modules varies according to apache install, but in ubuntu linux, the procedure is
$ sudo a2enmod proxy_ajp
In other distributions, you might want to enable the modules by adding
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_ajp_module /usr/lib/apache2/modules/mod_proxy_ajp.so
<IfModule mod_proxy.c>
  #turning ProxyRequests on and allowing proxying from all may allow
  #spammers to use your proxy to send email.

  ProxyRequests On

  <Proxy Ajp://127.0.0.1>
      Options Indexes FollowSymLinks MultiViews
      AllowOverride None
      Order allow,deny
      allow from all
  </Proxy>

  # Enable/disable the handling of HTTP/1.1 "Via:" headers.
  # ("Full" adds the server version; "Block" removes all outgoing Via: headers)
  # Set to one of: Off | On | Full | Block

  ProxyVia On
</IfModule>
to httpd.conf (with appropriate paths and IP).

Now, setup your application to start at boot, as described in the last section. Ensure that the --ajp parameter is used for application startup. This will add the jetty ajp connector at port+4, ie if your app starts on port 8420, an ajp connector will be started and listen on port 8424. To manually specify the ajp port, provide a --ajp-port [port] parameter. If started manually with the ajp parameter(s), the ajp port will be displayed during startup (and in the log). You probably want to also specify the -b localhost parameter, which will bind the application to localhost, which will prevent external access. This reduces security concerns.

Once it's configured to run on boot, modify the apache vhost configuration to proxy via ajp:

ProxyPass / ajp://localhost:8424/
ProxyPassReverse / ajp://localhost:8424/
Replace 8424 with the ajp port. A final vhost configuration may look like:
<VirtualHost *:80>
  ServerAdmin webmaster@localhost
  ServerName myhost.com

  DocumentRoot /path/to/application/html
  <Directory /path/to/application/html/>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Order allow,deny
    allow from all
  </Directory>

  ErrorLog /path/to/application/logs/error.log
  LogLevel warn
  CustomLog /path/to/application/logs/access.log combined

  ProxyPass / ajp://localhost:8424/
  ProxyPassReverse / ajp://localhost:8424/
</VirtualHost>
This will tell apache serve myhost.com using /path/to/application/html as the html directory. All requests will be passed via ajp to port 8424 on localhost. Of course, the configuration could be taken further than this, with apache serving static content, failover, etc.

For more complex integrations, it is possible to exclude paths from proxy, which will then be served by apache. This is desirable for heavily hit paths, or to use apache functionality (dir listing, etc). An example including exclusion is:

<VirtualHost *:80>
  ServerAdmin webmaster@localhost
  ServerName myhost.com

  DocumentRoot /path/to/application/html
  <Directory /path/to/application/html/>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Order allow,deny
    allow from all
  </Directory>

  ErrorLog /path/to/application/logs/error.log
  LogLevel warn
  CustomLog /path/to/application/logs/access.log combined

  ProxyPassReverse / http://localhost:8127/
  
  # except the following (in order)
  ProxyPass /project !
  ProxyPass /misc !
  ProxyPass /webalizer !
  ProxyPass /docs !
  # proxy everything
  ProxyPass / ajp://localhost:8127/
</VirtualHost>
this will proxy everything excepti the /project, /misc, /webalizer and /docs directories. Note that the exlusions *must appear before the general proxy statement(s)*.
Themes and User Agent Specific Content
Themes are different presentations chosen by users. By default, the default theme is default, unsurprisingly. Items (pages, images, css, javascript, etc) specific for each theme can be referenced by including the theme name as a suffix of the file. For example, say images/logo.png (as a file within the html/ directory) were included in a page (as the default theme). An alternative logo for the blue theme could be created as images/logo.blue.png. If the user is using the blue theme, requests for images/logo.png would result in images/logo.blue.png being delivered to the browser, if images/logo.blue.png exists. In such a way, there does not need to be a consideration for themes within the produced HTML. The same thing could apply to pages and css, so test/index.blue.page would be delivered instead of test/index.page and css/test/style.blue.css would be used instead of css/test/style.css. Simarly, test/index.blue.js would be delivered for test/index.js. This mechanism allows for the complete redefinition of pages for themes, where required. It is designed in such a way that there needs to be minimal changes required to support differing presentation.

This same concept is used for delivering different web items according to requesting User-Agent. There are several stock user agent matchers defined, with the ability to define custom matchers. By default the user agent matchers match the User-Agent header passed in the request.

So, if test/some.page were requested by on an iPhone/iPod touch, test/some.ipod.page would be delivered, if it existed. The same applies to the same items as themes: css, javascript, pages and images. With this mechanism, it is easy to define specific content tailored to a specific device. For example, the ipod has a smaller screen and custom tags, so the page might be reformated to better fit the screen or use the custom tags. Again, this mechanism allows for customization for the device without having to support each device directly within the site itself.

UserAgent matchers can be added by implementing the UserAgentMatcher interface and using the org.benow.java.packager.Packager procedure before jar'ing. The Packager procedure is already defined in build.xml, so an ant jar is all that is required to register the custom UserAgentMatcher. The @Position annotation can be used to determine load order of the UserAgentMatcher. An example (as found in src/java/test/org/benow/web/SampleUserAgentMatcher.java is:

@Position(Position.FIRST)
public class SampleUserAgentMatcher implements UserAgentMatcher {

  @Override
  public String getTypeName() {
    return "sample";
  }

  @Override
  public boolean matches(String uaString) {
    return uaString.contains("Sample");
  }

}
The theme/ua customization applies also to the site template (var/site/page.xsl), so var/site/page.ipod.xsl would be used as the template for ipod/iphone devices.

Themes and UA matching can also be used in conjuction. A request for test/test.page by a user with the blue theme on a ipod touch would use test/test.blue.ipod.page if it existed.

Bundled Web Content
The web framework is built to facilitate modularity of web content. The goal is to have web functionality become available by simply including a (properly formatted) jar in the classpath. If a requested file does not exist on the file system, then the jars on the classpath are searched for the requested file, and the content is decompressed and streamed from within the jar. Content may live in a jar and be delivered as if it were in a file, but it actually exists within a jar. This allows for functionality and presentation to be completely described in a jar. The support for a process can be included by having the jar on the classpath or removed by removing the jar from the classpath.

An example of this functionality is the admin pages. If you have the web running on localhost:8080, hit http://localhost:8080/admin/dev/menu.page. You'll see the menu administration page be delivered. One would expect to find menu.page as html/admin/dev/menu.page within the project root, but it is not there. The page is being delivered from the benow-web.jar itself. To verify this, you can view the contents of benow-web.jar:

$ jar tvf benow-web.jar | grep admin
 50970 Mon Aug 31 17:53:02 PDT 2009 WEB-INF/html/admin/dev/index.page
 19590 Mon Aug 31 17:53:02 PDT 2009 WEB-INF/html/admin/dev/menu.page
   925 Mon Aug 31 17:53:02 PDT 2009 WEB-INF/html/admin/dev/resources.page
  5484 Mon Aug 31 17:53:02 PDT 2009 WEB-INF/html/admin/dev/service.page
  3712 Mon Aug 31 17:53:02 PDT 2009 WEB-INF/html/admin/index.page
  1337 Mon Aug 31 17:53:02 PDT 2009 WEB-INF/html/admin/log/index.page
 31069 Mon Aug 31 17:53:02 PDT 2009 WEB-INF/html/admin/security/index.page
 16708 Mon Aug 31 17:53:02 PDT 2009 WEB-INF/html/admin/security/login.page
 17236 Mon Aug 31 17:53:02 PDT 2009 WEB-INF/html/admin/security/role.page
 20623 Mon Aug 31 17:53:02 PDT 2009 WEB-INF/html/admin/test/index.page
If you wish to package your own web application for easy reuse with the BeNOW Web Framework, the resources should be registered and included via ant before building the jar in build.xml. By default, the packaging of lib/xsl, var/site and html are included when an ant jar is done. To package other files, modify the Packager command within the jar target in build.xml
Controlling Rendering with the Render Object
Each page has a render parameter which is a java object (xsl.RenderControl) that may be used to control page rendering:
<xsl:param name="render"/>
This can be used for interacting with the page rendering process including The render control is usually accessed within the template matching /, the root of the input document. Ensure to include <xsl:apply-templates/> to continue the XSL processing.
Setting the page title
The page title can be set via pageTitle:
<xsl:template match="/">
  <xsl:value-of select="java:set($render,java:'pageTitle','Page Creation Examples')"/>
  <xsl:apply-templates/>
</xsl:template>
Setting the help context
The help context is the name of the help wiki sheet which corresponds to the page. A wiki sheet is a user editable chunk of content that is included in a page. The help pages are wiki sheets which provide usage information for a page. The help context for the page can be changed by setting the 'helpContext' in the render control:
<xsl:template match="/">
  <xsl:value-of select="java:set($render,java:'helpContext','test')"/>
  <xsl:apply-templates/>
</xsl:template>
This would show the var/wiki/help/test sheet to the user if they were to click the help link from the top right of the page.
Setting the content type
Setting the content type can be useful if the returned content is not html (such as a CSV list, XML, etc). The render control can be used for this:
<xsl:template match="/">
  <xsl:value-of select="java:set($render,java:'contentType','text/xml')"/>
  <xsl:apply-templates/>
</xsl:template>
Supressing the header
The header can be suppressed, so only the content of the page will be displayed (no menu, logo, etc). This can be done by setting supressHeader:
<xsl:template match="/">
  <xsl:value-of select="java:set($render,java:'supressHeader','true')"/>
  <xsl:apply-templates/>
</xsl:template>
Supressing all site template content
The site template in its entirety may be supressed with supressAll:
<xsl:template match="/">
  <xsl:value-of select="java:set($render,java:'supressAll','true')"/>
  <xsl:apply-templates/>
</xsl:template>
This will output only the content of the page... no html tag, no body tag, etc. With supressAll, it's up to you to include everything.
Web Configuration Pages
The BeNOW tutorials are a work in progress. If you have any comments or suggestions, please email <andy@benow.ca>.