| /* |
| * 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.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.security.cert.CertificateFactory; |
| import java.security.cert.X509Certificate; |
| |
| import org.apache.tomcat.util.buf.ByteChunk; |
| import org.apache.tomcat.util.buf.MessageBytes; |
| import org.apache.tomcat.util.http.BaseRequest; |
| import org.apache.tomcat.util.http.HttpMessages; |
| import org.apache.tomcat.util.http.MimeHeaders; |
| |
| |
| /** |
| * Handle messages related with basic request information. |
| * |
| * This object can handle the following incoming messages: |
| * - "FORWARD_REQUEST" input message ( sent when a request is passed from the web server ) |
| * - "PING REQUEST" input message (sent by the web server to determine if tomcat is not frozen, |
| * a PONG REPLY will be sent back) |
| * - "RECEIVE_BODY_CHUNK" input ( sent by container to pass more body, in response to GET_BODY_CHUNK ) |
| * |
| * It can handle the following outgoing messages: |
| * - SEND_HEADERS. Pass the status code and headers. |
| * - SEND_BODY_CHUNK. Send a chunk of body |
| * - GET_BODY_CHUNK. Request a chunk of body data |
| * - END_RESPONSE. Notify the end of a request processing. |
| * |
| * @author Henri Gomez [hgomez@apache.org] |
| * @author Dan Milstein [danmil@shore.net] |
| * @author Keith Wannamaker [Keith@Wannamaker.org] |
| * @author Costin Manolache |
| */ |
| public class RequestHandler extends AjpHandler |
| { |
| // XXX Will move to a registry system. |
| |
| // 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; |
| public static final byte JK_AJP13_CPING_REQUEST = 10; |
| |
| // 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_CPONG_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_SSL_KEY_SIZE = 11; // ajp14 originally, now in ajp13 with jk 1.2/2.0 |
| public static final byte SC_A_SECRET = 12; |
| public static final byte SC_A_STORED_METHOD = 13; |
| |
| // 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", |
| "MKWORKSPACE", |
| "UPDATE", |
| "LABEL", |
| "MERGE", |
| "BASELINE-CONTROL", |
| "MKACTIVITY" |
| }; |
| public static final int SC_M_JK_STORED = (byte) 0xFF; |
| |
| |
| // 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" |
| }; |
| |
| public RequestHandler() |
| { |
| } |
| |
| public void init( Ajp13 ajp14 ) { |
| // register incoming message handlers |
| ajp14.registerMessageType( JK_AJP13_FORWARD_REQUEST, |
| "JK_AJP13_FORWARD_REQUEST", |
| this, null); // 2 |
| // register outgoing messages handler |
| ajp14.registerMessageType( JK_AJP13_SEND_BODY_CHUNK, // 3 |
| "JK_AJP13_SEND_BODY_CHUNK", |
| this,null ); |
| ajp14.registerMessageType( JK_AJP13_SEND_HEADERS, // 4 |
| "JK_AJP13_SEND_HEADERS", |
| this,null ); |
| ajp14.registerMessageType( JK_AJP13_END_RESPONSE, // 5 |
| "JK_AJP13_END_RESPONSE", |
| this,null ); |
| ajp14.registerMessageType( JK_AJP13_GET_BODY_CHUNK, // 6 |
| "JK_AJP13_GET_BODY_CHUNK", |
| this, null ); |
| ajp14.registerMessageType( JK_AJP13_CPING_REQUEST, |
| "JK_AJP13_PING_REQUEST", |
| this, null); // 10 |
| ajp14.registerMessageType( JK_AJP13_CPONG_REPLY, |
| "JK_AJP13_PONG_REPLY", |
| this, null); // 9 |
| } |
| |
| /** |
| * Send a CPONG REPLY to web server to its CPING request |
| * |
| * @param ch the Ajp13 channel |
| * @param outBuf the Ajp13Packet output packet to use |
| */ |
| public int sendCPong(Ajp13 ch, Ajp13Packet outBuf) |
| { |
| outBuf.reset(); |
| outBuf.appendByte(JK_AJP13_CPONG_REPLY); |
| |
| try |
| { |
| ch.send(outBuf); |
| } |
| catch (IOException ioe) |
| { |
| log("can't send pong reply"); |
| } |
| |
| return (999); // success but no need to process farther |
| } |
| |
| // -------------------- Incoming message -------------------- |
| public int handleAjpMessage( int type, Ajp13 channel, |
| Ajp13Packet ajp, BaseRequest req ) |
| throws IOException |
| { |
| switch( type ) { |
| case RequestHandler.JK_AJP13_FORWARD_REQUEST: |
| return decodeRequest(channel, channel.hBuf, req ); |
| |
| default: |
| return UNKNOWN; |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| protected int decodeRequest(Ajp13 ch, Ajp13Packet msg, BaseRequest req) |
| throws IOException |
| { |
| |
| if (debug > 0) { |
| log("decodeRequest()"); |
| } |
| |
| // XXX Awful return values |
| |
| boolean isSSL = false; |
| |
| // Translate the HTTP method code to a String. |
| byte methodCode = msg.getByte(); |
| if (methodCode != SC_M_JK_STORED) |
| 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.headers(); |
| 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) { |
| // |
| // header name is encoded as an int |
| // |
| msg.getInt(); // To advance the read position |
| hName = headerTransArray[hId - 1]; |
| vMB= headers.addValue(hName); |
| msg.getMessageBytes(vMB); |
| |
| if (hId == SC_REQ_CONTENT_LENGTH) { |
| // just read content-length header |
| int contentLength = (vMB == null) ? -1 : vMB.getInt(); |
| req.setContentLength(contentLength); |
| } else if (hId == SC_REQ_CONTENT_TYPE) { |
| // just read content-type header |
| ByteChunk bchunk = vMB.getByteChunk(); |
| req.contentType().setBytes(bchunk.getBytes(), |
| bchunk.getOffset(), |
| bchunk.getLength()); |
| } else if (hId == SC_REQ_AUTHORIZATION) { |
| ByteChunk bchunk = vMB.getByteChunk(); |
| req.authorization().setBytes(bchunk.getBytes(), |
| bchunk.getOffset(), |
| bchunk.getLength()); |
| } |
| } else { |
| // |
| // header name is a string |
| // |
| // 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 : |
| 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; |
| // Transform the string into certificate. |
| String certString = msg.getString(); |
| byte[] certData = certString.getBytes(); |
| ByteArrayInputStream bais = new ByteArrayInputStream(certData); |
| |
| // Fill the first element. |
| X509Certificate jsseCerts[] = null; |
| try { |
| CertificateFactory cf = |
| CertificateFactory.getInstance("X.509"); |
| X509Certificate cert = (X509Certificate) |
| cf.generateCertificate(bais); |
| jsseCerts = new X509Certificate[1]; |
| jsseCerts[0] = cert; |
| } catch(java.security.cert.CertificateException e) { |
| log("Certificate convertion failed" + e ); |
| } |
| |
| req.setAttribute("javax.servlet.request.X509Certificate", |
| jsseCerts); |
| break; |
| |
| case SC_A_SSL_CIPHER : |
| isSSL = true; |
| req.setAttribute("javax.servlet.request.cipher_suite", |
| msg.getString()); |
| break; |
| |
| case SC_A_SECRET : |
| // If a request has a secret attribute, set it on |
| // channel - it'll be visible to the caller ( Interceptor, |
| // Connector ) and it can check it against its settings before |
| // trusting us. |
| String secret=msg.getString(); |
| if(secret!=null) { |
| ch.setSecret( secret ); |
| } |
| 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; |
| |
| case SC_A_SSL_KEY_SIZE: // Ajp13 ! |
| isSSL = true; |
| req.setAttribute("javax.servlet.request.key_size", |
| Integer.toString(msg.getInt())); |
| break; |
| |
| case SC_A_STORED_METHOD: |
| req.method().setString(msg.getString()); |
| break; |
| |
| default: |
| // Ignore. Assume a single-string value - we shouldn't |
| // allow anything else. |
| msg.getString(); |
| break; |
| } |
| } |
| |
| if(isSSL) { |
| req.setScheme(req.SCHEME_HTTPS); |
| req.setSecure(true); |
| } |
| |
| // set cookies on request now that we have all headers |
| req.cookies().setHeaders(req.headers()); |
| |
| // Check to see if there should be a body packet coming along |
| // immediately after |
| if(req.getContentLength() > 0) { |
| |
| /* Read present data */ |
| int err = ch.receive(ch.inBuf); |
| if(err < 0) { |
| return 500; |
| } |
| |
| ch.blen = ch.inBuf.peekInt(); |
| ch.pos = 0; |
| ch.inBuf.getBytes(ch.bodyBuff); |
| } |
| |
| if (debug > 5) { |
| log(req.toString()); |
| } |
| |
| return 200; // Success |
| } |
| |
| |
| // -------------------- Messages from container to server ------------------ |
| |
| /** |
| * Send the HTTP headers back to the web server and on to the browser. |
| * |
| * @param status The HTTP status code to send. |
| * @param statusMessage the HTTP status message to send. |
| * @param headers The set of all headers. |
| */ |
| public void sendHeaders(Ajp13 ch, Ajp13Packet outBuf, |
| int status, String statusMessage, |
| MimeHeaders headers) |
| throws IOException |
| { |
| // XXX if more headers that MAX_SIZE, send 2 packets! |
| if( statusMessage==null ) statusMessage=HttpMessages.getMessage(status); |
| outBuf.reset(); |
| outBuf.appendByte(JK_AJP13_SEND_HEADERS); |
| outBuf.appendInt(status); |
| |
| outBuf.appendString(statusMessage); |
| |
| 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(); |
| ch.send(outBuf); |
| } |
| |
| |
| /** |
| * Signal the web server that the servlet has finished handling this |
| * request, and that the connection can be reused. |
| */ |
| public void finish(Ajp13 ch, Ajp13Packet outBuf) throws IOException { |
| if (debug > 0) log("finish()"); |
| |
| outBuf.reset(); |
| outBuf.appendByte(JK_AJP13_END_RESPONSE); |
| outBuf.appendBool(true); // Reuse this connection |
| outBuf.end(); |
| ch.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(Ajp13 ch, Ajp13Packet outBuf, |
| byte b[], int off, int len) |
| throws IOException |
| { |
| if (debug > 0) log("doWrite(byte[], " + off + ", " + len + ")"); |
| |
| int sent = 0; |
| while(sent < len) { |
| int to_send = len - sent; |
| to_send = to_send > Ajp13.MAX_SEND_SIZE ? Ajp13.MAX_SEND_SIZE : to_send; |
| |
| outBuf.reset(); |
| outBuf.appendByte(JK_AJP13_SEND_BODY_CHUNK); |
| outBuf.appendBytes(b, off + sent, to_send); |
| ch.send(outBuf); |
| sent += to_send; |
| } |
| } |
| |
| // -------------------- Utils -------------------- |
| |
| /** |
| * 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; |
| } |
| |
| private int debug=0; |
| private Logger logger = new Logger(); |
| |
| public void setDebug(int debug) { |
| this.debug = debug; |
| } |
| |
| public void setLogger(Logger l) { |
| this.logger = l; |
| } |
| |
| void log(String s) { |
| logger.log("[RequestHandler] " + s ); |
| } |
| |
| // ==================== Servlet Input Support ================= |
| // XXX DEPRECATED |
| |
| public int available(Ajp13 ch) throws IOException { |
| if (debug > 0) { |
| log("available()"); |
| } |
| |
| if (ch.pos >= ch.blen) { |
| if( ! refillReadBuffer(ch)) { |
| return 0; |
| } |
| } |
| return ch.blen - ch.pos; |
| } |
| |
| /** |
| * Return the next byte of request body data (to a servlet). |
| */ |
| public int doRead(Ajp13 ch) throws IOException |
| { |
| if (debug > 0) { |
| log("doRead()"); |
| } |
| |
| if(ch.pos >= ch.blen) { |
| if( ! refillReadBuffer(ch)) { |
| return -1; |
| } |
| } |
| return ch.bodyBuff[ch.pos++] & 0xFF; |
| } |
| |
| /** |
| * 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. |
| */ |
| public int doRead(Ajp13 ch, byte[] b, int off, int len) throws IOException |
| { |
| if (debug > 0) { |
| log("doRead(byte[], int, int)"); |
| } |
| |
| if(ch.pos >= ch.blen) { |
| if( ! refillReadBuffer(ch)) { |
| return -1; |
| } |
| } |
| |
| if(ch.pos + len <= ch.blen) { // Fear the off by one error |
| // Sanity check b.length > off + len? |
| System.arraycopy(ch.bodyBuff, ch.pos, b, off, len); |
| ch.pos += len; |
| return len; |
| } |
| |
| // Not enough data (blen < pos + len) |
| int toCopy = len; |
| while(toCopy > 0) { |
| int bytesRemaining = ch.blen - ch.pos; |
| if(bytesRemaining < 0) |
| bytesRemaining = 0; |
| int c = bytesRemaining < toCopy ? bytesRemaining : toCopy; |
| |
| System.arraycopy(ch.bodyBuff, ch.pos, b, off, c); |
| |
| toCopy -= c; |
| |
| off += c; |
| ch.pos += c; // In case we exactly consume the buffer |
| |
| if(toCopy > 0) |
| if( ! refillReadBuffer(ch)) { // 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. |
| */ |
| public boolean refillReadBuffer(Ajp13 ch) throws IOException |
| { |
| if (debug > 0) { |
| 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?? |
| ch.inBuf.reset(); |
| ch.inBuf.appendByte(JK_AJP13_GET_BODY_CHUNK); |
| ch.inBuf.appendInt(Ajp13.MAX_READ_SIZE); |
| ch.send(ch.inBuf); |
| |
| int err = ch.receive(ch.inBuf); |
| if(err < 0) { |
| throw new IOException(); |
| } |
| |
| // check for empty packet, which means end of stream |
| if (ch.inBuf.getLen() == 0) { |
| if (debug > 0) { |
| log("refillReadBuffer(): " |
| + "received empty packet -> end of stream"); |
| } |
| ch.blen = 0; |
| ch.pos = 0; |
| return false; |
| } |
| |
| ch.blen = ch.inBuf.peekInt(); |
| ch.pos = 0; |
| ch.inBuf.getBytes(ch.bodyBuff); |
| |
| return (ch.blen > 0); |
| } |
| |
| // ==================== Servlet Output Support ================= |
| |
| /** |
| */ |
| public void beginSendHeaders(Ajp13 ch, Ajp13Packet outBuf, |
| int status, |
| String statusMessage, |
| int numHeaders) throws IOException { |
| |
| if (debug > 0) { |
| log("sendHeaders()"); |
| } |
| |
| // XXX if more headers that MAX_SIZE, send 2 packets! |
| |
| outBuf.reset(); |
| outBuf.appendByte(JK_AJP13_SEND_HEADERS); |
| |
| if (debug > 0) { |
| 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(Ajp13Packet outBuf, |
| 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(Ajp13 ch, Ajp13Packet outBuf) |
| throws IOException |
| { |
| outBuf.end(); |
| ch.send(outBuf); |
| } |
| |
| /** |
| * 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(Ajp13 ch, Ajp13Packet outBuf, |
| int status, MimeHeaders headers) |
| throws IOException |
| { |
| sendHeaders(ch, outBuf, status, HttpMessages.getMessage(status), |
| headers); |
| } |
| |
| |
| } |