以下の例は、カスタム・アドバイザーを実装できる方法を示します。
このサンプル・ソース・コードは、標準 Load Balancer HTTP アドバイザーに似ています。 以下のように機能します。
このアドバイザーは通常モードで操作するので、ロード測定はソケット・オープン、送信、受信、およびクローズ操作を実行するために必要な経過時間 (ミリ秒) に基づきます。
package CustomAdvisors;
import com.ibm.internet.lb.advisors.*;
public class ADV_sample extends ADV_Base implements ADV_MethodInterface {
static final String ADV_NAME ="Sample";
static final int ADV_DEF_ADV_ON_PORT = 80;
static final int ADV_DEF_INTERVAL = 7;
static final String ADV_SEND_REQUEST =
"HEAD / HTTP/1.0¥r¥nAccept: */*¥r¥nUser-Agent: " +
"IBM_Load_Balancer_HTTP_Advisor¥r¥n¥r¥n";
//--------
// Constructor
public ADV_sample() {
super(ADV_NAME, "3.0.0.0-03.31.00",
ADV_DEF_ADV_ON_PORT, ADV_DEF_INTERVAL, "",
false);
super.setAdvisor( this );
}
//--------
// ADV_AdvisorInitialize
public void ADV_AdvisorInitialize() {
return; // usually an empty routine
}
//--------
// getLoad
public int getLoad(int iConnectTime, ADV_Thread caller) {
int iRc;
int iLoad = ADV_HOST_INACCESSIBLE; // initialize to inaccessible
iRc = caller.send(ADV_SEND_REQUEST); // send the HTTP request to
// the server
if (0 <= iRc) { // if the send is successful
StringBuffer sbReceiveData = new StringBuffer(""); // allocate a buffer
// for the response
iRc = caller.receive(sbReceiveData); // receive the result
// parse the result here if you need to
if (0 <= iRc) { // if the receive is successful
iLoad = 0; // return 0 for success
} // (advisor's load value is ignored by
} // base in normal mode)
return iLoad;
}
}
このサンプルでは、アドバイザー・ベースによる標準ソケットのオープンの抑制を例示しています。その代わりに、このアドバイザーは、サーバーを照会するためにサイド・ストリーム Java™ ソケットをオープンします。このプロシージャーは、通常のクライアント・トラフィックと異なるポートを使用してアドバイザー照会を listen するサーバーのために役立ちます。
この例では、サーバーはポート 11999 上で listen していて、 照会されたときに 16 進 int "4" でロード値を戻します。 このサンプルは置換モードで実行されます。 すなわち、アドバイザー・コンストラクターの最終パラメーターが true に設定されて、アドバイザー基本コードは経過時間ではなく戻されたロード値を使用します。
初期化ルーチンでの supressBaseOpeningSocket() に対する呼び出しに注意してください。データが送信されないときの基本ソケットの抑制は不要です。例えば、アドバイザーがサーバーに接続できることを確認するためにソケットをオープンする場合などです。 この選択を行う前には、アプリケーションの必要性を注意深く調べてください。
package CustomAdvisors;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.Date;
import com.ibm.internet.lb.advisors.*;
import com.ibm.internet.lb.common.*;
import com.ibm.internet.lb.server.SRV_ConfigServer;
public class ADV_sidea extends ADV_Base implements ADV_MethodInterface {
static final String ADV_NAME = "sidea";
static final int ADV_DEF_ADV_ON_PORT = 12345;
static final int ADV_DEF_INTERVAL = 7;
// create an array of bytes with the load request message
static final byte[] abHealth = {(byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x04};
public ADV_sidea() {
super(ADV_NAME, "3.0.0.0-03.31.00", ADV_DEF_ADV_ON_PORT,
ADV_DEF_INTERVAL, "",
true); // replace mode parameter is true
super.setAdvisor( this );
}
//--------
// ADV_AdvisorInitialize
public void ADV_AdvisorInitialize()
{
suppressBaseOpeningSocket(); // tell base code not to open the
// standard socket
return;
}
//--------
// getLoad
public int getLoad(int iConnectTime, ADV_Thread caller) {
int iRc;
int iLoad = ADV_HOST_INACCESSIBLE; // -1
int iControlPort = 11999; // port on which to communicate with the server
String sServer = caller.getCurrentServerId(); // address of server to query
try {
socket soServer = new Socket(sServer, iControlPort); // open socket to
// server
DataInputStream disServer = new DataInputStream(
soServer.getInputStream());
DataOutputStream dosServer = new DataOutputStream(
soServer.getOutputStream());
int iRecvTimeout = 10000; // set timeout (in milliseconds)
// for receiving data
soServer.setSoTimeout(iRecvTimeout);
dosServer.writeInt(4); // send a message to the server
dosServer.flush();
iLoad = disServer.readByte(); // receive the response from the server
} catch (exception e) {
system.out.println("Caught exception " + e);
}
return iLoad; // return the load reported from the server
}
}
このカスタム・アドバイザー・サンプルは、サーバーの 1 つのポートに対する失敗を検出する機能を説明しています。 これは、そのポートの状況と、同一サーバー・マシン上にある別のポート上で実行されている 異なるサーバー・デーモンの状況の両方に基づいています。 例えば、ポート 80 の HTTP デーモンが応答を停止する場合には、ポート 443 の SSL デーモンへのルーティング・トラフィックも停止することができます。
このアドバイザーは、応答を送信しないサーバーは機能を停止したと見なして、ダウンのマークを付けるので、 標準アドバイザーよりも積極的です。標準アドバイザーは応答のないサーバーを非常に低速であると見なします。このアドバイザーは HTTP ポートおよび SSL ポートのいずれかの応答がないと、両方のポートがダウンしたことを示す マークをサーバーに付けます。
このカスタム・アドバイザーを使用するには、アドバイザーの HTTP ポート上にある インスタンスと SSL ポート上にあるインスタンスを管理者が開始します。 アドバイザーは HTTP 用と SSL 用の 2 つの静的グローバル・ハッシュ・テーブルを検証します。各アドバイザーは そのサーバー・デーモンとの通信を試行し、そのハッシュ・テーブルにこのイベントの結果を保管します。 各アドバイザーが基本アドバイザークラスに戻す値は、 その固有のサーバー・デーモンと通信する能力およびそのデーモンと通信するパートナー・アドバイザーの能力によって異なります。
以下のカスタム・メソッドが使用されます。
次のエラー条件が検出されます。
このサンプルは HTTP 用のポート 80 および SSL 用の 443 をリンクするように書かれていますが、ポートの組み合わせは任意に調整できます。
package CustomAdvisors;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.Date;
import com.ibm.internet.lb.advisors.*;
import com.ibm.internet.lb.common.*;
import com.ibm.internet.lb.manager.*;
import com.ibm.internet.lb.server.SRV_ConfigServer;
//--------
// Define the table element for the hash tables used in this custom advisor
class ADV_nte implements Cloneable {
private String sCluster;
private int iPort;
private String sServer;
private int iLoad;
private Date dTimestamp;
//--------
// constructor
public ADV_nte(String sClusterIn, int iPortIn, String sServerIn,
int iLoadIn) {
sCluster = sClusterIn;
iPort = iPortIn;
sServer = sServerIn;
iLoad = iLoadIn;
dTimestamp = new Date();
}
//--------
// check whether this element is current or expired
public boolean isCurrent(ADV_twop oThis) {
boolean bCurrent;
int iLifetimeMs = 3 * 1000 * oThis.getInterval(); // set lifetime as
// 3 advisor cycles
Date dNow = new Date();
Date dExpires = new Date(dTimestamp.getTime() + iLifetimeMs);
if (dNow.after(dExpires)) {
bCurrent = false;
} else {
bCurrent = true;
}
return bCurrent;
}
//--------
// value accessor(s)
public int getLoadValue() { return iLoad; }
//--------
// clone (avoids corruption between threads)
public synchronized Object Clone() {
try {
return super.clone();
} catch (cloneNotSupportedException e) {
return null;
}
}
}
//--------
// define the custom advisor
public class ADV_twop extends ADV_Base
implements ADV_MethodInterface, ADV_AdvisorVersionInterface {
static final int ADV_TWOP_PORT_HTTP = 80;
static final int ADV_TWOP_PORT_SSL = 443;
//--------
// define tables to hold port-specific history information
static HashTable htTwopHTTP = new Hashtable();
static HashTable htTwopSSL = new Hashtable();
static final String ADV_TWOP_NAME = "twop";
static final int ADV_TWOP_DEF_ADV_ON_PORT = 80;
static final int ADV_TWOP_DEF_INTERVAL = 7;
static final String ADV_HTTP_REQUEST_STRING =
"HEAD / HTTP/1.0¥r¥nAccept: */*¥r¥nUser-Agent: " +
"IBM_LB_Custom_Advisor¥r¥n¥r¥n";
//--------
// create byte array with SSL client hello message
public static final byte[] abClientHello = {
(byte)0x80, (byte)0x1c,
(byte)0x01, // client hello
(byte)0x03, (byte)0x00, // SSL version
(byte)0x00, (byte)0x03, // cipher spec len (bytes)
(byte)0x00, (byte)0x00, // session ID len (bytes)
(byte)0x00, (byte)0x10, // challenge data len (bytes)
(byte)0x00, (byte)0x00, (byte)0x03, // cipher spec
(byte)0x1A, (byte)0xFC, (byte)0xE5, (byte)Ox20, // challenge data
(byte)0xFD, (byte)0x3A, (byte)0x3C, (byte)0x18,
(byte)0xAB, (byte)0x67, (byte)0xB0, (byte)0x52,
(byte)0xB1, (byte)0x1D, (byte)0x55, (byte)0x44, (byte)0x0D, (byte)0x0A };
//--------
// constructor
public ADV_twop() {
super(ADV_TWOP_NAME, VERSION, ADV_TWOP_DEF_ADV_ON_PORT,
ADV_TWOP_DEF_INTERVAL, "",
false); // false = load balancer times the response
setAdvisor ( this );
}
//--------
// ADV_AdvisorInitialize
public void ADV_AdvisorInitialize() {
return;
}
//--------
// synchronized PUT and GET access routines for the hash tables
synchronized ADV_nte getNte(Hashtable ht, String sName, String sHashKey) {
ADV_nte nte = (ADV_nte)(ht.get(sHashKey));
if (null != nte) {
nte = (ADV_nte)nte.clone();
}
return nte;
}
synchronized void putNte(Hashtable ht, String sName, String sHashKey,
ADV_nte nte) {
ht.put(sHashKey,nte);
return;
}
//--------
// getLoadHTTP - determine HTTP load based on server response
int getLoadHTTP(int iConnectTime, ADV_Thread caller) {
int iLoad = ADV_HOST_INACCESSIBLE;
int iRc = caller.send(ADV_HTTP_REQUEST_STRING); // send request message
// to server
if (0 <= iRc) { // did the request return a failure?
StringBuffer sbReceiveData = new StringBuffer("") // allocate a buffer
// for the response
iRc = caller.receive(sbReceiveData); // get response from server
if (0 <= iRc) { // did the receive return a failure?
if (0 < sbReceiveData.length()) { // is data there?
iLoad = SUCCESS; // ignore retrieved data and
// return success code
}
}
}
return iLoad;
}
//--------
// getLoadSSL() - determine SSL load based on server response
int getLoadSSL(int iConnectTime, ASV_Thread caller) {
int iLoad = ADV_HOST_INACCESSIBLE;
int iRc;
CMNByteArrayWrapper cbawClientHello = new CMNByteArrayWrapper(
abClientHello);
Socket socket = caller.getSocket();
try {
socket.getOutputStream().write(abClientHello);
// Perform a receive.
socket.getInputStream().read();
// If receive is successful, return load of 0. We are not concerned with
// data's contents, and the load is calculated by the ADV_Thread thread.
iLoad = 0;
} catch (IOException e) {
// Upon error, iLoad will default to it.
}
return iLoad;
}
//--------
// getLoad - merge results from the HTTP and SSL methods
public int getLoad(int iConnectTime, ADV_Thread caller) {
int iLoadHTTP;
int iLoadSSL;
int iLoad;
int iRc;
String sCluster = caller.getCurrentClusterId(); // current cluster address
int iPort = getAdviseOnPort();
String sServer = caller.getCurrentServerId();
String sHashKey = sCluster = ":" + sServer; // hash table key
if (ADV_TWOP_PORT_HTTP == iPort) { // handle an HTTP server
iLoadHTTP = getLoadHTTP(iConnectTime, caller); // get the load for HTTP
ADV_nte nteHTTP = newADV_nte(sCluster, iPort, sServer, iLoadHTTP);
putNte(htTwopHTTP, "HTTP", sHashKey, nteHTTP); // save HTTP load
// information
ADV_nte nteSSL = getNte(htTwopSSL, "SSL", sHashKey); // get SSL
// information
if (null != nteSSL) {
if (true == nteSSL.isCurrent(this)) { // check the time stamp
if (ADV_HOST_INACCESSIBLE != nteSSL.getLoadValue()) { // is SSL
// working?
iLoad = iLoadHTTP;
} else { // SSL is not working, so mark the HTTP server down
iLoad= ADV_HOST_INACCESSIBLE;
}
} else { // SSL information is expired, so mark the
// HTTP server down
iLoad = ADV_HOST_INACCESSIBLE;
}
} else { // no load information about SSL, report
// getLoadHTTP() results
iLoad = iLoadHTTP;
}
}
else if (ADV_TWOP_PORT_SSL == iPort) { // handle an SSL server
iLoadSSL = getLoadSSL(iConnectTime, caller); // get load for SSL
ADV_nte nteSSL = new ADV_nte(sCluster, iPort, sServer, iLoadSSL);
putNte(htTwopSSL, "SSL", sHashKey, nteSSL); // save SSL load info.
ADV_nte nteHTTP = getNte(htTwopHTTP, "SSL", sHashKey); // get HTTP
// information
if (null != nteHTTP) {
if (true == nteHTTP.isCurrent(this)) { // check the timestamp
if (ADV_HOST_INACCESSIBLE != nteHTTP.getLoadValue()) { // is HTTP
// working?
iLoad = iLoadSSL;
} else { // HTTP server is not working, so mark SSL down
iLoad = ADV_HOST_INACCESSIBLE;
}
} else { // expired information from HTTP, so mark SSL down
iLoad = ADV_HOST_INACCESSIBLE;
}
} else { // no load information about HTTP, report
// getLoadSSL() results
iLoad = iLoadSSL;
}
}
//--------
// error handler
else {
iLoad = ADV_HOST_INACCESSIBLE;
}
return iLoad;
}
}
WebSphere® Application Server 用のカスタム・アドバイザーのサンプル は install_path/servers/samples/CustomAdvisors/ ディレクトリーに入っています。完全なコードはこの資料では掲載していません。
完全なアドバイザーはサンプルよりわずかに複雑です。上記の StringTokenizer の例よりコンパクトな特殊化された構文解析ルーチンを追加します。
サンプル・コードのより複雑な部分は Java サーブレットにあります。 このサーブレットには、他のメソッドと共に、サーブレット仕様が必要とする init() および service() という 2 つのメソッドと、 Java.lang.thread クラスが必要とする run() というメソッドが含まれています。
サーブレット・コードの関連フラグメントは、以下のようになります。
...
public void init(ServletConfig config) throws ServletException {
super.init(config);
...
_checker = new Thread(this);
_checker.start();
}
public void run() {
setStatus(GOOD);
while (true) {
if (!getKeepRunning())
return;
setStatus(figureLoad());
setLastUpdate(new java.util.Date());
try {
_checker.sleep(_interval * 1000);
} catch (Exception ignore) { ; }
}
}
public void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
ServletOutputStream out = null;
try {
out = res.getOutputStream();
} catch (Exception e) { ... }
...
res.setContentType("text/x-application-LBAdvisor");
out.println(getStatusString());
out.println(getLastUpdate().toString());
out.flush();
return;
}
...
アプリケーション・サーバーの既存パーツに対する標準呼び出しを使用したり、 またはカスタム・アドバイザーのサーバー側で相対するコードの新規部分を追加して、 戻されたロード値を調べてサーバー動作を変更することができます。 Java StringTokenizer クラスおよびその関連メソッド は、この調査を簡単にします。
通常の HTTP コマンドのコンテンツは GET /index.html HTTP/1.0 です。
このコマンドに対する通常の応答は、以下のようになります。
HTTP/1.1 200 OK
Date: Mon, 20 November 2000 14:09:57 GMT
Server: Apache/1.3.12 (Linux and UNIX)
Content-Location: index.html.en
Vary: negotiate
TCN: choice
Last-Modified: Fri, 20 Oct 2000 15:58:35 GMT
ETag: "14f3e5-1a8-39f06bab;39f06a02"
Accept-Ranges: bytes
Content-Length: 424
Connection: close
Content-Type: text/html
Content-Language: en
<!DOCTYPE HTML PUBLIC "-//w3c//DTD HTML 3.2 Final//EN">
<HTML><HEAD><TITLE>Test Page</TITLE></HEAD>
<BODY><H1>Apache server</H1>
<HR>
<P><P>This Web server is running Apache 1.3.12.
<P><HR>
<P><IMG SRC="apache_pb.gif" ALT="">
</BODY></HTML>
関心のある項目 (特に HTTP 戻りコード) は先頭行に入っています。
HTTP 仕様は戻りコードを分類し、以下のように要約できます。
サーバーが戻す可能性のあるコードが正確に分かる場合は、コードをこの例ほど詳細にする必要はありません。ただし、検出する戻りコードを制限すると、プログラムの将来の柔軟性を制限することになる場合があります。
以下の例は、最小限の HTTP クライアントが含まれている、スタンドアロン Java プログラムです。この例は HTTP 応答を調べるための単純な汎用パーサーを起動します。
import java.io.*;
import java.util.*;
import java.net.*;
public class ParseTest {
static final int iPort = 80;
static final String sServer = "www.ibm.com";
static final String sQuery = "GET /index.html HTTP/1.0¥r¥n¥r¥n";
static final String sHTTP10 = "HTTP/1.0";
static final String sHTTP11 = "HTTP/1.1";
public static void main(String[] Arg) {
String sHTTPVersion = null;
String sHTTPReturnCode = null;
String sResponse = null;
int iRc = 0;
BufferedReader brIn = null;
PrintWriter psOut = null;
Socket soServer= null;
StringBuffer sbText = new StringBuffer(40);
try {
soServer = new Socket(sServer, iPort);
brIn = new BufferedReader(new InputStreamReader(
soServer.getInputStream()));
psOut = new PrintWriter(soServer.getOutputStream());
psOut.println(sQuery);
psOut.flush();
sResponse = brIn.readLine();
try {
soServer.close();
} catch (Exception sc) {;}
} catch (Exception swr) {;}
StringTokenizer st = new StringTokenizer(sResponse, " ");
if (true == st.hasMoreTokens()) {
sHTTPVersion = st.nextToken();
if (sHTTPVersion.equals(sHTTP110) || sHTTPVersion.equals(sHTTP11)) {
System.out.println("HTTP Version: " + sHTTPVersion);
} else {
System.out.println("Invalid HTTP Version: " + sHTTPVersion);
}
} else {
System.out.println("Nothing was returned");
return;
}
if (true == st.hasMoreTokens()) {
sHTTPReturnCode = st.nextToken();
try {
iRc = Integer.parseInt(sHTTPReturnCode);
} catch (NumberFormatException ne) {;}
switch (iRc) {
case(200):
System.out.println("HTTP Response code: OK, " + iRc);
break;
case(400): case(401): case(402): case(403): case(404):
System.out.println("HTTP Response code: Client Error, " + iRc);
break;
case(500): case(501): case(502): case(503):
System.out.println("HTTP Response code: Server Error, " + iRc);
break;
default:
System.out.println("HTTP Response code: Unknown, " + iRc);
break;
}
}
if (true == st.hasMoreTokens()) {
while (true == st.hasMoreTokens()) {
sbText.append(st.nextToken());
sbText.append(" ");
}
System.out.println("HTTP Response phrase: " + sbText.toString());
}
}
}