package org.apache.coyote.http2;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import org.apache.coyote.AbstractProcessor;
import org.apache.coyote.ActionCode;
import org.apache.coyote.Adapter;
import org.apache.coyote.ContainerThreadMarker;
import org.apache.coyote.ContinueResponseTiming;
import org.apache.coyote.ErrorState;
import org.apache.coyote.Request;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.Response;
import org.apache.coyote.http11.filters.GzipOutputFilter;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.http.FastHttpDateFormat;
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
import org.apache.tomcat.util.net.DispatchType;
import org.apache.tomcat.util.net.SendfileState;
import org.apache.tomcat.util.net.SocketEvent;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.res.StringManager;
class StreamProcessor extends AbstractProcessor {
private static final Log log = LogFactory.getLog(StreamProcessor.class);
private static final StringManager sm = StringManager.getManager(StreamProcessor.class);
private final Http2UpgradeHandler handler;
private final Stream stream;
private SendfileData sendfileData = null;
private SendfileState sendfileState = null;
StreamProcessor(Http2UpgradeHandler handler, Stream stream, Adapter adapter,
SocketWrapperBase<?> socketWrapper) {
super(adapter, stream.getCoyoteRequest(), stream.getCoyoteResponse());
this.handler = handler;
this.stream = stream;
setSocketWrapper(socketWrapper);
}
final void process(SocketEvent event) {
try {
synchronized (this) {
ContainerThreadMarker.set();
SocketState state = SocketState.CLOSED;
try {
state = process(socketWrapper, event);
if (state == SocketState.LONG) {
handler.getProtocol().getHttp11Protocol().addWaitingProcessor(this);
} else if (state == SocketState.CLOSED) {
handler.getProtocol().getHttp11Protocol().removeWaitingProcessor(this);
if (!getErrorState().isConnectionIoAllowed()) {
ConnectionException ce = new ConnectionException(sm.getString(
"streamProcessor.error.connection", stream.getConnectionId(),
stream.getIdAsString()), Http2Error.INTERNAL_ERROR);
stream.close(ce);
} else if (!getErrorState().isIoAllowed()) {
StreamException se = stream.getResetException();
if (se == null) {
se = new StreamException(sm.getString(
"streamProcessor.error.stream", stream.getConnectionId(),
stream.getIdAsString()), Http2Error.INTERNAL_ERROR,
stream.getIdAsInt());
}
stream.close(se);
} else {
if (!stream.isActive()) {
stream.recycle();
}
}
}
} catch (Exception e) {
String msg = sm.getString("streamProcessor.error.connection",
stream.getConnectionId(), stream.getIdAsString());
if (log.isDebugEnabled()) {
log.debug(msg, e);
}
ConnectionException ce = new ConnectionException(msg, Http2Error.INTERNAL_ERROR);
ce.initCause(e);
stream.close(ce);
state = SocketState.CLOSED;
} finally {
if (state == SocketState.CLOSED) {
recycle();
}
ContainerThreadMarker.clear();
}
}
} finally {
handler.executeQueuedStream();
}
}
@Override
protected final void prepareResponse() throws IOException {
response.setCommitted(true);
if (handler.hasAsyncIO() && handler.getProtocol().getUseSendfile()) {
prepareSendfile();
}
prepareHeaders(request, response, sendfileData == null, handler.getProtocol(), stream);
stream.writeHeaders();
}
private void prepareSendfile() {
String fileName = (String) stream.getCoyoteRequest().getAttribute(
org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
if (fileName != null) {
sendfileData = new SendfileData();
sendfileData.path = new File(fileName).toPath();
sendfileData.pos = ((Long) stream.getCoyoteRequest().getAttribute(
org.apache.coyote.Constants.SENDFILE_FILE_START_ATTR)).longValue();
sendfileData.end = ((Long) stream.getCoyoteRequest().getAttribute(
org.apache.coyote.Constants.SENDFILE_FILE_END_ATTR)).longValue();
sendfileData.left = sendfileData.end - sendfileData.pos;
sendfileData.stream = stream;
}
}
static void (Request coyoteRequest, Response coyoteResponse, boolean noSendfile,
Http2Protocol protocol, Stream stream) {
MimeHeaders headers = coyoteResponse.getMimeHeaders();
int statusCode = coyoteResponse.getStatus();
headers.addValue(":status").setString(Integer.toString(statusCode));
if (noSendfile && protocol != null &&
protocol.useCompression(coyoteRequest, coyoteResponse)) {
stream.addOutputFilter(new GzipOutputFilter());
}
if (!(statusCode < 200 || statusCode == 204 || statusCode == 205 || statusCode == 304)) {
String contentType = coyoteResponse.getContentType();
if (contentType != null) {
headers.setValue("content-type").setString(contentType);
}
String contentLanguage = coyoteResponse.getContentLanguage();
if (contentLanguage != null) {
headers.setValue("content-language").setString(contentLanguage);
}
long contentLength = coyoteResponse.getContentLengthLong();
if (contentLength != -1 && headers.getValue("content-length") == null) {
headers.addValue("content-length").setLong(contentLength);
}
} else {
if (statusCode == 205) {
coyoteResponse.setContentLength(0);
} else {
coyoteResponse.setContentLength(-1);
}
}
if (statusCode >= 200 && headers.getValue("date") == null) {
headers.addValue("date").setString(FastHttpDateFormat.getCurrentDate());
}
}
@Override
protected final void finishResponse() throws IOException {
sendfileState = handler.processSendfile(sendfileData);
if (!(sendfileState == SendfileState.PENDING)) {
stream.getOutputBuffer().end();
}
}
@Override
protected final void ack(ContinueResponseTiming continueResponseTiming) {
if (continueResponseTiming == ContinueResponseTiming.ALWAYS ||
continueResponseTiming == handler.getProtocol().getContinueResponseTimingInternal()) {
if (!response.isCommitted() && request.hasExpectation()) {
try {
stream.writeAck();
} catch (IOException ioe) {
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, ioe);
}
}
}
}
@Override
protected final void flush() throws IOException {
stream.getOutputBuffer().flush();
}
@Override
protected final int available(boolean doRead) {
return stream.getInputBuffer().available();
}
@Override
protected final void setRequestBody(ByteChunk body) {
stream.getInputBuffer().insertReplayedBody(body);
try {
stream.receivedEndOfStream();
} catch (ConnectionException e) {
}
}
@Override
protected final void setSwallowResponse() {
}
@Override
protected final void disableSwallowRequest() {
}
@Override
protected void processSocketEvent(SocketEvent event, boolean dispatch) {
if (dispatch) {
handler.processStreamOnContainerThread(this, event);
} else {
this.process(event);
}
}
@Override
protected final boolean isReadyForRead() {
return stream.getInputBuffer().isReadyForRead();
}
@Override
protected final boolean isRequestBodyFullyRead() {
return stream.getInputBuffer().isRequestBodyFullyRead();
}
@Override
protected final void registerReadInterest() {
throw new UnsupportedOperationException();
}
@Override
protected final boolean isReadyForWrite() {
return stream.isReadyForWrite();
}
@Override
protected final void executeDispatches() {
Iterator<DispatchType> dispatches = getIteratorAndClearDispatches();
while (dispatches != null && dispatches.hasNext()) {
DispatchType dispatchType = dispatches.next();
processSocketEvent(dispatchType.getSocketStatus(), true);
}
}
@Override
protected final boolean isPushSupported() {
return stream.isPushSupported();
}
@Override
protected final void doPush(Request pushTarget) {
try {
stream.push(pushTarget);
} catch (IOException ioe) {
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, ioe);
response.setErrorException(ioe);
}
}
@Override
protected boolean isTrailerFieldsReady() {
return stream.isTrailerFieldsReady();
}
@Override
protected boolean isTrailerFieldsSupported() {
return stream.isTrailerFieldsSupported();
}
@Override
protected Object getConnectionID() {
return stream.getConnectionId();
}
@Override
protected Object getStreamID() {
return stream.getIdAsString().toString();
}
@Override
public final void recycle() {
RequestGroupInfo global = handler.getProtocol().getGlobal();
if (global != null) {
global.removeRequestProcessor(request.getRequestProcessor());
}
setSocketWrapper(null);
}
@Override
protected final Log getLog() {
return log;
}
@Override
public final void pause() {
}
@Override
public final SocketState service(SocketWrapperBase<?> socket) throws IOException {
try {
adapter.service(request, response);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("streamProcessor.service.error"), e);
}
response.setStatus(500);
setErrorState(ErrorState.CLOSE_NOW, e);
}
if (!isAsync()) {
endRequest();
}
if (sendfileState == SendfileState.PENDING) {
return SocketState.SENDFILE;
} else if (getErrorState().isError()) {
action(ActionCode.CLOSE, null);
request.updateCounters();
return SocketState.CLOSED;
} else if (isAsync()) {
return SocketState.LONG;
} else {
action(ActionCode.CLOSE, null);
request.updateCounters();
return SocketState.CLOSED;
}
}
@Override
protected final boolean flushBufferedWrite() throws IOException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("streamProcessor.flushBufferedWrite.entry",
stream.getConnectionId(), stream.getIdAsString()));
}
if (stream.flush(false)) {
if (stream.isReadyForWrite()) {
throw new IllegalStateException();
}
return true;
}
return false;
}
@Override
protected final SocketState dispatchEndRequest() throws IOException {
endRequest();
return SocketState.CLOSED;
}
private void endRequest() throws IOException {
if (!stream.isInputFinished() && getErrorState().isIoAllowed()) {
if (handler.hasAsyncIO() && !stream.isContentLengthInconsistent()) {
return;
}
StreamException se = new StreamException(
sm.getString("streamProcessor.cancel", stream.getConnectionId(),
stream.getIdAsString()), Http2Error.CANCEL, stream.getIdAsInt());
handler.sendStreamReset(se);
}
}
}