/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.coyote.ajp;
privatestaticfinal Log log = LogFactory.getLog(AjpProcessor.class); /** * The string manager for this package.
*/ privatestaticfinal StringManager sm = StringManager.getManager(AjpProcessor.class);
/** * End message array.
*/ privatestaticfinalbyte[] endMessageArray; privatestaticfinalbyte[] endAndCloseMessageArray;
static { // Allocate the end message array
AjpMessage endMessage = new AjpMessage(16);
endMessage.reset();
endMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
endMessage.appendByte(1);
endMessage.end();
endMessageArray = newbyte[endMessage.getLen()];
System.arraycopy(endMessage.getBuffer(), 0, endMessageArray, 0, endMessage.getLen());
// Allocate the end and close message array
AjpMessage endAndCloseMessage = new AjpMessage(16);
endAndCloseMessage.reset();
endAndCloseMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
endAndCloseMessage.appendByte(0);
endAndCloseMessage.end();
endAndCloseMessageArray = newbyte[endAndCloseMessage.getLen()];
System.arraycopy(endAndCloseMessage.getBuffer(), 0, endAndCloseMessageArray, 0, endAndCloseMessage.getLen());
/** * GetBody message array. Not static like the other message arrays since the message varies with packetSize and that * can vary per connector.
*/ privatefinalbyte[] getBodyMessageArray;
/** * Header message. Note that this header is merely the one used during the processing of the first message of a * "request", so it might not be a request header. It will stay unchanged during the processing of the whole * request.
*/ privatefinal AjpMessage requestHeaderMessage;
/** * Message used for response composition.
*/ privatefinal AjpMessage responseMessage;
/** * Location of next write of the response message (used with non-blocking writes when the message may not be written * in a single write). A value of -1 indicates that no message has been written to the buffer.
*/ privateint responseMsgPos = -1;
/** * Body message.
*/ privatefinal AjpMessage bodyMessage;
/** * Body message.
*/ privatefinal MessageBytes bodyBytes = MessageBytes.newInstance();
/** * Temp message bytes used for processing.
*/ privatefinal MessageBytes tmpMB = MessageBytes.newInstance();
/** * Indicates that a 'get body chunk' message has been sent but the body chunk has not yet been received.
*/ privateboolean waitingForBodyMessage = false;
public AjpProcessor(AbstractAjpProtocol<?> protocol, Adapter adapter) { super(adapter); this.protocol = protocol;
int packetSize = protocol.getPacketSize(); // Calculate maximum chunk size as packetSize may have been changed from // the default (Constants.MAX_PACKET_SIZE) this.outputMaxChunkSize = packetSize - Constants.SEND_HEAD_LEN;
request.setInputBuffer(new SocketInputBuffer());
requestHeaderMessage = new AjpMessage(packetSize);
responseMessage = new AjpMessage(packetSize);
bodyMessage = new AjpMessage(packetSize);
// Set the getBody message buffer
AjpMessage getBodyMessage = new AjpMessage(16);
getBodyMessage.reset();
getBodyMessage.appendByte(Constants.JK_AJP13_GET_BODY_CHUNK); // Adjust read size if packetSize != default (Constants.MAX_PACKET_SIZE)
getBodyMessage.appendInt(Constants.MAX_READ_SIZE + packetSize - Constants.MAX_PACKET_SIZE);
getBodyMessage.end();
getBodyMessageArray = newbyte[getBodyMessage.getLen()];
System.arraycopy(getBodyMessage.getBuffer(), 0, getBodyMessageArray, 0, getBodyMessage.getLen());
// --------------------------------------------------------- Public Methods
@Override protectedboolean flushBufferedWrite() throws IOException { if (hasDataToWrite()) {
socketWrapper.flush(false); if (hasDataToWrite()) { // There is data to write but go via Response to // maintain a consistent view of non-blocking state
response.checkRegisterForWrite(); returntrue;
}
} returnfalse;
}
@Override protected SocketState dispatchEndRequest() { // Set keep alive timeout for next request
socketWrapper.setReadTimeout(protocol.getKeepAliveTimeout());
recycle(); if (protocol.isPaused()) { return SocketState.CLOSED;
} else { return SocketState.OPEN;
}
}
@Override public SocketState service(SocketWrapperBase<?> socket) throws IOException {
RequestInfo rp = request.getRequestProcessor();
rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
// Setting up the socket this.socketWrapper = socket;
boolean cping = false; // Expected to block on the first read as there should be at least one // AJP message to read. boolean firstRead = true;
while (!getErrorState().isError() && !protocol.isPaused()) { // Parsing the request header try { // Get first message of the request if (!readMessage(requestHeaderMessage, firstRead)) { break;
}
firstRead = false;
// Processing the request so make sure the connection rather // than keep-alive timeout is used
socketWrapper.setReadTimeout(protocol.getConnectionTimeout());
// Check message type, process right away and break if // not regular request processing int type = requestHeaderMessage.getByte(); if (type == Constants.JK_AJP13_CPING_REQUEST) { if (protocol.isPaused()) {
recycle(); break;
}
cping = true; try {
socketWrapper.write(true, pongMessageArray, 0, pongMessageArray.length);
socketWrapper.flush(true);
} catch (IOException e) { if (getLog().isDebugEnabled()) {
getLog().debug("Pong message failed", e);
}
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
}
recycle(); continue;
} elseif (type != Constants.JK_AJP13_FORWARD_REQUEST) { // Unexpected packet type. Unread body packets should have // been swallowed in finish(). if (getLog().isDebugEnabled()) {
getLog().debug("Unexpected message: " + type);
}
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, null); break;
}
request.setStartTimeNanos(System.nanoTime());
} catch (IOException e) {
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e); break;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
getLog().debug(sm.getString("ajpprocessor.header.error"), t); // 400 - Bad Request
response.setStatus(400);
setErrorState(ErrorState.CLOSE_CLEAN, t);
}
if (getErrorState().isIoAllowed()) { // Setting up filters, and parse some request headers
rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE); try {
prepareRequest();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
getLog().debug(sm.getString("ajpprocessor.request.prepare"), t); // 500 - Internal Server Error
response.setStatus(500);
setErrorState(ErrorState.CLOSE_CLEAN, t);
}
}
if (getErrorState().isIoAllowed() && !cping && protocol.isPaused()) { // 503 - Service unavailable
response.setStatus(503);
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
cping = false;
// Process the request in the adapter if (getErrorState().isIoAllowed()) { try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
getAdapter().service(request, response);
} catch (InterruptedIOException e) {
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
getLog().error(sm.getString("ajpprocessor.request.process"), t); // 500 - Internal Server Error
response.setStatus(500);
setErrorState(ErrorState.CLOSE_CLEAN, t);
getAdapter().log(request, response, 0);
}
}
if (isAsync() && !getErrorState().isError()) { break;
}
// Finish the response if not done yet if (!responseFinished && getErrorState().isIoAllowed()) { try {
action(ActionCode.COMMIT, null);
finishResponse();
} catch (IOException ioe) {
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, ioe);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setErrorState(ErrorState.CLOSE_NOW, t);
}
}
// If there was an error, make sure the request is counted as // and error, and update the statistics counter if (getErrorState().isError()) {
response.setStatus(500);
}
request.updateCounters();
// Methods used by SocketInputBuffer /** * Read an AJP body message. Used to read both the 'special' packet in ajp13 and to receive the data after we send a * GET_BODY packet. * * @param block If there is no data available to read when this method is called, should this call block until data * becomes available? * * @return <code>true</code> if at least one body byte was read, otherwise <code>false</code>
*/ privateboolean receive(boolean block) throws IOException {
bodyMessage.reset();
if (!readMessage(bodyMessage, block)) { returnfalse;
}
waitingForBodyMessage = false;
// No data received. if (bodyMessage.getLen() == 0) { // just the header returnfalse;
} int blen = bodyMessage.peekInt(); if (blen == 0) { returnfalse;
}
/** * Read an AJP message. * * @param message The message to populate * @param block If there is no data available to read when this method is called, should this call block until * data becomes available? * * @return true if the message has been read, false if no data was read * * @throws IOException any other failure, including incomplete reads
*/ privateboolean readMessage(AjpMessage message, boolean block) throws IOException {
byte[] buf = message.getBuffer();
if (!read(buf, 0, Constants.H_SIZE, block)) { returnfalse;
}
int messageLength = message.processHeader(true); if (messageLength < 0) { // Invalid AJP header signature thrownew IOException(sm.getString("ajpmessage.invalidLength", Integer.valueOf(messageLength)));
} elseif (messageLength == 0) { // Zero length message. returntrue;
} else { if (messageLength > message.getBuffer().length) { // Message too long for the buffer // Need to trigger a 400 response
String msg = sm.getString("ajpprocessor.header.tooLong", Integer.valueOf(messageLength),
Integer.valueOf(buf.length));
log.error(msg); thrownew IllegalArgumentException(msg);
}
read(buf, Constants.H_SIZE, messageLength, true); returntrue;
}
}
/** * Get more request body data from the web server and store it in the internal buffer. * * @param block <code>true</code> if this is blocking IO * * @return <code>true</code> if there is more data, <code>false</code> if not. * * @throws IOException An IO error occurred
*/ protectedboolean refillReadBuffer(boolean block) throws IOException { // When using replay (e.g. after FORM auth) all the data to read has // been buffered so there is no opportunity to refill the buffer. if (replay) {
endOfStream = true; // we've read everything there is
} if (endOfStream) { returnfalse;
}
if (first) {
first = false; long contentLength = request.getContentLengthLong(); // - When content length > 0, AJP sends the first body message // automatically. // - When content length == 0, AJP does not send a body message. // - When content length is unknown, AJP does not send the first // body message automatically. if (contentLength > 0) {
waitingForBodyMessage = true;
} elseif (contentLength == 0) {
endOfStream = true; returnfalse;
}
}
// Request more data immediately if (!waitingForBodyMessage) {
socketWrapper.write(true, getBodyMessageArray, 0, getBodyMessageArray.length);
socketWrapper.flush(true);
waitingForBodyMessage = true;
}
// Set this every time in case limit has been changed via JMX
headers.setLimit(protocol.getMaxHeaderCount());
boolean contentLengthSet = false; int hCount = requestHeaderMessage.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 = requestHeaderMessage.peekInt(); int hId = isc & 0xFF;
MessageBytes vMB = null;
isc &= 0xFF00; if (0xA000 == isc) {
requestHeaderMessage.getInt(); // To advance the read position
hName = Constants.getHeaderForCode(hId - 1);
vMB = headers.addValue(hName);
} else { // reset hId -- if the header currently being read // happens to be 7 or 8 bytes long, the code below // will think it's the content-type header or the // content-length header - SC_REQ_CONTENT_TYPE=7, // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected // behaviour. see bug 5861 for more information.
hId = -1;
requestHeaderMessage.getBytes(tmpMB);
ByteChunk bc = tmpMB.getByteChunk();
vMB = headers.addValue(bc.getBuffer(), bc.getStart(), bc.getLength());
}
requestHeaderMessage.getBytes(vMB);
if (hId == Constants.SC_REQ_CONTENT_LENGTH || (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) { long cl = vMB.getLong(); if (contentLengthSet) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
setErrorState(ErrorState.CLOSE_CLEAN, null);
} else {
contentLengthSet = true; // Set the content-length header for the request
request.setContentLength(cl);
}
} elseif (hId == Constants.SC_REQ_CONTENT_TYPE || (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) { // just read the content-type header, so set it
ByteChunk bchunk = vMB.getByteChunk();
request.contentType().setBytes(bchunk.getBytes(), bchunk.getOffset(), bchunk.getLength());
}
}
case Constants.SC_A_REQ_ATTRIBUTE:
requestHeaderMessage.getBytes(tmpMB);
String n = tmpMB.toString();
requestHeaderMessage.getBytes(tmpMB);
String v = tmpMB.toString(); /* * AJP13 misses to forward the local IP address and the remote port. Allow the AJP connector to add * this info via private request attributes. We will accept the forwarded data and remove it from * the public list of request attributes.
*/ if (n.equals(Constants.SC_A_REQ_LOCAL_ADDR)) {
request.localAddr().setString(v);
} elseif (n.equals(Constants.SC_A_REQ_REMOTE_PORT)) { try {
request.setRemotePort(Integer.parseInt(v));
} catch (NumberFormatException nfe) { // Ignore invalid value
}
} elseif (n.equals(Constants.SC_A_SSL_PROTOCOL)) {
request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, v);
} elseif (n.equals("JK_LB_ACTIVATION")) {
request.setAttribute(n, v);
} elseif (jakartaAttributeMapping.containsKey(n)) { // AJP uses the Java Servlet attribute names. // Need to convert these to Jakarta Servlet.
request.setAttribute(jakartaAttributeMapping.get(n), v);
} elseif (iisTlsAttributes.contains(n)) { // Allow IIS TLS attributes
request.setAttribute(n, v);
} else { // All 'known' attributes will be processed by the previous // blocks. Any remaining attribute is an 'arbitrary' one.
Pattern pattern = protocol.getAllowedRequestAttributesPatternInternal(); if (pattern != null && pattern.matcher(n).matches()) {
request.setAttribute(n, v);
} else {
log.warn(sm.getString("ajpprocessor.unknownAttribute", n));
response.setStatus(403);
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
} break;
case Constants.SC_A_CONTEXT:
requestHeaderMessage.getBytes(tmpMB); // nothing break;
case Constants.SC_A_SERVLET_PATH:
requestHeaderMessage.getBytes(tmpMB); // nothing break;
case Constants.SC_A_REMOTE_USER: boolean tomcatAuthorization = protocol.getTomcatAuthorization(); if (tomcatAuthorization || !protocol.getTomcatAuthentication()) { // Implies tomcatAuthentication == false
requestHeaderMessage.getBytes(request.getRemoteUser());
request.setRemoteUserNeedsAuthorization(tomcatAuthorization);
} else { // Ignore user information from reverse proxy
requestHeaderMessage.getBytes(tmpMB);
} break;
case Constants.SC_A_AUTH_TYPE: if (protocol.getTomcatAuthorization() || !protocol.getTomcatAuthentication()) { // Implies tomcatAuthentication == false
requestHeaderMessage.getBytes(request.getAuthType());
} else { // Ignore user information from reverse proxy
requestHeaderMessage.getBytes(tmpMB);
} break;
case Constants.SC_A_QUERY_STRING:
requestHeaderMessage.getBytes(request.queryString()); break;
case Constants.SC_A_JVM_ROUTE:
requestHeaderMessage.getBytes(tmpMB); // nothing break;
case Constants.SC_A_SSL_CERT: // SSL certificate extraction is lazy, moved to JkCoyoteHandler
requestHeaderMessage.getBytes(certificates); break;
case Constants.SC_A_SSL_CIPHER:
requestHeaderMessage.getBytes(tmpMB);
request.setAttribute(SSLSupport.CIPHER_SUITE_KEY, tmpMB.toString()); break;
case Constants.SC_A_SSL_SESSION:
requestHeaderMessage.getBytes(tmpMB);
request.setAttribute(SSLSupport.SESSION_ID_KEY, tmpMB.toString()); break;
case Constants.SC_A_SSL_KEY_SIZE:
request.setAttribute(SSLSupport.KEY_SIZE_KEY, Integer.valueOf(requestHeaderMessage.getInt())); break;
case Constants.SC_A_STORED_METHOD:
requestHeaderMessage.getBytes(request.method()); break;
case Constants.SC_A_SECRET:
requestHeaderMessage.getBytes(tmpMB); if (secret != null && secret.length() > 0) {
secretPresentInRequest = true; if (!tmpMB.equals(secret)) {
response.setStatus(403);
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
} break;
default: // Ignore unknown attribute for backward compatibility break;
}
}
// Check if secret was submitted if required if (secret != null && secret.length() > 0 && !secretPresentInRequest) {
response.setStatus(403);
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
// Check for a full URI (including protocol://host:port/)
ByteChunk uriBC = request.requestURI().getByteChunk(); if (uriBC.startsWithIgnoreCase("http", 0)) {
int pos = uriBC.indexOf("://", 0, 3, 4); int uriBCStart = uriBC.getStart(); int slashPos = -1; if (pos != -1) { byte[] uriB = uriBC.getBytes();
slashPos = uriBC.indexOf('/', pos + 3); if (slashPos == -1) {
slashPos = uriBC.getLength(); // Set URI as "/"
request.requestURI().setBytes(uriB, uriBCStart + pos + 1, 1);
} else {
request.requestURI().setBytes(uriB, uriBCStart + slashPos, uriBC.getLength() - slashPos);
}
MessageBytes hostMB = headers.setValue("host");
hostMB.setBytes(uriB, uriBCStart + pos + 3, slashPos - pos - 3);
}
if (!getErrorState().isIoAllowed()) {
getAdapter().log(request, response, 0);
}
}
/** * {@inheritDoc} * <p> * This implementation populates the server name from the local name provided by the AJP message.
*/
@Override protectedvoid populateHost() { try {
request.serverName().duplicate(request.localName());
} catch (IOException e) {
response.setStatus(400);
setErrorState(ErrorState.CLOSE_CLEAN, e);
}
}
/** * {@inheritDoc} * <p> * This implementation populates the server port from the local port provided by the AJP message.
*/
@Override protectedvoid populatePort() { // No host information (HTTP/1.0)
request.setServerPort(request.getLocalPort());
}
/** * When committing the response, we have to validate the set of headers, as well as setup the response filters.
*/
@Override protectedfinalvoid prepareResponse() throws IOException {
response.setCommitted(true);
// Responses with certain status codes and/or methods are not permitted to include a response body. int statusCode = response.getStatus(); if (statusCode < 200 || statusCode == 204 || statusCode == 205 || statusCode == 304 ||
request.method().equals("HEAD")) { // No entity body
swallowResponse = true;
}
// Prepare special headers
MimeHeaders headers = response.getMimeHeaders();
String contentType = response.getContentType(); if (contentType != null) {
headers.setValue("Content-Type").setString(contentType);
}
String contentLanguage = response.getContentLanguage(); if (contentLanguage != null) {
headers.setValue("Content-Language").setString(contentLanguage);
} long contentLength = response.getContentLengthLong(); if (contentLength >= 0) {
headers.setValue("Content-Length").setLong(contentLength);
}
tmpMB.recycle();
responseMsgPos = -1;
int numHeaders = headers.size(); boolean needAjpMessageHeader = true; while (needAjpMessageHeader) { // Write AJP message header
responseMessage.reset();
responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS);
// Write HTTP response line
responseMessage.appendInt(statusCode); // Reason phrase is optional but mod_jk + httpd 2.x fails with a null // reason phrase - bug 45026
tmpMB.setString(Integer.toString(response.getStatus()));
responseMessage.appendBytes(tmpMB);
/** * Callback to write data from the buffer.
*/
@Override protectedfinalvoid flush() throws IOException { // Calling code should ensure that there is no data in the buffers for // non-blocking writes. // TODO Validate the assertion above if (!responseFinished) { if (protocol.getAjpFlush()) { // Send the flush message
socketWrapper.write(true, flushMessageArray, 0, flushMessageArray.length);
}
socketWrapper.flush(true);
}
}
// Swallow the unread body packet if present if (waitingForBodyMessage || first && request.getContentLengthLong() > 0) {
refillReadBuffer(true);
}
// Add the end message if (getErrorState().isError()) {
socketWrapper.write(true, endAndCloseMessageArray, 0, endAndCloseMessageArray.length);
} else {
socketWrapper.write(true, endMessageArray, 0, endMessageArray.length);
}
socketWrapper.flush(true);
}
@Override protectedfinalvoid ack(ContinueResponseTiming continueResponseTiming) { // NO-OP for AJP
}
@Override protectedfinalint available(boolean doRead) { if (endOfStream) { return 0;
} if (empty && doRead) { try {
refillReadBuffer(false);
} catch (IOException timeout) { // Not ideal. This will indicate that data is available // which should trigger a read which in turn will trigger // another IOException and that one can be thrown. return 1;
}
} if (empty) { return 0;
} else { return request.getInputBuffer().available();
}
}
@Override protectedfinalvoid disableSwallowRequest() { /* * NO-OP With AJP, Tomcat controls when the client sends request body data. At most there will be a single * packet to read and that will be handled in finishResponse().
*/
}
@Override protectedfinalboolean getPopulateRequestAttributesFromSocket() { // NO-OPs the attribute requests since they are pre-populated when // parsing the first AJP message. returnfalse;
}
@Override protectedfinalvoid populateRequestAttributeRemoteHost() { // Get remote host name using a DNS resolution if (request.remoteHost().isNull()) { try {
request.remoteHost().setString(InetAddress.getByName(request.remoteAddr().toString()).getHostName());
} catch (IOException iex) { // Ignore
}
}
}
@Override protectedboolean isTrailerFieldsReady() { // AJP does not support trailers so return true so app can request the // trailers and find out that there are none. returntrue;
}
/** * Read at least the specified amount of bytes, and place them in the input buffer. Note that if any data is * available to read then this method will always block until at least the specified number of bytes have been read. * * @param buf Buffer to read data into * @param pos Start position * @param n The minimum number of bytes to read * @param block If there is no data available to read when this method is called, should this call block until data * becomes available? * * @return <code>true</code> if the requested number of bytes were read else <code>false</code> * * @throws IOException If an I/O error occurs during the read
*/ privateboolean read(byte[] buf, int pos, int n, boolean block) throws IOException { int read = socketWrapper.read(block, buf, pos, n); if (read > 0 && read < n) { int left = n - read; int start = pos + read; while (left > 0) {
read = socketWrapper.read(true, buf, start, left); if (read == -1) { thrownew EOFException();
}
left = left - read;
start = start + read;
}
} elseif (read == -1) { thrownew EOFException();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.