4.17 JSM HTTPサーバー
JSMにはHTTPサーバーが含まれており、スタティックなファイル・リソースとして使用したり、カスタムJavaクラス経由で動的なコンテンツとしても使用できます。
HTTPサーバーはXMLファイルと複数のmanager.propertiesエントリーにより構成されています。XML構成ファイルはhttpdプロパティにより指定されます。HTTPサーバーのライセンスは、通常のJSMライセンス・システムまたはaXesアプリケーション・サーバー・ライセンスにより取得できます。
manager.properties
httpd=system/httpd.xml
httpd.axes.release=V1R3M6
httpd.axes.instance=AXES
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="HTTP Instance" 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 secure="false" store="pki/wwwssl.jks" password="password" port="4555" interface="*ALL"
backlog="256" 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"/>
-->
<deny address="222.66.167"/> <!-- China -->
<deny address="222.66.167.142"/> <!-- China -->
<deny address="221.210.48"/> <!-- China -->
<deny address="221.210.48.254"/> <!-- China -->
</access>
<mimetype>
<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="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"/>
<!--
Defaults to Java Activation Framework FileTypeMap, refer to system/filetype.txt file
-->
</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="icab"/>
<deny useragent="opera"/>
<deny useragent="chrome"/>
<deny useragent="safari"/>
<deny useragent="firefox"/>
<deny useragent="explorer"/>
<deny useragent="kronqueror"/>
<deny useragent="ipod"/>
<deny useragent="iphone"/>
<deny useragent="msnbot"/>
<deny useragent="lansaua"/>
<deny useragent="yahoobot"/>
<deny useragent="googlebot"/>
<deny useragent="googletoolbar"/>
<allow useragent="*"/>
<allow useragent="?"/>
<allow useragent="icab"/>
<allow useragent="opera"/>
<allow useragent="chrome"/>
<allow useragent="safari"/>
<allow useragent="firefox"/>
<allow useragent="explorer"/>
<allow useragent="kronqueror"/>
<allow useragent="ipod"/>
<allow useragent="iphone"/>
<allow useragent="msnbot"/>
<allow useragent="lansaua"/>
<allow useragent="yahoobot"/>
<allow useragent="googlebot"/>
<allow useragent="googletoolbar"/>
<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
-->
<allow useragent="iphone"/>
<allow useragent="opera"/>
<allow useragent="chrome"/>
<allow useragent="safari"/>
<allow useragent="firefox"/>
<allow useragent="explorer"/>
<deny useragent="*"/>
</access>
<protect>
<match uri="/restricted" realm="Restricted Realm">
<user name="user" auth="tpUq/4Pn5A64fVZyQ0gOJ8ZWqkY="/><!-- password is password -->
</match>
</protect>
<script>
<match uri="/axes/spoolservice" class="com.lansa.jsm.service.HTTPServiceSpool" trace="false" clienttrace="true">
<!-- Native driver, Add CHGJOB CCSID(37) to RUNJSMEXT program to set job ccsid
<parameter name="database.url" value="jdbc:db2://LOCALHOST/AXES"/>
<parameter name="database.driver" value="com.ibm.db2.jdbc.app.DB2Driver"/>
-->
<parameter name="database.url" value="jdbc:as400://LOCALHOST/AXES"/>
<parameter name="database.driver" value="com.ibm.as400.access.AS400JDBCDriver"/>
</match>
<match uri="/" class="com.lansa.jsm.JSMHTTPServiceFile" trace="false" clienttrace="false"/>
</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,
Properties serviceParameters,
JSMHTTPTransport transport,
JSMHTTPRequest request ) ;
}
JSMHTTPVirtual public methods
String getHost ()
boolean isActive () ;
File getDocumentRoot ()
File getDocumentIndex ()
File getFile ( String path )
String getContentType ( File file )
void logError ( JSMHTTPTransport transport, JSMHTTPRequest request, String message )
void logException ( JSMHTTPTransport transport, Throwable t )
JSMHTTPTransport public methods
int getId ()
String getClientAddress ()
InetAddress getInetAddress ()
InputStream getInputStream ()
OutputStream getOutputStream ()
byte[] readInputStream ( int length )
void sendNotFound ( String message )
void sendForbidden ( String message )
void sendNotImplemented ( String message )
JSMHTTPRequest public methods
String getMethod ()
String getVersion ()
String getResourceRaw ()
String getResourcePath ()
String getHead ()
String getProperty ( String key )
String Enumeration getPropertyNames ()
Properties getQueryParameters ()
String getHost ()
int getContentLength ()
boolean canAcceptGZIP ()
String getUserPassword ()
String getUserAgent ()
String getUserAgentVersion ()
boolean isUserAgent ( String agent )
boolean isUserAgentIE6 ()
次のJavaクラスはスタティック・ファイルの要求を取り扱うJSM HTTPサーバークラスです。
JSMHTTPServiceFile
package com.lansa.jsm ;
import java.io.* ;
import java.util.Properties ;
public final class JSMHTTPServiceFile implements JSMHTTPService
{
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 ;
private Properties m_serviceParameters = null ;
public JSMHTTPServiceFile ()
{
}
public final void doRequest ( JSMTrace trace,
JSMHTTPVirtual virtual,
Properties serviceParameters,
JSMHTTPTransport transport,
JSMHTTPRequest request )
{
try
{
m_trace = trace ;
m_virtual = virtual ;
m_request = request ;
m_transport = transport ;
m_serviceParameters = serviceParameters ;
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 () ) ;
}
/*
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 ) ;
}
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
*/
int contentLength = (int)sendFile.length () ;
String contentType = m_virtual.getContentType ( sendFile ) ;
StringBuffer response = new StringBuffer ( 128 ) ;
response.append ( "HTTP/1.0 200 OK\r\n" ) ;
response.append ( "Content-Type: " + contentType + "\r\n" ) ;
response.append ( "Content-Length: " + contentLength + "\r\n" ) ;
if ( isImage ( contentType ) )
{
response.append ( "Cache-Control: max-age=300\r\n" ) ;
}
if ( isGZIP ( contentType, contentLength, sendFile ) )
{
response.append ( "Content-Encoding: gzip\r\n" ) ;
}
response.append ( "Connection: close\r\n" ) ;
response.append ( "\r\n" ) ;
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 )
{
try
{
if ( m_trace != null )
{
m_trace.println ( "Send file response: ", sendFile.getCanonicalPath () ) ;
}
/*
Create protocol
*/
int contentLength = (int)sendFile.length () ;
String contentType = m_virtual.getContentType ( sendFile ) ;
StringBuffer response = new StringBuffer ( 128 ) ;
response.append ( "HTTP/1.0 200 OK\r\n" ) ;
response.append ( "Content-Type: " + contentType + "\r\n" ) ;
response.append ( "Content-Length: " + contentLength + "\r\n" ) ;
if ( isImage ( contentType ) )
{
response.append ( "Cache-Control: max-age=300\r\n" ) ;
}
if ( isGZIP ( contentType, contentLength, sendFile ) )
{
response.append ( "Content-Encoding: gzip\r\n" ) ;
}
response.append ( "Connection: close\r\n" ) ;
response.append ( "\r\n" ) ;
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
*/
InputStream inputStream = new FileInputStream ( sendFile ) ;
JSMHTTPHelper.sendStream ( inputStream, outputStream ) ;
inputStream.close () ;
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 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 isImage ( String contentType )
{
if ( contentType.toLowerCase().startsWith ( "image/" ) )
{
return true ;
}
return false ;
}
private final static boolean isGZIP ( String contentType, int contentLength, File file ) throws IOException
{
if ( contentLength < 10 )
{
return false ;
}
/*
Restrict to known MIME types
text/
image/
application/pdf
application/xml
etc..
*/
if ( contentType.toLowerCase().startsWith ( "application/octet-stream" ) )
{
return false ;
}
/*
www.gzip.org/zlib/rfc-gzip.html
0 - ID1 ( Identification 1 ) 0x1F
1 - ID2 ( Identification 2 ) 0x8B
2 - CM ( compression method ) 0-7 reserved, 8 = deflate
3 - BYTE FLAG ( bits 0-7 )
4 -
5 - MTIME ( Modification Time ) uses 4 bytes
6 -
7 -
8 - XFL ( Extra Flags )
9 - OS ( Operating system )
ASCII 0x1F is the control character US or Unit Separator
*/
byte[] head = new byte[10] ;
FileInputStream inputStream = new FileInputStream ( file ) ;
inputStream.read ( head ) ;
inputStream.close () ;
if ( head[0] == (byte)0x1F &&
head[1] == (byte)0x8B &&
head[2] == (byte)0x08 ) // Deflate compression
{
return true ;
}
return false ;
}
}