seguin 01/05/15 08:23:07 Modified: jk/src/java/org/apache/ajp Ajp13Packet.java jk/src/java/org/apache/ajp/tomcat4 Ajp13Connector.java Ajp13InputStream.java Ajp13OutputStream.java Ajp13Processor.java Ajp13Request.java Ajp13Response.java Added: jk/src/java/org/apache/ajp Ajp13.java AjpRequest.java MessageBytes.java jk/src/java/org/apache/ajp/test TestAjp13.java Removed: jk/src/java/org/apache/ajp/tomcat4 Ajp13.java Log: refactoring... a work in progress... Revision Changes Path 1.2 +14 -13 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/Ajp13Packet.java Index: Ajp13Packet.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/Ajp13Packet.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- Ajp13Packet.java 2001/05/12 05:52:39 1.1 +++ Ajp13Packet.java 2001/05/15 15:22:23 1.2 @@ -68,6 +68,9 @@ * packets. */ public class Ajp13Packet { + + public static final String DEFAULT_CHAR_ENCODING = "8859_1"; + byte buff[]; // Holds the bytes of the packet int pos; // The current read or write position in the buffer @@ -129,7 +132,7 @@ /** * Prepare this packet for accumulating a message from the container to * the web server. Set the write position to just after the header - * (but leave the length unwritten, because it is as yet unknown). + * (but leave the length unwritten, because it is as yet unknown). */ public void reset() { len = 4; @@ -288,19 +291,17 @@ public boolean getBool() { return (getByte() == (byte) 1); } - - public static final String DEFAULT_CHAR_ENCODING = "8859_1"; -// public void getMessageBytes( MessageBytes mb ) { -// int length = getInt(); -// if( (length == 0xFFFF) || (length == -1) ) { -// mb.setString( null ); -// return; -// } -// mb.setBytes( buff, pos, length ); -// pos += length; -// pos++; // Skip the terminating \0 -// } + public void getMessageBytes(MessageBytes mb) { + int length = getInt(); + if( (length == 0xFFFF) || (length == -1) ) { + mb.setString( null ); + return; + } + mb.setBytes( buff, pos, length ); + pos += length; + pos++; // Skip the terminating \0 + } // public MessageBytes addHeader( MimeHeaders headers ) { // int length = getInt(); 1.1 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/Ajp13.java Index: Ajp13.java =================================================================== /* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. 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. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR * ITS 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. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.ajp; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Enumeration; /** * Represents a single, persistent connection between the web server and * the servlet container. Uses the Apache JServ Protocol version 1.3 for * communication. Because this protocal does not multiplex requests, this * connection can only be associated with a single request-handling cycle * at a time.<P> * * This class contains knowledge about how an individual packet is laid out * (via the internal <CODE>Ajp13Packet</CODE> class), and also about the * stages of communicaton between the server and the servlet container. It * translates from Tomcat's internal servlet support methods * (e.g. doWrite) to the correct packets to send to the web server. * * @see Ajp13Interceptor * * @author Dan Milstein [[EMAIL PROTECTED]] * @author Keith Wannamaker [[EMAIL PROTECTED]] * @author Kevin Seguin [[EMAIL PROTECTED]] */ public class Ajp13 { public static final int MAX_PACKET_SIZE=8192; public static final int H_SIZE=4; // Size of basic packet header public static final int MAX_READ_SIZE = MAX_PACKET_SIZE - H_SIZE - 2; public static final int MAX_SEND_SIZE = MAX_PACKET_SIZE - H_SIZE - 4; // Prefix codes for message types from server to container public static final byte JK_AJP13_FORWARD_REQUEST = 2; public static final byte JK_AJP13_SHUTDOWN = 7; // Prefix codes for message types from container to server public static final byte JK_AJP13_SEND_BODY_CHUNK = 3; public static final byte JK_AJP13_SEND_HEADERS = 4; public static final byte JK_AJP13_END_RESPONSE = 5; public static final byte JK_AJP13_GET_BODY_CHUNK = 6; // Integer codes for common response header strings public static final int SC_RESP_CONTENT_TYPE = 0xA001; public static final int SC_RESP_CONTENT_LANGUAGE = 0xA002; public static final int SC_RESP_CONTENT_LENGTH = 0xA003; public static final int SC_RESP_DATE = 0xA004; public static final int SC_RESP_LAST_MODIFIED = 0xA005; public static final int SC_RESP_LOCATION = 0xA006; public static final int SC_RESP_SET_COOKIE = 0xA007; public static final int SC_RESP_SET_COOKIE2 = 0xA008; public static final int SC_RESP_SERVLET_ENGINE = 0xA009; public static final int SC_RESP_STATUS = 0xA00A; public static final int SC_RESP_WWW_AUTHENTICATE = 0xA00B; // Integer codes for common (optional) request attribute names public static final byte SC_A_CONTEXT = 1; // XXX Unused public static final byte SC_A_SERVLET_PATH = 2; // XXX Unused public static final byte SC_A_REMOTE_USER = 3; public static final byte SC_A_AUTH_TYPE = 4; public static final byte SC_A_QUERY_STRING = 5; public static final byte SC_A_JVM_ROUTE = 6; public static final byte SC_A_SSL_CERT = 7; public static final byte SC_A_SSL_CIPHER = 8; public static final byte SC_A_SSL_SESSION = 9; // Used for attributes which are not in the list above public static final byte SC_A_REQ_ATTRIBUTE = 10; // Terminates list of attributes public static final byte SC_A_ARE_DONE = (byte)0xFF; // Translates integer codes to names of HTTP methods public static final String []methodTransArray = { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", "ACL" }; // id's for common request headers public static final int SC_REQ_ACCEPT = 1; public static final int SC_REQ_ACCEPT_CHARSET = 2; public static final int SC_REQ_ACCEPT_ENCODING = 3; public static final int SC_REQ_ACCEPT_LANGUAGE = 4; public static final int SC_REQ_AUTHORIZATION = 5; public static final int SC_REQ_CONNECTION = 6; public static final int SC_REQ_CONTENT_TYPE = 7; public static final int SC_REQ_CONTENT_LENGTH = 8; public static final int SC_REQ_COOKIE = 9; public static final int SC_REQ_COOKIE2 = 10; public static final int SC_REQ_HOST = 11; public static final int SC_REQ_PRAGMA = 12; public static final int SC_REQ_REFERER = 13; public static final int SC_REQ_USER_AGENT = 14; // Translates integer codes to request header names public static final String []headerTransArray = { "accept", "accept-charset", "accept-encoding", "accept-language", "authorization", "connection", "content-type", "content-length", "cookie", "cookie2", "host", "pragma", "referer", "user-agent" }; // ============ Instance Properties ==================== OutputStream out; InputStream in; // Buffer used of output body and headers Ajp13Packet outBuf = new Ajp13Packet( MAX_PACKET_SIZE ); // Buffer used for input body Ajp13Packet inBuf = new Ajp13Packet( MAX_PACKET_SIZE ); // Buffer used for request head ( and headers ) Ajp13Packet hBuf=new Ajp13Packet( MAX_PACKET_SIZE ); // Holds incoming reads of request body data (*not* header data) byte []bodyBuff = new byte[MAX_READ_SIZE]; int blen; // Length of current chunk of body data in buffer int pos; // Current read position within that buffer private int debug = 10; /** * XXX place holder... */ Logger logger = new Logger(); class Logger { void log(String msg) { System.out.println("[Ajp13] " + msg); } void log(String msg, Throwable t) { System.out.println("[Ajp13] " + msg); t.printStackTrace(System.out); } } public void recycle() { if (debug > 0) { logger.log("recycle()"); } // This is a touch cargo-cultish, but I think wise. blen = 0; pos = 0; } /** * Associate an open socket with this instance. */ public void setSocket( Socket socket ) throws IOException { if (debug > 0) { logger.log("setSocket()"); } socket.setSoLinger( true, 100); out = socket.getOutputStream(); in = socket.getInputStream(); pos = 0; } /** * Read a new packet from the web server and decode it. If it's a * forwarded request, store its properties in the passed-in AjpRequest * object. * * @param req An empty (newly-recycled) request object. * * @return 200 in case of a successful read of a forwarded request, 500 * if there were errors in the reading of the request, and -2 if the * server is asking the container to shut itself down. */ public int receiveNextRequest(AjpRequest req) throws IOException { if (debug > 0) { logger.log("receiveNextRequest()"); } // XXX The return values are awful. int err = receive(hBuf); if(err < 0) { return 500; } int type = (int)hBuf.getByte(); switch(type) { case JK_AJP13_FORWARD_REQUEST: return decodeRequest(req, hBuf); case JK_AJP13_SHUTDOWN: return -2; } return 200; // XXX This is actually an error condition } /** * Parse a FORWARD_REQUEST packet from the web server and store its * properties in the passed-in request object. * * @param req An empty (newly-recycled) request object. * @param msg Holds the packet which has just been sent by the web * server, with its read position just past the packet header (which in * this case includes the prefix code for FORWARD_REQUEST). * * @return 200 in case of a successful decoduing, 500 in case of error. */ private int decodeRequest(AjpRequest req, Ajp13Packet msg) throws IOException { if (debug > 0) { logger.log("decodeRequest()"); } // XXX Awful return values boolean isSSL = false; int contentLength = -1; // Translate the HTTP method code to a String. byte methodCode = msg.getByte(); req.method.setString(methodTransArray[(int)methodCode - 1]); msg.getMessageBytes(req.protocol); msg.getMessageBytes(req.requestURI); msg.getMessageBytes(req.remoteAddr); msg.getMessageBytes(req.remoteHost); msg.getMessageBytes(req.serverName); req.serverPort = msg.getInt(); isSSL = msg.getBool(); // Decode headers int hCount = msg.getInt(); for(int i = 0 ; i < hCount ; i++) { MessageBytes hName = new MessageBytes(); MessageBytes hValue = new MessageBytes(); // Header names are encoded as either an integer code starting // with 0xA0, or as a normal string (in which case the first // two bytes are the length). int isc = msg.peekInt(); int hId = isc & 0xFF; isc &= 0xFF00; if(0xA000 == isc) { msg.getInt(); // To advance the read position hName.setString(headerTransArray[hId - 1]); } else { msg.getMessageBytes(hName); hId = -1; } switch (hId) { case SC_REQ_CONTENT_TYPE: msg.getMessageBytes(req.contentType); break; case SC_REQ_CONTENT_LENGTH: try { contentLength = Integer.parseInt(msg.getString()); } catch (Exception e) { logger.log("parse content-length", e); } break; case SC_REQ_COOKIE: case SC_REQ_COOKIE2: msg.getMessageBytes(hValue); req.addCookies(hValue); break; default: msg.getMessageBytes(hValue); req.addHeader(hName.getString(), hValue); break; } } byte attributeCode; for(attributeCode = msg.getByte() ; attributeCode != SC_A_ARE_DONE ; attributeCode = msg.getByte()) { switch(attributeCode) { case SC_A_CONTEXT : break; case SC_A_SERVLET_PATH : break; case SC_A_REMOTE_USER : msg.getMessageBytes(req.remoteUser); break; case SC_A_AUTH_TYPE : msg.getMessageBytes(req.authType); break; case SC_A_QUERY_STRING : msg.getMessageBytes(req.queryString); break; case SC_A_JVM_ROUTE : msg.getMessageBytes(req.jvmRoute); break; case SC_A_SSL_CERT : isSSL = true; req.setAttribute("javax.servlet.request.X509Certificate", msg.getString()); break; case SC_A_SSL_CIPHER : isSSL = true; req.setAttribute("javax.servlet.request.cipher_suite", msg.getString()); break; case SC_A_SSL_SESSION : isSSL = true; req.setAttribute("javax.servlet.request.ssl_session", msg.getString()); break; case SC_A_REQ_ATTRIBUTE : req.setAttribute(msg.getString(), msg.getString()); break; default: return 500; // Error } } req.secure = isSSL; if(isSSL) { req.scheme = req.SCHEME_HTTPS; } else { req.scheme = req.SCHEME_HTTP; } // Check to see if there should be a body packet coming along // immediately after if(contentLength > 0) { if (debug > 0) { logger.log("contentLength = " + contentLength + ", reading data ..."); } req.contentLength = contentLength; /* Read present data */ int err = receive(inBuf); if(err < 0) { return 500; } blen = inBuf.peekInt(); pos = 0; inBuf.getBytes(bodyBuff); } if (debug > 5) { logger.log(req.toString()); } return 200; // Success } // ==================== Servlet Input Support ================= public int available() throws IOException { if (debug > 0) { logger.log("available()"); } if (pos >= blen) { if( ! refillReadBuffer()) { return 0; } } return blen - pos; } /** * Return the next byte of request body data (to a servlet). * * @see Request#doRead */ public int doRead() throws IOException { if (debug > 0) { logger.log("doRead()"); } if(pos >= blen) { if( ! refillReadBuffer()) { return -1; } } return (char) bodyBuff[pos++]; } /** * Store a chunk of request data into the passed-in byte buffer. * * @param b A buffer to fill with data from the request. * @param off The offset in the buffer at which to start filling. * @param len The number of bytes to copy into the buffer. * * @return The number of bytes actually copied into the buffer, or -1 * if the end of the stream has been reached. * * @see Request#doRead */ public int doRead(byte[] b, int off, int len) throws IOException { if (debug > 0) { logger.log("doRead(byte[], int, int)"); } if(pos >= blen) { if( ! refillReadBuffer()) { return -1; } } if(pos + len <= blen) { // Fear the off by one error // Sanity check b.length > off + len? System.arraycopy(bodyBuff, pos, b, off, len); pos += len; return len; } // Not enough data (blen < pos + len) int toCopy = len; while(toCopy > 0) { int bytesRemaining = blen - pos; if(bytesRemaining < 0) bytesRemaining = 0; int c = bytesRemaining < toCopy ? bytesRemaining : toCopy; System.arraycopy(bodyBuff, pos, b, off, c); toCopy -= c; off += c; pos += c; // In case we exactly consume the buffer if(toCopy > 0) if( ! refillReadBuffer()) { // Resets blen and pos break; } } return len - toCopy; } /** * Get more request body data from the web server and store it in the * internal buffer. * * @return true if there is more data, false if not. */ private boolean refillReadBuffer() throws IOException { if (debug > 0) { logger.log("refillReadBuffer()"); } // If the server returns an empty packet, assume that that end of // the stream has been reached (yuck -- fix protocol??). // Why not use outBuf?? inBuf.reset(); inBuf.appendByte(JK_AJP13_GET_BODY_CHUNK); inBuf.appendInt(MAX_READ_SIZE); send(inBuf); int err = receive(inBuf); if(err < 0) { throw new IOException(); } blen = inBuf.peekInt(); pos = 0; inBuf.getBytes(bodyBuff); return (blen > 0); } // ==================== Servlet Output Support ================= /** */ public void beginSendHeaders(int status, String statusMessage, int numHeaders) throws IOException { if (debug > 0) { logger.log("sendHeaders()"); } // XXX if more headers that MAX_SIZE, send 2 packets! outBuf.reset(); outBuf.appendByte(JK_AJP13_SEND_HEADERS); if (debug > 0) { logger.log("status is: " + status + "(" + statusMessage + ")"); } // set status code and message outBuf.appendInt(status); outBuf.appendString(statusMessage); // write the number of headers... outBuf.appendInt(numHeaders); } public void sendHeader(String name, String value) throws IOException { int sc = headerNameToSc(name); if(-1 != sc) { outBuf.appendInt(sc); } else { outBuf.appendString(name); } outBuf.appendString(value); } public void endSendHeaders() throws IOException { outBuf.end(); send(outBuf); } /** * Translate an HTTP response header name to an integer code if * possible. Case is ignored. * * @param name The name of the response header to translate. * * @return The code for that header name, or -1 if no code exists. */ protected int headerNameToSc(String name) { switch(name.charAt(0)) { case 'c': case 'C': if(name.equalsIgnoreCase("Content-Type")) { return SC_RESP_CONTENT_TYPE; } else if(name.equalsIgnoreCase("Content-Language")) { return SC_RESP_CONTENT_LANGUAGE; } else if(name.equalsIgnoreCase("Content-Length")) { return SC_RESP_CONTENT_LENGTH; } break; case 'd': case 'D': if(name.equalsIgnoreCase("Date")) { return SC_RESP_DATE; } break; case 'l': case 'L': if(name.equalsIgnoreCase("Last-Modified")) { return SC_RESP_LAST_MODIFIED; } else if(name.equalsIgnoreCase("Location")) { return SC_RESP_LOCATION; } break; case 's': case 'S': if(name.equalsIgnoreCase("Set-Cookie")) { return SC_RESP_SET_COOKIE; } else if(name.equalsIgnoreCase("Set-Cookie2")) { return SC_RESP_SET_COOKIE2; } break; case 'w': case 'W': if(name.equalsIgnoreCase("WWW-Authenticate")) { return SC_RESP_WWW_AUTHENTICATE; } break; } return -1; } /** * Signal the web server that the servlet has finished handling this * request, and that the connection can be reused. */ public void finish() throws IOException { if (debug > 0) { logger.log("finish()"); } outBuf.reset(); outBuf.appendByte(JK_AJP13_END_RESPONSE); outBuf.appendBool(true); // Reuse this connection outBuf.end(); send(outBuf); } /** * Send a chunk of response body data to the web server and on to the * browser. * * @param b A huffer of bytes to send. * @param off The offset into the buffer from which to start sending. * @param len The number of bytes to send. */ public void doWrite(byte b[], int off, int len) throws IOException { if (debug > 0) { logger.log("doWrite(byte[], " + off + ", " + len + ")"); } int sent = 0; while(sent < len) { int to_send = len - sent; to_send = to_send > MAX_SEND_SIZE ? MAX_SEND_SIZE : to_send; outBuf.reset(); outBuf.appendByte(JK_AJP13_SEND_BODY_CHUNK); outBuf.appendBytes(b, off + sent, to_send); send(outBuf); sent += to_send; } } // ========= Internal Packet-Handling Methods ================= /** * Read in a packet from the web server and store it in the passed-in * <CODE>Ajp13Packet</CODE> object. * * @param msg The object into which to store the incoming packet -- any * current contents will be overwritten. * * @return The number of bytes read on a successful read or -1 if there * was an error. **/ private int receive(Ajp13Packet msg) throws IOException { if (debug > 0) { logger.log("receive()"); } // XXX If the length in the packet header doesn't agree with the // actual number of bytes read, it should probably return an error // value. Also, callers of this method never use the length // returned -- should probably return true/false instead. byte b[] = msg.getBuff(); int rd = in.read( b, 0, H_SIZE ); if(rd <= 0) { logger.log("bad read: " + rd); return rd; } int len = msg.checkIn(); logger.log("receive: len = " + len); // XXX check if enough space - it's assert()-ed !!! int total_read = 0; while (total_read < len) { rd = in.read( b, 4 + total_read, len - total_read); if (rd == -1) { System.out.println( "Incomplete read, deal with it " + len + " " + rd); break; // XXX log // XXX Return an error code? } total_read += rd; } logger.log("receive: total read = " + total_read); return total_read; } /** * Send a packet to the web server. Works for any type of message. * * @param msg A packet with accumulated data to send to the server -- * this method will write out the length in the header. */ private void send( Ajp13Packet msg ) throws IOException { if (debug > 0) { logger.log("send()"); } msg.end(); // Write the packet header byte b[] = msg.getBuff(); int len = msg.getLen(); if (debug > 0) { logger.log("sending msg, len = " + len); } out.write( b, 0, len ); } /** * Close the socket connection to the web server. In general, sockets * are maintained across many requests, so this will not be called * after finish(). * * @see Ajp13Interceptor#processConnection */ public void close() throws IOException { if (debug > 0) { logger.log("close()"); } if(null != out) { out.close(); } if(null !=in) { in.close(); } } } 1.1 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/AjpRequest.java Index: AjpRequest.java =================================================================== /* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. 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. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR * ITS 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. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.ajp; import java.io.PrintWriter; import java.io.StringWriter; import java.util.HashMap; import java.util.LinkedList; import java.util.Iterator; public class AjpRequest { public static final String SCHEME_HTTP = "http"; public static final String SCHEME_HTTPS = "https"; private final static Iterator emptyItr = new LinkedList().iterator(); MessageBytes method = new MessageBytes(); MessageBytes protocol = new MessageBytes(); MessageBytes requestURI = new MessageBytes(); MessageBytes remoteAddr = new MessageBytes(); MessageBytes remoteHost = new MessageBytes(); MessageBytes serverName = new MessageBytes(); int serverPort = 80; MessageBytes remoteUser = new MessageBytes(); MessageBytes authType = new MessageBytes(); MessageBytes queryString = new MessageBytes(); MessageBytes jvmRoute = new MessageBytes(); String scheme = SCHEME_HTTP; boolean secure = false; int contentLength = 0; MessageBytes contentType = new MessageBytes(); HashMap headers = new HashMap(); HashMap attributes = new HashMap(); LinkedList cookies = new LinkedList(); /** * recylce this Request */ public void recycle() { method.recycle(); protocol.recycle(); requestURI.recycle(); remoteAddr.recycle(); remoteHost.recycle(); serverName.recycle(); serverPort = 80; remoteUser.recycle(); authType.recycle(); queryString.recycle(); jvmRoute.recycle(); scheme = SCHEME_HTTP; secure = false; contentLength = 0; contentType.recycle(); headers.clear(); attributes.clear(); cookies.clear(); } public MessageBytes getMethod() { return method; } public MessageBytes getProtocol() { return protocol; } public MessageBytes getRequestURI() { return requestURI; } public MessageBytes getRemoteAddr() { return remoteAddr; } public MessageBytes getRemoteHost() { return remoteHost; } public MessageBytes getServerName() { return serverName; } public int getServerPort() { return serverPort; } public MessageBytes getRemoteUser() { return remoteUser; } public MessageBytes getAuthType() { return authType; } public MessageBytes getQueryString() { return queryString; } public MessageBytes getJvmRoute() { return jvmRoute; } public String getScheme() { return scheme; } public boolean getSecure() { return secure; } public int getContentLength() { return contentLength; } public MessageBytes getContentType() { return contentType; } public void addHeader(String name, MessageBytes value) { if (name == null || value == null) { return; } String lname = name.toLowerCase(); LinkedList values = (LinkedList)headers.get(lname); if (values == null) { values = new LinkedList(); headers.put(lname, values); } values.add(value); } public Iterator getHeaders(String name) { if (name == null) { return emptyItr; } String lname = name.toLowerCase(); LinkedList values = (LinkedList)headers.get(lname); if (values == null) { return emptyItr; } return values.iterator(); } public Iterator getHeaderNames() { return headers.keySet().iterator(); } public void addCookies(MessageBytes cookies) { this.cookies.add(cookies); } public Iterator getCookies() { return cookies.iterator(); } public void setAttribute(String name, Object value) { if (name == null || value == null) { return; } attributes.put(name, value); } public Object getAttribute(String name) { if (name == null) { return null; } return attributes.get(name); } public Iterator getAttributeNames() { return attributes.keySet().iterator(); } /** * ** SLOW ** for debugging only! */ public String toString() { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.println("=== AjpRequest ==="); pw.println("method = " + method.toString()); pw.println("protocol = " + protocol.toString()); pw.println("requestURI = " + requestURI.toString()); pw.println("remoteAddr = " + remoteAddr.toString()); pw.println("remoteHost = " + remoteHost.toString()); pw.println("serverName = " + serverName.toString()); pw.println("serverPort = " + serverPort); pw.println("remoteUser = " + remoteUser.toString()); pw.println("authType = " + authType.toString()); pw.println("queryString = " + queryString.toString()); pw.println("jvmRoute = " + jvmRoute.toString()); pw.println("scheme = " + scheme.toString()); pw.println("secure = " + secure); pw.println("contentLength = " + contentLength); pw.println("contentType = " + contentType); pw.println("attributes = " + attributes.toString()); pw.println("headers = " + headers.toString()); pw.println("cookies = " + cookies.toString()); return sw.toString(); } } 1.1 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/MessageBytes.java Index: MessageBytes.java =================================================================== /* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. 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. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR * ITS 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. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.ajp; import java.io.UnsupportedEncodingException; /** * a cheap rip-off of MessageBytes from tomcat 3 */ public class MessageBytes { private static final String DEFAULT_ENCODING = "ISO-8859-1"; private int off; private int len; private byte[] bytes; private String str; private boolean gotStr; private String enc; /** * creates and uninitialized MessageBytes object */ public MessageBytes() { recycle(); } /** * recycles this object. */ public void recycle() { off = 0; len = 0; bytes = null; str = null; gotStr = false; enc = DEFAULT_ENCODING; } public void setBytes(byte[] bytes, int off, int len) { recycle(); this.bytes = bytes; this.off = off; this.len = len; } public byte[] getBytes() { return bytes; } public int getOffset() { return off; } public int getLength() { return len; } public void setEncoding(String enc) { this.enc = enc; } public String getEncoding() { return enc; } public void setString(String str) { this.str = str; gotStr = true; } public String getString() throws UnsupportedEncodingException { if (!gotStr) { if (bytes == null || len == 0) { setString(null); } else { setString(new String(bytes, off, len, enc)); } } return str; } public String toString() { try { return getString(); } catch (UnsupportedEncodingException e) { throw new RuntimeException("root cause: " + e.toString()); } } } 1.1 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/test/TestAjp13.java Index: TestAjp13.java =================================================================== package org.apache.ajp.test; import org.apache.ajp.*; import java.io.*; import java.net.*; import java.util.*; public class TestAjp13 { public static void main(String[] args) throws Exception { ServerSocket server = new ServerSocket(8009); System.out.println("TestAjp13 running..."); Socket socket = server.accept(); Ajp13 ajp13 = new Ajp13(); AjpRequest request = new AjpRequest(); ajp13.setSocket(socket); boolean moreRequests = true; while (moreRequests) { int status = 0; try { status = ajp13.receiveNextRequest(request); } catch (IOException e) { System.out.println("process: ajp13.receiveNextRequest -> " + e); } if( status==-2) { // special case - shutdown // XXX need better communication, refactor it // if( !doShutdown(socket.getLocalAddress(), // socket.getInetAddress())) { // moreRequests = false; // continue; // } break; } if( status != 200 ) break; System.out.println(request); String message = "<html><body><pre>" + "hello from ajp13: " + System.getProperty("line.separator") + request.toString() + "</pre></body></html>"; ajp13.beginSendHeaders(200, "OK", 3); ajp13.sendHeader("content-type", "text/html"); ajp13.sendHeader("content-length", String.valueOf(message.length())); ajp13.sendHeader("my-header", "my value"); ajp13.endSendHeaders(); byte[] b = message.getBytes(); ajp13.doWrite(b, 0, b.length); ajp13.finish(); request.recycle(); } try { ajp13.close(); } catch (IOException e) { System.out.println("process: ajp13.close ->" + e); } try { socket.close(); } catch (IOException e) { System.out.println("process: socket.close ->" + e); } socket = null; System.out.println("process: done"); } } 1.3 +5 -5 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Connector.java Index: Ajp13Connector.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Connector.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- Ajp13Connector.java 2001/05/12 06:16:39 1.2 +++ Ajp13Connector.java 2001/05/15 15:22:42 1.3 @@ -1,7 +1,7 @@ /* - * $Header: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Connector.java,v 1.2 2001/05/12 06:16:39 seguin Exp $ - * $Revision: 1.2 $ - * $Date: 2001/05/12 06:16:39 $ + * $Header: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Connector.java,v 1.3 2001/05/15 15:22:42 seguin Exp $ + * $Revision: 1.3 $ + * $Date: 2001/05/15 15:22:42 $ * * ==================================================================== * @@ -92,7 +92,7 @@ * Implementation of an Ajp13 connector. * * @author Kevin Seguin - * @version $Revision: 1.2 $ $Date: 2001/05/12 06:16:39 $ + * @version $Revision: 1.3 $ $Date: 2001/05/15 15:22:42 $ */ @@ -620,7 +620,7 @@ */ public Request createRequest() { - Ajp13Request request = new Ajp13Request(); + Ajp13Request request = new Ajp13Request(this); request.setConnector(this); return (request); 1.2 +2 -0 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13InputStream.java Index: Ajp13InputStream.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13InputStream.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- Ajp13InputStream.java 2001/05/12 05:52:40 1.1 +++ Ajp13InputStream.java 2001/05/15 15:22:44 1.2 @@ -63,6 +63,8 @@ import javax.servlet.ServletInputStream; +import org.apache.ajp.Ajp13; + public class Ajp13InputStream extends ServletInputStream { private Ajp13 ajp13; 1.2 +2 -0 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13OutputStream.java Index: Ajp13OutputStream.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13OutputStream.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- Ajp13OutputStream.java 2001/05/12 05:52:40 1.1 +++ Ajp13OutputStream.java 2001/05/15 15:22:45 1.2 @@ -60,6 +60,8 @@ import java.io.*; +import org.apache.ajp.Ajp13; + public class Ajp13OutputStream extends OutputStream { private Ajp13 ajp13; 1.2 +11 -8 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Processor.java Index: Ajp13Processor.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Processor.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- Ajp13Processor.java 2001/05/12 05:52:41 1.1 +++ Ajp13Processor.java 2001/05/15 15:22:47 1.2 @@ -1,7 +1,7 @@ /* - * $Header: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Processor.java,v 1.1 2001/05/12 05:52:41 seguin Exp $ - * $Revision: 1.1 $ - * $Date: 2001/05/12 05:52:41 $ + * $Header: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Processor.java,v 1.2 2001/05/15 15:22:47 seguin Exp $ + * $Revision: 1.2 $ + * $Date: 2001/05/15 15:22:47 $ * * ==================================================================== * @@ -97,10 +97,12 @@ import org.apache.catalina.util.StringManager; import org.apache.catalina.util.StringParser; +import org.apache.ajp.Ajp13; +import org.apache.ajp.AjpRequest; /** * @author Kevin Seguin - * @version $Revision: 1.1 $ $Date: 2001/05/12 05:52:41 $ + * @version $Revision: 1.2 $ $Date: 2001/05/15 15:22:47 $ */ final class Ajp13Processor @@ -138,8 +140,8 @@ // ----------------------------------------------------- Instance Variables private Ajp13Logger logger = new Ajp13Logger(); + private AjpRequest ajpRequest = new AjpRequest(); - /** * Is there a new socket available? */ @@ -317,10 +319,9 @@ */ private void process(Socket socket) { - Ajp13 ajp13 = new Ajp13(connector, id); + Ajp13 ajp13 = new Ajp13(); Ajp13InputStream input = new Ajp13InputStream(ajp13); Ajp13OutputStream output = new Ajp13OutputStream(ajp13); - request.setAjp13(ajp13); response.setAjp13(ajp13); try { @@ -334,7 +335,7 @@ int status = 0; try { - status = ajp13.receiveNextRequest(request); + status = ajp13.receiveNextRequest(ajpRequest); } catch (IOException e) { logger.log("process: ajp13.receiveNextRequest", e); } @@ -355,6 +356,7 @@ try { // set up request + request.setAjpRequest(ajpRequest); request.setResponse(response); request.setStream(input); @@ -375,6 +377,7 @@ } // Recycling the request and the response objects + ajpRequest.recycle(); request.recycle(); response.recycle(); 1.2 +174 -5 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Request.java Index: Ajp13Request.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Request.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- Ajp13Request.java 2001/05/12 05:52:41 1.1 +++ Ajp13Request.java 2001/05/15 15:22:50 1.2 @@ -59,23 +59,192 @@ package org.apache.ajp.tomcat4; import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import java.util.List; +import java.util.Iterator; + import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Cookie; import org.apache.catalina.connector.HttpRequestBase; +import org.apache.catalina.Globals; +import org.apache.catalina.util.RequestUtil; + +import org.apache.ajp.AjpRequest; +import org.apache.ajp.MessageBytes; public class Ajp13Request extends HttpRequestBase { + + private static final String match = + ";" + Globals.SESSION_PARAMETER_NAME + "="; + + private static int id = 1; + + private Ajp13Logger logger = new Ajp13Logger(); + private int debug; + + public Ajp13Request(Ajp13Connector connector) { + super(); + this.debug = connector.getDebug(); + this.logger.setConnector(connector); + this.logger.setName("Ajp13Request[" + (id++) + "]"); + } + + public void recycle() { + super.recycle(); + } + + void setAjpRequest(AjpRequest ajp) throws UnsupportedEncodingException { + // XXX make this guy wrap AjpRequest so + // we're more efficient (that's the whole point of + // all of the MessageBytes in AjpRequest) + + setMethod(ajp.getMethod().getString()); + setProtocol(ajp.getProtocol().getString()); + setRequestURI(ajp.getRequestURI().getString()); + setRemoteAddr(ajp.getRemoteAddr().getString()); + setRemoteHost(ajp.getRemoteHost().getString()); + setServerName(ajp.getServerName().getString()); + setServerPort(ajp.getServerPort()); + + String remoteUser = ajp.getRemoteUser().getString(); + if (remoteUser != null) { + setUserPrincipal(new Ajp13Principal(remoteUser)); + } + + setAuthType(ajp.getAuthType().getString()); + setQueryString(ajp.getQueryString().getString()); + setScheme(ajp.getScheme()); + setSecure(ajp.getSecure()); + setContentLength(ajp.getContentLength()); + + String contentType = ajp.getContentType().getString(); + if (contentType != null) { + setContentType(contentType); + } + + Iterator itr = ajp.getHeaderNames(); + while (itr.hasNext()) { + String name = (String)itr.next(); + Iterator itr2 = ajp.getHeaders(name); + while (itr2.hasNext()) { + MessageBytes value = (MessageBytes)itr2.next(); + addHeader(name, value.getString()); + } + } + + itr = ajp.getAttributeNames(); + while (itr.hasNext()) { + String name = (String)itr.next(); + setAttribute(name, ajp.getAttribute(name)); + } + + itr = ajp.getCookies(); + while (itr.hasNext()) { + MessageBytes cookies = (MessageBytes)itr.next(); + addCookies(cookies.getString()); + } + } - private Ajp13 ajp13; +// public Object getAttribute(String name) { +// return ajp.getAttribute(name); +// } + +// public Enumeration getAttributeNames() { +// return new Enumerator(ajp.getAttributeNames()); +// } + + public void setRequestURI(String uri) { + int semicolon = uri.indexOf(match); + if (semicolon >= 0) { + String rest = uri.substring(semicolon + match.length()); + int semicolon2 = rest.indexOf(";"); + if (semicolon2 >= 0) { + setRequestedSessionId(rest.substring(0, semicolon2)); + rest = rest.substring(semicolon2); + } else { + setRequestedSessionId(rest); + rest = ""; + } + setRequestedSessionURL(true); + uri = uri.substring(0, semicolon) + rest; + if (debug >= 1) + logger.log(" Requested URL session id is " + + ((HttpServletRequest) getRequest()) + .getRequestedSessionId()); + } else { + setRequestedSessionId(null); + setRequestedSessionURL(false); + } - void setAjp13(Ajp13 ajp13) { - this.ajp13 = ajp13; + super.setRequestURI(uri); } - Ajp13 getAjp13() { - return this.ajp13; + private void addCookies(String cookiesHeader) { + Cookie cookies[] = RequestUtil.parseCookieHeader(cookiesHeader); + for (int j = 0; j < cookies.length; j++) { + Cookie cookie = cookies[j]; + if (cookie.getName().equals(Globals.SESSION_COOKIE_NAME)) { + // Override anything requested in the URL + if (!isRequestedSessionIdFromCookie()) { + // Accept only the first session id cookie + setRequestedSessionId(cookie.getValue()); + setRequestedSessionCookie(true); + setRequestedSessionURL(false); + if (debug > 0) { + logger.log(" Requested cookie session id is " + + ((HttpServletRequest) getRequest()) + .getRequestedSessionId()); + } + } + } + if (debug > 0) { + logger.log(" Adding cookie " + cookie.getName() + "=" + + cookie.getValue()); + } + addCookie(cookie); + } } public ServletInputStream createInputStream() throws IOException { return (ServletInputStream)getStream(); + } +} + +class Ajp13Principal implements java.security.Principal { + String user; + + Ajp13Principal(String user) { + this.user = user; + } + public boolean equals(Object o) { + if (o == null) { + return false; + } else if (!(o instanceof Ajp13Principal)) { + return false; + } else if (o == this) { + return true; + } else if (this.user == null && ((Ajp13Principal)o).user == null) { + return true; + } else if (user != null) { + return user.equals( ((Ajp13Principal)o).user); + } else { + return false; + } + } + + public String getName() { + return user; + } + + public int hashCode() { + if (user == null) return 0; + else return user.hashCode(); + } + + public String toString() { + return getName(); } } 1.2 +49 -6 jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Response.java Index: Ajp13Response.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4/Ajp13Response.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- Ajp13Response.java 2001/05/12 05:52:41 1.1 +++ Ajp13Response.java 2001/05/15 15:22:51 1.2 @@ -70,12 +70,15 @@ import org.apache.catalina.Globals; import org.apache.catalina.util.CookieTools; +import org.apache.ajp.Ajp13; + public class Ajp13Response extends HttpResponseBase { private Ajp13 ajp13; private boolean finished = false; private boolean headersSent = false; - + private StringBuffer cookieValue = new StringBuffer(); + String getStatusMessage() { return getStatusMessage(getStatus()); } @@ -94,12 +97,14 @@ } headersSent = true; + int numHeaders = 0; + if (getContentType() != null) { - addHeader("Content-Type", getContentType()); + numHeaders++; } if (getContentLength() >= 0) { - addIntHeader("Content-Length", getContentLength()); + numHeaders++; } // Add the session ID cookie if necessary @@ -128,15 +133,53 @@ Iterator items = cookies.iterator(); while (items.hasNext()) { Cookie cookie = (Cookie) items.next(); + + cookieValue.delete(0, cookieValue.length()); + CookieTools.getCookieHeaderValue(cookie, cookieValue); + addHeader(CookieTools.getCookieHeaderName(cookie), - CookieTools.getCookieHeaderValue(cookie)); + cookieValue.toString()); } } + // figure out how many headers... + // can have multiple headers of the same name... + // need to loop through headers once to get total + // count, once to add header to outBuf + String[] hnames = getHeaderNames(); + Object[] hvalues = new Object[hnames.length]; + + int i; + for (i = 0; i < hnames.length; ++i) { + String[] tmp = getHeaderValues(hnames[i]); + numHeaders += tmp.length; + hvalues[i] = tmp; + } + + ajp13.beginSendHeaders(getStatus(), getStatusMessage(getStatus()), numHeaders); + + // send each header + if (getContentType() != null) { + ajp13.sendHeader("Content-Type", getContentType()); + } + + if (getContentLength() >= 0) { + ajp13.sendHeader("Content-Length", String.valueOf(getContentLength())); + } + + for (i = 0; i < hnames.length; ++i) { + String name = hnames[i]; + String[] values = (String[])hvalues[i]; + + for (int j = 0; j < values.length; ++j) { + ajp13.sendHeader(name, values[j]); + } + } + + ajp13.endSendHeaders(); + // The response is now committed committed = true; - - this.ajp13.sendHeaders(this); } public void finishResponse() throws IOException {