4.17 JSM HTTP Server

JSM includes a HTTP server that can be used to serve static file resources or dynamic content via custom Java classes.

The HTTP server is configured by an XML file and manager.properties entries. The XML configuration file is specified by the httpd property.

manager.properties

 httpd=system/httpd.xml

 

The HTTP server configuration file can describe multiple HTTP instances and multiple virtual hosts with an instance. The HTTP instance specifies the TCP/IP communication options and access and error log file locations. IP address access control for allow and deny can be specified at the instance level. Instance wide MIME file type mapping can also be specified. The default value for the port attribute is 80 and the default value for the interface is *ALL. The backlog default value is 256.

With a HTTP instance, multiple virtual host sections can be specified. When a HTTP request is read, the HTTP Host property is used to locate the virtual host section to be used to process the request. The virtual host name is case insensitive compared to the Host property name. If a port component exists on the Host property then this is removed before it is used to find a matching virtual host name. If no virtual host section is found then the default '*' virtual host is used. A virtual host element can also contain a root and index attribute to override the instance root and index values. If an instance or virtual elements have an attribute active value of false, then the element is ignored.

IP address access control for allow and deny can be specified at the virtual host level. Also user agent and content length access control can be specified. MIME file type mapping can also be specified.

Basic authentication realms can be setup to restrict access to various resource locations. The virtual host protect element is used to specify a restricted resource.

Java classes can be executed based on various resource paths. The virtual host script element is used to associate a Java classes to a particular resource path. The trace attribute enables JSM tracing of the HTTP transaction. The clienttrace attribute allows tracing to be enabled from the browser URL using the query string URL parameter trace=true.

 

httpd.xml

 

<?xml version="1.0" encoding="UTF-8"?>

 

<configuration>

 

  <instance name="WebServer" active="true" root="www/instance/htdocs" index="index.html">

 

    <errorlog enabled="true" file="www/instance/logs/error.log"/>

 

    <accesslog enabled="true" file="www/instance/logs/access.log"/>

 

    <listen port="4563" interface="*ALL" backlog="256"

            secure="false" store="pki/wwwssl.jks" password="password"

            buffersend="-1" bufferreceive="-1" nodelay="false" timeout="5"/>

 

    <access>

 

      <!--

            Once a true condition occurs no more evaluations are done.

 

            <deny address="*"/>

            <deny address="10.2.1.45"/>

 

            <allow address="*"/>

            <allow address="10.2.1.45"/>

      -->

 

    </access>

 

    <mimetype>

 

      <!--

            These are the default values.

        -->

 

      <map extension="png"  type="image/png"/>

      <map extension="gif"  type="image/gif"/>

      <map extension="jpg"  type="image/jpeg"/>

      <map extension="jpeg" type="image/jpeg"/>

      <map extension="tiff" type="image/tiff"/>

      <map extension="ico"  type="image/x-icon"/>

      <map extension="svg"  type="image/svg+xml"/>

      <map extension="pdf"  type="application/pdf"/>

      <map extension="css"  type="text/css; charset=utf-8"/>

      <map extension="xsl"  type="text/xls; charset=utf-8"/>

      <map extension="xml"  type="text/xml; charset=utf-8"/>

      <map extension="htm"  type="text/html; charset=utf-8"/>

      <map extension="html" type="text/html; charset=utf-8"/>

      <map extension="js"   type="application/x-javascript; charset=utf-8"/>

 

    </mimetype>

 

    <virtual host="*" active="true">

 

      <access>

 

        <!--

              Once a true condition occurs no more evaluations are done.

 

              <deny address="*"/>

              <deny address="10.2.1"/>

              <deny address="10.2.1.45"/>

 

              <allow address="*"/>

              <allow address="10.2.1"/>

              <allow address="10.2.1.45"/>

 

              <deny useragent="*"/>

              <deny useragent="?"/>

              <deny useragent="webos"/>

              <deny useragent="opera"/>

              <deny useragent="chrome"/>

              <deny useragent="safari"/>

              <deny useragent="android"/>

              <deny useragent="firefox"/>

              <deny useragent="explorer"/>

              <deny useragent="imac"/>

              <deny useragent="ipad"/>

              <deny useragent="ipod"/>

              <deny useragent="iphone"/>

              <deny useragent="iwork"/>

              <deny useragent="msnbot"/>

              <deny useragent="lansaua"/>

              <deny useragent="yahoobot"/>

              <deny useragent="googlebot"/>

              <deny useragent="googletoolbar"/>

              <deny useragent="longreach"/>

              <deny useragent="webdavnav"/>

 

              <allow useragent="*"/>

              <allow useragent="?"/>

              <allow useragent="webos"/>

              <allow useragent="opera"/>

              <allow useragent="chrome"/>

              <allow useragent="safari"/>

              <allow useragent="android"/>

              <allow useragent="firefox"/>

              <allow useragent="explorer"/>

              <allow useragent="imac"/>

              <allow useragent="ipad"/>

              <allow useragent="ipod"/>

              <allow useragent="iphone"/>

              <allow useragent="iwork"/>

              <allow useragent="msnbot"/>

              <allow useragent="lansaua"/>

              <allow useragent="yahoobot"/>

              <allow useragent="googlebot"/>

              <allow useragent="googletoolbar"/>

              <allow useragent="longreach"/>

              <allow useragent="webdavnav"/>

 

              <deny contentlength="4096"/>     Deny access if content length is greater than value

              <allow contentlength="4096"/>    Allow access if content length less than or equal to value

 

              Zero content length from the browser is a special case and access is allowed for no content connections

        -->

 

        <!--

              The default is to allow access for all addresses, useragents and content lengths

        -->

 

      </access>

 

      <protect>

 

        <realm name="Area 51">

          <!-- access is a hash of user, password and realm -->

          <user name="user" access="bb644a9819425bfd8586b408896a1031"/>

        </realm>

 

        <match uri="/restricted" realm="Area 51" authentication="basic,digest"/>

 

      </protect>

 

      <script>

 

        <match uri="/ping.jsp" class="com.lansa.jsm.JSMHTTPServicePing" trace="false" clienttrace="false"/>

 

        <match uri="/" class="com.lansa.jsm.JSMHTTPServiceFile" trace="false" clienttrace="false">

           <parameter name="cache.maxage"       value="28800"/>

           <parameter name="cache.maxage.pdf"   value="28800"/>

           <parameter name="cache.maxage.image" value="28800"/>

        </match>

 

      </script>

 

      <mimetype>

 

        <map extension="pdf" type="application/pdf"/>

 

        <!--

              Defaults to instance mimetype

          -->

 

      </mimetype>

 

    </virtual>

 

  </instance>

 

