|
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 Software 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.ajp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import org.apache.tomcat.util.http.BaseRequest;
import org.apache.tomcat.util.http.HttpMessages;
import org.apache.tomcat.util.http.MimeHeaders;
/**
* 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 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]
* @author Kevin Seguin [seguin@apache.org]
* @author Henri Gomez [hgomez@apache.org]
* @author Costin Manolache
*/
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;
// 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;
// ============ Instance Properties ====================
OutputStream out;
InputStream in;
// Buffer used of output body and headers
public 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
boolean end_of_stream; // true if we've received an empty packet
// Required handler - essential request processing
public RequestHandler reqHandler;
// AJP14 - detect protocol,set communication parameters, login
// If no password is set, use only Ajp13 messaging
boolean backwardCompat=true;
boolean logged=false;
String secret=null;
public Ajp13() {
super();
initBuf();
reqHandler=new RequestHandler();
reqHandler.init( this );
}
public Ajp13(RequestHandler reqHandler ) {
super();
initBuf();
this.reqHandler=reqHandler;
reqHandler.init( this );
}
/** Will be overriden
*/
public void initBuf() {
outBuf = new Ajp13Packet( MAX_PACKET_SIZE );
inBuf = new Ajp13Packet( MAX_PACKET_SIZE );
hBuf=new Ajp13Packet( MAX_PACKET_SIZE );
}
public void recycle() {
if (debug > 0) {
logger.log("recycle()");
}
// This is a touch cargo-cultish, but I think wise.
blen = 0;
pos = 0;
end_of_stream = false;
logged=false;
}
/**
* 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;
}
/**
* Backward compat mode, no login needed
*/
public void setBackward(boolean b)
{
backwardCompat=b;
}
public boolean isLogged() {
return logged;
}
void setLogged( boolean b ) {
logged=b;
}
public void setSecret( String s ) {
secret=s;
}
public String getSecret() {
return secret;
}
// -------------------- Handlers registry --------------------
static final int MAX_HANDLERS=32;
static final int RESERVED=16; // reserved names, backward compat
// Note that we don't make distinction between in and out
// messages ( i.e. one id is used only in one direction )
AjpHandler handlers[]=new AjpHandler[MAX_HANDLERS];
String handlerName[]=new String[MAX_HANDLERS];
int currentId=RESERVED;
public int registerMessageType( int id, String name, AjpHandler h,
String sig[] )
{
if( id < 0 ) {
// try to find it by name
for( int i=0; i< handlerName.length; i++ )
if( name.equals( handlerName[i] ) ) return i;
handlerName[currentId]=name;
handlers[currentId]=h;
currentId++;
return currentId;
}
// fixed id
handlerName[id]=name;
handlers[id]=h;
return id;
}
// -------------------- Main dispatch --------------------
/**
* 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(BaseRequest req) throws IOException {
if (debug > 0) {
logger.log("receiveNextRequest()");
}
// 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) {
if(debug >0 ) logger.log( "IOException receiving message ");
return -1; // Indicate it's a disconnection from the remote end
}
if(err < 0) {
if(debug >0 ) logger.log( "Error receiving message ");
return 500;
}
int type = (int)hBuf.getByte();
// System.out.println("XXX " + this );
return handleMessage( type, hBuf, req );
}
/** Override for ajp14, temporary
*/
public int handleMessage( int type, Ajp13Packet hBuf, BaseRequest req )
throws IOException
{
if( type > handlers.length ) {
logger.log( "Invalid handler " + type );
return 500;
}
if( debug > 0 )
logger.log( "Received " + type + " " + handlerName[type]);
// Ajp14, unlogged
if( ! backwardCompat && ! isLogged() ) {
if( type != NegociationHandler.JK_AJP14_LOGINIT_CMD &&
type != NegociationHandler.JK_AJP14_LOGCOMP_CMD ) {
logger.log( "Ajp14 error: not logged " +
type + " " + handlerName[type]);
return 300;
}
// else continue
}
// Ajp13 messages
switch(type) {
case RequestHandler.JK_AJP13_FORWARD_REQUEST:
return reqHandler.decodeRequest(this, hBuf, req);
case RequestHandler.JK_AJP13_CPING_REQUEST:
return reqHandler.sendCPong(this, outBuf);
case RequestHandler.JK_AJP13_SHUTDOWN:
return -2;
}
// logged || loging message
AjpHandler handler=handlers[type];
if( handler==null ) {
logger.log( "Unknown message " + type + handlerName[type] );
return 200;
}
if( debug > 0 )
logger.log( "Ajp14 handler " + handler );
return handler.handleAjpMessage( type, this, hBuf, req );
}
// ==================== Servlet Input Support =================
/** @deprecated -- Will use reqHandler, make sure nobody else
calls this */
public int available() throws IOException {
return reqHandler.available(this);
}
public int doRead() throws IOException
{
return reqHandler.doRead( this );
}
public int doRead(byte[] b, int off, int len) throws IOException
{
return reqHandler.doRead( this, b, off, len );
}
private boolean refillReadBuffer() throws IOException
{
return reqHandler.refillReadBuffer(this);
}
public void beginSendHeaders(int status,
String statusMessage,
int numHeaders) throws IOException {
reqHandler.beginSendHeaders( this, outBuf,
status, statusMessage,
numHeaders);
}
public void sendHeader(String name, String value) throws IOException {
reqHandler.sendHeader( outBuf, name, value );
}
public void endSendHeaders() throws IOException {
reqHandler.endSendHeaders(this, outBuf);
}
public void sendHeaders(int status, MimeHeaders headers)
throws IOException
{
reqHandler.sendHeaders(this, outBuf, status,
HttpMessages.getMessage(status),
headers);
}
public void sendHeaders(int status, String statusMessage,
MimeHeaders headers)
throws IOException
{
reqHandler.sendHeaders( this, outBuf, status,
statusMessage, headers );
}
public void finish() throws IOException {
reqHandler.finish(this, outBuf );
}
public void doWrite(byte b[], int off, int len) throws IOException {
reqHandler.doWrite( this, outBuf, b, off, len );
}
// ========= 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 (debug > 10) {
logger.log("read got # " + got);
}
// connection just closed by remote.
if (got <= 0) {
// This happens periodically, as apache restarts
// periodically.
// It should be more gracefull ! - another feature for Ajp14
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.
**/
public 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 = readN(in, b, 0, H_SIZE );
// XXX - connection closed (JK_AJP13_COMM_CLOSED)
// - connection broken (JK_AJP13_COMM_BROKEN)
//
if(rd < 0) {
// Most likely normal apache restart.
return rd;
}
int len = msg.checkIn();
if( debug > 5 )
logger.log( "Received " + rd + " " + len + " " + b[0] );
// XXX check if enough space - it's assert()-ed !!!
int total_read = 0;
total_read = readN(in, b, H_SIZE, len);
// it's ok to have read 0 bytes when len=0 -- this means
// the end of the stream has been reached.
if (total_read < 0) {
logger.log("can't read body, waited #" + len);
return JK_AJP13_BAD_BODY;
}
if (total_read != len) {
logger.log( "incomplete read, waited #" + len +
" got only " + total_read);
return JK_AJP13_INCOMPLETE_BODY;
}
if (debug > 0)
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.
*/
public 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 > 5 )
logger.log("send() " + len + " " + b[0] );
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();
}
setLogged( false ); // no more logged now
}
// -------------------- Debug --------------------
protected int debug = 0;
public void setDebug(int debug) {
this.debug = debug;
this.reqHandler.setDebug(debug);
}
public void setLogger(Logger l) {
this.logger = l;
this.reqHandler.setLogger(l);
}
/**
* XXX place holder...
*/
Logger logger = new Logger();
}
|