alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*   
 *  Copyright 1999-2004 The Apache Sofware Foundation.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.tomcat.modules.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

import org.apache.tomcat.core.OutputBuffer;
import org.apache.tomcat.core.Request;
import org.apache.tomcat.util.aaa.SimplePrincipal;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.compat.Jdk11Compat;
import org.apache.tomcat.util.http.HttpMessages;
import org.apache.tomcat.util.http.MimeHeaders;

/* Frozen, bug fixes only: all active development goes in
     jakarta-tomcat-connectors/jk/org/apache/ajp/Ajp14*
*/

/**
 * 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.

* * This class contains knowledge about how an individual packet is laid out * (via the internal Ajp13Packet 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] */ 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; public static final byte JK_AJP13_PING_REQUEST = 8; // Error code for Ajp13 public static final int JK_AJP13_BAD_HEADER = -100; public static final int JK_AJP13_NO_HEADER = -101; public static final int JK_AJP13_COMM_CLOSED = -102; public static final int JK_AJP13_COMM_BROKEN = -103; public static final int JK_AJP13_BAD_BODY = -104; public static final int JK_AJP13_INCOMPLETE_BODY = -105; // 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; public static final byte JK_AJP13_PONG_REPLY = 9; // 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; public static final byte SC_A_SECRET = 12; // 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", "REPORT", "VERSION-CONTROL", "CHECKIN", "CHECKOUT", "UNCHECKOUT", "SEARCH" }; // 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 ==================== static Jdk11Compat jdk11Compat=Jdk11Compat.getJdkCompat(); OutputStream out; InputStream in; int dL=0; // Buffer used of output body and headers OutputBuffer headersWriter=new OutputBuffer(MAX_PACKET_SIZE); Ajp13Packet outBuf = new Ajp13Packet( headersWriter ); // 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 boolean end_of_stream; // true if we've received an empty packet String secret=null; // True to ignore HTTP server auth private boolean tomcatAuthentication=true; public Ajp13() { super(); } public void recycle() { // This is a touch cargo-cultish, but I think wise. blen = 0; pos = 0; end_of_stream = false; if( dL>0 ) d( "recycle()"); headersWriter.recycle(); } public boolean isTomcatAuthentication() { return tomcatAuthentication; } public void setTomcatAuthentication(boolean newTomcatAuthentication) { tomcatAuthentication = newTomcatAuthentication; } /** * Associate an open socket with this instance. */ public void setSocket( Socket socket ) throws IOException { socket.setSoLinger( true, 100); out = socket.getOutputStream(); in = socket.getInputStream(); pos = 0; } public String getSecret() { return secret; } /** * Read a new packet from the web server and decode it. If it's a * forwarded request, store its properties in the passed-in Request * 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, 999 if request * is a low level request which has been processed by low layer and * -2 if the server is asking the container to shut itself down. * */ public int receiveNextRequest(Request req) throws IOException { // XXX The return values are awful. int err = 0; // if we receive an IOException here, it must be because // the remote just closed the ajp13 connection, and it's not // an error, we just need to close the AJP13 connection try { err = receive(hBuf); } catch (IOException ioe) { return -1; // Indicate it's a disconnection from the remote end } // if any error, just drop the ajp13 connection 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: if( hBuf.getLen() > 3 ) { // we have a secret secret=hBuf.getString(); } return -2; case JK_AJP13_PING_REQUEST: return sendPong(); } return 200; // XXX This is actually an error condition } /** * Send a PONG REPLY to web server to its PING request * * @param ch the Ajp13 channel * @param outBuf the Ajp13Packet output packet to use */ private int sendPong() { outBuf.reset(); outBuf.appendByte(JK_AJP13_PONG_REPLY); try { send(outBuf); } catch (IOException ioe) { d("can't send pong reply"); } return (999); // success but no need to process farther } /** * 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( Request req, Ajp13Packet msg ) throws IOException { // XXX Awful return values boolean isSSL = false; // 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.setServerPort( msg.getInt()); isSSL = msg.getBool(); // Decode headers MimeHeaders headers = req.getMimeHeaders(); int hCount = msg.getInt(); for(int i = 0 ; i < hCount ; i++) { String hName = null; // 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; MessageBytes vMB=null; isc &= 0xFF00; if(0xA000 == isc) { msg.getInt(); // To advance the read position hName = headerTransArray[hId - 1]; vMB= headers.addValue( hName ); } else { // XXX Not very elegant vMB=msg.addHeader( headers ); if( vMB==null) return 500; // wrong packet } msg.getMessageBytes( vMB ); } byte attributeCode; for(attributeCode = msg.getByte() ; attributeCode != SC_A_ARE_DONE ; attributeCode = msg.getByte()) { switch(attributeCode) { case SC_A_CONTEXT : // contextPath = msg.getString(); break; case SC_A_SERVLET_PATH : //log("SC_A_SERVLET_PATH not in use " + msg.getString()); break; case SC_A_REMOTE_USER : if (isTomcatAuthentication()) { // Ignore auth done by HTTP Server msg.getString(); } else { // Honor auth done by HTTP Server req.setRemoteUser( msg.getString()); // XXX recycle ? // Note that roles are not integrated with apache req.setUserPrincipal( new SimplePrincipal( req.getRemoteUser() )); } break; case SC_A_AUTH_TYPE : req.setAuthType( msg.getString()); break; case SC_A_QUERY_STRING : msg.getMessageBytes( req.queryString()); break; case SC_A_JVM_ROUTE : req.setJvmRoute(msg.getString()); break; case SC_A_SSL_CERT : isSSL = true; // Transform the string into certificate. String certString = msg.getString(); byte[] certData = certString.getBytes(); try { Object jsseCerts=jdk11Compat.getX509Certificates(certData); req.setAttribute("javax.servlet.request.X509Certificate", jsseCerts); } catch( Exception e) { d("Certificate convertion failed" + e ); // Save it at least as string... JDK1.1 doesn't // have X509Certificate class req.setAttribute("javax.servlet.request.X509Certificate", certString); } break; case SC_A_SECRET : String s=msg.getString(); if( s!=null ) secret=s; 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: // Ignore. Assume a single-string value - we shouldn't // allow anything else. msg.getString(); break; } } if(isSSL) { req.scheme().setString("https"); } // Check to see if there should be a body packet coming along // immediately after MessageBytes clB=headers.getValue("content-length"); int contentLength = (clB==null) ? -1 : clB.getInt(); if( dL > 0 ) d("Content-Length: " + contentLength ); if(contentLength != 0) { req.setContentLength( contentLength ); /* Read present data */ int err = receive(inBuf); if(err < 0) { return 500; } // We may get an empty packet ( no data available right now ) pos = 0; blen=0; if( inBuf.getLen() != 0 ) { blen = inBuf.peekInt(); int cpl=inBuf.getBytes(bodyBuff); if( dL > 0 ) d( "Copy into body buffer " + bodyBuff + " " + cpl + " " + blen + " " + new String( bodyBuff, 0, cpl )); } } return 200; // Success } // ==================== Servlet Input Support ================= /** * Return the next byte of request body data (to a servlet). * * @see Ajp13Request#doRead */ public int doRead() throws IOException { if(pos >= blen) { if( ! refillReadBuffer()) { return -1; } } return bodyBuff[pos++] & 0xFF; // prevent sign extension of byte value } /** * 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 Ajp13Request#doRead */ public int doRead(byte[] b, int off, int len) throws IOException { 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); if( dL > 0 ) d("doRead1: " + pos + " " + len + " " + blen + " " + new String( b, off, len ) + " " + Thread.currentThread()); pos += len; return len; } // Not enough data (blen < pos + len) or chunked encoded 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); if( dL > 0 ) d("doRead2: " + pos + " " + len + " " + blen + " " + c + " " + new String( b, off, c ) + " " + new String( bodyBuff, pos, 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 the server returns an empty packet, assume that that end of // the stream has been reached (yuck -- fix protocol??). if (end_of_stream) { return false; } // Why not use outBuf?? inBuf.reset(); inBuf.appendByte(JK_AJP13_GET_BODY_CHUNK); inBuf.appendInt(MAX_READ_SIZE); if( dL>0 ) d("refillReadBuffer " + Thread.currentThread()); send(inBuf); int err = receive(inBuf); if(err < 0) { throw new IOException(); } // No data received. if( inBuf.getLen() == 0 ) { pos=0; blen=0; end_of_stream = true; return false; } blen = inBuf.peekInt(); pos = 0; int cpl=inBuf.getBytes(bodyBuff); if( dL > 0 ) d( "Copy into body buffer2 " + bodyBuff + " " + cpl + " " + blen + " " + new String( bodyBuff, 0, cpl )); return (blen > 0); } // ==================== Servlet Output Support ================= /** * Send the HTTP headers back to the web server and on to the browser. * * @param status The HTTP status code to send. * @param headers The set of all headers. */ public void sendHeaders(int status, MimeHeaders headers) throws IOException { // XXX if more headers that MAX_SIZE, send 2 packets! outBuf.reset(); outBuf.appendByte(JK_AJP13_SEND_HEADERS); outBuf.appendInt(status); outBuf.appendString(HttpMessages.getMessage( status )); int numHeaders = headers.size(); outBuf.appendInt(numHeaders); for( int i=0 ; i < numHeaders ; i++ ) { String headerName = headers.getName(i).toString(); int sc = headerNameToSc(headerName); if(-1 != sc) { outBuf.appendInt(sc); } else { outBuf.appendString(headerName); } outBuf.appendString(headers.getValue(i).toString() ); } 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 { 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 { 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 N bytes from the InputStream, and ensure we got them all * Under heavy load we could experience many fragmented packets * just read Unix Network Programming to recall that a call to * read didn't ensure you got all the data you want * * from read() Linux manual * * On success, the number of bytes read is returned (zero indicates end of file), * and the file position is advanced by this number. * It is not an error if this number is smaller than the number of bytes requested; * this may happen for example because fewer bytes * are actually available right now (maybe because we were close to end-of-file, * or because we are reading from a pipe, or from a * terminal), or because read() was interrupted by a signal. * On error, -1 is returned, and errno is set appropriately. In this * case it is left unspecified whether the file position (if any) changes. * **/ private int readN(InputStream in, byte[] b, int offset, int len) throws IOException { int pos = 0; int got; while(pos < len) { got = in.read(b, pos + offset, len - pos); if (dL > 10) d("read got # " + got); // connection just closed by remote if (got == 0) return JK_AJP13_COMM_CLOSED; // connection dropped by remote if (got < 0) return JK_AJP13_COMM_BROKEN; pos += got; } return pos; } /** * Read in a packet from the web server and store it in the passed-in * Ajp13Packet 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 { // 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 = readN(in, b, 0, H_SIZE ); // XXX - connection closed (JK_AJP13_COMM_CLOSED) // - connection broken (JK_AJP13_COMM_BROKEN) // if(rd < 0) { // if (rd != JK_AJP13_COMM_CLOSED) // d("can't read header: " + rd); return rd; } int len = msg.checkIn(); // XXX - check if we received a non AJP13 packet if (len < 0) { return JK_AJP13_BAD_HEADER; } // XXX check if enough space - it's assert()-ed !!! int total_read = 0; total_read = readN(in, b, H_SIZE, len); if (total_read < 0) { d("can't read body, waited #" + len); return JK_AJP13_BAD_BODY; } if (total_read != len) { d( "incomplete read, waited #" + len + " got only " + total_read); return JK_AJP13_INCOMPLETE_BODY; } if( dL>0 ) msg.dump("Ajp13.receive() " + rd + " " + len ); 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 { msg.end(); // Write the packet header byte b[] = msg.getBuff(); int len = msg.getLen(); out.write( b, 0, len ); if( dL>0 ) msg.dump("Ajp13.send()"); } /** * 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(null != out) { out.close(); } if(null !=in) { in.close(); } } public void setDebug(int i) { dL = i; } private void d(String s ) { System.err.println( "Ajp13: " + s ); } }

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.