/*
* Copyright(c) 2005 Center for E-Commerce Infrastructure Development, The
* University of Hong Kong (HKU). All Rights Reserved.
*
* This software is licensed under the GNU GENERAL PUBLIC LICENSE Version 2.0 [1]
*
* [1] http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
*/
package hk.hku.cecid.corvus.http;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.PushbackInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.HashMap;
import java.net.ServerSocket;
import java.net.Socket;
/**
* The <code>SimpleHttpMonitor</code> is a very very simple HTTP monitor used for testing.
*/
public class SimpleHttpMonitor implements Runnable
{
/* The stream used for capturing data */
private InputStream fullHTTPInStream;
private InputStream contentHTTPInStream;
private ByteArrayOutputStream fullHTTPOutStream = new ByteArrayOutputStream();
/* The HTTP header from the last HTTP request */
private Map headers = new HashMap();
/* The content length of last HTTP request */
private int contentLength = -1;
/* The start offset of HTTP entity body from the captured HTTP request. */
private int contentStartOffset = -1;
/* The content type of last HTTP request */
private String contentType;
private int responseContentLength = 0;
/* The port used for listening data */
private int port;
/* The server thread for executing the HTTP monitor */
private Thread serverThread = new Thread(this);
/* The server socket for listening socket connection */
private ServerSocket sSocket;
/* Whether the monitor has been stopped ? */
private volatile boolean stopped = false;
public static final String CONTENT_LENGTH = "Content-Length";
public static final String CONTENT_TYPE = "Content-Type";
protected static final byte[] STATUS_200 = "HTTP/1.1 200 OK ".getBytes();
protected static final byte[] HD_SERVIER = "Server: WSC_HTTP_monitor".getBytes();
protected static final byte[] HD_CT_LEN = "Content-Length: ".getBytes();
protected static final byte[] HD_CT_TYPE = "Content-type: text/plain".getBytes();
protected static final byte[] CRLF = "\r\n".getBytes();
/**
* Explicit Constructor.
*
* @param port The port number listening HTTP request.
*/
public SimpleHttpMonitor(int port){
this.port = port;
}
/**
* Start the HTTP monitor.
*/
public void start(){
this.serverThread.start();
}
/**
* Stop the HTTP monitor.
*/
public void stop(){
if (this.serverThread.isAlive() && !this.serverThread.isInterrupted()){
this.stopped = true;
this.serverThread.interrupt();
try{
this.sSocket.close();
}catch(IOException ioex){
// log information
}
}
this.serverThread = null;
}
/**
* [@EVENT] This method is invoked when a socket connection is accepted.
*/
protected void onAccept(final Socket s){
}
/**
* [@EVENT] This method is invoked after the socket is accepted.
*/
protected void onRequest(final InputStream ins) throws IOException
{
// Parse the HTTP Header
this.parseHttpHeader(ins, fullHTTPOutStream);
// Get the content length.
int len = this.getContentLength();
// Mark the content offset.
this.contentStartOffset = fullHTTPOutStream.size();
// Parse the HTTP body
this.parseHttpBody (ins, fullHTTPOutStream, len);
}
/**
* [@EVENT] This method is invoked before calling onResponse. It ask the
* sub-class implementation for the content length of this response. Default
* return 0.
*/
protected int onResponseLength(){
return 0;
}
/**
* [@EVENT] This method is invoked when the HTTP content has been parsed, and now ready
* to write content to socket.
*/
protected void onResponse(final OutputStream os) throws IOException {
os.write(STATUS_200);
os.write(CRLF);
os.write(HD_SERVIER);
os.write(CRLF);
os.write(HD_CT_LEN);
os.write(String.valueOf(this.responseContentLength).getBytes());
os.write(CRLF);
os.write(HD_CT_TYPE);
os.write(CRLF);
os.write(CRLF);
}
/**
* The thread execution method.
*/
public void run()
{
try{
this.sSocket = new ServerSocket(this.port);
while(!stopped)
{
Socket s = null;
try
{
// Accept a new connection.
s = this.sSocket.accept();
// Invoke event.
this.onAccept(s);
// Create a buffered socket input stream.
InputStream bsis = new BufferedInputStream(s.getInputStream());
this.onRequest(bsis);
// Redirect to input capture stream.
byte[] request = fullHTTPOutStream.toByteArray();
if (this.contentStartOffset != -1){
// Capture only the content.
this.contentHTTPInStream = new ByteArrayInputStream(
request, this.contentStartOffset, request.length);
}
// Capture the whole stream.
fullHTTPInStream = new ByteArrayInputStream(request);
fullHTTPOutStream.close();
// Invoke event for query the response content-length.
this.responseContentLength = this.onResponseLength();
// Create a buffered socket output stream.
OutputStream bsos = new BufferedOutputStream(s.getOutputStream());
this.onResponse(bsos);
bsos.flush();
bsos.close();
}
catch(IOException ioex)
{
if (!this.stopped)
ioex.printStackTrace();
}
finally
{
if (s != null && s.isClosed())
s.close();
}
}
}
catch(Throwable t)
{
t.printStackTrace();
}
finally
{
try{
if (sSocket != null && !sSocket.isClosed())
sSocket.close();
}
catch(IOException ioex){
ioex.printStackTrace();
}
}
}
/**
* @return The content length of last HTTP request.
*/
public int getContentLength(){
if (contentLength == -1){
String contentLen = (String) this.headers.get(CONTENT_LENGTH);
if (contentLen != null){
try{
this.contentLength = Integer.parseInt(contentLen);
return this.contentLength;
}catch(NumberFormatException nfe){} // TODO:
}
}
return -1;
}
/**
* @return The content type of last HTTP request
*/
public String getContentType(){
if (contentType == null)
this.contentType = (String) this.headers.get(CONTENT_TYPE);
return contentType;
}
/**
* @return Get the last HTTP request header monitored.
* */
public Map getHeaders(){
return this.headers;
}
/**
* @return Get the last HTTP request content.
* */
public InputStream getInputStream(){
return this.fullHTTPInStream;
}
/**
* Get the input stream containing the HTTP body content from the last HTTP request.
* It is different from {@link #getInputStream()} because that returns the stream
* containing (HTTP header + HTTP body content).
*
* @return the input stream containing the HTTP body content from the last HTTP request.
*/
public InputStream getContentStream(){
return this.contentHTTPInStream;
}
/*
* Reset the data
*/
private void resetData(){
// Reset the configuration and header field.
fullHTTPOutStream.reset();
this.headers.clear();
this.contentLength = -1;
this.contentStartOffset = -1;
this.contentType = null;
}
/*
*
*/
private void parseHttpHeader(InputStream sins, OutputStream capouts)
throws IOException
{
if (sins == null)
throw new NullPointerException("Missing 'SocketInputStream' for parsing Http line.");
if (capouts == null)
throw new NullPointerException("Missing 'CapturedOutputStream' for captugin Http line.");
PushbackInputStream pbis = new PushbackInputStream(sins);
char pc = (char) -1; char c;
while ((c = (char)pbis.read()) != -1)
{
if (c != '\r'){
pbis.unread(c);
this.parseHttpLine(pbis, capouts);
}
else {
pc = c;
c = (char) pbis.read();
if (c == '\n' && pc == '\r'){
capouts.write((int)pc);
capouts.write((int)c);
break; // Parse end-line
}
}
}
}
/*
*
*/
private void parseHttpLine(InputStream sins, OutputStream capouts)
throws IOException
{
char pc = (char) -1;
char c;
boolean colonized = false;
String name = null; String value = null;
int len = 128;
int count = 0;
char[] cs = new char[len];
while ((c = (char)sins.read()) != -1){
capouts.write(c);
if (c == '\n' && pc == '\r'){
// Get the value
if (colonized){
// Ignore the captured colon and CR
value = new String(cs, 1, count - 1).trim();
this.headers.put(name, value);
}
break;
}
if (c == ':' && !colonized){
// Get the header
name = new String(cs, 0, count).trim();
colonized = true;
count = 0;
}
// Update the previous char pointer.
pc = c;
cs[count++] = c;
// Re-allocate if the buffer is not big enough.
if (count == len){
char[] ncs = new char[len * 2];
System.arraycopy(cs, 0, ncs, 0, count);
cs = ncs;
}
}
}
/*
*
*/
private void parseHttpBody(InputStream sins, OutputStream capouts, int contentLength)
throws IOException
{
int read = 0;
char c;
while ( read++ < contentLength ){
c = (char)sins.read();
capouts.write(c);
}
}
}