Gnomon Manual

Mark Weaver

Abstract

Gnomon is an XML/XSLT processor for IIS, based on Apache's Xalan XSLT processor and Xerces XML processor. It is used to apply XSL stylesheets to XML pages on the server side with minimal effort on the part of the user. It also allows XSL to access HTTP request (cookies, forms, query-strings, cgi variables), session state and databases, allowing stylesheets to react to input.

Even if these extra features are not used, then gnomon provides a very straightforward way to develop and test stylesheets designed to produce html pages.

This product includes software developed by the Apache Software Foundation (http://www.apache.org/).


Table of Contents

1. Overview
2. Quick Start
Installation
Upgrading from a previous release
Simple examples
3. Manual Installation
4. Building Gnomon
5. gnomon.config
Overview
A complete sample configuration
<config>
<post-limit>
<errors override-parents="yes|no" warnings-as-errors="0|1">
Error codes
Error handler syntax.
<cache>
<select-cache-methods xpath="attribute-path">
<stylesheets>
<fetch-method>file|http<fetch-method/>
<use-processing-instruction value = "0|1">
<select-stylesheets xpath="stylesheets-path"/>
<default-stylesheets override-parents="first,last">
<first>
<last>
<stylesheet href="some-stylesheet"/>
<uri-codepage>codepage (default utf-8)</uri-codepage>
<uri-rewrites override-parents="yes">
<http-request>
<extensions override-parents="no|yes">
<extension path="c:\Program Files\gnomon\bin\gnomonSessionExtension.dll">
6. Gnomon built-in functions
Overview
Cookies
GET and POST data
Server Variables
XML Parsing
Dictionaries
Counters
Including templates
7. Templates
8. SQL Extension
9. Session Extension
10. Tidy Extension
11. Performance tuning and performance counters
Overview
Concurrent requests and worker threads
Request backlog queue
Configurable registry entries
Performance counters
Performance Tuning
A. License

Chapter 1. Overview

Gnomon is an ISAPI extension that is capable of applying one or more XSLT transformations to XML documents. These XML documents may be static (e.g. disk files) or dynamic (e.g. generated by ASP, or ASP .NET).

In addition, gnomon provides XSLT functions that allow XSL access to:

  • CGI variables (ASP ServerVariables collection)
  • Forms and posted data
  • Cookies (read/write)
  • Databases (through OLEDB)
  • Database persisted session state (for loadbalanced servers)

Gnomon has support for caching both source XML documents and compiled XSLT stylesheets.

Gnomon uses the Xalan-C XSLT processor as its XSLT processing engine.

Chapter 2. Quick Start

Installation

Firstly, download the current gnomon installer package. When installing, be sure to install the samples. All being well then gnomon will add a document mapping for .acds pages to itself and create a virtual directory under the chosen (or default website) called /gnomon. If this process fails then try checking the steps listed in the manual installation chapter.

Warning

On Windows Server 2003 (IIS6), web service (ISAPI) extensions must be enabled explicitly before they can be used. You can do this from Internet Services Manager, by clicking on the web extensions tab and adding gnomon as an allowed extension. The path to the gnomon ISAPI extension is typically "C:\Program Files\Gnomon\bin\gnomon.dll" (depending on where you installed it). Alternatively, you can enable all ISAPI extensions, but this is obviously not recommended for production servers.

Warning

In this chapter all links point to localhost, assuming that is where you have installed the samples. If localhost is not the correct server, then you will need to edit the links manually.

Upgrading from a previous release

To upgrade gnomon from a previous release, first stop any web services and close any instances of perfmon. This step saves having to reboot the server to complete installation by releasing any in use gnomon files. Then simply run the installer package as described in the quick start section.

If you are using the gnomon COM+ package for session, gnomonASP handling, or other code you will need to recreate this package when upgrading. To do this, delete the existing gnomon COM+ package using the Component Services applet in Control Panel/Administrative tools, and then run the gnomon COM+ setup program. You can either do this before install, when you are offered the option of creating the package, or after install by using the link installed in the start menu.

Simple examples

You should be able to view the first sample after installation. Essentially, what this demonstrates is the default operation of gnomon, which is:

  • Fetch an XML source document via a request to localhost (this is fully configurable, but typical)
  • Fetch an XSL stylesheet by the same method as indicated by a processing instruction in the XML page
  • Apply the stylesheet to the source document and serve the result to the browser

Stylesheets are always cached by gnomon. The same compiled stylesheet is used for any subsequent transforms. To ease development, gnomon tracks changes in stylesheets and dependencies, and automatically recompiles the stylesheet when it changes.

The second sample demonstrates the use of some gnomon extension functions by dumping out the query-string, form, server-variables and cookies collections. These functions are installed in the namespace http://www.npsl.co.uk/gnomon and are described in detail in the chapter on gnomon built-in functions.

The third sample demonstrates dynamically generating XML from an ASP page. The page simply writes out the current time, but the principal can be expanded to dynamically generate any valid XML page (perhaps including content from a database).

If you are having trouble with gnomon starting, that is it IIS reports a problem with loading the ISAPI extension DLL when you request a resource, then you should look in the Application Log (in Control Panel/Administrative Tools/Event Viewer). Gnomon reports fatal errors encountered during startup here. If these error messages contain insufficient information to figure out what is stopping gnomon starting, then you can post a message to the mailing list for support.

Chapter 3. Manual Installation

Gnomon is an ISAPI extension, and as such needs to be configured to handle one or more specific file types. IIS maps processing to ISAPI extensions by file extension. The file extension used by NPSL (and that which is setup by the sample configuration application) is .ACDS. You can add ISAPI mappings at any point in the tree shown in the Internet Information Services mmc snap-in (found in administrative tools). Gnomon should be configured to handle the chosen extension for GET and POST requests.

Warning

On Windows Server 2003 (IIS6), then web service (ISAPI) extensions must be enabled explicitly before they can be used. You can do this from Internet Services Manager, by clicking on the web extensions tab and adding gnomon as an allowed extension. The path to the gnomon ISAPI extension is typically "C:\Program Files\Gnomon\bin\gnomon.dll" (depending on where you installed it). Alternatively, you can enable all ISAPI extensions, but this is obviously not recommended for production servers.

If you want to generate pages dynamically using ASP or ASP.NET, you can either use rewrites in gnomon.config to request .asp/.aspx pages, or you can add a mapping for a different extension. It is possible to get pages from any source, so you could use PHP, perl, etc, or even a different server.

NPSL use ASP to process files with a .xml extension as this gives greater clarity on the process; this is not required. If you choose to follow the same configuration then you should add a script mapping for .xml files pointing to the ASP script engine (this is typically found in c:\winnt\system32\inetsrv\asp.dll) -- check the existing ASP mapping.

If you choose not to add a .XML mapping or use a different extension then be sure to modify the URI rewrites in gnomon.config (see the chapter on the gnomon.config file for more information on how to do this).

Chapter 4. Building Gnomon

If you choose not to use the gnomon binary package then you will have to build it from source. Currently gnomon relies on a number of libraries:

  • OW32 (Open-Win32) libraries. This library is by the same author as gnomon and can be retrieved from the same subversion repository (the top level module is called OW32).
  • NPSL shared components. These are also by the same author and in the same subversion repository as gnomon, the top level module is called shared. This is only required if you want to build the gnomon session and SQL extensions. You will need to add shared/include and shared/include/NPSLDictionary to the VC++ include paths (Tools->Options->Projects->Visual C++ directories) for this to work.
  • Xalan-C is the XSLT processor used by gnomon.
  • Xerces-C is the XML parser used by Xalan (and hence gnomon).gnomon.
  • Boost are a set of useful C++ templates. gnomon uses the regular expression engine and the lexical_cast template from boost.
  • ICU. This library is optional, but is required for the XSLT format-number() function to work correctly with Xalan, and hence is highly recommended. It also adds support for a large number of additional codepages (which may not be of interest).
  • Microsoft Platform SDK. This is installed by default with VC++ 6 and Visual Studio .NET (so you do not need to get it separately). Other compilers may require this to be installed. Specifically, OW32 relies on this for standard windows and socket definitions, and gnomon relies on them for the ISAPI extension headers.
  • HTML Tidy Library Project. This library is optional, and is required for building the gnomonTidyExtension only. This allows "loose" parsing of html from within XSLT which can be useful for legacy applications.

The include and library directories for all of the above must be in the include and library paths for the compiler. The following items are optional:

  • Wise InstallBuilder - this is used to create the installation executable for gnomon. The script is presently set up for NPSL use, and may require modification if paths to certain support files are different.
  • ATL or replacement - This is required for building gnomon's session, SQL extension, and gnomonASP object. None of these are necessary, and gnomon may be used without them. (If there is sufficient interest, then the relevant code may be rewritten to use alternative libraries, or patches for such will be gratefully accepted).

Xalan and Xerces both require small patches, which can be downloaded from the gnomon home page and are also included in the gnomon source in subversion. (These patches are being considered for inclusion in the respective projects). For Xalan, the patch adds support for returning XML fragments from extension functions. For Xerces, the patch adds the SAX2 EntityResolver2 feature, which is required for correctly resolving certain external URIs. Both patches are required to build gnomon.

For gnomon version 1.1 the patches work in a slightly different way, they expose the new XMLEntityResolver to Xalan that was introduced into Xerces with version 2.4 and up.

Xalan and Xerces as provided by the installer are both configured to use the ICU for transcoding. For Xalan, this has the important implication that the format-number() function operates correctly. If you choose to build without the ICU, then things should work, but this configuration has not been tested.

Currently gnomon only builds with Visual Studio .NET 2003. Project files are not available for .NET 2002 at this time, but you should be able to create them straightforwardly. For some reason, the project files are not backwardly compatible. If you want me to do this, send a mail to the gnomon list and I'll make the appropriate project files available. It should also not be too difficult to build gnomon with Visual C++ 6. Any ports are gratefully accepted.

Warning

When building gnomon, be careful with the optimisation settings. Visual Studio .NET 2003 cannot compile gnomon correctly with /O2 optimisation settings -- it generates incorrect call stack unwind code. /O1 works well, and is about as fast as /O2 (without the downside of the code being incorrect). You will know that this is the case if the XSL processor throws exceptions and gnomon reports the error code UNHANDLED_EXCEPTION. The project files provided have the correct optimisation settings for release builds. This problem was not evident with .NET 2002.

Finally, to build gnomon with all of the above in place, just open the gnomon solution file and press build. If you have trouble, again, contact the list for support.

Gnomon can be extended by writing special extension DLLs. Extensions generally install one or more external functions into a particular namespace to make them available from XSLT. The gnomon extension API is not currently documented, but the code is well commented (with javadoc style comments). It is recommended that you use the SQL, tidy and session extensions provided with the distribution as a basis for new extension functions. Better documentation for extensions will be provided in future, depending on the level of interest.

Chapter 5. gnomon.config

Overview

The gnomon configuration file allows you to tailor the behaviour of gnomon as desired. Gnomon's configuration is hierarchical; most settings are inherited from the parent configuration file (as detailed below). There is an inbuilt default configuration, which represents the root of the hierarchy, this is used if no other configuration is present. Directories represent the levels of the hierachy, you can provide a separate gnomon.config in each directory. If no configuration file is preseent, then all settings are inherited from the previous level. The first possible provided configuration file is at the root of each website (this represents the second level of the configuration hierarchy, the first is the default configuration). It is possible to have multiple sites each with different configuration files, this is handled transparently.

A complete sample configuration

A complete sample configuration is listed below. Each element of the configuration file is described in detail in the following sections.

	<config>
		<!-- 
			Limit the amount of POST data that gnomon will handle.
			0 is unlimited (not recommended!)
		 -->
		<post-limit size="1048576"/>

		<!--
			Define custom error handlers for errors that may be produced
			during processing.  This allows you to show users a generic
			error page, and possibly have code that logs or alerts
			based on the errors.
			
			The warnings-as-errors attribute allows you to force gnomon
			to report warnings encountered during XSLT processing.
			By default, they are ignored.
		 -->
		<errors override-parents="yes" warnings-as-errors="1">
			<!-- 
				Generic ASP error handling page, for all errors 
				except SERVER_TOO_BUSY (which only supports the redirect 
				and file methods)
			-->
			<handler code="*">
				<uri method="POST">/errors/gnomon_error.asp</uri>
			</handler>
			<!--
				A straightforward error message to display if the
				server is currently too busy to handle the load
			 -->
			<handler code="SERVER_TOO_BUSY">
				<file>c:\inetpub\wwwroot\gnomon\gnomon_too_busy.html</file>
			</handler>
		</errors>

		<cache>
			<!--
				Find the cachable HTTP methods for the XML files (templates) using this xpath expression 
			   	Override the parent's setting to no-cache by setting this empty.
			 -->
			<select-cache-methods xpath="/document/@cache-methods"/>
			-->
		</cache>
	
		<stylesheets>
			<!--
				How to get stylesheets.  http or file are supported.
				file is the default (maps the URI to a file and reads it in),
				as it is faster and typically stylesheets are not
				generated dynamically.
			 -->
			<fetch-method>file</fetch-method>
			<!-- 
				Use processing instructions.  These are less flexible than the select-stylesheets
				method as they give no opportunity to override default stylesheets.
			  -->
			<use-processing-instruction value = "1"/>
			<!-- 
				Find the stylesheets from the document using this xpath expression.
				Note that use-processing-instruction overrides this.
			 -->
			<select-stylesheets xpath="/document/stylesheets">
			<!--
				List any default stylesheets to apply before/after those listed by the
				individual document
			 -->
			<default-stylesheets override-parents="first,last">
				<first>
					<stylesheet href="/styles/some-first-sheet.xsl"/>
				</first>
				<last>
					<stylesheet href="/styles/some-last-sheet.xsl"/>
				</last>
			</default-stylesheets>
		</stylesheets>
	
		<!-- The codepage to use when parsing GET/POST data -->	
		<uri-codepage>utf-8</uri-codepage>
		
		<!-- 
			Used for redirecting URIs, basically for making loopback requests rather than external ones.
			e.g. where www.npsl.co.uk resolves to an external load balanced IP, we actually want
			to fetch the URI from localhost.
			override-parents will clear any inherited rewrites.
			All rewrites are applied in order.  Use conditionals in the format to prevent inadvertent rewrites.
			(This may change in future if it proves to be sufficiently inflexible)
		 -->
		<uri-rewrites override-parents="yes">
			<!-- Lazy all hosts match -->
			<uri match="(https?://)([^:/]*)(:[^/]*)?(/)?([^?]*)\.acds(\?[^#?]*)?" format="?1http\://localhost$3/$5.xml$6:$0" case-sensitive="no" />
		</uri-rewrites>

		<!--
			Settings for the http request engine
		 -->		
		<http-request>
			<!-- 
				Sets the send/recv timeout in seconds.  The default
				is 0 (infinite), which is fine if you are not accessing
				external resources, as IIS's default handling will ensure
				that loopback requests complete one way or another...
			 -->
			<timeout>120</timeout>
		</http-request>
		
		<!--
			Extension function DLLs.
			These are cached, but are installed per configuration file (with inheritance and override-parents).
			There are no default extensions.
		 -->
		 <extensions override-parents="yes">
			<extension path="C:\Program Files\gnomon\bin\gnomonSessionExtension.dll"/>
			<extension path="C:\Program Files\gnomon\bin\gnomonSQLExtension.dll"/>
			<extension path="C:\Program Files\gnomon\bin\gnomonTidyExtension.dll"/>
		</extensions>
	</config>
	

<config>

<config> is the root node of the configuration file and contains all other elements.

<post-limit>

This element contains the maximum number of bytes of POST data that gnomon is prepared to deal with. By default, the limit is 5Mb. If you want to disable POST data limiting you can indicate this explicitly with a value of 0). This may be applicable if you are using, e.g. Microsoft's URLScan to limit POST data at an earlier stage, but the double protection doesn't hurt.

