blob: 3f38c260332b411ffe60d29e8efaf06de8fa44a4 [file] [log] [blame]
/*
* 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.<P>
*
* This class contains knowledge about how an individual packet is laid out
* (via the <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@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
* <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.
**/
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();
}