001: /*
002: * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: */
007: package winstone.ajp13;
008:
009: import java.io.ByteArrayOutputStream;
010: import java.io.IOException;
011: import java.io.OutputStream;
012: import java.io.UnsupportedEncodingException;
013: import java.util.Hashtable;
014: import java.util.Iterator;
015: import java.util.Map;
016:
017: import javax.servlet.http.Cookie;
018:
019: import winstone.Logger;
020: import winstone.WinstoneException;
021: import winstone.WinstoneOutputStream;
022:
023: /**
024: * Extends the winstone output stream, so that the ajp13 protocol requirements
025: * can be fulfilled.
026: *
027: * @author mailto: <a href="rick_knowles@hotmail.com">Rick Knowles</a>
028: * @version $Id: Ajp13OutputStream.java,v 1.7 2007/05/05 00:52:50 rickknowles Exp $
029: */
030: public class Ajp13OutputStream extends WinstoneOutputStream {
031: // Container originated packet types
032: byte CONTAINER_SEND_BODY_CHUNK = 0x03;
033: byte CONTAINER_SEND_HEADERS = 0x04;
034: byte CONTAINER_END_RESPONSE = 0x05;
035:
036: // byte CONTAINER_GET_BODY_CHUNK = 0x06;
037: // byte CONTAINER_CPONG_REPLY = 0x09;
038:
039: static Map headerCodes = null;
040:
041: static {
042: headerCodes = new Hashtable();
043: headerCodes.put("content-type",
044: new byte[] { (byte) 0xA0, 0x01 });
045: headerCodes.put("content-language", new byte[] { (byte) 0xA0,
046: 0x02 });
047: headerCodes.put("content-length", new byte[] { (byte) 0xA0,
048: 0x03 });
049: headerCodes.put("date", new byte[] { (byte) 0xA0, 0x04 });
050: headerCodes.put("last-modified",
051: new byte[] { (byte) 0xA0, 0x05 });
052: headerCodes.put("location", new byte[] { (byte) 0xA0, 0x06 });
053: headerCodes.put("set-cookie", new byte[] { (byte) 0xA0, 0x07 });
054: headerCodes
055: .put("set-cookie2", new byte[] { (byte) 0xA0, 0x08 });
056: headerCodes.put("servlet-engine", new byte[] { (byte) 0xA0,
057: 0x09 });
058: headerCodes.put("server", new byte[] { (byte) 0xA0, 0x09 });
059: headerCodes.put("status", new byte[] { (byte) 0xA0, 0x0A });
060: headerCodes.put("www-authenticate", new byte[] { (byte) 0xA0,
061: 0x0B });
062: }
063:
064: private String headerEncoding;
065:
066: public Ajp13OutputStream(OutputStream outStream,
067: String headerEncoding) {
068: super (outStream, false);
069: this .headerEncoding = headerEncoding;
070: }
071:
072: public void commit() throws IOException {
073: Logger.log(Logger.FULL_DEBUG, Ajp13Listener.AJP_RESOURCES,
074: "Ajp13OutputStream.CommittedBytes", ""
075: + this .bytesCommitted);
076: this .buffer.flush();
077:
078: // If we haven't written the headers yet, write them out
079: if (!this .committed) {
080: this .owner.validateHeaders();
081: this .committed = true;
082:
083: ByteArrayOutputStream headerArrayStream = new ByteArrayOutputStream();
084: for (Iterator i = this .owner.getHeaders().iterator(); i
085: .hasNext();) {
086: String header = (String) i.next();
087: int colonPos = header.indexOf(':');
088: if (colonPos == -1)
089: throw new WinstoneException(
090: Ajp13Listener.AJP_RESOURCES.getString(
091: "Ajp13OutputStream.NoColonHeader",
092: header));
093: String headerName = header.substring(0, colonPos)
094: .trim();
095: String headerValue = header.substring(colonPos + 1)
096: .trim();
097: byte headerCode[] = (byte[]) headerCodes.get(headerName
098: .toLowerCase());
099: if (headerCode == null) {
100: headerArrayStream.write(getStringBlock(headerName));
101: } else {
102: headerArrayStream.write(headerCode);
103: }
104: headerArrayStream.write(getStringBlock(headerValue));
105: }
106:
107: for (Iterator i = this .owner.getCookies().iterator(); i
108: .hasNext();) {
109: Cookie cookie = (Cookie) i.next();
110: String cookieText = this .owner.writeCookie(cookie);
111: int colonPos = cookieText.indexOf(':');
112: if (colonPos == -1)
113: throw new WinstoneException(
114: Ajp13Listener.AJP_RESOURCES.getString(
115: "Ajp13OutputStream.NoColonHeader",
116: cookieText));
117: String headerName = cookieText.substring(0, colonPos)
118: .trim();
119: String headerValue = cookieText.substring(colonPos + 1)
120: .trim();
121: byte headerCode[] = (byte[]) headerCodes.get(headerName
122: .toLowerCase());
123: if (headerCode == null) {
124: headerArrayStream.write(getStringBlock(headerName));
125: } else {
126: headerArrayStream.write(headerCode);
127: }
128: headerArrayStream.write(getStringBlock(headerValue));
129: }
130:
131: // Write packet header + prefix + status code + status msg + header
132: // count
133: byte headerArray[] = headerArrayStream.toByteArray();
134: byte headerPacket[] = new byte[12];
135: headerPacket[0] = (byte) 0x41;
136: headerPacket[1] = (byte) 0x42;
137: setIntBlock(headerArray.length + 8, headerPacket, 2);
138: headerPacket[4] = CONTAINER_SEND_HEADERS;
139: setIntBlock(this .owner.getStatus(), headerPacket, 5);
140: setIntBlock(0, headerPacket, 7); // empty msg
141: headerPacket[9] = (byte) 0x00;
142: setIntBlock(this .owner.getHeaders().size()
143: + this .owner.getCookies().size(), headerPacket, 10);
144:
145: // Ajp13Listener.packetDump(headerPacket, headerPacket.length);
146: // Ajp13Listener.packetDump(headerArray, headerArray.length);
147:
148: this .outStream.write(headerPacket);
149: this .outStream.write(headerArray);
150: }
151:
152: // Write out the contents of the buffer in max 8k chunks
153: byte bufferContents[] = this .buffer.toByteArray();
154: int position = 0;
155: while (position < bufferContents.length) {
156: int packetLength = Math.min(bufferContents.length
157: - position, 8184);
158: byte responsePacket[] = new byte[packetLength + 8];
159: responsePacket[0] = 0x41;
160: responsePacket[1] = 0x42;
161: setIntBlock(packetLength + 4, responsePacket, 2);
162: responsePacket[4] = CONTAINER_SEND_BODY_CHUNK;
163: setIntBlock(packetLength, responsePacket, 5);
164: System.arraycopy(bufferContents, position, responsePacket,
165: 7, packetLength);
166: responsePacket[packetLength + 7] = 0x00;
167: position += packetLength;
168:
169: // Ajp13Listener.packetDump(responsePacket, responsePacket.length);
170: this .outStream.write(responsePacket);
171: }
172:
173: this .buffer.reset();
174: this .bufferPosition = 0;
175: }
176:
177: public void finishResponse() throws IOException {
178: // Send end response packet
179: byte endResponse[] = new byte[] { 0x41, 0x42, 0x00, 0x02,
180: CONTAINER_END_RESPONSE, 1 };
181: // Ajp13Listener.packetDump(endResponse, endResponse.length);
182: this .outStream.write(endResponse);
183: }
184:
185: /**
186: * Useful generic method for getting ajp13 format integers in a packet.
187: */
188: public byte[] getIntBlock(int integer) {
189: byte hi = (byte) (0xFF & (integer >> 8));
190: byte lo = (byte) (0xFF & (integer - (hi << 8)));
191: return new byte[] { hi, lo };
192: }
193:
194: /**
195: * Useful generic method for setting ajp13 format integers in a packet.
196: */
197: public static void setIntBlock(int integer, byte packet[],
198: int offset) {
199: byte hi = (byte) (0xFF & (integer >> 8));
200: byte lo = (byte) (0xFF & (integer - (hi << 8)));
201: packet[offset] = hi;
202: packet[offset + 1] = lo;
203: }
204:
205: /**
206: * Useful generic method for getting ajp13 format strings in a packet.
207: */
208: public byte[] getStringBlock(String text)
209: throws UnsupportedEncodingException {
210: byte textBytes[] = text.getBytes(headerEncoding);
211: byte outArray[] = new byte[textBytes.length + 3];
212: System.arraycopy(getIntBlock(textBytes.length), 0, outArray, 0,
213: 2);
214: System.arraycopy(textBytes, 0, outArray, 2, textBytes.length);
215: outArray[textBytes.length + 2] = 0x00;
216: return outArray;
217: }
218:
219: }
|