If the maximum size of the POST data is exceeded, then gnomon will attempt to output an error message and will cease to read POST data. Unfortunately by this stage, IIS will have replied to the POST request with a '100 Continue' response (where applicable, basically all browsers these days). Now RFC2616 states:

8.2.2 Monitoring Connections for Error Status Messages

An HTTP/1.1 (or later) client sending a message-body SHOULD monitor the network connection for an error status while it is transmitting the request. If the client sees an error status, it SHOULD immediately cease transmitting the body.

However, it seems most clients DO NOT follow this sage advice, and will instead simply report that the server closed the connection. If this is a problem to you, then you will need something like Microsoft's URLScan ISAPI filter which is able to enforce the limit at an earlier stage in the process.

On a final note, if you are using ASP to handle large forms, then you should be aware of Q273482, PRB: "Request object, ASP 0107 (0x80004005)" Error When You Post a Form. In short "The size limit of each form field is exactly 102,399 bytes". If you really want to handle large forms from ASP, there are some 3rd party objects that will do this for you, or you can use Request.BinaryRead and handle the data yourself.

<errors override-parents="yes|no" warnings-as-errors="0|1">

This node encapsulates the configuration for gnomon error handling.

The attribute warnings-as-errors enables you to report the XSLT warning stream from Xalan. By default,this stream is suppressed unless an error occurs in the page. Viewing warnings may help with debugging your XSLT.

The rest of this node is concerned with the custom error handling functionality that gnomon provides. This is modelled on the custom error documents built in to IIS. For each type of gnomon error that occurs, you may specify how the error document finally presented to the user is generated.

By default, error handling mechanisms are inherited from the parent configuration file. To clear all inherited error handling mechanisms (resetting them to the default pages generated by gnomon), then you can set the "override-parents" attribute to "yes". It is not generally necessary to do this, as any custom error handlers specified in a child configuration file will automatically override the value for that error handler that was specified in the parent configuration file.

Error codes

An error handler can provide a document from one of the following sources (except for SERVER_TOO_BUSY, see the explicit note below):

  • default - an internal page generated by gnomon
  • file - an external file resource
  • uri redirect - a redirect to a different page
  • uri GET - any web accessible resource fetched by gnomon and forwarded to the client
  • uri POST - any web accessible resourced fetched by gnomon and forwarded to the client. A detailed error report is provided in the POST data.

An error handler is begun with a handler node indicating the specific error that is being handled (e.g. <handler code="SERVER_TOO_BUSY">). The next node specifies the handling mechanism, and additional data that is required for it.

The list of possible error codes is:

  • GNOMON_CONFIG_INVALID. This error is generated when a gnomon configuration file is invalid. Note that whilst it may not be possible to have parsed the configuration that is in error at all (and hence if this handler is specified in that configuration file it may not be used), the handler from the parent configuration file will be used. It is therefore possible to handle this error with at least one good configuration at the root level.
  • TOO_MUCH_POST_DATA. This error is produced when the client exceeds the configured POST data size limit.
  • TRANSCODER_FAILURE. This error is produced when gnomon is unable to create a transcoder for the encoding specified in the configuration file, or when data supplied to that transcoder is invalid. If transcoder creation has failed, then this means that either the encoding string is incorrect, or that the encoding is unsupported. Incorrect data supplied to the transcoder usually indicates that the client and server have different ideas about the encodings in use. You should note that gnomon expects all data to be in the same encoding, including POST and query string data. See the Xalan and ICU documentation for a list of supported encodings.
  • URI_FETCH_FAILURE. This error is produced if a network error is encountered during fetching external resources (XML or XSL) via HTTP. It is not, in general, recommended that this document is set to a GET or POST request (as those requests are likely to also fail if the server is having trouble), but rather to a file or redirect instead. It is possible to do this however.
  • UNHANDLED_EXCEPTION. This is an internal error code that should never occur.
  • UNEXPECTED_FAILURE. This is an internal error code that should never occur.
  • SELECT_PI_FAILURE. This indicates that gnomon is configured to use XML processing instructions to decide which stylesheets to apply, but no running the XPath expression "/processing-instruction('xml-stylesheet')" failed.
  • SELECT_STYLESHEETS_FAILURE. This indicates that gnomon is configured to use an XPath expression to select the list of stylesheets to apply to the XML document, but was unable to execute the expression, most likely because it is syntactically incorrect.
  • NO_STYLESHEETS_SELECTED. This error occurs when no stylesheets were selected for the document. This means that there are no processing instructions, no stylesheets were selected via an XPath expression, and there are no default stylesheets specified in gnomon.config.
  • STYLESHEET_COMPILE_FAILURE. This error is produced when a stylesheet cannot be compiled.
  • EXTENSION_PROBLEM. This error occurs if a gnomon extension reports a problem, or could not be loaded.
  • XML_PARSE_FAILURE. This error occurs if the source XML document could not be parsed.
  • TRANSFORM_FAILURE. The indicates that the XSL transformation failed.
  • SERVER_TOO_BUSY. This indicates that gnomon has exceeded the configured request queue length and is thus too busy to process any more requests. Note: only the URI redirect, file and default handlers are supported for this error. (Making additional GET and POST requests seem a bad idea when the server is under load already!).