</configuration>

 

 

To write a custom Java classes to process HTTP requests from the HTTP server requires the Java class to implement the com.lansa.jsm.JSMHTTPService interface.

JSMHTTPService interface

 

public interface JSMHTTPService

{

    public void doRequest ( JSMTrace trace,

                            JSMHTTPVirtual virtual,

                            JSMHTTPContext context,

                            JSMHTTPTransport transport,

                            JSMHTTPRequest request ) ;

}

 

JSMHTTPVirtual public methods

 

String getHost ()

boolean isActive () ;

File getDocumentRoot ()

File getDocumentIndex ()

File getFile ( String path )

String getContentType ( File file )

void logException ( JSMHTTPTransport transport, Throwable t )

void logError ( JSMHTTPTransport transport, JSMHTTPRequest request, String message )

 

JSMHTTPContext public methods

 

HashMap getServiceParameters ()

JSMHTTPHost[] getServiceHosts ()

 

JSMHTTPHost public methods

 

String getName ()

HashMap getParameters ()

 

JSMHTTPTransport public methods

 

int getId ()

Socket getSocket ()

boolean isSecure ()

String getClientAddress ()

InetAddress getInetAddress ()

InputStream getInputStream ()

OutputStream getOutputStream ()

void consumeInputStream ( JSMHTTPRequest request )

byte[] readInputStream ( int length )

void sendNotFound ( String message )

void sendForbidden ( String message )

void sendNotImplemented ( String message )

 

JSMHTTPRequest public methods

 

String getHead ()

String getMethod ()

String getVersion ()

String getResourceRaw ()

String getResourcePath ()

Properties getProperties ()

String getProperty ( String key )

JSMHTTPNameValue[] getQueryNameValues ()

String getHost ()

long getContentLength ()

boolean canAcceptGZIP ()

String getUserAgent ()

String getUserAgentVersion ()

boolean isUserAgent ( String agent )

boolean isUserAgentIE6 ()

 

JSMHTTPNameValue public methods

 

String getName ()

String getValue ()

 

 

The following Java class is the JSM HTTP server class that handles static file requests.

Example

 

package com.acme.service ;

 

import java.io.* ;

 

import java.util.Date ;

import java.util.HashMap ;

 

import java.util.zip.GZIPInputStream ;

 

import com.lansa.jsm.* ;

 

public final class Example implements JSMHTTPService

{

    private final static String CRLF = "\r\n" ;

    private final static String EMPTY_STRING = "" ;

    private final static String ENCODING_UTF8 = "UTF-8" ;

 

