You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by se...@apache.org on 2001/05/15 17:23:07 UTC

cvs commit: jakarta-tomcat-connectors/jk/src/java/org/apache/ajp/tomcat4 Ajp13Connector.java Ajp13InputStream.java Ajp13OutputStream.java Ajp13Processor.java Ajp13Request.java Ajp13Response.java Ajp13.java

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 apache@apache.org.
   *
   * 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 [danmil@shore.net]
   * @author Keith Wannamaker [Keith@Wannamaker.org]
   * @author Kevin Seguin [seguin@motive.com]
   */
  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 apache@apache.org.
   *
   * 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 apache@apache.org.
   *
   * 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 {