Error handler syntax.

Default handler example:

			<handler code="TRANSFORM_FAILURE">
				<default/>
			</handler>
	

This causes gnomon to generate error pages internally, and is the initial default mechanism for all pages. If one of the error mechanisms listed below fails (e.g. because a file is specified that does not exist or an external URI could not be accessed due to a network error), then gnomon falls back to the internal default pages.

File handler example:

			<handler code="SERVER_TOO_BUSY">
				<file>c:\inetpub\wwwroot\gnomon\gnomon_too_busy.html</file>
			</handler>
	

This causes gnomon to return the specified file to the client.

URI redirect example:

			<handler code="STYLESHEET_COMPILE_FAILURE">
				<uri method="REDIRECT">http://www.myserver.com/compile_failure.html</uri>
			</handler>
	

This causes gnomon to produce a 302 Object Moved response with the Location: header set to the specified resource.

URI GET example:

			<handler code="STYLESHEET_COMPILE_FAILURE">
				<uri method="GET">/errors/gnomon_compile_failure.asp</uri>
			</handler>
	

This causes gnomon to get the specified resource via HTTP and return it to the client. If this is not possible then the default error handling mechanism is used. Note that this resource SHOULD set an appropriate error status code (e.g. using something like Response.Status = "500 Internal Server Error" from ASP). Gnomon will forward the page regardless of the HTTP error status, but in the case that the page cannot be retrieved due to a network error, then it will fall back to the default handling.

URI POST example:

			<handler code="TRANSFORM_FAILURE">
				<uri method="GET">/errors/gnomon_transform_failure.asp</uri>
			</handler>
	

This causes gnomon to post data to the specified resource describing the problem that was encountered. The same conditions about network failure as described for the HTTP GET method also apply to the POST method.

The posted data is in XML form (what else?). The posted XML is URI encoded (as is necessary for a form POST) and is stored in the request variable "errors". Thus, if this is an ASP page for example, Request.Form("errors") would give you XML suitable for loading directly into an XML parser.

The posted XML data looks like:

					<errors>
						<error code="TRANSFORM_FAILURE">
							<source>xml-parser</source>
							<description>One of my nodes dropped off!</description>
							<uri>http://localhost/test.xml</uri>
							<line>23</line>
							<column>9</column>
						</error>
						<warning code="TRANSFORM_FAILURE">
							<source>xsl-processor</source>
							<description>A warning</description>
						</warning>
						<message code="TRANSFORM_FAILURE">
							<source>xsl-processor</source>
							<description>An interesting message</description>
						</message>
					</errors>
	

Each error, warning or message is contained within the root node errors.

The source node gives the originator of the error. This is one of gnomon, xml-parser or xsl-processor.

The description node gives a textual description of what went wrong.

The uri, line and column nodes indicate the location that the error occurred. These are optional (as for some errors this information is not relevant).

<cache>

<cache> acts as a container for configuration settings describing how gnomon should cache included source documents.

<select-cache-methods xpath="attribute-path">

The xpath expression attribute is used to select an attribute in XML documents which contains a list of HTTP methods to cache the document for. It is of interest to cache source documents for the transform when the XSLT is made dynamic via reference to a database or request state. Within the attribute (or node), the HTTP methods are provided in a comma separated list, e.g.:

<select-cache-methods xpath="/document/config/@cache-methods">

and in the source document:

<document> <config cache-methods="get,post"> </document>

The currently support methods are get and post.

<stylesheets>

This node encapsulates configuration for the stylesheets that are applied to XML documents. The basic mode of operation is to use any xml-stylesheet processing instructions encountered. Multiple occurences of these directives are allowed, but not well defined -- here gnomon applies all of the stylesheets as listed, in order. For multiple transforms, the resultant document of each intermediate transform must be a valid XML document.

<fetch-method>file|http<fetch-method/>

This node tells gnomon how to fetch stylesheets. In the default mode of operation, then the stylesheet URI is mapped to a physical path first, and the stylesheet is read from disk. If your stylesheets reside on another server, then you can also get them with HTTP. This mode of operation was introduced with gnomon 1.1 -- previous versions always used HTTP to fetch stylesheets. The file method is substantially better performing than the HTTP method, even for a single server.

<use-processing-instruction value = "0|1">

This directive, defaulting to 1, directs gnomon to look for and use any xml-stylesheet processing instructions that occur in the source document. Any xml-stylesheet instructions with a type that is not of text/xsl or application/xslt+xml is ignored.

<select-stylesheets xpath="stylesheets-path"/>

This node provides an alternative (and more flexlible) method of determining which stylesheets to apply to the source document. This node is of lower precedence than the >use-processing-instruction>&. The xpath expression (e.g. xpath="/document/stylesheets") should select a node something like: <stylesheets override-parents="first,last"> <stylesheet href="some-stylesheet"/> <stylesheets> The override-parents attribute allows preventing application of the listed first, last or both default stylesheets (see below). Each stylesheet is applied in order, as for the xml-stylesheet processing instructions.

<default-stylesheets override-parents="first,last">

This node is a container for specifying any default stylesheets to be applied. Stylesheets are inserted before or after the list specified in the document, depending on whether they occur in the first or last container. It is not possible to override these stylesheets with processing instructions. Any stylesheets inherited from parents are inserted before any <first> stylesheets at this configuration level, and likewise any inherited stylesheets in the <last> container are inserted after any after any stylesheets at this configuration level. override-parents will clear any stylesheets from all parents, for those which appear in the first or last containers as specified in the attributes.

<first>

This container specifies stylesheets to insert before the stylesheets listed for the current document, using either processing instructions or the select-stylesheets instruction.

<last>

This container specifies stylesheets to insert after the stylesheets listed for the current document, using either processing instructions or the select-stylesheets instruction.

<stylesheet href="some-stylesheet"/>

This node is listed under the first or last containers to give the location for a stylesheet.

<uri-codepage>codepage (default utf-8)</uri-codepage>

This specifies the code page to use when interpreting post and query string data. For i18n, the most reasonable choice is some form of unicode, and for web pages, this usually means utf-8. Note that ASP usually interprets this data in the local codepage, to make ASP behave consistently with non-ASCII data, you should set the codepage explicitly. There are two ways of doing this, the first is with Session.CodePage (=65001 for utf-8), or better, with the metabase W3SVC/ASPCodepage property.

<uri-rewrites override-parents="yes">

This section tells gnomon how to rewrite URIs that it needs to fetch. This is typically used for redirecting requests for the initial page to either a static resource, or a dynamic page produced by ASP or ASP.NET.

The rewrites use the boost::regex_merge algorithm, and are all applied in order. The default match rule rewrites all incoming requests for .acds pages to point to .xml pages stored on the local server. (These resources could come from an external server if required). It is:

<uri match="(https?://)([^:/]*)(:[^/]*)?(/)?([^?]*)\.acds(\?[^#?]*)?" format="?1http\://localhost$3/$5.xml$6:$0" case-sensitive="no" /gt;

The first portion (the match) approximately follows extended POSIX regular expression syntax. The format string directs how to rewrite the URI based on the match. The ?: construction is a conditional which operates as for the ?: operator in the C language. ?1 tests for the first subexpression of the match being non-empty. If it is true, then the URI is rewritten as indicated (part up to the :), otherwise the expression is left untouch ($0 contains the original input to the match).

The case-sensitive attribute (defaults to no) allows URIs to be matched regardless of case.

<http-request>

This section configures settings for the http requests made by gnomon. At present, only the send/recv timeouts can be set. The timeout values is in seconds, and must be contained within a timeout node, e.g. <timeout;>60</timeout;>. If the request times out then a URI_FETCH_ERROR is reported.

<extensions override-parents="no|yes">

This section allows the use of gnomon extension DLLs. These are objects that can add additional extension functions into gnomon -- they allow it to be extended at run time. For more information on how to write extension dlls, see the gnomon extensions chapter By default there are no extension DLLs, and any extension DLLs are inherited from the parent level of the configuration hierarchy. You can prevent this behaviour by setting the override-parents attribute to the appropriate value.

<extension path="c:\Program Files\gnomon\bin\gnomonSessionExtension.dll">

Listed inside the extensions container are the paths to each extension DLL. The extension interface of each DLL takes care of providing any extension functions necessary.

Gnomon caches extension DLLs globally for speed purposes, but makes them available only at the configured scope.

Chapter 6. Gnomon built-in functions

Overview

Gnomon provides a number of built-in functions that allow accessing the HTTP request state and modifying the response state. These functions cover reading HTTP headers, query string and POST data, and cookies.

The namespace for the builtin extension functions is http://www.npsl.co.uk/gnomon. To use them, map this namespace to a prefix in your stylesheet and then call the function via its qualified name, for example:

	<?xml version="1.0"?>
	
	<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
		xmlns:gnomon="http://www.npsl.co.uk/gnomon"
		xmlns:exsl="http://exslt.org/common"
		extension-element-prefixes="exsl gnomon">
	
		<xsl:output method="html" indent="yes" version="4.0" encoding="utf-8" media-type="text/html />
	
		<xsl:template match="/">
			<html>
				<head><title>Cookie test</title></head>
				<body>
				<p>The single value of the foo cookie is: <xsl:value-of select="gnomon:read-cookies('foo','')"/></p>
				</body>
			</html>
		</xsl:template>
	</xsl:stylesheet>