    private JSMTrace m_trace = null ;

    private JSMHTTPRequest m_request = null ;

    private JSMHTTPVirtual m_virtual = null ;

    private JSMHTTPTransport m_transport = null ;

 

    /*

        RFC2616 - Hypertext Transfer Protocol - HTTP/1.1

    */

 

    private HashMap m_serviceParameters = null ; // Not synchronized

 

    public Example ()

    {

    }

 

    public final void doRequest ( JSMTrace trace,

                                  JSMHTTPVirtual virtual,

                                  JSMHTTPContext context,

                                  JSMHTTPTransport transport,

                                  JSMHTTPRequest request )

    {

        try

        {

            m_trace = trace ;

 

            m_virtual = virtual ;

 

            m_request = request ;

 

            m_transport = transport ;

 

            m_serviceParameters = context.getServiceParameters () ;

 

            handleRequest () ;

        }

        catch ( Throwable t )

        {

            /*

                Log exception

            */

 

            m_virtual.logException ( m_transport, t ) ;

 

            if ( m_trace == null )

            {

                System.out.println ( "JSMHTTPServiceFile: handle request exception: " + t.getMessage () ) ;

 

                t.printStackTrace () ;

            }

            else

            {

                m_trace.print ( t ) ;

            }

        }

    }

 

    private final void handleRequest () throws IOException

    {

        if ( m_trace != null )

        {

            m_trace.println ( "Handle request for resource path: ", m_request.getResourcePath () ) ;

        }

 

        /*

            No request content is expected for GET and HEAD methods

 

            Somebody might have used a POST and content

 

            Need to consume any content on the socket input stream

 

            This allows the browser to switch over and read the HTTP response

        */

 

        m_transport.consumeInputStream ( m_request.getContentLength () ) ;

 

        /*

            Check method

        */

 

        if ( !isAllowedMethod ( m_request.getMethod () ) )

        {

            m_virtual.logError ( m_transport, m_request, "Method is not implemented" ) ;

 

            m_transport.sendNotImplemented ( m_request.getMethod () ) ;

 

            return ;

        }

 

        /*

            Get file

        */

 

        String path = m_request.getResourcePath () ;

 

        File file = m_virtual.getFile ( path ) ;

 

        if ( file == null )

        {

            if ( m_trace != null )

            {

                m_trace.println ( "File not found" ) ;

            }

 

            m_virtual.logError ( m_transport, m_request, "File not found" ) ;

 

            m_transport.sendNotFound ( path ) ;

 

            return ;

        }

 

        /*

            File found

        */

 

        if ( file.isDirectory () )

        {

            if ( m_trace != null )

            {

                m_trace.println ( "File is a directory: ", file.getAbsolutePath () ) ;

            }

 

            m_virtual.logError ( m_transport, m_request, "File is a directory" ) ;

 

            m_transport.sendNotFound ( path ) ;

 

            return ;

        }

 

        if ( m_request.getMethod().equals ( "HEAD" ) )

        {

            /*

                HEAD file

            */

 

            sendHEAD ( file ) ;

 

            return ;

        }

 

        /*

            GET file

        */

 

        sendFile ( file ) ;

 

        /*

            Remove one-shot directory file

        */

 

        if ( file.getParentFile().getName().equals ( "one-shot" ) )

        {

            if ( !file.delete () )

            {

                if ( m_trace != null )

                {

                    m_trace.println ( "Cannot delete one-shot file: ", file.getAbsolutePath () ) ;

                }

 

                m_virtual.logError ( m_transport, m_request, "Cannot delete one-shot file" ) ;

            }

        }

 

    }

 

    private final void sendHEAD ( File sendFile )

