JSMにはHTTPサーバーが含まれており、スタティックなファイル・リソースとして使用したり、カスタムJavaクラス経由で動的なコンテンツとしても使用できます。
HTTPサーバーはXMLファイルとmanager.propertiesエントリーにより構成されています。XML構成ファイルはhttpdプロパティにより指定されます。
manager.properties
httpd=system/httpd.xml
HTTPサーバー構成ファイルでは、複数のHTTPインスタンスやインスタンスを持つ複数の仮想ホストを記述できます。HTTPインスタンスはTCP/IP通信のオプションとアクセス、エラー・ログ・ファイルの場所を指定します。IPアドレスの許可・拒否のアクセス・コントロールはインスタンス・レベルで指定できます。MMEファイル・タイプのインスタンス全域内のマッピングも指定可能です。ポート属性の省略値は80で、インターフェイスの省略値は*ALLです。バックログの省略値は、256です。
HTTPインスタンスを使って、複数の仮想ホスト・セクションが指定できます。HTTP要求が読み込まれると、HTTPホスト・プロパティを使って仮想ホスト・セクションが探し出され、これが要求を処理する際に使用されます。ホスト・プロパティと比べると、仮想ホスト名では大文字・小文字の区別がされます。ホスト・プロパティにポート・コンポーネントが存在する場合、一致する仮想ホスト名の検索に使用される前に削除されます。仮想ホスト・セクションが見つからない場合は、省略値の'*'仮想ホストが使用されます。仮想ホスト要素にはルートとインデックス属性があり、インスタンスのルートとインデックスの値を上書きします。インスタンスまたは仮想要素にアクティブなfalse値がない場合は、この要素は無視されます。
IPアドレスの許可・拒否のアクセス・コントロールは仮想ホスト・レベルで指定できます。ユーザー・エージャントとコンテンツ長アクセス・コントロールも指定可能です。MMEファイル・タイプのマッピングも指定できます。
基本認証領域では様々なリソースの場所へのアクセス制限を設定します。仮想ホストの保護要素は制限されたリソースを指定する際に使用されます。
Javaクラスは様々なリソースのパスに基づき実行されます。仮想ホストのスクリプト要素はJavaクラスと特定のリソース・パスを関連付ける際に利用されます。trace属性はHTTPトランザクションのJSMトレースを可能にします。clienttrace属性は、クエリー文字列 URLパラメータ trace=true を使って、ブラウザーのURLからのトレースを可能にします。
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>
カスタムのJavaクラスを書いて、HTTPサーバーからのHTTP要求を処理するには、Javaクラスで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 ( long length )
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 )
String Enumeration getPropertyNames ()
Properties getQueryParameters ()
String getHost ()
long getContentLength ()
boolean canAcceptGZIP ()
String getUserAgent ()
String getUserAgentVersion ()
boolean isUserAgent ( String agent )
boolean isUserAgentIE6 ()
次のJavaクラスはスタティック・ファイルの要求を取り扱うJSM HTTPサーバークラスです。
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 ) ;
}
}