Cookies

Gnomon's cookie handling is modelled on that of ASP (so that cookies can be accessed in the same way from ASP and ASP.NET pages). This means that cookies can have either a single value, or be considered as a key/value dictionary. These uses are mutually exclusive; setting a key/value pair will destroy any existing single value and vice versa.

ASP also limits itself to the ASCII character set, any characters with values outside the range 1-127 are silently discarded. Gnomon follows this model for compatibility. (Strictly speaking, cookies are opaque -- hence we could store any data, especially if it is URIEncoded to save confusing clients).

Gnomon also supports only the cookie attributes that ASP does. These are:

  • Expires - this is a string of the form Wdy, DD-Mon-YY HH:MM:SS GMT
  • Domain - this is a string which limits the cookie to the specified domain(s)
  • Path - this is a string which limits the cookie to the specified path
  • Secure - this is a boolean which indicates that the client should only send the cookie to the server via a secure channel

The cookies collection is updated in linear processing order. If a cookie is added or modified, then the new or modified cookie will be available to any included templates (even if they are generated from ASP or ASP.NET pages). This gives a consistent view on cookies.

read-cookies()

This function returns a nodeset representing all cookies in the request. A sample nodeset is:

	<cookies>
		<cookie name="foo" secure="0" path="/" value="single-value"/>
		<cookie name="foo2">
			<value name="key1" value="value1"/>
			<value name="key2" value="value2"/>
		</cookie>

read-cookies(cookie-name)

This function returns a nodeset representing a single cookie. The nodeset consists of a cookie node as shown above, or is empty if the cookie is not found.

read-cookies(cookie-name, key-name)

This function returns a string representing the named key from the cookie. If the key name is '' (empty string) then the single value of the cookie is returned. If the named key is not present, and empty string is returned.

read-cookies(cookie-name, attribute-or-key-name, attribute-flag)

This function allows the Expires, Domain, Path and Secure attributes to be read from the cookie. Note that if the cookie is sent by the client, typically these attributes are not reported -- you can read the back if they have been written by an earlier call to write-cookies or via a call to an ASP page. If the attribute-flag is zero, this function behaves exactly as for the two argument version above. If the attribute-flag is non-zero, this function will interpret the attribute-or-key-name parameter as an attribute name. The returned value is a string for Expires, Domain and Path or a number for Secure.

write-cookies(cookie-name, value)

This function sets the single value of the given cookie. If the cookie does not exist, a new cookie is created. This will destroy any dictionary values of the cookie.

write-cookies(cookie-name, key-name, vakue)

This function sets a key of the cookie to the specified value. This will destroy any single value previously set.

write-cookies(cookie-name, key-or-attribute-name, value, attribute-flag)

This function allows write access to the attributes of the cookie. If attribute-flag is 0, then this function behaves exactly as the three argument version above.

GET and POST data

Gnomon allows full access to the GET (query string) and POST (form) data of a request. Currently only POST data in application/x-www-form-urlencoded is processed; that is multipart form encodings are not yet handled. (However, generally these are used for file uploads, so you would not want to process these in gnomon).

For non-ASCII data, handling of GET and POST data can require some care. There is little agreement about the codepage that such data should be sent in. The typical action of a web browser is to send data in the original encoding of the page. By default, ASP interprets such data in the local code page of the server (as can be seen, this is generally wrong). The preferred solution that I adopt is to send all pages in UTF-8, set the codepage for ASP to UTF-8, and configure gnomon to interpret URIEncoded data as UTF-8. You can pick a different codepage if you like; but the key to good inter-operation is a consistent choice! See the note on configuring ASP and gnomon's URI codepage for more information.

Gnomon gives a single overview on request data. That is query-string, post and template collections. POST data can be split per form to POST appropriate data back to different templates. For more information on gnomon templates, see the templates chapter. The request function can be limited to return data from individual templates and collections. Templates are identified by template-id (which, as discussed, is always avaialble as a parameter), and the collection by a letter:

  • q - search query string data
  • f - search POST (form) data
  • p - search parameters

The collections are searched and returned in the order specified in the search string (e.g. qfp). The search string is case-sensitive, and the request function will raise an error if any other value is specified. It is permissble, though pointless, to search a particular collection more than once.

Each item in a collection can have more than one value. (This can occur in real data for example in the case of multiple select lists). The values are considered as a vector, and can be accessed by index. Indexes start at zero.

request(search-string)

This function returns values for all templates from the given collections. The returned value is an XML fragment, which typically looks like:

	<request>
		<collection id="0">
			<query-string>
				<item name="foo" value="value1,value2">
					<value index="0">value1</value>
					<value index="1">value2</value>
				</item>
				<item name="bar" value="single-value">
					<value index="0">single-value</value>
				</item>
			</query-string>
		</collection>
	</request>

request(search-string, template-id)

This function returns values for the specified template from any given collections. The returned value is an XML fragment, which is as detailed above apart from the fact that the "collection" node is the root of the fragment.

request(search-string, template-id, key-name)

This function returns a string which is the value associated with the given key. For example, if the URI of a document is http://localhost/test.acds?foo=value&bar=value1&bar=value2 then request('q', 0, 'foo') returns 'value'. If multiple values are present for a given key, then they are returned as a comma separated list. Commas within values are not quoted, so you should use one of the other forms of this function (particularly the next one) if multiple values are of interest. For example, request('q', '0', 'bar') would return 'value1,value2'. If the key does not exist, an empty string is returned.

request(search-string, template-id, key-name, value-index)

This function returns the value with index value-index associated with key-name. This allows you to return individual items that are associated with the same key. If the key or index are not found, then an empty string is returned. As in the above example, request('q', 0, 'bar', 0) returns 'value1' and request('q', 0, 'bar', 1) returns 'value2'.

Server Variables

Gnomon provides access to CGI variables. See the documentation for the ASP.ServerVariables collection for more details on which variables exist. Note that all headers sent in the request in the server variables collection, and are renamed to HTTP_header, e.g. server-variables('HTTP_HOST') will return the the Host: header. (This is normal for CGI applications).

server-variables()

This function returns a nodeset containing all of the known CGI variables. A sample nodeset is:

	<server-variables>
		<variable name="APPL_MD_PATH" value="/LM/W3SVC/1/Root"/>
	</server-variables>

server-variables(variable-name)

This function returns the value of a single server variable. If the variable is not found then an empty string is returned.

XML Parsing

parse-xml(xml-string)

This extension function parses an XML fragment and returns a nodeset containing the parsed XML. This allows you further process XML that is stored in strings using normal XSLT. This can be very useful if the XML has been retrieved by a database query. For example:

	<xsl:variable name="frag">
		<some-xml>
			<node/>
		</some-xml>
	</xsl:variable>
	<xsl:apply-templates select="gnomon:parse-xml($frag)"/>

parse-xml(xml-string, ignore-errors)

This function behaves exactly for the single argument form if ignore errors is not 1. Otherwise, rather than reporting any errors that arise during xml parsing, an empty nodeset is returned.

Dictionaries

Gnomon provides a single global dictionary that can be used to store any XSL object. This violates the functional programming style of XSLT (as it could be argued does write-cookies) Since Xalan processes XSLT linearly, this works, and using dictionaries (and counters -- see below), you can make XSLT behave as a standard linear programming language. You can consider this a hack, but nonetheless, it's very useful.

dictionary()

This returns the entire contents of the dictionary as a nodeset.

dictionary(key)

This returns the object associated with key. This object may be any valid XSL object -- nodeset, boolean, result tree fragment, number or string.

dictionary(key, value)>

This associates the value with the key. As above the object may be any valid XSL object. For example:

	<xsl:variable name="dummy" select="gnomon:dictionary('foo',bar')"/>
	<xsl:variable name="foo" select="gnomon:dictionary('foo')"/>

This would store the value 'bar' in the variable 'foo'.

Counters

Gnomon provides a dictionary of named counters. Each counter has an initial value of zero. Counters, like dictionaries, allow linear programming in XSL, and can be considered simply as a useful hack. Counters are 32 bit signed integers, and may become negative.

counter(counter-name)

This returns the value of the named counter. All counters exist, and have an initial value of zero.

counter(counter-name,action ('inc'|'dec'))

This increments or decrements the named counter according to the specified action. The return value is the new value of the counter. If action is not inc or dec then an error is raised.

counter(counter-name,action ('inc','dec','set'),value)

This increments, decrements or sets the named counter. If the action is set, then the counter is given the specified value. If the action is inc or dec, then the counter is incremented or decremented by the given value. The function returns the new value of the counter. If the action is not inc or dec then an error is raised.

Including templates

Gnomon provides a function for including XML documents that is similar to the XSL document() function, save for the fact that parameters can be passed to the included document. These parameters are accessible through the request function described earlier in this chapter. The included XML documents are gnomon as templates, this is described in detail in the chapter on templates.

include-xml(document-href, parameters)

The document-href specifies the URI of the included document. If this is a relative path then it is resolved relative to the parent document. The parameters specifies a nodeset containing a list of parameter nodes. The name attribute of these nodes identifies the name of the parameter, and the contents are its value. For example:

	<xsl:variable name="params">
		<parameter name="foo">bar</parameter>
	</xsl:variable>
	<xsl:variable name="dummy" select="gnomon:include-xml('../some-include.xml', $params)"/>

Chapter 7. Templates

Gnomon has the ability to include other documents in the root level document. (This is separate from the XSL document() function, which acts as normal.) When this functionality is used, then gnomon calls all such documents templates. Templates differ from normal included documents in three ways:

  1. Templates can have parameters. These act as a special collection that the included template can access through the request() function
  2. Templates can receive POST data that is only intended for the particular template. This allows you to include a collection of forms implemented as separate ASP pages, and when the entire post is performed, each page is invoked as if it was invoked from the top level.
  3. Templates have an id. This id is zero for the top level template, and increments by one for each included template.

The functionality for splitting POST data into per-template data is dependent on the existence of a special value in the post data - gnomon-multipart-form. If this value is present, then the data will be split, otherwise it will be interpreted as all belonging to the top level template (i.e. as if the POST data splitting was turned off).

