This section discusses writing custom advisors for the Load Balancer.
Advisors are software agents that work within Load Balancer to provide information about the load on a given server. A different advisor exists for each standard protocol (HTTP, SSL, and others). Periodically, the Load Balancer base code performs an advisor cycle, during which it individually evaluates the status of all servers in its configuration.
By writing your own advisors for the Load Balancer, you can customize how your server machines' load is determined.
In general, advisors work to enable load balancing in the following manner.
Standard advisors provided with the Load Balancer include advisors for the following functions. Detailed information about these advisors is available in the WebSphere® Application Server Load Balancer Administration Guide
To support proprietary protocols for which standard advisors are not provided, you must write custom advisors.
A custom advisor is a small piece of Java™ code, provided as a class file, that is called by the Load Balancer base code to determine the load on a server. The base code provides all necessary administrative services, including starting and stopping an instance of the custom advisor, providing status and reports, recording history information in a log file, and reporting advisor results to the manager component.
When the Load Balancer base code calls a custom advisor, the following steps happen.
Custom advisors can be designed to interact with the Load Balancer in either normal mode or replace mode.
The choice for the mode of operation is specified in the custom advisor file as a parameter in the constructor method. (Each advisor operates in only one of these modes, based on its design.)
In normal mode, the custom advisor exchanges data with the server, and the base advisor code times the exchange and calculates the load value. The base code then reports this load value to the manager. The custom advisor returns the value zero to indicate success, or negative one to indicate an error.
To specify normal mode, set the replace flag in the constructor to false.
In replace mode, the base code does not perform any timing measurements. The custom advisor code performs whatever operations are specified, based on its unique requirements, and then returns an actual load number. The base code accepts the load number and reports it, unaltered, to the manager. For best results, normalize your load numbers between 10 and 1000, with 10 representing a fast server and 1000 representing a slow server.
To specify replace mode, set the replace flag in the constructor to true.
Custom advisor file names must follow the form ADV_name.java, where name is the name that you choose for your advisor. The complete name must start with the prefix ADV_ in uppercase letters, and all subsequent characters must be lowercase letters. The requirement for lowercase letters ensures that the command for running the advisor is not case sensitive.
According to Java conventions, the name of the class defined within the file must match the name of the file.
You must write custom advisors in the Java language and compile them with a Java compiler that is at the same level as the Load Balancer code. To check the version of Java on your system, run the following command from the install_path/java/bin directory:
java -fullversion
If the current directory is not part of your path, you will need to specify that Java should be run from the current directory to ensure you are getting the correct version information. In this case, run the following command from theinstall_path/java/bin directory:
./java -fullversion
The following files are referenced during compilation:
Your classpath environment variable must point to both the custom advisor file and the base classes file during the compilation. A compile command might have the following format: For UNIX® Windows systems, a sample compile command is:
install_path/java/bin/javac -classpath /opt/ibm/edge/lb/servers/lib/ibmlb.jar ADV_name.java
where:
The output of the compilation is a class file, for example, ADV_name.class. Before starting the advisor, copy the class file to the install_path/servers/lib/CustomAdvisors/ directory.
To run the custom advisor, you must first copy the advisor's class file to the lib/CustomAdvisors/ subdirectory on the Load Balancer machine. For example, for a custom advisor named myping, the file path is install_path/servers/lib/CustomAdvisors/ADV_myping.class
Configure the Load Balancer, start its manager function, and issue the command to start your custom advisor. The custom advisor is specified by its name, excluding the ADV_ prefix and the file extension:
dscontrol advisor start myping port_number
The port number specified in the command is the port on which the advisor will open a connection with the target server.
Like all advisors, a custom advisor extends the functionality of the advisor base class, which is called ADV_Base. The advisor base performs most of the advisor's functions, such as reporting loads back to the manager for use in the manager's weight algorithm. The advisor base also performs socket connect and close operations and provides send and receive methods for use by the advisor. The advisor is used only for sending and receiving data on the specified port for the server that is being investigated. The TCP methods provided within the advisor base are timed to calculate load. A flag within the constructor of the advisor base overwrites the existing load with the new load returned from the advisor, if desired.
Advisors have the following base class methods:
Details about these required routines appear later in this section.
Custom advisors are called after native, or standard, advisors have been searched. If the Load Balancer does not find a specified advisor among the list of standard advisors, it consults the list of custom advisors. Additional information about using advisors is available in the WebSphere Application Server Load Balancer Administration Guide.
Remember the following requirements for custom advisor names and paths.
public <advisor_name> ( String sName; String sVersion; int iDefaultPort; int iInterval; String sDefaultLogFileName; boolean replace )
void ADV_AdvisorInitialize()
This method is provided to perform any initialization that might be required for the custom advisor. This method is called after the advisor base module starts.
In many cases, including the standard advisors, this method is not used and its code consists of a return statement only. This method can be used to call the suppressBaseOpeningSocket method, which is valid only from within this method.
int getLoad( int iConnectTime; ADV_Thread *caller )
The methods, or functions, described in the following sections can be called from custom advisors. These methods are supported by the advisor base code.
Some of these function calls can be made directly, for example, function_name(), but others require the prefix caller. Caller represents the base advisor instance that supports the custom advisor that is being executed.
The ADVLOG function allows a custom advisor to write a text message to the advisor base log file. The format follows:
void ADVLOG (int logLevel, String message)
The getAdvisorName function returns a Java string with the suffix portion of your custom advisor's name. For example, for an advisor named ADV_cdload.java, this function returns the value cdload.
This function takes no parameters.
Note that it is not possible for this value to change during one instantiation of an advisor.
The getAdviseOnPort function returns the port number on which the calling custom advisor is running. The return value is a Java integer (int), and the function takes no parameters.
Note that it is not possible for this value to change during one instantiation of an advisor.
The getCurrentServerId function returns a Java string which is a unique representation for the current server.
Typically, this value changes each time you call your custom advisor, because the advisor base code queries all server machines in series.
This function takes no parameters.
The getCurrentClusterId function call returns a Java string which is a unique representation for the current cluster.
Typically, this value changes each time you call your custom advisor, because the advisor base queries all clusters in series.
This function takes no parameters.
The getSocket function call returns a Java socket which represents the socket opened to the current server for communication.
This function takes no parameters.
The getInterval function returns the advisor interval, that is, the number of seconds between advisor cycles. This value is equal to the default value set in the custom advisor's constructor, unless the value has been modified at run time by using the dscontrol command.
The return value is a Java integer (int). The function takes no parameters.
The getLatestLoad function allows a custom advisor to obtain the latest load value for a given server object. The load values are maintained in internal tables by the advisor base code and the manager daemon.
int caller.getLatestLoad (String clusterId, int port, String serverId)
The three arguments together define one server object.
The return value is an integer.
This function call is useful if you want to make the behavior of one protocol or port dependent on the behavior of another. For example, you might use this function call in a custom advisor that disabled a particular application server if the Telnet server on that same machine was disabled.
The receive function gets information from the socket connection.
caller.receive(StringBuffer *response)
The parameter response is a string buffer into which the retrieved data is placed. Additionally, the function returns an integer value with the following significance:
The send function uses the established socket connection to send a packet of data to the server, using the specified port.
caller.send(String command)
The parameter command is a string containing the data to send to the server. The function returns an integer value with the following significance:
The suppressBaseOpeningSocket function call allows a custom advisor to specify whether the base advisor code opens a TCP socket to the server on the custom advisor's behalf. If your advisor does not use direct communication with the server to determine its status, it might not be necessary to open this socket.
This function call can be issued only once, and it must be issued from the ADV_AdvisorInitialize routine.
The function takes no parameters.
The following examples show how custom advisors can be implemented.
This sample source code is similar to the standard Load Balancer HTTP advisor. It functions as follows:
This advisor operates in normal mode, so the load measurement is based on the elapsed time in milliseconds required to perform the socket open, send, receive, and close operations.
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; } }
This sample illustrates suppressing the standard socket opened by the advisor base. Instead, this advisor opens a side stream Java socket to query a server. This procedure can be useful for servers that use a different port from normal client traffic to listen for an advisor query.
In this example, a server is listening on port 11999 and when queried returns a load value with a hexadecimal int "4". This sample runs in replace mode, that is, the last parameter of the advisor constructor is set to true and the advisor base code uses the returned load value rather than the elapsed time.
Note the call to supressBaseOpeningSocket() in the initialization routine. Suppressing the base socket when no data will be sent is not required. For example, you might want to open the socket to ensure that the advisor can contact the server. Examine the needs of your application carefully before making this choice.
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 } }
This custom advisor sample demonstrates the capability to detect failure for one port of a server based upon both its own status and on the status of a different server daemon that is running on another port on the same server machine. For example, if the HTTP daemon on port 80 stops responding, you might also want to stop routing traffic to the SSL daemon on port 443.
This advisor is more aggressive than standard advisors, because it considers any server that does not send a response to have stopped functioning, and marks it as down. Standard advisors consider unresponsive servers to be very slow. This advisor marks a server as down for both the HTTP port and the SSL port based on a lack of response from either port.
To use this custom advisor, the administrator starts two instances of the advisor: one on the HTTP port, and one on the SSL port. The advisor instantiates two static global hash tables, one for HTTP and one for SSL. Each advisor tries to communicate with its server daemon and stores the results of this event in its hash table. The value that each advisor returns to the base advisor class depends on both the ability to communicate with its own server daemon and the ability of the partner advisor to communicate with its daemon.
The following custom methods are used.
The following error conditions are detected.
This sample is written to link ports 80 for HTTP and 443 for SSL, but it can be tailored to any combination of ports.
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; } }
A sample custom advisor for WebSphere Application Server is included in the install_path/servers/samples/CustomAdvisors/ directory. The full code is not duplicated in this document.
The complete advisor is only slightly more complex than the sample. It adds a specialized parsing routine that is more compact than the StringTokenizer example shown above.
The more complex part of the sample code is in the Java servlet. Among other methods, the servlet contains two methods required by the servlet specification: init() and service(), and one method, run(), that is required by the Java.lang.thread class.
The relevant fragments of the servlet code appear below.
... 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; } ...
Whether you use a standard call to an existing part of the application server or add a new piece of code to be the server-side counterpart of your custom advisor, you possibly want to examine the load values returned and change server behavior. The Java StringTokenizer class, and its associated methods, make this investigation easy to do.
The content of a typical HTTP command might be GET /index.html HTTP/1.0
A typical response to this command might be the following.
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>
The items of interest are contained in the first line, specifically the HTTP return code.
The HTTP specification classifies return codes that can be summarized as follows:
If you know very precisely what codes the server can possibly return, your code might not need to be as detailed as this example. However, keep in mind that limiting the return codes you detect might limit the future flexibility of your program.
The following example is a stand-alone Java program that contains a minimal HTTP client. The example invokes a simple, general-purpose parser for examining HTTP responses.
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()); } } }