    {

        /*

            The HEAD response is the same as the GET response, except no content is sent

 

            The Content-Length of the file is included, but no content

        */

 

        try

        {

            if ( m_trace != null )

            {

                m_trace.println ( "Send HEAD response: ", sendFile.getCanonicalPath () ) ;

            }

 

            /*

                Create protocol

 

                RFC2616 - HTTP/1.1

 

                If the server chooses to close the connection immediately after sending the response,

                it should send a Connection header including the close token

            */

 

            long contentLength = sendFile.length () ;

 

            boolean isCompressed = JSMHTTPHelper.isCompressed ( sendFile ) ;

 

            if ( isCompressed && !canAcceptCompressed () )

            {

                isCompressed = false ;

 

                contentLength = JSMHTTPHelper.getUncompressedContentLength ( sendFile ) ;

            }

 

            String contentType = m_virtual.getContentType ( sendFile ) ;

 

            StringBuffer response = new StringBuffer ( 512 ) ;

 

            response.append ( "HTTP/1.1 200 OK" ) ;

            response.append ( CRLF ) ;

 

            response.append ( "Date: " ) ;

            response.append ( JSMDateTime.getFormattedHTTPDate ( new Date () ) ) ;

            response.append ( CRLF ) ;

 

            response.append ( "Content-Type: " ) ;

            response.append ( contentType ) ;

            response.append ( CRLF ) ;

 

            response.append ( "Content-Length: " ) ;

            response.append ( Long.toString ( contentLength ) ) ;

            response.append ( CRLF ) ;

 

            if ( isCompressed )

            {

                response.append ( "Content-Encoding: gzip" ) ;

                response.append ( CRLF ) ;

            }

 

            /*

                Response date is mandatory for caching to work

            */

 

            int cacheAge = getCacheAge ( contentType, sendFile ) ;

 

            if ( cacheAge <= 0 )

            {

                response.append ( "Cache-Control: max-age=0, s-maxage=0, must-revalidate, proxy-revalidate, no-cache" ) ;

                response.append ( CRLF ) ;

            }

 

            if ( JSMHTTPHelper.isTextPlain ( contentType ) )

            {

                /*

                    Stop IE8 from doing content sniffing

                */

 

                response.append ( "X-Content-Type-Options: nosniff" ) ;

                response.append ( CRLF ) ;

            }

 

            response.append ( "Connection: close" ) ;

            response.append ( CRLF ) ;

 

            response.append ( CRLF ) ;

 

            byte[] protocol = response.toString().getBytes ( ENCODING_UTF8 ) ;

 

            if ( m_trace != null )

            {

                File file = m_trace.createTraceFile ( "HTTP_PROTOCOL_RESPONSE.TXT" ) ;

 

                JSMHTTPHelper.outputToFile ( file, protocol ) ;

            }

 

            /*

                Send response

 

                If the client socket is closed, then a broken pipe exception will occur

            */

 

            OutputStream outputStream = m_transport.getOutputStream () ;

 

            outputStream.write ( protocol ) ;

 

            outputStream.flush () ;

        }

        catch ( IOException e )

        {

            /*

                The user can close the browser, before all the content is sent

            */

 

            if ( m_trace != null )

            {

                m_trace.println ( "Error sending HEAD response" ) ;

            }

 

            m_virtual.logError ( m_transport, m_request, "Error sending HEAD response" ) ;

        }

    }

 

    private final void sendFile ( File sendFile )