The name of each value in a valid gnomon-multipart-form follow a specific format. That is, the name is templateid_valuename. Any value without a valid prefix is assigned to the top level template (id zero). It is expected that to facilitate producing such forms, the XSL will assign the names for the form element based on the value of request('template-id').

Parameters are an extra, optional collection that is provided when using templates. The contents of the parameters collection can be accessed through request('p', ...). When parameters are based between templates, they are URI encoded according to the codepage set in the gnomon.config file. In general, parameters allow controlling the processing of the include template, for example looking up different database items. You are free to ignore the parameters collection if desired.

Parameters are passed to included templates by the use of an extra HTTP header: X-Gnomon-Parameters. The header looks like:

	X-Gnomon-Parameters: encoding=utf-8; foo=value1&bar=value2

i.e., essentially using the encoding x-www-form-urlencoded. Gnomon uses the encoding specified in the uri-codepage option in the gnomon.config file (this defaults to UTF8). Any templates that wish to access gnomon's parameter data should do so through the use of this header (e.g. in ASP, you can use Request.ServerVariables("HTTP_X_GNOMON_PARAMETERS" to access it.

For ASP only, gnomon provides a replacement for the ASP request object that allows parameters to be accessed in a straightforward fashion. This object is called gnomonASP.Request and can be instantiated created by a call to Server.CreateObject. If not using parameters, the ASP Request collection can be accessed as normal.

	Dim gnomonRequest
	Set gnomonRequest = Server.CreateObject("gnomonASP.Request")

This extension works exactly as for the Request object, save that:

  • gnomonRequest.Parameters exists as a collection
  • gnomonRequest(key-name) with no parameters searches the collections in the order gnomonRequest.QueryString, gnomonRequest.Form, gnomonRequest.Parameters, gnomonRequest.Cookies, gnomonRequest.ClientCertificates, gnomonRequest.ServerVariables -- i.e. as for ASP but with the insertion of the Parameters collection.

The gnomonASP object must be in a COM+ package. This is because it uses COM+ object context to access ASP objects. The installer will create an appropriate package, if used. If not used, you should place gnomonASP.dll into an empty library package.

include-xml(document-href, parameters)

This function includes the template indicated by document-href, passing the parameters collection as described above.

Chapter 8. SQL Extension

The SQL extension allows gnomon to access databases via OLEDB. The SQL extension provides principally for SELECT queries and stored procedure calls that return rowsets. You can make other calls (UPDATE, INSERT, etc), but output parameters (other than rowsets) are not presently supported.

sql-new('connection')

This function is slightly misnamed, in that it does not, in fact, create a new connection -- it merely performs a registry key lookup to find a connection string. (This is a convience that fits well into other NPSL code). This may change in future (when I think of a better way of doing it).

The connection string searched for is under the following registry key:

HKEY_LOCAL_MACHINE\Software\NPSL\npslda2\[CONNECTION NAME]\ConnectionString

Here, [CONNECTION NAME] is the argument passed to sql-new. Whilst strange, this function is reasonably convenient and keeps long connection strings out of XSLT, so its use is recommended.

sql-query(connection-string, query, parameters)

This executes a SQL query on the database identified by the given connection string. The query should return a rowset (i.e. be a SELECT statement). The function returns the result of the query as an XML fragment. To reduce database load, it is better to store the result of a query in a variable (or gnomon:dictionary.)

If you have chosen to follow the NPSL format and store connection strings in the registry, then you can do something like the following:

	<xsl:variable name="connstr" select="gnomon:sql-new(gnomon:server-variables('HTTP_HOST'))" />
	<xsl:variable name="params">
		<parameter><xsl:variable name="emp_id" select="gnomon:request('q','EMP_ID')"/></parameter>
	</xsl:variable>
	<xsl:variable name="sqldata" select="gnomon:sql-query($connstr, 'SELECT FROM EMP WHERE emp_id = ?', $params)" />

This looks for a database named as the host serving the request, and selects an employee based on an ID passed in the query string, a sample request may be something like http://www.mycompany.com/showempdetails.acds?emp_id=100. Security considerations aside, this shows how dynamic gnomon allows XSLT to become.

The parameters argument specifies any parameters to bind to the query. parameters is a nodeset consisting of <parameter> nodes. Any parameters in the query text should be specified by using a ? (this is standard ODBC call syntax). An example of the parameters variable:

	<parameter>value</parameter>
	<parameter>value</parameter>

The resulting rowset is as follows:

	<sql>
		<metadata>
			<column-header column-label="label" position="0"/>
		</metadata>
		<row-set>
			<row>
				<col>
					<value>some-value</value>
				</col>
				<col>
					<value>array-value-0</value>
					<value>array-value-1</value>
				</col>
			</row>
		</row-set>
	</sql>

An example of invocation:

	<xsl:variable name="result" select="gnomon:sql-query('Provider=OraOLEDB.Oracle;PLSQLRset=1;User ID=scott;Password=tiger;Data Source=test;Persist Security Info=True;', 'SELECT from emp')"/>
	<xsl:apply-templates select="$result"/>

Unusually, the sql-query function is capabable of processing array valued columns -- this currently is only known to be possible with custom OLEDB providers. (NPSL use a custom OLEDB provider for their content management system).

sql-sp(connection-string, sp_name, parameters)

This executes a call to a stored procedure on the database identified by the given connection string. The stored procedure should return a rowset, which will be exposed as an XML fragment exactly as above. Any parameters in the parameters collection are bound to the stored procedure call.

Chapter 9. Session Extension

The session extension allows for storing session state in a database. This extension does not provide access to ASP sessions, but instead replaces them with a method of storing state that works across multiple (load balanced) servers. The replacement session works much as for the ASP session object, in that it provides a dictionary of key/value pairs.

The connection string for the database holding the session state must be found under the following registry key:

HKEY_LOCAL_MACHINE\Software\NPSL\npslda2\security\ConnectionString

(This name comes from other NPSL code).

The session extension/NPSLSession3 object have a number of other options that can be controlled by setting registry values under the registry key

HKEY_LOCAL_MACHINE\Software\NPSL\Session3

These are:

  • AutomaticNewSessions (DWORD). This flag defaults to 1, and controls whether the session handling code will generate a new session if one does not already exist.
  • HandleTimeouts (DWORD). This flag controls whether the session object will update the last used flag in the database if the session is only read. This defaults to 1. This may be useful if you are handling session expiry by a different method.
  • SQLServerUseUtcDate (DWORD). This flag defaults to 1 and uses the getutcdate() function rather than getdate() when talking to SQL server. getutcdate() is not available on old versions of SQL server so you can unset this flag if using such a database.
  • CompressData (DWORD). This flag defaults to 0. If set, it compresses saved session data using zlib. This can substantially improve performance with large sessions by reducing database load.

gnomon does not provide functions to access the session timeout, or migrate or abandon the session. The NPSLSession3 object (described below) has properties/methods that can Abandon, Migrate and set the Timeout of sessions from ASP code, so this needs to be done from an ASP page for now.

Note that neither the gnomon session extension nor the NPSLSession3 object actually delete session data. When a session has expired, it is simply hidden (by the WHERE clauses of SQL queries). To delete the actual data, you should add a database job that cleans up old sessions based on the last_used column of the sessionstate table. This is generally a more effective process than having each call to the session object have to check for expired sessions. Note that we cannot rely on the user to tell us when the session is finished: them closing the browser doesn't tell us that they have finished.

The session can be accessed from ASP pages using the NPSLSession3.SessionX object (the bizarre name is historical). When instantiated, this object works as for the ASP session object, but will share the values that are accessed through the gnomon:session function. To ensure consistency across included templates, before any document (including those included through the XSL document() function), session state is saved, and after the inclusion of the external document, it is re-read.

For example,

	Dim SessionX = CreateObject("NPSLSession3.SessionX")
	SessionX("foo") = bar
	Response.Write "foo is " & SessionX("foo")

The session extension supports Oracle and SQL server databases (other databases may be supported in future -- the modifications required to support a different database should be minor). In Oracle, a table of the following format must exist:

	CREATE TABLE SESSIONSTATE ( 
	  SESSION_ID  CHAR (36)     NOT NULL, 
	  TIMEOUT     NUMBER (10), 
	  LAST_USED   DATE, 
	  STATE       BLOB, 
	  CONSTRAINT PK_SESSIONSTATE
	  PRIMARY KEY ( SESSION_ID ))
	ORGANIZATION INDEX  NOCOMPRESS  PCTTHRESHOLD 50 TABLESPACE INDX; 

For old version of Oracle (<= 7, i.e. those not supporting BLOBs), a LONG RAW column may be used.

In SQL server a table of the following format must exist:

	CREATE TABLE [SessionState] (
		[SESSION_ID] [uniqueidentifier] NOT NULL ,
		[TIMEOUT] [real] NULL ,
		[LAST_USED] [datetime] NULL ,
		[STATE] [image] NULL ,
		CONSTRAINT [PK_SESSIONSTATE] PRIMARY KEY  CLUSTERED 
		(
			[SESSION_ID]
		)  ON [PRIMARY] 
	) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

The NPSLSession3.SessionX object must be placed into a COM+ library package, as it uses COM+ context to access the ASP objects. If you have not selected to configure the gnomon COM+ package at installation time and you wish to use this object, you should perform these steps manually.

Both NPSLSession3 and gnomon's session extension rely on a dictionary object: NPSLDictionary. This object is essentially the same as Scripting.Dictionary, save for the fact that it is thread safe (therefore suitable for use as a cross-page cache in the application object), and can be persisted. The source code for the dictionary object is provided, and the dictionary object will be installed by the installer. The dictionary object does not use COM+, so nothing special needs to be done with it.

session(key)

This function returns the named key from the current session. If the key is not present, then an empty string is returned.

session(key, value)

This function sets the key to the given value.

Chapter 10. Tidy Extension

The HTML tidy extension uses the HTML tidy parser to loosely parse HTML, cleans it up so that it is valid XML, and then produces a nodeset from it. This support is useful for applying XSLT to legacy HTML pages (although this is obviously a more expensive process than parsing XML pages).

parse-html(html-string)

