Files
alfresco-community-repo/source/java/org/alfresco/heartbeat/HeartBeatTest.java
Dave Ward ef972b4353 Merged V3.0 to HEAD (again)
11824: Added heartbeat client functionality and unit tests to Alfresco Server.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@12518 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2008-12-19 16:52:57 +00:00

313 lines
10 KiB
Java

/*
* Copyright (C) 2005-2008 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.heartbeat;
import java.beans.XMLDecoder;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import org.alfresco.util.BaseSpringTest;
import org.alfresco.util.security.DecryptingInputStream;
/**
* An integration test for the heartbeat service. Fakes an HTTP endpoint with a server socket in order to sure the
* service is functioning correctly.
*
* @author dward
*/
public class HeartBeatTest extends BaseSpringTest
{
/**
* Test heart beat.
*
* @throws Exception
* the exception
*/
@SuppressWarnings("unchecked")
public void testHeartBeat() throws Exception
{
// Load the private key from the trial key store
PrivateKey privateKey;
{
final KeyStore keyStore = KeyStore.getInstance("JKS");
final InputStream in = getClass().getResourceAsStream(HeartBeat.PUBLIC_STORE);
keyStore.load(in, HeartBeat.PUBLIC_STORE_PWD);
in.close();
privateKey = (PrivateKey) keyStore.getKey("test", HeartBeat.PUBLIC_STORE_PWD);
}
// Construct a heartbeat instance in test mode (beats every second using test public key)
new HeartBeat(getApplicationContext(), true);
ServerSocket serverSocket = new ServerSocket(9999);
// Now attempt to parse 4 of the 'beats'
for (int i = 0; i < 4; i++)
{
Socket clientSocket = serverSocket.accept();
XMLDecoder decoder = null;
InputStream in = null;
OutputStream out = null;
try
{
in = new GZIPInputStream(new DecryptingInputStream(new HttpChunkedInputStream(clientSocket
.getInputStream()), privateKey), 1024);
out = clientSocket.getOutputStream();
decoder = new XMLDecoder(in);
Map<String, String> params = (Map<String, String>) decoder.readObject();
out.write("HTTP/1.1 200 OK\r\n\r\n".getBytes("ASCII"));
System.out.println(params);
}
finally
{
if (decoder != null)
{
try
{
decoder.close();
in = null;
}
catch (final Exception e)
{
}
}
if (in != null)
{
try
{
in.close();
}
catch (final Exception e)
{
}
}
if (out != null)
{
try
{
out.close();
}
catch (final Exception e)
{
}
}
try
{
clientSocket.close();
}
catch (Exception e)
{
}
}
}
serverSocket.close();
}
/**
* Wraps a raw byte stream in a chunked HTTP request to look like a regular input stream. Skips headers and parses
* chunk sizes.
*/
public static class HttpChunkedInputStream extends InputStream
{
/** The raw input stream. */
private final InputStream socketIn;
/** A buffer for parsing headers. */
private StringBuilder headerBuff = new StringBuilder(100);
/** The current chunk size. */
private int chunkSize;
/** The current position in the chunk. */
private int chunkPosition;
/** Have we got to the end of the last chunk? */
private boolean isAtEnd;
/**
* Instantiates a new http chunked input stream.
*
* @param socketIn
* raw input stream from an HTTP request
* @throws IOException
* Signals that an I/O exception has occurred.
*/
public HttpChunkedInputStream(InputStream socketIn) throws IOException
{
this.socketIn = socketIn;
for (;;)
{
if (getNextHeader().length() == 0)
{
break;
}
}
setNextChunkSize();
}
/**
* Gets the next header.
*
* @return the next header
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private String getNextHeader() throws IOException
{
int b;
while ((b = socketIn.read()) != '\n')
{
if (b == -1)
{
throw new EOFException();
}
headerBuff.append((char) b); // cast to char acceptable because this is ASCII
}
String header = headerBuff.toString().trim();
headerBuff.setLength(0);
return header;
}
/**
* Sets the next chunk size by parsing a chunk header. May detect an end of file condition and set isAtEnd =
* true.
*
* @return the next chunk size
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private int setNextChunkSize() throws IOException
{
String chunkHeader = getNextHeader();
int sepIndex = chunkHeader.indexOf(';');
if (sepIndex != -1)
{
chunkHeader = chunkHeader.substring(0, sepIndex).trim();
}
this.chunkSize = Integer.parseInt(chunkHeader, 16);
this.chunkPosition = 0;
if (this.chunkSize == 0)
{
this.isAtEnd = true;
}
return this.chunkSize;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#close()
*/
@Override
public void close() throws IOException
{
// We intentionally avoid closing the socket input stream here, as that seems to close the entire socket,
// and stops us from being able to write a response!
// this.socketIn.close();
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read()
*/
@Override
public int read() throws IOException
{
final byte[] buf = new byte[1];
int bytesRead;
while ((bytesRead = read(buf)) == 0)
{
;
}
return bytesRead == -1 ? -1 : buf[0] & 0xFF;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read(byte[], int, int)
*/
@Override
public int read(byte[] b, int off, int len) throws IOException
{
if (len == 0)
{
return 0;
}
if (this.isAtEnd)
{
return -1;
}
int bytesToRead = len;
while (bytesToRead > 0)
{
if (this.chunkPosition >= this.chunkSize)
{
// Skip the \r\n after this chunk
String eol = getNextHeader();
if (eol.length() > 0)
{
throw new IOException("Bad chunk format");
}
// Read the new chunk header
setNextChunkSize();
if (this.isAtEnd)
{
// Skip past the trailers. We have to do this in case the same connection is recycled for the
// next request
for (;;)
{
if (getNextHeader().length() == 0)
{
break;
}
}
break;
}
}
int bytesRead = Math.min(bytesToRead, this.chunkSize - this.chunkPosition);
bytesRead = this.socketIn.read(b, off, bytesRead);
if (bytesRead == -1)
{
break;
}
bytesToRead -= bytesRead;
off += bytesRead;
this.chunkPosition += bytesRead;
}
return bytesToRead == len ? -1 : len - bytesToRead;
}
}
}