JSM には HTTP サーバーが含まれており、スタティックなファイル・リソースとして使用したり、カスタム Java クラス経由で動的なコンテンツとしても使用できます。
HTTP サーバーは XML ファイルと manager.properties のエントリーにより構成されています。XML 構成ファイルは httpd プロパティにより指定されます。
manager.properties
httpd=system/httpd.xml
HTTP サーバー構成ファイルでは、複数の HTTP インスタンスや1 つのインスタンス内の複数の仮想ホストを記述できます。HTTP インスタンスは TCP/IP 通信のオプションとアクセス、エラー・ログ・ファイルの場所を指定します。IPアドレスの許可・拒否のアクセス・コントロールはインスタンス・レベルで指定できます。MMEファイル・タイプのインスタンス全域内のマッピングも指定可能です。ポート属性の省略値は80で、インターフェイスの省略値は*ALLです。バックログの省略値は、256です。
HTTP インスタンス内に、複数の仮想ホスト・セクションが指定できます。HTTP要求が読み込まれると、HTTPホスト・プロパティを使って仮想ホスト・セクションが探し出され、これが要求を処理する際に使用されます。ホスト・プロパティと比べると、仮想ホスト名では大文字・小文字の区別がされます。ホスト・プロパティにポート・コンポーネントが存在する場合、一致する仮想ホスト名の検索に使用される前に削除されます。仮想ホスト・セクションが見つからない場合は、省略値の '*' 仮想ホストが使用されます。仮想ホスト要素にはルートとインデックス属性があり、インスタンスのルートとインデックスの値を上書きします。インスタンスまたは仮想要素にアクティブなfalse値がない場合は、この要素は無視されます。
IPアドレスの許可・拒否のアクセス・コントロールは仮想ホスト・レベルで指定できます。ユーザー・エージャントとコンテンツ長アクセス・コントロールも指定可能です。MMEファイル・タイプのマッピングも指定できます。
Javaクラスは様々なリソースのパスに基づき実行されます。仮想ホストのスクリプト要素はJavaクラスと特定のリソース・パスを関連付ける際に利用されます。trace属性はHTTPトランザクションのJSMトレースを可能にします。
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="edge"/>
<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="longreach"/>
<allow useragent="*"/>
<allow useragent="?"/>
<allow useragent="edge"/>
<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="longreach"/>
<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>
<script>
<match uri="/ping.jsp" class="com.lansa.jsm.JSMHTTPServicePing" trace="false"/>
<match uri="/" class="com.lansa.jsm.JSMHTTPServiceFile" trace="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 のインターフェースが実装されていなければなりません。
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 ()
次の Java クラスはスタティックなファイル要求を取り扱う JSM HTTP サーバークラスです。
例
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 - ハイパーテキスト転送プロトコル - 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 )
\{
/*
例外のログ
*/
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 () ) ;
\}
/*
GET と HEAD メソッドに対し、要求コンテンツは期待されない
POST が使われた可能性もある
ソケット入力ストリームのコンテンツはすべて使う必要あり
これにより、ブラウザは切り替えて HTTP 応答を読み込むことができる
*/
m_transport.consumeInputStream ( m_request.getContentLength () ) ;
/*
メソッド確認
*/
if ( !isAllowedMethod ( m_request.getMethod () ) )
\{
m_virtual.logError ( m_transport, m_request, "Method is not implemented" ) ;
m_transport.sendNotImplemented ( m_request.getMethod () ) ;
return ;
\}
/*
ファイル取得
*/
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 ;
\}
/*
ファイルを検知
*/
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 ファイル
*/
sendHEAD ( file ) ;
return ;
\}
/*
GET ファイル
*/
sendFile ( 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 )
\{
/*
コンテンツが送信されない場合を除き、HEAD 応答は GET 応答と同じ
ファイルのコンテンツ長は含まれるが、コンテンツは無し
*/
try
\{
if ( m_trace != null )
\{
m_trace.println ( "Send HEAD response: ", sendFile.getCanonicalPath () ) ;
\}
/*
プロトコルの作成
RFC2616 - HTTP/1.1
サーバーが応答送信の直後に接続を閉じる選択をした場合
close トークンを含む接続ヘッダーを送信する必要がある
*/
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 ) ;
\}
/*
応答日時は作業するキャッシングのために必須
*/
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 ) )
\{
/*
IE8 のコンテンツ・スニッフィング行為を停止
*/
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 ) ;
\}
/*
応答の送信
クライアント・ソケットが閉じている場合、パイプ破壊の例外が発生
*/
OutputStream outputStream = m_transport.getOutputStream () ;
outputStream.write ( protocol ) ;
outputStream.flush () ;
\}
catch ( IOException e )
\{
/*
ユーザーは、すべてのコンテンツが送信される前にブラウザを閉じることができる
*/
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 () ) ;
\}
/*
プロトコルの作成
RFC2616 - HTTP/1.1
サーバーが応答送信の直後に接続を閉じる選択をした場合
close トークンを含む接続ヘッダーを送信する必要がある
*/
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 ) ;
\}
/*
応答日時は作業するキャッシングのために必須
*/
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 ) )
\{
/*
IE8 のコンテンツ・スニッフィング行為を停止
*/
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 ) ;
\}
/*
応答の送信
クライアント・ソケットが閉じている場合、パイプ破壊の例外が発生
*/
OutputStream outputStream = m_transport.getOutputStream () ;
outputStream.write ( protocol ) ;
/*
ファイル・コンテンツ送信
*/
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 )
\{
/*
ユーザーは、すべてのコンテンツが送信される前にブラウザを閉じることができる
*/
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 )
\{
/*
標準 HTTP メソッド
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 ()
\{
/*
キャッシュが 0 の場合、IE は pdf コンテンツを Adobe に渡さない
サンプル・ブラウザ URI /axes/dbmhelp.pdf
*/
return getServiceParameterInteger ( "CACHE.MAXAGE.PDF" ) ;
\}
private final int getCacheAgeImage ()
\{
/*
YUI/IE イメージ・キャッシング
IE はイメージに対する要求を頻繁に行っている
ブラウザにイメージをキャッシュするよう伝える、デフォルトはキャッシュ無し
*/
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 ) ;
\}
\}
|