/*
Copyright (c) 2003-2009 ITerative Consulting Pty Ltd. All Rights Reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.
o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package HTTPSupport;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLServerSocketFactory;
import org.apache.log4j.Logger;
import Framework.DateFormat;
import Framework.DateTimeData;
import Framework.DistributedAccessException;
import Framework.ErrorMgr;
import Framework.TextData;
/**
* The HTTPHelper class provides miscellaneous utility methods, to perform tasks such as getting the HTTPFactory object, the HTTPConfigManager object, and advertising an HTTP server object to listen for HTTP messages.
*
*/
public class HTTPHelper {
private static Logger _log = Logger.getLogger(HTTPHelper.class);
private HTTPConfigManager configManger;
private int defaultPort = 80;
private int defaultSecurePort = 443;
private int defaultInstances = 1;
private int defaultTimeout = 0;
private boolean defaultSecure = false;
public HTTPHelper() {
super();
}
/**
* Advertises the HTTP server object obj using the param
* string.
*
* @param obj
* This parameter specifies the object to advertise. The object
* must implement a receiver interface, either MessageReceiver or
* HTTPReceiver.
* @param param
* This parameter specifies the parameters to advertise the
* object. Use the following syntax:
* Option=value,[Option=value,][...]
*
* The following table describes the options you can specify and their
* values:
*
* Option Value Port Port number to use for advertising the object. Default
* value is 80 for a non-secured connection, and 443 for a secured
* connection. Instances Number of objects x.HTTPSupport
* instantiates to dispatch requests. The default value is 1 and the object
* instance specified as the first Advertise method parameter is used. If
* the value is 10, x.HTTPSupport uses the class type of
* the object to instantiate 9 other objects and uses the 10 objects to
* dispatch the requests. Timeout Maximum idle time in seconds that an
* HTTP server object, booked by an application session,
* keeps the connection. x.HTTPSupport might abort the
* application session of the server object after the timeout period if the
* object is not busy. The default value is 0, which indicates no timeout.
* SECURE Indicates that the connection should use SSL. Specify this option
* without a value. By default, an advertised object uses a TCP (unsecured)
* connection. SECRECY Specify one of the following values if the connection
* is secured:NONE, LOW, MEDIUM, HIGH, ANYDefaults to ANY.Data is not
* encrypted (NONE) or is encrypted to varying levels of secrecy as
* determined by algorithmic key length. LOW is 40-bit (export quality),
* MEDIUM is between 40 and 128 bits; HIGH is 128-bit or longer. If the
* other endpoint specifies ANY, the highest level of secrecy is used.
* INTEGRITY Specify one of the following values if the connection is
* secured:NONE, LOW, MEDIUM, HIGH, ANYDefaults to ANY.Message integrity is
* a security feature for detecting tampering of data. Messages can have no
* integrity (NONE) or have varying levels as determined by algorithmic key
* length. LOW is limited integrity (currently matches no digests), MEDIUM
* is MD5; HIGH is SHA. If the other endpoint specifies ANY, the highest
* level of integrity is used. COMPRESSION Specify one of the following data
* compression values if the connection is secured:NONE, SPEED, SIZE,
* ANYDefaults to NONE.SSL 3.0 does not define any methods to compress data
* before it is encrypted and sent but requires a NULL compression method.
* RESUME Specify one of the following values if the connection is
* secured:NEVER, ALWAYSDefaults to ALWAYS.A listening connection set to
* resume the secured connection allows clients to reconnect quickly.
* AUTHENTICATE Specify one of the following values if the connection is
* secured:ANONYMOUS, SERVER, BOTH, ANYDefaults to ANONYMOUS.If
* authentication is Anonymous, secrecy and integrity are applied but not
* authentication of identity. The SERVER option authenticates only the
* server. The BOTH option authenticates both server and client.
* SSL_CERTFILE A string indicating the name of the certificate file. For
* example, '/certdir/certfile' SSL_CERTPASSWORD A string indicating the
* certificate password to be used to create the certificate. For example,
* 'xf89ApQq' SSL_CERTPASSWORDFILE A string indicating the name of the
* certificate password file. This option allows the use of a hidden
* password. The entire content of the file is read as a text password. This
* option can be used instead of or in conjunction with SSL_CERTPASSWORD.
* For example, '/certpwddir/certpwdfile' SSL_ROOTFILE A string indicating
* the name of the root of certificates file. For example,
* '/rootdir/rootfile' SSL_ROOTPASSWORD A string indicating the root of
* certificates file password. For example, '67mmmnHyx23'
* SSL_ROOTPASSWORDFILE A string indicating the name of the password file
* for the root of certificates file. This option allows the use of a hidden
* password. The entire content of the file is read as a text password. This
* option can be used instead of or in conjunction with SSL_ROOTPASSWORD.
* For example, '/rootpwddir/rootpwdfile'
*/
public void advertise(final Object pReceiver, String pParam) {
this.parseParameters(pParam);
_log.debug("This server is listening on port: " + this.defaultPort);
try {
if (this.defaultSecure) {
/*
* CONFIGURING SSL --------------- see
* x.HTTPSupport.KeystoreConverter.java for how to
* convert a PKCS12 keystore into a java Keystore
*
* make sure the java keystore is located under the root of your
* tomcat installation
*
* add the following JVM arguments to your application server:
* (Tomcat: Start -> All Programs -> Apache Tomcat 5.5 -> Configure
* Tomcat, 'Java' Tab, 'Java Options' textfield)
* -Djavax.net.ssl.keyStore= <your keystore name>
* -Djavax.net.ssl.keyStorePassword= <your keystore password>
*
* for debugging purposes, the following JVM arguments generate a
* detailed log trace:
* -Djava.protocol.handler.pkgs=com.sun.net.ssl.internal.www.protocol
* -Djavax.net.debug=ssl
*/
final ServerSocket server = SSLServerSocketFactory.getDefault()
.createServerSocket(this.defaultSecurePort,
this.defaultInstances);
server.setSoTimeout(this.defaultTimeout);
Thread t = new Thread(new Runnable() {
public void run() {
try {
while (true)
new ProcessThread(server.accept(), pReceiver);
} catch (IOException ioe) {
}
}
});
t.setDaemon(true);
t.start();
} else {
final ServerSocket server = ServerSocketFactory.getDefault()
.createServerSocket(this.defaultPort,
this.defaultInstances);
server.setSoTimeout(this.defaultTimeout);
Thread t = new Thread(new Runnable() {
public void run() {
try {
while (true)
new ProcessThread(server.accept(), pReceiver);
} catch (IOException ioe) {
}
}
});
t.setDaemon(true);
t.start();
}
} catch (SocketException e) {
DistributedAccessException errorVar = new DistributedAccessException("Failed to avertise using " + pParam, e);
ErrorMgr.addError(errorVar);
throw errorVar;
} catch (IOException e) {
DistributedAccessException errorVar = new DistributedAccessException("Failed to avertise using " + pParam, e);
ErrorMgr.addError(errorVar);
throw errorVar;
}
}
private void parseParameters(String param) {
String[] parameters = param.split(","); // Get the individual parameters
String[] option;
Integer port = null;
for (int i = 0, sz = parameters.length; i < sz; i++) {
option = parameters[i].split("="); // Get the indivdual option and
// its value
if (option[0].equalsIgnoreCase("Port")) {
port = new Integer(option[1]);
} else if (option[0].equalsIgnoreCase("Instances")) {
this.defaultInstances = Integer.parseInt(option[1]);
} else if (option[0].equalsIgnoreCase("Timeout")) {
this.defaultTimeout = Integer.parseInt(option[1]);
} else if (option[0].equalsIgnoreCase("SECURE")) {
this.defaultSecure = Boolean.parseBoolean(option[1]);
} else { // put the option into the HttpConfigurationObject
// ignore invalid options
}
}
if (port != null){
if (this.defaultSecure)
this.defaultSecurePort = port.intValue();
else
this.defaultPort = port.intValue();
}
}
/**
* The FindConfigManager method returns the x.HTTPSupport
* configuration manager object. There is one configuration manager instance
* for x.HTTPSupport.
*/
public HTTPConfigManager findConfigManager() {
if (this.configManger == null) {
this.configManger = new HTTPConfigManager();
}
return this.configManger;
}
/**
* The FindFactory method returns the factory of
* x.HTTPSupport. There is one factory instance for
* x.HTTPSupport. The factory instantiates entities, as
* well as HTTP response and request objects.
*
* @return
*/
public HTTPFactory findFactory() {
throw new RuntimeException(
"x.HTTPSupport.HTTPHelper.findFactory() is not implemented");
}
/**
* The FindServerManager method returns the server manager associated with
* the advertised object.
*
* @param obj
* This parameter is the object used in the Advertise() method
* call used to get the dispatcher instance.
*
*
* @return
*/
public HTTPServerManager findServerManager(Object obj) {
throw new UnsupportedOperationException("HTTPHelper.findServerManager() is not implemented");
}
private class ProcessThread implements Runnable {
private Socket socket;
private Object receiver = null;
public ProcessThread(Socket s, Object pReceiver) {
socket = s;
receiver = pReceiver;
Thread t = new Thread(this);
t.setDaemon(true);
t.start();
}
public void run() {
// BufferedReader in = null;
BufferedWriter out = null;
try {
// InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
out = new BufferedWriter(new OutputStreamWriter(os));
// AD:10/11/2008: - Added socket parameter to the constructer.
GenericRequest request = new HTTPBaseRequest(socket);
// GenericRequest request = new FAD_HTTPRequest(/*is*/socket);
GenericResponse response = new HTTPBaseResponse();
// GenericResponse response = new FAD_HTTPResponse();
// Set the host in the header
String host = socket.getInetAddress().getHostAddress() + ":" + socket.getLocalPort();
request.setHeader(Constants.HTTP_HEADER_HOST, host);
// Useful until someone needs a HTTPReceiver with doHead, doGet,
// etc support.
MessageReceiver receiver = (MessageReceiver) this.receiver;
receiver.initialize(new MessageConfig()); //Until we need
// sesssion management
receiver.process(request, response); // The receiver takes care
// of things...
receiver.terminate(); // ...as it does here
// Should be O.K. from here on out...unless the outbound stream
// goes wonky
out.write("HTTP/1.0 200 OK\n"); // @NoChangeOnCopy
if (response.getContentType() == null) {
out.append("Content-type: text/html\n");
} else {
out.append("Content-type: " + response.getContentType() + "\n");
}
TextData body = new TextData();
response.getBody().readText(body);
// Set the content length
response.getBody().setIntHeader(Constants.HTTP_HEADER_CONTENT_LENGTH, body.length());
out.append("Content-length: " + response.getContentLength());
out.newLine();
out.newLine();
out.append(body.toString());
out.newLine();
} catch (IOException e) {
try {
out.write("HTTP/1.0 500 Internal Server Error\n"); // @NoChangeOnCopy
} catch (IOException e1) {
}
} catch (Error t) {
t.printStackTrace();
throw t;
} catch (RuntimeException t) {
t.printStackTrace();
throw t;
} finally {
try {
out.flush();
out.close();
socket.close();
} catch (IOException e) {
// Something really bad has happened!
e.printStackTrace();
}
}
}
}
/**
* @param date
* @return
*/
public String toStringDate(DateTimeData date) {
DateFormat gmtfmt = new DateFormat();
gmtfmt.setTemplate("ddd, dd mmm yyyy hh:mm:ss GMT");
return gmtfmt.formatDate(date).toString();
}
/**
* @param date
* @return
*/
public DateTimeData fromStringDate(String date) {
DateFormat gmtfmt = new DateFormat();
gmtfmt.setTemplate("ddd, dd mmm yyyy hh:mm:ss GMT");
return gmtfmt.decodeDate(date);
}
}