init
This commit is contained in:
964
java/org/apache/coyote/AbstractProcessor.java
Normal file
964
java/org/apache/coyote/AbstractProcessor.java
Normal file
@@ -0,0 +1,964 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
|
||||
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.parser.Host;
|
||||
import org.apache.tomcat.util.log.UserDataHelper;
|
||||
import org.apache.tomcat.util.net.AbstractEndpoint;
|
||||
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
|
||||
import org.apache.tomcat.util.net.DispatchType;
|
||||
import org.apache.tomcat.util.net.SSLSupport;
|
||||
import org.apache.tomcat.util.net.SocketEvent;
|
||||
import org.apache.tomcat.util.net.SocketWrapperBase;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Provides functionality and attributes common to all supported protocols
|
||||
* (currently HTTP and AJP) for processing a single request/response.
|
||||
*/
|
||||
public abstract class AbstractProcessor extends AbstractProcessorLight implements ActionHook {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(AbstractProcessor.class);
|
||||
|
||||
// Used to avoid useless B2C conversion on the host name.
|
||||
protected char[] hostNameC = new char[0];
|
||||
|
||||
protected Adapter adapter;
|
||||
protected final AsyncStateMachine asyncStateMachine;
|
||||
private volatile long asyncTimeout = -1;
|
||||
/*
|
||||
* Tracks the current async generation when a timeout is dispatched. In the
|
||||
* time it takes for a container thread to be allocated and the timeout
|
||||
* processing to start, it is possible that the application completes this
|
||||
* generation of async processing and starts a new one. If the timeout is
|
||||
* then processed against the new generation, response mix-up can occur.
|
||||
* This field is used to ensure that any timeout event processed is for the
|
||||
* current async generation. This prevents the response mix-up.
|
||||
*/
|
||||
private volatile long asyncTimeoutGeneration = 0;
|
||||
protected final AbstractEndpoint<?> endpoint;
|
||||
protected final Request request;
|
||||
protected final Response response;
|
||||
protected volatile SocketWrapperBase<?> socketWrapper = null;
|
||||
protected volatile SSLSupport sslSupport;
|
||||
|
||||
|
||||
/**
|
||||
* Error state for the request/response currently being processed.
|
||||
*/
|
||||
private ErrorState errorState = ErrorState.NONE;
|
||||
|
||||
protected final UserDataHelper userDataHelper;
|
||||
|
||||
public AbstractProcessor(AbstractEndpoint<?> endpoint) {
|
||||
this(endpoint, new Request(), new Response());
|
||||
}
|
||||
|
||||
|
||||
protected AbstractProcessor(AbstractEndpoint<?> endpoint, Request coyoteRequest,
|
||||
Response coyoteResponse) {
|
||||
this.endpoint = endpoint;
|
||||
asyncStateMachine = new AsyncStateMachine(this);
|
||||
request = coyoteRequest;
|
||||
response = coyoteResponse;
|
||||
response.setHook(this);
|
||||
request.setResponse(response);
|
||||
request.setHook(this);
|
||||
userDataHelper = new UserDataHelper(getLog());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the current error state to the new error state if the new error
|
||||
* state is more severe than the current error state.
|
||||
* @param errorState The error status details
|
||||
* @param t The error which occurred
|
||||
*/
|
||||
protected void setErrorState(ErrorState errorState, Throwable t) {
|
||||
// Use the return value to avoid processing more than one async error
|
||||
// in a single async cycle.
|
||||
boolean setError = response.setError();
|
||||
boolean blockIo = this.errorState.isIoAllowed() && !errorState.isIoAllowed();
|
||||
this.errorState = this.errorState.getMostSevere(errorState);
|
||||
// Don't change the status code for IOException since that is almost
|
||||
// certainly a client disconnect in which case it is preferable to keep
|
||||
// the original status code http://markmail.org/message/4cxpwmxhtgnrwh7n
|
||||
if (response.getStatus() < 400 && !(t instanceof IOException)) {
|
||||
response.setStatus(500);
|
||||
}
|
||||
if (t != null) {
|
||||
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
|
||||
}
|
||||
if (blockIo && isAsync() && setError) {
|
||||
if (asyncStateMachine.asyncError()) {
|
||||
processSocketEvent(SocketEvent.ERROR, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected ErrorState getErrorState() {
|
||||
return errorState;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Request getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the associated adapter.
|
||||
*
|
||||
* @param adapter the new adapter
|
||||
*/
|
||||
public void setAdapter(Adapter adapter) {
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the associated adapter.
|
||||
*
|
||||
* @return the associated adapter
|
||||
*/
|
||||
public Adapter getAdapter() {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the socket wrapper being used.
|
||||
* @param socketWrapper The socket wrapper
|
||||
*/
|
||||
protected void setSocketWrapper(SocketWrapperBase<?> socketWrapper) {
|
||||
this.socketWrapper = socketWrapper;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the socket wrapper being used.
|
||||
*/
|
||||
protected final SocketWrapperBase<?> getSocketWrapper() {
|
||||
return socketWrapper;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void setSslSupport(SSLSupport sslSupport) {
|
||||
this.sslSupport = sslSupport;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the Executor used by the underlying endpoint.
|
||||
*/
|
||||
protected Executor getExecutor() {
|
||||
return endpoint.getExecutor();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isAsync() {
|
||||
return asyncStateMachine.isAsync();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SocketState asyncPostProcess() {
|
||||
return asyncStateMachine.asyncPostProcess();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final SocketState dispatch(SocketEvent status) throws IOException {
|
||||
|
||||
if (status == SocketEvent.OPEN_WRITE && response.getWriteListener() != null) {
|
||||
asyncStateMachine.asyncOperation();
|
||||
try {
|
||||
if (flushBufferedWrite()) {
|
||||
return SocketState.LONG;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (getLog().isDebugEnabled()) {
|
||||
getLog().debug("Unable to write async data.", ioe);
|
||||
}
|
||||
status = SocketEvent.ERROR;
|
||||
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
|
||||
}
|
||||
} else if (status == SocketEvent.OPEN_READ && request.getReadListener() != null) {
|
||||
dispatchNonBlockingRead();
|
||||
} else if (status == SocketEvent.ERROR) {
|
||||
// An I/O error occurred on a non-container thread. This includes:
|
||||
// - read/write timeouts fired by the Poller (NIO & APR)
|
||||
// - completion handler failures in NIO2
|
||||
|
||||
if (request.getAttribute(RequestDispatcher.ERROR_EXCEPTION) == null) {
|
||||
// Because the error did not occur on a container thread the
|
||||
// request's error attribute has not been set. If an exception
|
||||
// is available from the socketWrapper, use it to set the
|
||||
// request's error attribute here so it is visible to the error
|
||||
// handling.
|
||||
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, socketWrapper.getError());
|
||||
}
|
||||
|
||||
if (request.getReadListener() != null || response.getWriteListener() != null) {
|
||||
// The error occurred during non-blocking I/O. Set the correct
|
||||
// state else the error handling will trigger an ISE.
|
||||
asyncStateMachine.asyncOperation();
|
||||
}
|
||||
}
|
||||
|
||||
RequestInfo rp = request.getRequestProcessor();
|
||||
try {
|
||||
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
|
||||
if (!getAdapter().asyncDispatch(request, response, status)) {
|
||||
setErrorState(ErrorState.CLOSE_NOW, null);
|
||||
}
|
||||
} catch (InterruptedIOException e) {
|
||||
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
|
||||
} catch (Throwable t) {
|
||||
ExceptionUtils.handleThrowable(t);
|
||||
setErrorState(ErrorState.CLOSE_NOW, t);
|
||||
getLog().error(sm.getString("http11processor.request.process"), t);
|
||||
}
|
||||
|
||||
rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
|
||||
|
||||
SocketState state;
|
||||
|
||||
if (getErrorState().isError()) {
|
||||
request.updateCounters();
|
||||
state = SocketState.CLOSED;
|
||||
} else if (isAsync()) {
|
||||
state = SocketState.LONG;
|
||||
} else {
|
||||
request.updateCounters();
|
||||
state = dispatchEndRequest();
|
||||
}
|
||||
|
||||
if (getLog().isDebugEnabled()) {
|
||||
getLog().debug("Socket: [" + socketWrapper +
|
||||
"], Status in: [" + status +
|
||||
"], State out: [" + state + "]");
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
protected void parseHost(MessageBytes valueMB) {
|
||||
if (valueMB == null || valueMB.isNull()) {
|
||||
populateHost();
|
||||
populatePort();
|
||||
return;
|
||||
} else if (valueMB.getLength() == 0) {
|
||||
// Empty Host header so set sever name to empty string
|
||||
request.serverName().setString("");
|
||||
populatePort();
|
||||
return;
|
||||
}
|
||||
|
||||
ByteChunk valueBC = valueMB.getByteChunk();
|
||||
byte[] valueB = valueBC.getBytes();
|
||||
int valueL = valueBC.getLength();
|
||||
int valueS = valueBC.getStart();
|
||||
if (hostNameC.length < valueL) {
|
||||
hostNameC = new char[valueL];
|
||||
}
|
||||
|
||||
try {
|
||||
// Validates the host name
|
||||
int colonPos = Host.parse(valueMB);
|
||||
|
||||
// Extract the port information first, if any
|
||||
if (colonPos != -1) {
|
||||
int port = 0;
|
||||
for (int i = colonPos + 1; i < valueL; i++) {
|
||||
char c = (char) valueB[i + valueS];
|
||||
if (c < '0' || c > '9') {
|
||||
response.setStatus(400);
|
||||
setErrorState(ErrorState.CLOSE_CLEAN, null);
|
||||
return;
|
||||
}
|
||||
port = port * 10 + c - '0';
|
||||
}
|
||||
request.setServerPort(port);
|
||||
|
||||
// Only need to copy the host name up to the :
|
||||
valueL = colonPos;
|
||||
}
|
||||
|
||||
// Extract the host name
|
||||
for (int i = 0; i < valueL; i++) {
|
||||
hostNameC[i] = (char) valueB[i + valueS];
|
||||
}
|
||||
request.serverName().setChars(hostNameC, 0, valueL);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
// IllegalArgumentException indicates that the host name is invalid
|
||||
UserDataHelper.Mode logMode = userDataHelper.getNextMode();
|
||||
if (logMode != null) {
|
||||
String message = sm.getString("abstractProcessor.hostInvalid", valueMB.toString());
|
||||
switch (logMode) {
|
||||
case INFO_THEN_DEBUG:
|
||||
message += sm.getString("abstractProcessor.fallToDebug");
|
||||
//$FALL-THROUGH$
|
||||
case INFO:
|
||||
getLog().info(message, e);
|
||||
break;
|
||||
case DEBUG:
|
||||
getLog().debug(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
response.setStatus(400);
|
||||
setErrorState(ErrorState.CLOSE_CLEAN, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a host header is not present in the request (e.g. HTTP/1.0).
|
||||
* It populates the server name with appropriate information. The source is
|
||||
* expected to vary by protocol.
|
||||
* <p>
|
||||
* The default implementation is a NO-OP.
|
||||
*/
|
||||
protected void populateHost() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a host header is not present or is empty in the request (e.g.
|
||||
* HTTP/1.0). It populates the server port with appropriate information. The
|
||||
* source is expected to vary by protocol.
|
||||
* <p>
|
||||
* The default implementation is a NO-OP.
|
||||
*/
|
||||
protected void populatePort() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void action(ActionCode actionCode, Object param) {
|
||||
switch (actionCode) {
|
||||
// 'Normal' servlet support
|
||||
case COMMIT: {
|
||||
if (!response.isCommitted()) {
|
||||
try {
|
||||
// Validate and write response headers
|
||||
prepareResponse();
|
||||
} catch (IOException e) {
|
||||
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLOSE: {
|
||||
action(ActionCode.COMMIT, null);
|
||||
try {
|
||||
finishResponse();
|
||||
} catch (CloseNowException cne) {
|
||||
setErrorState(ErrorState.CLOSE_NOW, cne);
|
||||
} catch (IOException e) {
|
||||
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACK: {
|
||||
ack();
|
||||
break;
|
||||
}
|
||||
case CLIENT_FLUSH: {
|
||||
action(ActionCode.COMMIT, null);
|
||||
try {
|
||||
flush();
|
||||
} catch (IOException e) {
|
||||
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
|
||||
response.setErrorException(e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AVAILABLE: {
|
||||
request.setAvailable(available(Boolean.TRUE.equals(param)));
|
||||
break;
|
||||
}
|
||||
case REQ_SET_BODY_REPLAY: {
|
||||
ByteChunk body = (ByteChunk) param;
|
||||
setRequestBody(body);
|
||||
break;
|
||||
}
|
||||
|
||||
// Error handling
|
||||
case IS_ERROR: {
|
||||
((AtomicBoolean) param).set(getErrorState().isError());
|
||||
break;
|
||||
}
|
||||
case IS_IO_ALLOWED: {
|
||||
((AtomicBoolean) param).set(getErrorState().isIoAllowed());
|
||||
break;
|
||||
}
|
||||
case CLOSE_NOW: {
|
||||
// Prevent further writes to the response
|
||||
setSwallowResponse();
|
||||
if (param instanceof Throwable) {
|
||||
setErrorState(ErrorState.CLOSE_NOW, (Throwable) param);
|
||||
} else {
|
||||
setErrorState(ErrorState.CLOSE_NOW, null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DISABLE_SWALLOW_INPUT: {
|
||||
// Aborted upload or similar.
|
||||
// No point reading the remainder of the request.
|
||||
disableSwallowRequest();
|
||||
// This is an error state. Make sure it is marked as such.
|
||||
setErrorState(ErrorState.CLOSE_CLEAN, null);
|
||||
break;
|
||||
}
|
||||
|
||||
// Request attribute support
|
||||
case REQ_HOST_ADDR_ATTRIBUTE: {
|
||||
if (getPopulateRequestAttributesFromSocket() && socketWrapper != null) {
|
||||
request.remoteAddr().setString(socketWrapper.getRemoteAddr());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case REQ_HOST_ATTRIBUTE: {
|
||||
populateRequestAttributeRemoteHost();
|
||||
break;
|
||||
}
|
||||
case REQ_LOCALPORT_ATTRIBUTE: {
|
||||
if (getPopulateRequestAttributesFromSocket() && socketWrapper != null) {
|
||||
request.setLocalPort(socketWrapper.getLocalPort());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case REQ_LOCAL_ADDR_ATTRIBUTE: {
|
||||
if (getPopulateRequestAttributesFromSocket() && socketWrapper != null) {
|
||||
request.localAddr().setString(socketWrapper.getLocalAddr());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case REQ_LOCAL_NAME_ATTRIBUTE: {
|
||||
if (getPopulateRequestAttributesFromSocket() && socketWrapper != null) {
|
||||
request.localName().setString(socketWrapper.getLocalName());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case REQ_REMOTEPORT_ATTRIBUTE: {
|
||||
if (getPopulateRequestAttributesFromSocket() && socketWrapper != null) {
|
||||
request.setRemotePort(socketWrapper.getRemotePort());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// SSL request attribute support
|
||||
case REQ_SSL_ATTRIBUTE: {
|
||||
populateSslRequestAttributes();
|
||||
break;
|
||||
}
|
||||
case REQ_SSL_CERTIFICATE: {
|
||||
try {
|
||||
sslReHandShake();
|
||||
} catch (IOException ioe) {
|
||||
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, ioe);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Servlet 3.0 asynchronous support
|
||||
case ASYNC_START: {
|
||||
asyncStateMachine.asyncStart((AsyncContextCallback) param);
|
||||
break;
|
||||
}
|
||||
case ASYNC_COMPLETE: {
|
||||
clearDispatches();
|
||||
if (asyncStateMachine.asyncComplete()) {
|
||||
processSocketEvent(SocketEvent.OPEN_READ, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ASYNC_DISPATCH: {
|
||||
if (asyncStateMachine.asyncDispatch()) {
|
||||
processSocketEvent(SocketEvent.OPEN_READ, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ASYNC_DISPATCHED: {
|
||||
asyncStateMachine.asyncDispatched();
|
||||
break;
|
||||
}
|
||||
case ASYNC_ERROR: {
|
||||
asyncStateMachine.asyncError();
|
||||
break;
|
||||
}
|
||||
case ASYNC_IS_ASYNC: {
|
||||
((AtomicBoolean) param).set(asyncStateMachine.isAsync());
|
||||
break;
|
||||
}
|
||||
case ASYNC_IS_COMPLETING: {
|
||||
((AtomicBoolean) param).set(asyncStateMachine.isCompleting());
|
||||
break;
|
||||
}
|
||||
case ASYNC_IS_DISPATCHING: {
|
||||
((AtomicBoolean) param).set(asyncStateMachine.isAsyncDispatching());
|
||||
break;
|
||||
}
|
||||
case ASYNC_IS_ERROR: {
|
||||
((AtomicBoolean) param).set(asyncStateMachine.isAsyncError());
|
||||
break;
|
||||
}
|
||||
case ASYNC_IS_STARTED: {
|
||||
((AtomicBoolean) param).set(asyncStateMachine.isAsyncStarted());
|
||||
break;
|
||||
}
|
||||
case ASYNC_IS_TIMINGOUT: {
|
||||
((AtomicBoolean) param).set(asyncStateMachine.isAsyncTimingOut());
|
||||
break;
|
||||
}
|
||||
case ASYNC_RUN: {
|
||||
asyncStateMachine.asyncRun((Runnable) param);
|
||||
break;
|
||||
}
|
||||
case ASYNC_SETTIMEOUT: {
|
||||
if (param == null) {
|
||||
return;
|
||||
}
|
||||
long timeout = ((Long) param).longValue();
|
||||
setAsyncTimeout(timeout);
|
||||
break;
|
||||
}
|
||||
case ASYNC_TIMEOUT: {
|
||||
AtomicBoolean result = (AtomicBoolean) param;
|
||||
result.set(asyncStateMachine.asyncTimeout());
|
||||
break;
|
||||
}
|
||||
case ASYNC_POST_PROCESS: {
|
||||
asyncStateMachine.asyncPostProcess();
|
||||
break;
|
||||
}
|
||||
|
||||
// Servlet 3.1 non-blocking I/O
|
||||
case REQUEST_BODY_FULLY_READ: {
|
||||
AtomicBoolean result = (AtomicBoolean) param;
|
||||
result.set(isRequestBodyFullyRead());
|
||||
break;
|
||||
}
|
||||
case NB_READ_INTEREST: {
|
||||
AtomicBoolean isReady = (AtomicBoolean)param;
|
||||
isReady.set(isReadyForRead());
|
||||
break;
|
||||
}
|
||||
case NB_WRITE_INTEREST: {
|
||||
AtomicBoolean isReady = (AtomicBoolean)param;
|
||||
isReady.set(isReadyForWrite());
|
||||
break;
|
||||
}
|
||||
case DISPATCH_READ: {
|
||||
addDispatch(DispatchType.NON_BLOCKING_READ);
|
||||
break;
|
||||
}
|
||||
case DISPATCH_WRITE: {
|
||||
addDispatch(DispatchType.NON_BLOCKING_WRITE);
|
||||
break;
|
||||
}
|
||||
case DISPATCH_EXECUTE: {
|
||||
executeDispatches();
|
||||
break;
|
||||
}
|
||||
|
||||
// Servlet 3.1 HTTP Upgrade
|
||||
case UPGRADE: {
|
||||
doHttpUpgrade((UpgradeToken) param);
|
||||
break;
|
||||
}
|
||||
|
||||
// Servlet 4.0 Push requests
|
||||
case IS_PUSH_SUPPORTED: {
|
||||
AtomicBoolean result = (AtomicBoolean) param;
|
||||
result.set(isPushSupported());
|
||||
break;
|
||||
}
|
||||
case PUSH_REQUEST: {
|
||||
doPush((Request) param);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform any necessary processing for a non-blocking read before
|
||||
* dispatching to the adapter.
|
||||
*/
|
||||
protected void dispatchNonBlockingRead() {
|
||||
asyncStateMachine.asyncOperation();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Sub-classes of this base class represent a single request/response pair.
|
||||
* The timeout to be processed is, therefore, the Servlet asynchronous
|
||||
* processing timeout.
|
||||
*/
|
||||
@Override
|
||||
public void timeoutAsync(long now) {
|
||||
if (now < 0) {
|
||||
doTimeoutAsync();
|
||||
} else {
|
||||
long asyncTimeout = getAsyncTimeout();
|
||||
if (asyncTimeout > 0) {
|
||||
long asyncStart = asyncStateMachine.getLastAsyncStart();
|
||||
if ((now - asyncStart) > asyncTimeout) {
|
||||
doTimeoutAsync();
|
||||
}
|
||||
} else if (!asyncStateMachine.isAvailable()) {
|
||||
// Timeout the async process if the associated web application
|
||||
// is no longer running.
|
||||
doTimeoutAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void doTimeoutAsync() {
|
||||
// Avoid multiple timeouts
|
||||
setAsyncTimeout(-1);
|
||||
asyncTimeoutGeneration = asyncStateMachine.getCurrentGeneration();
|
||||
processSocketEvent(SocketEvent.TIMEOUT, true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean checkAsyncTimeoutGeneration() {
|
||||
return asyncTimeoutGeneration == asyncStateMachine.getCurrentGeneration();
|
||||
}
|
||||
|
||||
|
||||
public void setAsyncTimeout(long timeout) {
|
||||
asyncTimeout = timeout;
|
||||
}
|
||||
|
||||
|
||||
public long getAsyncTimeout() {
|
||||
return asyncTimeout;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
errorState = ErrorState.NONE;
|
||||
asyncStateMachine.recycle();
|
||||
}
|
||||
|
||||
|
||||
protected abstract void prepareResponse() throws IOException;
|
||||
|
||||
|
||||
protected abstract void finishResponse() throws IOException;
|
||||
|
||||
|
||||
protected abstract void ack();
|
||||
|
||||
|
||||
protected abstract void flush() throws IOException;
|
||||
|
||||
|
||||
protected abstract int available(boolean doRead);
|
||||
|
||||
|
||||
protected abstract void setRequestBody(ByteChunk body);
|
||||
|
||||
|
||||
protected abstract void setSwallowResponse();
|
||||
|
||||
|
||||
protected abstract void disableSwallowRequest();
|
||||
|
||||
|
||||
/**
|
||||
* Processors that populate request attributes directly (e.g. AJP) should
|
||||
* over-ride this method and return {@code false}.
|
||||
*
|
||||
* @return {@code true} if the SocketWrapper should be used to populate the
|
||||
* request attributes, otherwise {@code false}.
|
||||
*/
|
||||
protected boolean getPopulateRequestAttributesFromSocket() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populate the remote host request attribute. Processors (e.g. AJP) that
|
||||
* populate this from an alternative source should override this method.
|
||||
*/
|
||||
protected void populateRequestAttributeRemoteHost() {
|
||||
if (getPopulateRequestAttributesFromSocket() && socketWrapper != null) {
|
||||
request.remoteHost().setString(socketWrapper.getRemoteHost());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populate the TLS related request attributes from the {@link SSLSupport}
|
||||
* instance associated with this processor. Protocols that populate TLS
|
||||
* attributes from a different source (e.g. AJP) should override this
|
||||
* method.
|
||||
*/
|
||||
protected void populateSslRequestAttributes() {
|
||||
try {
|
||||
if (sslSupport != null) {
|
||||
Object sslO = sslSupport.getCipherSuite();
|
||||
if (sslO != null) {
|
||||
request.setAttribute(SSLSupport.CIPHER_SUITE_KEY, sslO);
|
||||
}
|
||||
sslO = sslSupport.getPeerCertificateChain();
|
||||
if (sslO != null) {
|
||||
request.setAttribute(SSLSupport.CERTIFICATE_KEY, sslO);
|
||||
}
|
||||
sslO = sslSupport.getKeySize();
|
||||
if (sslO != null) {
|
||||
request.setAttribute (SSLSupport.KEY_SIZE_KEY, sslO);
|
||||
}
|
||||
sslO = sslSupport.getSessionId();
|
||||
if (sslO != null) {
|
||||
request.setAttribute(SSLSupport.SESSION_ID_KEY, sslO);
|
||||
}
|
||||
sslO = sslSupport.getProtocol();
|
||||
if (sslO != null) {
|
||||
request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, sslO);
|
||||
}
|
||||
request.setAttribute(SSLSupport.SESSION_MGR, sslSupport);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getLog().warn(sm.getString("abstractProcessor.socket.ssl"), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processors that can perform a TLS re-handshake (e.g. HTTP/1.1) should
|
||||
* override this method and implement the re-handshake.
|
||||
*
|
||||
* @throws IOException If authentication is required then there will be I/O
|
||||
* with the client and this exception will be thrown if
|
||||
* that goes wrong
|
||||
*/
|
||||
protected void sslReHandShake() throws IOException {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
protected void processSocketEvent(SocketEvent event, boolean dispatch) {
|
||||
SocketWrapperBase<?> socketWrapper = getSocketWrapper();
|
||||
if (socketWrapper != null) {
|
||||
socketWrapper.processSocket(event, dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected boolean isReadyForRead() {
|
||||
if (available(true) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isRequestBodyFullyRead()) {
|
||||
registerReadInterest();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected abstract boolean isRequestBodyFullyRead();
|
||||
|
||||
|
||||
protected abstract void registerReadInterest();
|
||||
|
||||
|
||||
protected abstract boolean isReadyForWrite();
|
||||
|
||||
|
||||
protected void executeDispatches() {
|
||||
SocketWrapperBase<?> socketWrapper = getSocketWrapper();
|
||||
Iterator<DispatchType> dispatches = getIteratorAndClearDispatches();
|
||||
if (socketWrapper != null) {
|
||||
synchronized (socketWrapper) {
|
||||
/*
|
||||
* This method is called when non-blocking IO is initiated by defining
|
||||
* a read and/or write listener in a non-container thread. It is called
|
||||
* once the non-container thread completes so that the first calls to
|
||||
* onWritePossible() and/or onDataAvailable() as appropriate are made by
|
||||
* the container.
|
||||
*
|
||||
* Processing the dispatches requires (for APR/native at least)
|
||||
* that the socket has been added to the waitingRequests queue. This may
|
||||
* not have occurred by the time that the non-container thread completes
|
||||
* triggering the call to this method. Therefore, the coded syncs on the
|
||||
* SocketWrapper as the container thread that initiated this
|
||||
* non-container thread holds a lock on the SocketWrapper. The container
|
||||
* thread will add the socket to the waitingRequests queue before
|
||||
* releasing the lock on the socketWrapper. Therefore, by obtaining the
|
||||
* lock on socketWrapper before processing the dispatches, we can be
|
||||
* sure that the socket has been added to the waitingRequests queue.
|
||||
*/
|
||||
while (dispatches != null && dispatches.hasNext()) {
|
||||
DispatchType dispatchType = dispatches.next();
|
||||
socketWrapper.processSocket(dispatchType.getSocketStatus(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* Processors that implement HTTP upgrade must override this method and
|
||||
* provide the necessary token.
|
||||
*/
|
||||
@Override
|
||||
public UpgradeToken getUpgradeToken() {
|
||||
// Should never reach this code but in case we do...
|
||||
throw new IllegalStateException(
|
||||
sm.getString("abstractProcessor.httpupgrade.notsupported"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process an HTTP upgrade. Processors that support HTTP upgrade should
|
||||
* override this method and process the provided token.
|
||||
*
|
||||
* @param upgradeToken Contains all the information necessary for the
|
||||
* Processor to process the upgrade
|
||||
*
|
||||
* @throws UnsupportedOperationException if the protocol does not support
|
||||
* HTTP upgrade
|
||||
*/
|
||||
protected void doHttpUpgrade(UpgradeToken upgradeToken) {
|
||||
// Should never happen
|
||||
throw new UnsupportedOperationException(
|
||||
sm.getString("abstractProcessor.httpupgrade.notsupported"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* Processors that implement HTTP upgrade must override this method.
|
||||
*/
|
||||
@Override
|
||||
public ByteBuffer getLeftoverInput() {
|
||||
// Should never reach this code but in case we do...
|
||||
throw new IllegalStateException(sm.getString("abstractProcessor.httpupgrade.notsupported"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* Processors that implement HTTP upgrade must override this method.
|
||||
*/
|
||||
@Override
|
||||
public boolean isUpgrade() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Protocols that support push should override this method and return {@code
|
||||
* true}.
|
||||
*
|
||||
* @return {@code true} if push is supported by this processor, otherwise
|
||||
* {@code false}.
|
||||
*/
|
||||
protected boolean isPushSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process a push. Processors that support push should override this method
|
||||
* and process the provided token.
|
||||
*
|
||||
* @param pushTarget Contains all the information necessary for the Processor
|
||||
* to process the push request
|
||||
*
|
||||
* @throws UnsupportedOperationException if the protocol does not support
|
||||
* push
|
||||
*/
|
||||
protected void doPush(Request pushTarget) {
|
||||
throw new UnsupportedOperationException(
|
||||
sm.getString("abstractProcessor.pushrequest.notsupported"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flush any pending writes. Used during non-blocking writes to flush any
|
||||
* remaining data from a previous incomplete write.
|
||||
*
|
||||
* @return <code>true</code> if data remains to be flushed at the end of
|
||||
* method
|
||||
*
|
||||
* @throws IOException If an I/O error occurs while attempting to flush the
|
||||
* data
|
||||
*/
|
||||
protected abstract boolean flushBufferedWrite() throws IOException ;
|
||||
|
||||
|
||||
/**
|
||||
* Perform any necessary clean-up processing if the dispatch resulted in the
|
||||
* completion of processing for the current request.
|
||||
*
|
||||
* @return The state to return for the socket once the clean-up for the
|
||||
* current request has completed
|
||||
*
|
||||
* @throws IOException If an I/O error occurs while attempting to end the
|
||||
* request
|
||||
*/
|
||||
protected abstract SocketState dispatchEndRequest() throws IOException;
|
||||
|
||||
|
||||
@Override
|
||||
protected final void logAccess(SocketWrapperBase<?> socketWrapper) throws IOException {
|
||||
// Set the socket wrapper so the access log can read the socket related
|
||||
// information (e.g. client IP)
|
||||
setSocketWrapper(socketWrapper);
|
||||
// Setup the minimal request information
|
||||
request.setStartTime(System.currentTimeMillis());
|
||||
// Setup the minimal response information
|
||||
response.setStatus(400);
|
||||
response.setError();
|
||||
getAdapter().log(request, response, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user