html-string is the html to parse. The result is an empty nodeset if the HTML could not be parsed, or a nodeset representing the entire parsed document if parsing was successful. Note that tidy produces a valid document structure, so this will always include the root (<html> node), <head> and <body> nodes.

Chapter 11. Performance tuning and performance counters

Overview

Gnomon has a number of settings that can be tuned to maximize performance. Whilst the default configuration works well, specific tuning for your application can provide significant benefits.

The settings that can be tuned are contained in the registry, and are read once at startup time. These settings cannot be modified whilst gnomon is running -- you will need to restart the web service in order to do this. Also note that by default these settings do not exist.

Concurrent requests and worker threads

In order to understand how to best tune gnomon, you will need to understand a little of how it works internally. There are two basic concepts:

  • Worker threads. Worker threads exist in a pool and perform all of the work associated with request processing.
  • Request objects. Request objects encapsulate the current state of a request, i.e. where it has got to in the read client data/fetch xml/transform/output pipeline.

Note that there is not necessarily a 1-1 mapping between threads and requests. It is entirely possible to have more requests executing than threads: this is because the requests are processed asynchronously. When a request is not assigned to a thread, it is effectively dormant (generally waiting on network I/O).

Technical detail (not required to follow the rest of this section): since Xalan operates in a mode in which it cannot be interrupted to perform network I/O the request code is run in a separate fiber. Fibers are self scheduled threads, with lightweight context switches. When Xalan requests a document, we save the request state by switching fiber, start network I/O and return the thread that was executing the transformation to the pool. When the network I/O completes, then we switch the fiber back in and carry on where we left off.

By default, gnomon creates 8 worker threads per processor, and allows 64 concurrent requests. Thus, on a single CPU machine, 8 requests can be concurrently performing CPU work, with the other 56 blocked on network I/O or waiting for a pooled thread to become available to handle them.

Note that these settings are per instance of gnomon. It is possible to have multiple instances of gnomon if you have websites or virtual directories set to run in different processes. For example, if you have two high (isolated) applications you will be running two instances of gnomon, or if you have two websites with two different protection levels -- perhaps one low (IIS process) and one medium (pooled). You should bear this in mind when performance tuning and consider also setting the application protection levels differently.

To decide on the number of worker threads, you should take the following factors into account:

  • How many processors there are in the machine -- at least one thread per CPU should be allocated to make full use of the resources available.
  • Whether the gnomon session or SQL extensions are used. Using either of these extensions involves database access, which can block a thread. Depending on the level of database activity, you may want to increase the number of threads.
  • Fairness. If only one thread per CPU is allocated, requests will (almost) be processed in sequential fashion. This is in fact the optimal setting for minimal overall CPU load since you have the least possible amount of thread switching overhead. However this does mean that you are queueing clients, so that a single request may have to wait for other requests to be completed first. Generally, you want to trade off between the average response time and the thread switching overhead.

To decide on the number of concurrent requests, consider the following:

  • How much memory the machine has. Each concurrent request potentially stores a full set of XSL transformation state: this can be quite large!
  • How long loopback requests take to complete. If requesting the source XML pages takes a long time, and the maximum number of concurrent requests is reached, then gnomon will have to wait for network I/O.

Here you can see that increasing the number of concurrent requests increases memory pressure on the server, which if pushed too far will lead to swapping to disk. This will considerably slow things down. However, you want enough concurrent requests to be executing so that the CPU is kept busy with work -- i.e. gnomon is not waiting on network I/O. Tuning this depends entirely on your application, i.e. the properties of the code generating the source XML.

Also note that increasing the number of worker threads beyond the number of concurrent requests will mean that there will always be idle threads in the pool.

Request backlog queue

Any requests that can't be handled immediately are placed into a queue. This is the case if the number of concurrently executing requests exceeds the configured maximum. If the backlog queue is exceeded, then a server SERVER_TOO_BUSY error is generated (and is handled by the error handling mechanism specified in the configuration file).

The goal when tuning the backlog queue length is to ensure that people do not have to wait too long for a server too busy message (say 10-15 seconds), but that outstanding requests are not rejected too early. The length of queue that you will want depends roughly on the average page generation time for your application. If the page generation time is very small, then you will want a longer backlog queue. If the page generation time is very larger, then you will want a shorter one. The best way to decide how long to make the backlog queue is to stress test your application with a large queue length, determine the average response time, and then set the queue length based on (Concurrent Requests * Desired Maximum Queuing Time) / Average Response Time.

As a point of interest, there was some experimentation to try to get gnomon to do this automatically; but it seemed that measurements of the current average response time was subject too much variation.

Configurable registry entries

The configurable registry entires are stored under the registry key

HKEY_LOCAL_MACHINE\Software\NPSL\gnomon

This key will be created by default by the gnomon installer, or you can create it manually if it doesn't exist. Gnomon will start without this registry key using default settings.

  • BackLogQueueLength (DWORD). This sets the number of requests that will be stored in the backlog queue when the maximum number of concurrent requests is reached. After the backlog queue is full, a SERVER_TOO_BUSY error is generated. The default value is 3000.
  • ConcurrentRequests (DWORD). This sets the maximum number of concurrent requests that can be executed. The default value is 64 * the number of processors installed in the machine.
  • WorkerThreads (DWORD). This sets the size of the worker thread pool. The default value is 8 * the number of processors installed in the machine.

Performance counters

Gnomon provides performance counters that can be used to give an overview of what it is doing at any one time. These performance counters are made available to Window's Performance Monitor as for any other performance counter. Note that until gnomon has started, these counters will not be visible. IIS does not start ISAPI extensions until at least one request has made, so if you are having trouble seeing the counters, make a page request first.

To view performance counters, start Performance Monitor (from Control Panel/Administrative Tools/Performance), request a page to ensure that gnomon is running, then click the '+' icon. The performance object is called 'gnomon XSLT processor' and should be visible in the list. Each performance counter provides detailed help. Click on the Explain... for a description. In addition, the available counters and their meanings are outlined below.

Note that if multiple instances of gnomon are running then the performance counters will be the aggregate of those used on all instances of gnomon. Also note that the average transformation time and last transformation time counters do not lock between instances of gnomon (and both are 64-bit), so in rare circumstances they may become corrupted and an erroneous value may be displayed temporarily. (This is not dangerous, merely cosmetic). Multiple instances of gnomon may be running if you have websites set to run in different processes, e.g. two high (isolated) sites or one low (IIS process) and one medium (pooled).

  • Average transform time. This is the average length of time taken to complete an XSLT transformation. Note that this time does not include the time that the request spent blocked on network I/O. This is an instantaneous snapshot and will vary as requests are processed.
  • Cached stylesheets. This is the number of cached compiled stylesheets.
  • Cached templates. This is the number of cached compiled templates (source XML pages).
  • Executing requests. This is the number of concurrently executing requests.
  • Last transform time (ms). This counter shows the amount of time in ms that the last transformation took, excluding network I/O. This is mainly useful for tuning XSLT during development. The time is measured with Window's high resolution timer.
  • Loopback request network errors. This is the total number of network errors encountered by gnomon when fetching documents via HTTP.
  • Loopback request network errors/sec. As for loopback request errors, but monitors the current rate of occurrence.
  • Request errors. The total number of requests that have resulted in an error for any reason. Errors include transformation failure, xml parse failure, source document returning an HTTP status code other than OK or redirect, etc.
  • Request errors/sec. As for request errors, but monitors the current rate of occurrence.
  • Request queue length. This counter is the number of queued requests awaiting execution.
  • Requests. This is the total number of requests received by gnomon.
  • Requests/sec. As for requests but monitors the rate of request occurrence.
  • Requests disconnected. This is the total number of times a client has disconnected in the middle of request processing. Gnomon detects this at various points and stops processing when it occurs.
  • Requests disconnected/sec. As for client disconnects, but monitors the current rate of occurrence.
  • Requests not found. This is the total number of times that a not found response (404 or 410 status) has been issued.
  • Requests not found/sec. As for requests not found, but monitors the current rate of occurrence.
  • Requests redirected. This is the total number of times that a redirect response (3XX status) has been issued.
  • Requests redirected/sec. As for requests redirected, but monitors the current rate of occurrence.
  • Requests succeeded. This is the total number of requests successfully processed (i.e. without error).
  • Requests succeeded/sec. As for requests succeeded but monitors the rate of occurrence.
  • Too busy replies. This is the total number of requests rejected by gnomon with a 503 Service Unavailable response. This occurs if the backlog queue is full and additional requests are received.
  • Too busy replies/sec. As for too busy replies but monitors the rate of occurrence.

Performance Tuning

Putting it all together

Bearing in mind the details outlined in the sections above, here is one way of approaching performance tuning.

Firstly, get hold of a copy of the freely available Microsoft Web Application Stress Tool. This is an excellent tool to help you stress test load your website and is useful in both eliciting problems and in load planning. Next, create an appropriate script in WAST to stress your site. Once you have this script setup and working, then you will want to decide what kind of load to tune for. Excellent resources for this are existing web and traffic logs. Also estimate the growth of this traffic with time to ensure that you have enough hardware to scale upwards.

When these numbers are determined, the stress level in WAST can be set according to the expected maximum load. Whilst the script is running, monitor the following gnomon performance counteres:

  • Average transform time
  • Requests succeded/sec
  • Request errors/sec
  • Too busy replies
  • Loopback request network errors

The goal of tuning is to maximise the Requests succeeded/sec figure whilst keeping the errors and too busy replies to a minimum. To tune the loopback request errors to be as low as possible, ensure that IIS itself is configured to handle the load that you are generating. See IIS documentation and MSDN for performance tuning IIS.

To tune concurrent requests and worker threads, take the factors outlined above into account to decide on some reasonable initial values. Start by using more concurrent requests than you think you will need, in order to tune the number of worker threads (bear in mind that these create additional memory pressures so do not increase the value too much). (Having more concurrent requests than are needed will prevent blocking on network I/O from affecting the test results).

Then, gradually decrease the number of threads (with at least one per processor) and the Requests succeded/sec figure should increase. This will also increase the variability of the response times, so you should decide on what balance of fairness and performance is acceptable. If you are using the session or sql extension, keep an eye on the CPU load here - if it begins to drop below 100% then you will need more threads as threads will be waiting for the database.

