| /* |
| * 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. |
| * |
| * @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(). |
| */ |
| 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(); |
| } |