    {

        InputStream inputStream = null ;

 

        try

        {

            if ( m_trace != null )

            {

                m_trace.println ( "Send file response: ", sendFile.getCanonicalPath () ) ;

            }

 

            /*

                Create protocol

 

                RFC2616 - HTTP/1.1

 

                If the server chooses to close the connection immediately after sending the response,

                it should send a Connection header including the close token

            */

 

            boolean sendChunked = false ;

 

            boolean uncompressContent = false ;

 

            long contentLength = sendFile.length () ;

 

            boolean isCompressed = JSMHTTPHelper.isCompressed ( sendFile ) ;

 

            if ( isCompressed && !canAcceptCompressed () )

            {

                isCompressed = false ;

 

                uncompressContent = true ;

 

                contentLength = JSMHTTPHelper.getUncompressedContentLength ( sendFile ) ;

            }

 

            String contentType = m_virtual.getContentType ( sendFile ) ;

 

            StringBuffer response = new StringBuffer ( 512 ) ;

 

            response.append ( "HTTP/1.1 200 OK" ) ;

            response.append ( CRLF ) ;

 

            response.append ( "Date: " ) ;

            response.append ( JSMDateTime.getFormattedHTTPDate ( new Date () ) ) ;

            response.append ( CRLF ) ;

 

            response.append ( "Content-Type: " ) ;

            response.append ( contentType ) ;

            response.append ( CRLF ) ;

 

            if ( sendChunked )

            {

                response.append ( "Transfer-Encoding: chunked" ) ;

                response.append ( CRLF ) ;

            }

            else

            {

                response.append ( "Content-Length: " ) ;

                response.append ( Long.toString ( contentLength ) ) ;

                response.append ( CRLF ) ;

            }

 

            if ( isCompressed )

            {

                response.append ( "Content-Encoding: gzip" ) ;

                response.append ( CRLF ) ;

            }

 

            /*

                Response Date: is mandatory for caching to work

            */

 

            int cacheAge = getCacheAge ( contentType, sendFile ) ;

 

            if ( cacheAge <= 0 )

            {

                response.append ( "Cache-Control: max-age=0, s-maxage=0, must-revalidate, proxy-revalidate, no-cache" ) ;

                response.append ( CRLF ) ;

            }

            else

            {

                response.append ( "Cache-Control: " ) ;

                response.append ( "max-age=" ) ;

                response.append ( Integer.toString ( cacheAge ) ) ;

                response.append ( ", s-maxage=" ) ;

                response.append ( Integer.toString ( cacheAge ) ) ;

                response.append ( CRLF ) ;

            }

 

            if ( JSMHTTPHelper.isTextPlain ( contentType ) )

            {

                /*

                    Stop IE8 from doing content sniffing

                */

 

                response.append ( "X-Content-Type-Options: nosniff" ) ;

                response.append ( CRLF ) ;

            }

 

            response.append ( "Connection: close" ) ;

            response.append ( CRLF ) ;

 

            response.append ( CRLF ) ;

 

            byte[] protocol = response.toString().getBytes ( ENCODING_UTF8 ) ;

 

            if ( m_trace != null )

            {

                File file = m_trace.createTraceFile ( "HTTP_PROTOCOL_RESPONSE.TXT" ) ;

 

                JSMHTTPHelper.outputToFile ( file, protocol ) ;

            }

 

            /*

                Send response

 

                If the client socket is closed, then a broken pipe exception will occur

            */

 

            OutputStream outputStream = m_transport.getOutputStream () ;

 

            outputStream.write ( protocol ) ;

 

            /*

                Send file content

            */

 

            if ( uncompressContent )

            {

                if ( m_trace != null )

                {

                    m_trace.println ( "Uncompress content" ) ;

                }

 

                inputStream = new GZIPInputStream ( new FileInputStream ( sendFile ), 16384 ) ;

            }

            else

            {

                inputStream = new FileInputStream ( sendFile ) ;

            }

 

            if ( sendChunked )

            {

                JSMHTTPHelper.sendChunked ( inputStream, outputStream ) ;

            }

            else

            {

                JSMHTTPHelper.sendStream ( inputStream, outputStream ) ;

            }

 

            inputStream.close () ;

 

            outputStream.flush () ;

        }

        catch ( IOException e )

        {

            /*

                The user can close the browser, before all the content is sent

            */

 

            if ( inputStream != null )

            {

                try

                {

                    inputStream.close () ;

                }

                catch ( Exception e2 )

                {

                }

            }

 

            if ( m_trace != null )

            {

                m_trace.println ( "Error sending file response" ) ;

            }

 

            m_virtual.logError ( m_transport, m_request, "Error sending file response" ) ;

        }

    }

 

    private final boolean isAllowedMethod ( String method )

    {

        /*

            Standard HTTP methods

 

            GET

            PUT

            POST

            HEAD

            TRACE

            DELETE

            OPTIONS

            CONNECT

        */

 

        if ( method.equals ( "GET" ) )

        {

            return true ;

        }

 

        if ( method.equals ( "HEAD" ) )

        {

            return true ;

        }

 

        return false ;

    }

 

    private final boolean canAcceptCompressed ()

    {

        if ( m_request.canAcceptGZIP () )

        {

            return true ;

        }

 

        return false ;

    }

 

    private final int getCacheAge ( String contentType, File sendFile )

    {

        int cacheAge = getCacheAge () ;

 

        if ( JSMHTTPHelper.isImage ( contentType ) )

        {

            return getCacheAgeImage () ;

        }

 

        if ( JSMHTTPHelper.isPDF ( contentType ) )

        {

            return getCacheAgePDF () ;

        }

 

        return cacheAge ;

    }

 

    private final int getCacheAge ()

    {

        return getServiceParameterInteger ( "CACHE.MAXAGE" ) ;

    }

 

    private final int getCacheAgePDF ()

    {

        /*

            IE does not pass the pdf content off to the Adobe reader if the cache is 0

 

            Example browser URI /axes/dbmhelp.pdf

        */

 

        return getServiceParameterInteger ( "CACHE.MAXAGE.PDF" ) ;

    }

 

    private final int getCacheAgeImage ()

    {

        /*

            YUI/IE image caching

 

            IE is making frequent requests for images

 

            Tell the browser to cache the image, the default is no cache

        */

 

        return getServiceParameterInteger ( "CACHE.MAXAGE.IMAGE" ) ;

    }

 

    private final int getServiceParameterInteger ( String property )

    {

        String value = (String)m_serviceParameters.get ( property ) ;

 

        if ( value == null )

        {

            return 0 ;

        }

 

        if ( value.equals ( EMPTY_STRING ) )

        {

            return 0 ;

        }

 

        return Integer.parseInt ( value ) ;

    }

}