To tune concurrent requests, decrease the number of concurrent requests until the CPU load starts to decrease. When this occurs then requests are blocking on network I/O so you should increase the number again to just above this point. Using the minimum number of concurrent requests leaves the maximum available memory for the rest of the server.

To tune the too busy replies, you should use WAST to measure the average response time when gnomon is under load. To do this first ensure that there are no too busy replies by increasing the backlog queue to a sufficiently large number. The backlog queue takes memory, so don't increase it too far otherwise the artificial memory pressure on the server will affect the results. Then having measured this response time then you can use the formula (Concurrent Requests * Desired Maximum Queuing Time) / Average Response Time to set the backlog queue to be length to be equivalent to the desired maximum queueing time on average. Any request that would take longer than that time will be rejected with a "503 Service Unavailable" response.

Appendix A. License

Gnomon is distributed under an open source derived from the Apache license. The license is reproduced below.

	The NPSL Software License, Version 1
	based on the Apache Software License, Version 1.1
	
	
	Copyright (c) 2003-2004 NPSL Ltd.  All rights reserved.
	
	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions
	are met:
	
	1. Redistributions of source code must retain the above copyright
	  notice, this list of conditions and the following disclaimer. 
	
	2. Redistributions in binary form must reproduce the above copyright
	  notice, this list of conditions and the following disclaimer in
	  the documentation and/or other materials provided with the
	  distribution.
	
	3. The end-user documentation included with the redistribution,
	  if any, must include the following acknowledgment:  
		 "This product includes software developed by 
	   NPSL Ltd. (http://www.npsl.co.uk/)."
	  Alternately, this acknowledgment may appear in the software itself,
	  if and wherever such third-party acknowledgments normally appear.
	
	4. The names "Gnomon" and "NPSL" must
	  not be used to endorse or promote products derived from this
	  software without prior written permission. For written 
	  permission, please contact mark@npsl.co.uk.
	
	5. Products derived from this software may not be called "gnomon",
	  nor may "NPSL" appear in their name, without prior written
	  permission of NPSL Ltd.
	
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
	WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
	OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
	DISCLAIMED.	IN NO EVENT SHALL NPSL LTD. OR
	ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
	LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
	USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
	ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
	OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
	OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
	SUCH DAMAGE.
		

Gnomon includes software developed by the Apache Software Foundation (http://www.apache.org/), namely Xalan and Xerces.

Gnomon includes OW32 (see http://www.blushingpenguin.com.) OW32 is licensed under the GNU Lesser General Public License, which is reproduced below.

			  GNU LESSER GENERAL PUBLIC LICENSE
			       Version 2.1, February 1999
	
	 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
	     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
	 Everyone is permitted to copy and distribute verbatim copies
	 of this license document, but changing it is not allowed.
	
	[This is the first released version of the Lesser GPL.  It also counts
	 as the successor of the GNU Library Public License, version 2, hence
	 the version number 2.1.]
	
				    Preamble
	
	  The licenses for most software are designed to take away your
	freedom to share and change it.  By contrast, the GNU General Public
	Licenses are intended to guarantee your freedom to share and change
	free software--to make sure the software is free for all its users.
	
	  This license, the Lesser General Public License, applies to some
	specially designated software packages--typically libraries--of the
	Free Software Foundation and other authors who decide to use it.  You
	can use it too, but we suggest you first think carefully about whether
	this license or the ordinary General Public License is the better
	strategy to use in any particular case, based on the explanations below.
	
	  When we speak of free software, we are referring to freedom of use,
	not price.  Our General Public Licenses are designed to make sure that
	you have the freedom to distribute copies of free software (and charge
	for this service if you wish); that you receive source code or can get
	it if you want it; that you can change the software and use pieces of
	it in new free programs; and that you are informed that you can do
	these things.
	
	  To protect your rights, we need to make restrictions that forbid
	distributors to deny you these rights or to ask you to surrender these
	rights.  These restrictions translate to certain responsibilities for
	you if you distribute copies of the library or if you modify it.
	
	  For example, if you distribute copies of the library, whether gratis
	or for a fee, you must give the recipients all the rights that we gave
	you.  You must make sure that they, too, receive or can get the source
	code.  If you link other code with the library, you must provide
	complete object files to the recipients, so that they can relink them
	with the library after making changes to the library and recompiling
	it.  And you must show them these terms so they know their rights.
	
	  We protect your rights with a two-step method: (1) we copyright the
	library, and (2) we offer you this license, which gives you legal
	permission to copy, distribute and/or modify the library.
	
	  To protect each distributor, we want to make it very clear that
	there is no warranty for the free library.  Also, if the library is
	modified by someone else and passed on, the recipients should know
	that what they have is not the original version, so that the original
	author's reputation will not be affected by problems that might be
	introduced by others.

	  Finally, software patents pose a constant threat to the existence of
	any free program.  We wish to make sure that a company cannot
	effectively restrict the users of a free program by obtaining a
	restrictive license from a patent holder.  Therefore, we insist that
	any patent license obtained for a version of the library must be
	consistent with the full freedom of use specified in this license.
	
	  Most GNU software, including some libraries, is covered by the
	ordinary GNU General Public License.  This license, the GNU Lesser
	General Public License, applies to certain designated libraries, and
	is quite different from the ordinary General Public License.  We use
	this license for certain libraries in order to permit linking those
	libraries into non-free programs.
	
	  When a program is linked with a library, whether statically or using
	a shared library, the combination of the two is legally speaking a
	combined work, a derivative of the original library.  The ordinary
	General Public License therefore permits such linking only if the
	entire combination fits its criteria of freedom.  The Lesser General
	Public License permits more lax criteria for linking other code with
	the library.
	
	  We call this license the "Lesser" General Public License because it
	does Less to protect the user's freedom than the ordinary General
	Public License.  It also provides other free software developers Less
	of an advantage over competing non-free programs.  These disadvantages
	are the reason we use the ordinary General Public License for many
	libraries.  However, the Lesser license provides advantages in certain
	special circumstances.
	
	  For example, on rare occasions, there may be a special need to
	encourage the widest possible use of a certain library, so that it becomes
	a de-facto standard.  To achieve this, non-free programs must be
	allowed to use the library.  A more frequent case is that a free
	library does the same job as widely used non-free libraries.  In this
	case, there is little to gain by limiting the free library to free
	software only, so we use the Lesser General Public License.
	
	  In other cases, permission to use a particular library in non-free
	programs enables a greater number of people to use a large body of
	free software.  For example, permission to use the GNU C Library in
	non-free programs enables many more people to use the whole GNU
	operating system, as well as its variant, the GNU/Linux operating
	system.
	
	  Although the Lesser General Public License is Less protective of the
	users' freedom, it does ensure that the user of a program that is
	linked with the Library has the freedom and the wherewithal to run
	that program using a modified version of the Library.
	
	  The precise terms and conditions for copying, distribution and
	modification follow.  Pay close attention to the difference between a
	"work based on the library" and a "work that uses the library".  The
	former contains code derived from the library, whereas the latter must
	be combined with the library in order to run.
	
				  GNU LESSER GENERAL PUBLIC LICENSE
	   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
	
	  0. This License Agreement applies to any software library or other
	program which contains a notice placed by the copyright holder or
	other authorized party saying it may be distributed under the terms of
	this Lesser General Public License (also called "this License").
	Each licensee is addressed as "you".
	
	  A "library" means a collection of software functions and/or data
	prepared so as to be conveniently linked with application programs
	(which use some of those functions and data) to form executables.
	
	  The "Library", below, refers to any such software library or work
	which has been distributed under these terms.  A "work based on the
	Library" means either the Library or any derivative work under
	copyright law: that is to say, a work containing the Library or a
	portion of it, either verbatim or with modifications and/or translated
	straightforwardly into another language.  (Hereinafter, translation is
	included without limitation in the term "modification".)
	
	  "Source code" for a work means the preferred form of the work for
	making modifications to it.  For a library, complete source code means
	all the source code for all modules it contains, plus any associated
	interface definition files, plus the scripts used to control compilation
	and installation of the library.
	
	  Activities other than copying, distribution and modification are not
	covered by this License; they are outside its scope.  The act of
	running a program using the Library is not restricted, and output from
	such a program is covered only if its contents constitute a work based
	on the Library (independent of the use of the Library in a tool for
	writing it).  Whether that is true depends on what the Library does
	and what the program that uses the Library does.
	  
	  1. You may copy and distribute verbatim copies of the Library's
	complete source code as you receive it, in any medium, provided that
	you conspicuously and appropriately publish on each copy an
	appropriate copyright notice and disclaimer of warranty; keep intact
	all the notices that refer to this License and to the absence of any
	warranty; and distribute a copy of this License along with the
	Library.
	
	  You may charge a fee for the physical act of transferring a copy,
	and you may at your option offer warranty protection in exchange for a
	fee.

	  2. You may modify your copy or copies of the Library or any portion
	of it, thus forming a work based on the Library, and copy and
	distribute such modifications or work under the terms of Section 1
	above, provided that you also meet all of these conditions:
	
	    a) The modified work must itself be a software library.
	
	    b) You must cause the files modified to carry prominent notices
	    stating that you changed the files and the date of any change.
	
	    c) You must cause the whole of the work to be licensed at no
	    charge to all third parties under the terms of this License.
	
	    d) If a facility in the modified Library refers to a function or a
	    table of data to be supplied by an application program that uses
	    the facility, other than as an argument passed when the facility
	    is invoked, then you must make a good faith effort to ensure that,
	    in the event an application does not supply such function or
	    table, the facility still operates, and performs whatever part of
	    its purpose remains meaningful.
	
	    (For example, a function in a library to compute square roots has
	    a purpose that is entirely well-defined independent of the
	    application.  Therefore, Subsection 2d requires that any
	    application-supplied function or table used by this function must
	    be optional: if the application does not supply it, the square
	    root function must still compute square roots.)
	
	These requirements apply to the modified work as a whole.  If
	identifiable sections of that work are not derived from the Library,
	and can be reasonably considered independent and separate works in
	themselves, then this License, and its terms, do not apply to those
	sections when you distribute them as separate works.  But when you
	distribute the same sections as part of a whole which is a work based
	on the Library, the distribution of the whole must be on the terms of
	this License, whose permissions for other licensees extend to the
	entire whole, and thus to each and every part regardless of who wrote
	it.
	
	Thus, it is not the intent of this section to claim rights or contest
	your rights to work written entirely by you; rather, the intent is to
	exercise the right to control the distribution of derivative or
	collective works based on the Library.
	
	In addition, mere aggregation of another work not based on the Library
	with the Library (or with a work based on the Library) on a volume of
	a storage or distribution medium does not bring the other work under
	the scope of this License.
	
	  3. You may opt to apply the terms of the ordinary GNU General Public
	License instead of this License to a given copy of the Library.  To do
	this, you must alter all the notices that refer to this License, so
	that they refer to the ordinary GNU General Public License, version 2,
	instead of to this License.  (If a newer version than version 2 of the
	ordinary GNU General Public License has appeared, then you can specify
	that version instead if you wish.)  Do not make any other change in
	these notices.
	
	  Once this change is made in a given copy, it is irreversible for
	that copy, so the ordinary GNU General Public License applies to all
	subsequent copies and derivative works made from that copy.
	
	  This option is useful when you wish to copy part of the code of
	the Library into a program that is not a library.
	
	  4. You may copy and distribute the Library (or a portion or
	derivative of it, under Section 2) in object code or executable form
	under the terms of Sections 1 and 2 above provided that you accompany
	it with the complete corresponding machine-readable source code, which
	must be distributed under the terms of Sections 1 and 2 above on a
	medium customarily used for software interchange.
	
	  If distribution of object code is made by offering access to copy
	from a designated place, then offering equivalent access to copy the
	source code from the same place satisfies the requirement to
	distribute the source code, even though third parties are not
	compelled to copy the source along with the object code.
	
	  5. A program that contains no derivative of any portion of the
	Library, but is designed to work with the Library by being compiled or
	linked with it, is called a "work that uses the Library".  Such a
	work, in isolation, is not a derivative work of the Library, and
	therefore falls outside the scope of this License.
	
	  However, linking a "work that uses the Library" with the Library
	creates an executable that is a derivative of the Library (because it
	contains portions of the Library), rather than a "work that uses the
	library".  The executable is therefore covered by this License.
	Section 6 states terms for distribution of such executables.
	
	  When a "work that uses the Library" uses material from a header file
	that is part of the Library, the object code for the work may be a
	derivative work of the Library even though the source code is not.
	Whether this is true is especially significant if the work can be
	linked without the Library, or if the work is itself a library.  The
	threshold for this to be true is not precisely defined by law.
	
	  If such an object file uses only numerical parameters, data
	structure layouts and accessors, and small macros and small inline
	functions (ten lines or less in length), then the use of the object
	file is unrestricted, regardless of whether it is legally a derivative
	work.  (Executables containing this object code plus portions of the
	Library will still fall under Section 6.)
	
	  Otherwise, if the work is a derivative of the Library, you may
	distribute the object code for the work under the terms of Section 6.
	Any executables containing that work also fall under Section 6,
	whether or not they are linked directly with the Library itself.
	
	  6. As an exception to the Sections above, you may also combine or
	link a "work that uses the Library" with the Library to produce a
	work containing portions of the Library, and distribute that work
	under terms of your choice, provided that the terms permit
	modification of the work for the customer's own use and reverse
	engineering for debugging such modifications.
	
	  You must give prominent notice with each copy of the work that the
	Library is used in it and that the Library and its use are covered by
	this License.  You must supply a copy of this License.  If the work
	during execution displays copyright notices, you must include the
	copyright notice for the Library among them, as well as a reference
	directing the user to the copy of this License.  Also, you must do one
	of these things:
	
	    a) Accompany the work with the complete corresponding
	    machine-readable source code for the Library including whatever
	    changes were used in the work (which must be distributed under
	    Sections 1 and 2 above); and, if the work is an executable linked
	    with the Library, with the complete machine-readable "work that
	    uses the Library", as object code and/or source code, so that the
	    user can modify the Library and then relink to produce a modified
	    executable containing the modified Library.  (It is understood
	    that the user who changes the contents of definitions files in the
	    Library will not necessarily be able to recompile the application
	    to use the modified definitions.)
	
	    b) Use a suitable shared library mechanism for linking with the
	    Library.  A suitable mechanism is one that (1) uses at run time a
	    copy of the library already present on the user's computer system,
	    rather than copying library functions into the executable, and (2)
	    will operate properly with a modified version of the library, if
	    the user installs one, as long as the modified version is
	    interface-compatible with the version that the work was made with.
	
	    c) Accompany the work with a written offer, valid for at
	    least three years, to give the same user the materials
	    specified in Subsection 6a, above, for a charge no more
	    than the cost of performing this distribution.
	
	    d) If distribution of the work is made by offering access to copy
	    from a designated place, offer equivalent access to copy the above
	    specified materials from the same place.
	
	    e) Verify that the user has already received a copy of these
	    materials or that you have already sent this user a copy.
	
	  For an executable, the required form of the "work that uses the
	Library" must include any data and utility programs needed for
	reproducing the executable from it.  However, as a special exception,
	the materials to be distributed need not include anything that is
	normally distributed (in either source or binary form) with the major
	components (compiler, kernel, and so on) of the operating system on
	which the executable runs, unless that component itself accompanies
	the executable.
	
	  It may happen that this requirement contradicts the license
	restrictions of other proprietary libraries that do not normally
	accompany the operating system.  Such a contradiction means you cannot
	use both them and the Library together in an executable that you
	distribute.
	
	  7. You may place library facilities that are a work based on the
	Library side-by-side in a single library together with other library
	facilities not covered by this License, and distribute such a combined
	library, provided that the separate distribution of the work based on
	the Library and of the other library facilities is otherwise
	permitted, and provided that you do these two things:
	
	    a) Accompany the combined library with a copy of the same work
	    based on the Library, uncombined with any other library
	    facilities.  This must be distributed under the terms of the
	    Sections above.
	
	    b) Give prominent notice with the combined library of the fact
	    that part of it is a work based on the Library, and explaining
	    where to find the accompanying uncombined form of the same work.
	
	  8. You may not copy, modify, sublicense, link with, or distribute
	the Library except as expressly provided under this License.  Any
	attempt otherwise to copy, modify, sublicense, link with, or
	distribute the Library is void, and will automatically terminate your
	rights under this License.  However, parties who have received copies,
	or rights, from you under this License will not have their licenses
	terminated so long as such parties remain in full compliance.
	
	  9. You are not required to accept this License, since you have not
	signed it.  However, nothing else grants you permission to modify or
	distribute the Library or its derivative works.  These actions are
	prohibited by law if you do not accept this License.  Therefore, by
	modifying or distributing the Library (or any work based on the
	Library), you indicate your acceptance of this License to do so, and
	all its terms and conditions for copying, distributing or modifying
	the Library or works based on it.
	
	  10. Each time you redistribute the Library (or any work based on the
	Library), the recipient automatically receives a license from the
	original licensor to copy, distribute, link with or modify the Library
	subject to these terms and conditions.  You may not impose any further
	restrictions on the recipients' exercise of the rights granted herein.
	You are not responsible for enforcing compliance by third parties with
	this License.
	
	  11. If, as a consequence of a court judgment or allegation of patent
	infringement or for any other reason (not limited to patent issues),
	conditions are imposed on you (whether by court order, agreement or
	otherwise) that contradict the conditions of this License, they do not
	excuse you from the conditions of this License.  If you cannot
	distribute so as to satisfy simultaneously your obligations under this
	License and any other pertinent obligations, then as a consequence you
	may not distribute the Library at all.  For example, if a patent
	license would not permit royalty-free redistribution of the Library by
	all those who receive copies directly or indirectly through you, then
	the only way you could satisfy both it and this License would be to
	refrain entirely from distribution of the Library.
	
	If any portion of this section is held invalid or unenforceable under any
	particular circumstance, the balance of the section is intended to apply,
	and the section as a whole is intended to apply in other circumstances.
	
	It is not the purpose of this section to induce you to infringe any
	patents or other property right claims or to contest validity of any
	such claims; this section has the sole purpose of protecting the
	integrity of the free software distribution system which is
	implemented by public license practices.  Many people have made
	generous contributions to the wide range of software distributed
	through that system in reliance on consistent application of that
	system; it is up to the author/donor to decide if he or she is willing
	to distribute software through any other system and a licensee cannot
	impose that choice.
	
	This section is intended to make thoroughly clear what is believed to
	be a consequence of the rest of this License.
	
	  12. If the distribution and/or use of the Library is restricted in
	certain countries either by patents or by copyrighted interfaces, the
	original copyright holder who places the Library under this License may add
	an explicit geographical distribution limitation excluding those countries,
	so that distribution is permitted only in or among countries not thus
	excluded.  In such case, this License incorporates the limitation as if
	written in the body of this License.
	
	  13. The Free Software Foundation may publish revised and/or new
	versions of the Lesser General Public License from time to time.
	Such new versions will be similar in spirit to the present version,
	but may differ in detail to address new problems or concerns.
	
	Each version is given a distinguishing version number.  If the Library
	specifies a version number of this License which applies to it and
	"any later version", you have the option of following the terms and
	conditions either of that version or of any later version published by
	the Free Software Foundation.  If the Library does not specify a
	license version number, you may choose any version ever published by
	the Free Software Foundation.
	
	  14. If you wish to incorporate parts of the Library into other free
	programs whose distribution conditions are incompatible with these,
	write to the author to ask for permission.  For software which is
	copyrighted by the Free Software Foundation, write to the Free
	Software Foundation; we sometimes make exceptions for this.  Our
	decision will be guided by the two goals of preserving the free status
	of all derivatives of our free software and of promoting the sharing
	and reuse of software generally.
	
				    NO WARRANTY
	
	  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
	WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
	EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
	OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
	KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
	PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
	LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
	THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
	
	  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
	WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
	AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
	FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
	CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
	LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
	RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
	FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
	SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
	DAMAGES.
	
			     END OF TERMS AND CONDITIONS