Advanced Web Topics
This tutorial covers some of the more advanced features and use the BeNOW Web API.
Contents
- Customizing the Web Service
- Starting on Boot
- Integrating with Apache
- Themes and User Agent Specific Content
- Bundled Web Content
- Controlling Rendering with the Render Object
- 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)...
Starting on boot requires creating a service wrapper to launch the application. This is a multi-step
process, but is not too complicated:
- Download and extract the service tools to
C:\Windows\system32 (these tools can also be
found on the NT Service Pack CD).
- From the start menu, choose run and specify 'srvinstw.exe'
- Progress through the wizard, choosing install a service and Local Machine
- Specify a name for the service.
- For the full path, specify
c:\windows\system32\srvany.exe (or the location where you extracted)
- Choose Service is its own process, System Account, Automatic and finish
The service is now created, but needs tuning:
- Start the registry editor, from start menu choose run and specify 'regedit.exe'
- Browse to
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SERVICE where
SERVICE is the name of the service you created
- Optionally, create a string value named
Description which describes your application. This description
will be visible from the service manager.
- Create a new key call
Parameters
- Within
Parameters create a new string value called Application with a value of the full
path to the application web.bat file and any parameters you wish (ie
c:\workspace\myapp\bin\web.bat -p 1234, which would start the application on port 1234
- Create another string value named
AppDirectory which specifies the path to the root of the application
(ie c:\workspace\myapp)
The service is now fully specified and can be started from the service manager. Open the service manager by choosing
Control Panel -> Administrative Tools -> Services from the start menu. Double click on the newly specified
service. You can start the service by hitting
start, and you should see application logging within
your log file within the application log dir (ie
c:\workspace\myapp\logs\web.log). The application
should start and be listening on the specified port, ie
http://localhost:1234/.
For Un*x (Linux, Solaris, etc)...
Currently only Debian/Ubuntu Linux starting is described. Starting in other un*x environments is similar.
To have the system boot under Debian/Ubuntu linux, edit etc/init.d/web.debian. Change the HOME line to reflect the install location and copy the file to /etc/init.d/web.
Make it executable and add it to the boot process:
sudo cp etc/init.d/web/debian /etc/init.d/web
sudo chmod +x /etc/init.d/web
sudo update-rc.d add web defaults
Start the service via
sudo /etc/init.d/web start, and it should start. Tail the logs (
tail -f logs/*.log) to see
activity. If started properly, it should be listening on
http://localhost:1413 or the chosen port/interface.
Changing the port to 80 will have it become the default web server, ie
http://localhost.
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.
- UserAgent contains 'MSIE 5' : ie5
- UserAgent contains 'MSIE 6' : ie6
- UserAgent contains 'MSIE' : ie
- UserAgent contains 'Firefox' : firefox
- UserAgent contains 'iPhone' : ipod
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
- Setting the page title
- Setting the help context
- Setting the content type
- Supressing the header
- Supressing all site template content
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>.