/*
* Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
* Distributed under the terms of either:
* - the common development and distribution license (CDDL), v1.0; or
* - the GNU Lesser General Public License, v2.1 or later
*/
package winstone.ajp13;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.Cookie;
import winstone.Logger;
import winstone.WinstoneException;
import winstone.WinstoneOutputStream;
/**
* Extends the winstone output stream, so that the ajp13 protocol requirements
* can be fulfilled.
*
* @author mailto: <a href="rick_knowles@hotmail.com">Rick Knowles</a>
* @version $Id: Ajp13OutputStream.java,v 1.7 2007/05/05 00:52:50 rickknowles Exp $
*/
public class Ajp13OutputStream extends WinstoneOutputStream {
// Container originated packet types
byte CONTAINER_SEND_BODY_CHUNK = 0x03;
byte CONTAINER_SEND_HEADERS = 0x04;
byte CONTAINER_END_RESPONSE = 0x05;
// byte CONTAINER_GET_BODY_CHUNK = 0x06;
// byte CONTAINER_CPONG_REPLY = 0x09;
static Map headerCodes = null;
static {
headerCodes = new Hashtable();
headerCodes.put("content-type", new byte[] { (byte) 0xA0, 0x01 });
headerCodes.put("content-language", new byte[] { (byte) 0xA0, 0x02 });
headerCodes.put("content-length", new byte[] { (byte) 0xA0, 0x03 });
headerCodes.put("date", new byte[] { (byte) 0xA0, 0x04 });
headerCodes.put("last-modified", new byte[] { (byte) 0xA0, 0x05 });
headerCodes.put("location", new byte[] { (byte) 0xA0, 0x06 });
headerCodes.put("set-cookie", new byte[] { (byte) 0xA0, 0x07 });
headerCodes.put("set-cookie2", new byte[] { (byte) 0xA0, 0x08 });
headerCodes.put("servlet-engine", new byte[] { (byte) 0xA0, 0x09 });
headerCodes.put("server", new byte[] { (byte) 0xA0, 0x09 });
headerCodes.put("status", new byte[] { (byte) 0xA0, 0x0A });
headerCodes.put("www-authenticate", new byte[] { (byte) 0xA0, 0x0B });
}
private String headerEncoding;
public Ajp13OutputStream(OutputStream outStream, String headerEncoding) {
super(outStream, false);
this.headerEncoding = headerEncoding;
}
public void commit() throws IOException {
Logger.log(Logger.FULL_DEBUG, Ajp13Listener.AJP_RESOURCES,
"Ajp13OutputStream.CommittedBytes", "" + this.bytesCommitted);
this.buffer.flush();
// If we haven't written the headers yet, write them out
if (!this.committed) {
this.owner.validateHeaders();
this.committed = true;
ByteArrayOutputStream headerArrayStream = new ByteArrayOutputStream();
for (Iterator i = this.owner.getHeaders().iterator(); i.hasNext();) {
String header = (String) i.next();
int colonPos = header.indexOf(':');
if (colonPos == -1)
throw new WinstoneException(Ajp13Listener.AJP_RESOURCES.getString(
"Ajp13OutputStream.NoColonHeader", header));
String headerName = header.substring(0, colonPos).trim();
String headerValue = header.substring(colonPos + 1).trim();
byte headerCode[] = (byte[]) headerCodes.get(headerName
.toLowerCase());
if (headerCode == null) {
headerArrayStream.write(getStringBlock(headerName));
} else {
headerArrayStream.write(headerCode);
}
headerArrayStream.write(getStringBlock(headerValue));
}
for (Iterator i = this.owner.getCookies().iterator(); i.hasNext();) {
Cookie cookie = (Cookie) i.next();
String cookieText = this.owner.writeCookie(cookie);
int colonPos = cookieText.indexOf(':');
if (colonPos == -1)
throw new WinstoneException(Ajp13Listener.AJP_RESOURCES.getString(
"Ajp13OutputStream.NoColonHeader", cookieText));
String headerName = cookieText.substring(0, colonPos).trim();
String headerValue = cookieText.substring(colonPos + 1).trim();
byte headerCode[] = (byte[]) headerCodes.get(headerName.toLowerCase());
if (headerCode == null) {
headerArrayStream.write(getStringBlock(headerName));
} else {
headerArrayStream.write(headerCode);
}
headerArrayStream.write(getStringBlock(headerValue));
}
// Write packet header + prefix + status code + status msg + header
// count
byte headerArray[] = headerArrayStream.toByteArray();
byte headerPacket[] = new byte[12];
headerPacket[0] = (byte) 0x41;
headerPacket[1] = (byte) 0x42;
setIntBlock(headerArray.length + 8, headerPacket, 2);
headerPacket[4] = CONTAINER_SEND_HEADERS;
setIntBlock(this.owner.getStatus(), headerPacket, 5);
setIntBlock(0, headerPacket, 7); // empty msg
headerPacket[9] = (byte) 0x00;
setIntBlock(this.owner.getHeaders().size()
+ this.owner.getCookies().size(), headerPacket, 10);
// Ajp13Listener.packetDump(headerPacket, headerPacket.length);
// Ajp13Listener.packetDump(headerArray, headerArray.length);
this.outStream.write(headerPacket);
this.outStream.write(headerArray);
}
// Write out the contents of the buffer in max 8k chunks
byte bufferContents[] = this.buffer.toByteArray();
int position = 0;
while (position < bufferContents.length) {
int packetLength = Math.min(bufferContents.length - position, 8184);
byte responsePacket[] = new byte[packetLength + 8];
responsePacket[0] = 0x41;
responsePacket[1] = 0x42;
setIntBlock(packetLength + 4, responsePacket, 2);
responsePacket[4] = CONTAINER_SEND_BODY_CHUNK;
setIntBlock(packetLength, responsePacket, 5);
System.arraycopy(bufferContents, position, responsePacket, 7, packetLength);
responsePacket[packetLength + 7] = 0x00;
position += packetLength;
// Ajp13Listener.packetDump(responsePacket, responsePacket.length);
this.outStream.write(responsePacket);
}
this.buffer.reset();
this.bufferPosition = 0;
}
public void finishResponse() throws IOException {
// Send end response packet
byte endResponse[] = new byte[] { 0x41, 0x42, 0x00, 0x02,
CONTAINER_END_RESPONSE, 1 };
// Ajp13Listener.packetDump(endResponse, endResponse.length);
this.outStream.write(endResponse);
}
/**
* Useful generic method for getting ajp13 format integers in a packet.
*/
public byte[] getIntBlock(int integer) {
byte hi = (byte) (0xFF & (integer >> 8));
byte lo = (byte) (0xFF & (integer - (hi << 8)));
return new byte[] { hi, lo };
}
/**
* Useful generic method for setting ajp13 format integers in a packet.
*/
public static void setIntBlock(int integer, byte packet[], int offset) {
byte hi = (byte) (0xFF & (integer >> 8));
byte lo = (byte) (0xFF & (integer - (hi << 8)));
packet[offset] = hi;
packet[offset + 1] = lo;
}
/**
* Useful generic method for getting ajp13 format strings in a packet.
*/
public byte[] getStringBlock(String text)
throws UnsupportedEncodingException {
byte textBytes[] = text.getBytes(headerEncoding);
byte outArray[] = new byte[textBytes.length + 3];
System.arraycopy(getIntBlock(textBytes.length), 0, outArray, 0, 2);
System.arraycopy(textBytes, 0, outArray, 2, textBytes.length);
outArray[textBytes.length + 2] = 0x00;
return outArray;
}
}