/*
* 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;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.servlet.http.HttpServletResponse;
import org.apache.coyote.AbstractProcessor;
import org.apache.coyote.ActionCode;
import org.apache.coyote.Adapter;
import org.apache.coyote.ErrorState;
import org.apache.coyote.InputBuffer;
import org.apache.coyote.OutputBuffer;
import org.apache.coyote.RequestInfo;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
import org.apache.tomcat.util.net.ApplicationBufferHandler;
import org.apache.tomcat.util.net.SSLSupport;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.res.StringManager;
AJP Processor implementation.
/**
* AJP Processor implementation.
*/
public class AjpProcessor extends AbstractProcessor {
private static final Log log = LogFactory.getLog(AjpProcessor.class);
The string manager for this package.
/**
* The string manager for this package.
*/
private static final StringManager sm = StringManager.getManager(AjpProcessor.class);
End message array.
/**
* End message array.
*/
private static final byte[] endMessageArray;
private static final byte[] endAndCloseMessageArray;
Flush message array.
/**
* Flush message array.
*/
private static final byte[] flushMessageArray;
Pong message array.
/**
* Pong message array.
*/
private static final byte[] pongMessageArray;
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 = new byte[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 = new byte[endAndCloseMessage.getLen()];
System.arraycopy(endAndCloseMessage.getBuffer(), 0, endAndCloseMessageArray, 0,
endAndCloseMessage.getLen());
// Allocate the flush message array
AjpMessage flushMessage = new AjpMessage(16);
flushMessage.reset();
flushMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
flushMessage.appendInt(0);
flushMessage.appendByte(0);
flushMessage.end();
flushMessageArray = new byte[flushMessage.getLen()];
System.arraycopy(flushMessage.getBuffer(), 0, flushMessageArray, 0,
flushMessage.getLen());
// Allocate the pong message array
AjpMessage pongMessage = new AjpMessage(16);
pongMessage.reset();
pongMessage.appendByte(Constants.JK_AJP13_CPONG_REPLY);
pongMessage.end();
pongMessageArray = new byte[pongMessage.getLen()];
System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray,
0, pongMessage.getLen());
}
// ----------------------------------------------------- Instance Variables
private final AbstractAjpProtocol<?> protocol;
GetBody message array. Not static like the other message arrays since the
message varies with packetSize and that can vary per connector.
/**
* GetBody message array. Not static like the other message arrays since the
* message varies with packetSize and that can vary per connector.
*/
private final byte[] getBodyMessageArray;
AJP packet size.
/**
* AJP packet size.
*/
private final int outputMaxChunkSize;
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.
/**
* 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.
*/
private final AjpMessage requestHeaderMessage;
Message used for response composition.
/**
* Message used for response composition.
*/
private final 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.
/**
* 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.
*/
private int responseMsgPos = -1;
Body message.
/**
* Body message.
*/
private final AjpMessage bodyMessage;
Body message.
/**
* Body message.
*/
private final MessageBytes bodyBytes = MessageBytes.newInstance();
Temp message bytes used for processing.
/**
* Temp message bytes used for processing.
*/
private final MessageBytes tmpMB = MessageBytes.newInstance();
Byte chunk for certs.
/**
* Byte chunk for certs.
*/
private final MessageBytes certificates = MessageBytes.newInstance();
End of stream flag.
/**
* End of stream flag.
*/
private boolean endOfStream = false;
Request body empty flag.
/**
* Request body empty flag.
*/
private boolean empty = true;
First read.
/**
* First read.
*/
private boolean first = true;
Indicates that a 'get body chunk' message has been sent but the body
chunk has not yet been received.
/**
* Indicates that a 'get body chunk' message has been sent but the body
* chunk has not yet been received.
*/
private boolean waitingForBodyMessage = false;
Replay read.
/**
* Replay read.
*/
private boolean replay = false;
Should any response body be swallowed and not sent to the client.
/**
* Should any response body be swallowed and not sent to the client.
*/
private boolean swallowResponse = false;
Finished response.
/**
* Finished response.
*/
private boolean responseFinished = false;
Bytes written to client for the current request.
/**
* Bytes written to client for the current request.
*/
private long bytesWritten = 0;
// ------------------------------------------------------------ Constructor
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 = new byte[getBodyMessage.getLen()];
System.arraycopy(getBodyMessage.getBuffer(), 0, getBodyMessageArray,
0, getBodyMessage.getLen());
response.setOutputBuffer(new SocketOutputBuffer());
}
// --------------------------------------------------------- Public Methods
@Override
protected boolean 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();
return true;
}
}
return false;
}
@Override
protected void dispatchNonBlockingRead() {
if (available(true) > 0) {
super.dispatchNonBlockingRead();
}
}
@Override
protected SocketState dispatchEndRequest() {
// Set keep alive timeout for next request
socketWrapper.setReadTimeout(protocol.getKeepAliveTimeout());
recycle();
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;
boolean keptAlive = false;
while (!getErrorState().isError() && !protocol.isPaused()) {
// Parsing the request header
try {
// Get first message of the request
if (!readMessage(requestHeaderMessage, !keptAlive)) {
break;
}
// 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) {
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
}
recycle();
continue;
} else if(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;
}
keptAlive = true;
request.setStartTime(System.currentTimeMillis());
} 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();
rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
// Set keep alive timeout for next request
socketWrapper.setReadTimeout(protocol.getKeepAliveTimeout());
recycle();
}
rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
if (getErrorState().isError() || protocol.isPaused()) {
return SocketState.CLOSED;
} else {
if (isAsync()) {
return SocketState.LONG;
} else {
return SocketState.OPEN;
}
}
}
@Override
public void recycle() {
getAdapter().checkRecycled(request, response);
super.recycle();
request.recycle();
response.recycle();
first = true;
endOfStream = false;
waitingForBodyMessage = false;
empty = true;
replay = false;
responseFinished = false;
certificates.recycle();
swallowResponse = false;
bytesWritten = 0;
}
@Override
public void pause() {
// NOOP for AJP
}
// ------------------------------------------------------ Protected Methods
// 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.
Params: - block – If there is no data available to read when this method is
called, should this call block until data becomes available?
Returns: true
if at least one body byte was read, otherwise
false
/**
* 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>
*/
private boolean receive(boolean block) throws IOException {
bodyMessage.reset();
if (!readMessage(bodyMessage, block)) {
return false;
}
waitingForBodyMessage = false;
// No data received.
if (bodyMessage.getLen() == 0) {
// just the header
return false;
}
int blen = bodyMessage.peekInt();
if (blen == 0) {
return false;
}
bodyMessage.getBodyBytes(bodyBytes);
empty = false;
return true;
}
Read an AJP message.
Params: - message – The message to populate
- block – If there is no data available to read when this method is
called, should this call block until data becomes available?
Throws: - IOException – any other failure, including incomplete reads
Returns: true if the message has been read, false if no data was read
/**
* 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
*/
private boolean readMessage(AjpMessage message, boolean block)
throws IOException {
byte[] buf = message.getBuffer();
if (!read(buf, 0, Constants.H_SIZE, block)) {
return false;
}
int messageLength = message.processHeader(true);
if (messageLength < 0) {
// Invalid AJP header signature
throw new IOException(sm.getString("ajpmessage.invalidLength",
Integer.valueOf(messageLength)));
}
else if (messageLength == 0) {
// Zero length message.
return true;
}
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);
throw new IllegalArgumentException(msg);
}
read(buf, Constants.H_SIZE, messageLength, true);
return true;
}
}
Get more request body data from the web server and store it in the
internal buffer.
Params: - block –
true
if this is blocking IO
Throws: - IOException – An IO error occurred
Returns: true
if there is more data,
false
if not.
/**
* 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
*/
protected boolean 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) {
return false;
}
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;
} else if (contentLength == 0) {
endOfStream = true;
return false;
}
}
// Request more data immediately
if (!waitingForBodyMessage) {
socketWrapper.write(true, getBodyMessageArray, 0, getBodyMessageArray.length);
socketWrapper.flush(true);
waitingForBodyMessage = true;
}
boolean moreData = receive(block);
if (!moreData && !waitingForBodyMessage) {
endOfStream = true;
}
return moreData;
}
After reading the request headers, we have to setup the request filters.
/**
* After reading the request headers, we have to setup the request filters.
*/
private void prepareRequest() {
// Translate the HTTP method code to a String.
byte methodCode = requestHeaderMessage.getByte();
if (methodCode != Constants.SC_M_JK_STORED) {
String methodName = Constants.getMethodForCode(methodCode - 1);
request.method().setString(methodName);
}
requestHeaderMessage.getBytes(request.protocol());
requestHeaderMessage.getBytes(request.requestURI());
requestHeaderMessage.getBytes(request.remoteAddr());
requestHeaderMessage.getBytes(request.remoteHost());
requestHeaderMessage.getBytes(request.localName());
request.setLocalPort(requestHeaderMessage.getInt());
boolean isSSL = requestHeaderMessage.getByte() != 0;
if (isSSL) {
request.scheme().setString("https");
}
// Decode headers
MimeHeaders headers = request.getMimeHeaders();
// 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);
}
} else if (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());
}
}
// Decode extra attributes
String requiredSecret = protocol.getRequiredSecret();
boolean secret = false;
byte attributeCode;
while ((attributeCode = requestHeaderMessage.getByte())
!= Constants.SC_A_ARE_DONE) {
switch (attributeCode) {
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);
} else if(n.equals(Constants.SC_A_REQ_REMOTE_PORT)) {
try {
request.setRemotePort(Integer.parseInt(v));
} catch (NumberFormatException nfe) {
// Ignore invalid value
}
} else if(n.equals(Constants.SC_A_SSL_PROTOCOL)) {
request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, v);
} else {
request.setAttribute(n, v );
}
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.getTomcatAuthentication()) {
// ignore server
requestHeaderMessage.getBytes(tmpMB);
} else {
requestHeaderMessage.getBytes(request.getAuthType());
}
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 (requiredSecret != null) {
secret = true;
if (!tmpMB.equals(requiredSecret)) {
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 ((requiredSecret != null) && !secret) {
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);
}
}
MessageBytes valueMB = request.getMimeHeaders().getValue("host");
parseHost(valueMB);
if (!getErrorState().isIoAllowed()) {
getAdapter().log(request, response, 0);
}
}
{@inheritDoc}
This implementation populates the server name from the local name
provided by the AJP message.
/**
* {@inheritDoc}
* <p>
* This implementation populates the server name from the local name
* provided by the AJP message.
*/
@Override
protected void populateHost() {
try {
request.serverName().duplicate(request.localName());
} catch (IOException e) {
response.setStatus(400);
setErrorState(ErrorState.CLOSE_CLEAN, e);
}
}
{@inheritDoc}
This implementation populates the server port from the local port
provided by the AJP message.
/**
* {@inheritDoc}
* <p>
* This implementation populates the server port from the local port
* provided by the AJP message.
*/
@Override
protected void 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.
/**
* When committing the response, we have to validate the set of headers, as
* well as setup the response filters.
*/
@Override
protected final void prepareResponse() throws IOException {
response.setCommitted(true);
tmpMB.recycle();
responseMsgPos = -1;
responseMessage.reset();
responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS);
// Responses with certain status codes are not permitted to include a
// response body.
int statusCode = response.getStatus();
if (statusCode < 200 || statusCode == 204 || statusCode == 205 ||
statusCode == 304) {
// No entity body
swallowResponse = true;
}
// Responses to HEAD requests are not permitted to include a response
// body.
MessageBytes methodMB = request.method();
if (methodMB.equals("HEAD")) {
// No entity body
swallowResponse = true;
}
// HTTP header contents
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);
// 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);
}
// Other headers
int numHeaders = headers.size();
responseMessage.appendInt(numHeaders);
for (int i = 0; i < numHeaders; i++) {
MessageBytes hN = headers.getName(i);
int hC = Constants.getResponseAjpIndex(hN.toString());
if (hC > 0) {
responseMessage.appendInt(hC);
}
else {
responseMessage.appendBytes(hN);
}
MessageBytes hV=headers.getValue(i);
responseMessage.appendBytes(hV);
}
// Write to buffer
responseMessage.end();
socketWrapper.write(true, responseMessage.getBuffer(), 0, responseMessage.getLen());
socketWrapper.flush(true);
}
Callback to write data from the buffer.
/**
* Callback to write data from the buffer.
*/
@Override
protected final void 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);
}
}
Finish AJP response.
/**
* Finish AJP response.
*/
@Override
protected final void finishResponse() throws IOException {
if (responseFinished)
return;
responseFinished = 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
protected final void ack() {
// NO-OP for AJP
}
@Override
protected final int 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 bodyBytes.getByteChunk().getLength();
}
}
@Override
protected final void setRequestBody(ByteChunk body) {
int length = body.getLength();
bodyBytes.setBytes(body.getBytes(), body.getStart(), length);
request.setContentLength(length);
first = false;
empty = false;
replay = true;
endOfStream = false;
}
@Override
protected final void setSwallowResponse() {
swallowResponse = true;
}
@Override
protected final void 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
protected final boolean getPopulateRequestAttributesFromSocket() {
// NO-OPs the attribute requests since they are pre-populated when
// parsing the first AJP message.
return false;
}
@Override
protected final void 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
protected final void populateSslRequestAttributes() {
if (!certificates.isNull()) {
ByteChunk certData = certificates.getByteChunk();
X509Certificate jsseCerts[] = null;
ByteArrayInputStream bais =
new ByteArrayInputStream(certData.getBytes(),
certData.getStart(),
certData.getLength());
// Fill the elements.
try {
CertificateFactory cf;
String clientCertProvider = protocol.getClientCertProvider();
if (clientCertProvider == null) {
cf = CertificateFactory.getInstance("X.509");
} else {
cf = CertificateFactory.getInstance("X.509",
clientCertProvider);
}
while(bais.available() > 0) {
X509Certificate cert = (X509Certificate)
cf.generateCertificate(bais);
if(jsseCerts == null) {
jsseCerts = new X509Certificate[1];
jsseCerts[0] = cert;
} else {
X509Certificate [] temp = new X509Certificate[jsseCerts.length+1];
System.arraycopy(jsseCerts,0,temp,0,jsseCerts.length);
temp[jsseCerts.length] = cert;
jsseCerts = temp;
}
}
} catch (java.security.cert.CertificateException e) {
getLog().error(sm.getString("ajpprocessor.certs.fail"), e);
return;
} catch (NoSuchProviderException e) {
getLog().error(sm.getString("ajpprocessor.certs.fail"), e);
return;
}
request.setAttribute(SSLSupport.CERTIFICATE_KEY, jsseCerts);
}
}
@Override
protected final boolean isRequestBodyFullyRead() {
return endOfStream;
}
@Override
protected final void registerReadInterest() {
socketWrapper.registerReadInterest();
}
@Override
protected final boolean isReadyForWrite() {
return responseMsgPos == -1 && socketWrapper.isReadyForWrite();
}
@Override
protected boolean isTrailerFieldsReady() {
// AJP does not support trailers so return true so app can request the
// trailers and find out that there are none.
return true;
}
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.
Params: - buf – Buffer to read data into
- pos – Start position
- n – The minimum number of bytes to read
- block – If there is no data available to read when this method is
called, should this call block until data becomes available?
Throws: Returns: true
if the requested number of bytes were read
else false
/**
* 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
*/
private boolean 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) {
throw new EOFException();
}
left = left - read;
start = start + read;
}
} else if (read == -1) {
throw new EOFException();
}
return read > 0;
}
private void writeData(ByteBuffer chunk) throws IOException {
boolean blocking = (response.getWriteListener() == null);
int len = chunk.remaining();
int off = 0;
// Write this chunk
while (len > 0) {
int thisTime = Math.min(len, outputMaxChunkSize);
responseMessage.reset();
responseMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
chunk.limit(chunk.position() + thisTime);
responseMessage.appendBytes(chunk);
responseMessage.end();
socketWrapper.write(blocking, responseMessage.getBuffer(), 0, responseMessage.getLen());
socketWrapper.flush(blocking);
len -= thisTime;
off += thisTime;
}
bytesWritten += off;
}
private boolean hasDataToWrite() {
return responseMsgPos != -1 || socketWrapper.hasDataToWrite();
}
@Override
protected Log getLog() {
return log;
}
// ------------------------------------- InputStreamInputBuffer Inner Class
This class is an input buffer which will read its data from an input
stream.
/**
* This class is an input buffer which will read its data from an input
* stream.
*/
protected class SocketInputBuffer implements InputBuffer {
@Override
public int doRead(ApplicationBufferHandler handler) throws IOException {
if (endOfStream) {
return -1;
}
if (empty) {
if (!refillReadBuffer(true)) {
return -1;
}
}
ByteChunk bc = bodyBytes.getByteChunk();
handler.setByteBuffer(ByteBuffer.wrap(bc.getBuffer(), bc.getStart(), bc.getLength()));
empty = true;
return handler.getByteBuffer().remaining();
}
}
// ----------------------------------- OutputStreamOutputBuffer Inner Class
This class is an output buffer which will write data to an output
stream.
/**
* This class is an output buffer which will write data to an output
* stream.
*/
protected class SocketOutputBuffer implements OutputBuffer {
@Override
public int doWrite(ByteBuffer chunk) throws IOException {
if (!response.isCommitted()) {
// Validate and write response headers
try {
prepareResponse();
} catch (IOException e) {
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
}
}
int len = 0;
if (!swallowResponse) {
try {
len = chunk.remaining();
writeData(chunk);
len -= chunk.remaining();
} catch (IOException ioe) {
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, ioe);
// Re-throw
throw ioe;
}
}
return len;
}
@Override
public long getBytesWritten() {
return bytesWritten;
}
}
}