init
This commit is contained in:
401
java/org/apache/coyote/http2/StreamProcessor.java
Normal file
401
java/org/apache/coyote/http2/StreamProcessor.java
Normal file
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* 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.http2;
|
||||
|
||||
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.ErrorState;
|
||||
import org.apache.coyote.Request;
|
||||
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.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;
|
||||
|
||||
|
||||
StreamProcessor(Http2UpgradeHandler handler, Stream stream, Adapter adapter,
|
||||
SocketWrapperBase<?> socketWrapper) {
|
||||
super(socketWrapper.getEndpoint(), stream.getCoyoteRequest(), stream.getCoyoteResponse());
|
||||
this.handler = handler;
|
||||
this.stream = stream;
|
||||
setAdapter(adapter);
|
||||
setSocketWrapper(socketWrapper);
|
||||
}
|
||||
|
||||
|
||||
final void process(SocketEvent event) {
|
||||
try {
|
||||
// FIXME: the regular processor syncs on socketWrapper, but here this deadlocks
|
||||
synchronized (this) {
|
||||
// HTTP/2 equivalent of AbstractConnectionHandler#process() without the
|
||||
// socket <-> processor mapping
|
||||
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.getIdentifier()), 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.getIdentifier()), Http2Error.INTERNAL_ERROR,
|
||||
stream.getIdAsInt());
|
||||
}
|
||||
stream.close(se);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String msg = sm.getString("streamProcessor.error.connection",
|
||||
stream.getConnectionId(), stream.getIdentifier());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(msg, e);
|
||||
}
|
||||
ConnectionException ce = new ConnectionException(msg, Http2Error.INTERNAL_ERROR);
|
||||
ce.initCause(e);
|
||||
stream.close(ce);
|
||||
} finally {
|
||||
ContainerThreadMarker.clear();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
handler.executeQueuedStream();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final void prepareResponse() throws IOException {
|
||||
response.setCommitted(true);
|
||||
prepareHeaders(request, response, handler.getProtocol(), stream);
|
||||
stream.writeHeaders();
|
||||
}
|
||||
|
||||
|
||||
// Static so it can be used by Stream to build the MimeHeaders required for
|
||||
// an ACK. For that use case coyoteRequest, protocol and stream will be null.
|
||||
static void prepareHeaders(Request coyoteRequest, Response coyoteResponse,
|
||||
Http2Protocol protocol, Stream stream) {
|
||||
MimeHeaders headers = coyoteResponse.getMimeHeaders();
|
||||
int statusCode = coyoteResponse.getStatus();
|
||||
|
||||
// Add the pseudo header for status
|
||||
headers.addValue(":status").setString(Integer.toString(statusCode));
|
||||
|
||||
// Check to see if a response body is present
|
||||
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);
|
||||
}
|
||||
// Add a content-length header if a content length has been set unless
|
||||
// the application has already added one
|
||||
long contentLength = coyoteResponse.getContentLengthLong();
|
||||
if (contentLength != -1 && headers.getValue("content-length") == null) {
|
||||
headers.addValue("content-length").setLong(contentLength);
|
||||
}
|
||||
} else {
|
||||
if (statusCode == 205) {
|
||||
// RFC 7231 requires the server to explicitly signal an empty
|
||||
// response in this case
|
||||
coyoteResponse.setContentLength(0);
|
||||
} else {
|
||||
coyoteResponse.setContentLength(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// Add date header unless it is an informational response or the
|
||||
// application has already set one
|
||||
if (statusCode >= 200 && headers.getValue("date") == null) {
|
||||
headers.addValue("date").setString(FastHttpDateFormat.getCurrentDate());
|
||||
}
|
||||
|
||||
if (protocol != null && protocol.useCompression(coyoteRequest, coyoteResponse)) {
|
||||
// Enable compression. Headers will have been set. Need to configure
|
||||
// output filter at this point.
|
||||
stream.addOutputFilter(new GzipOutputFilter());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final void finishResponse() throws IOException {
|
||||
stream.getOutputBuffer().end();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final void ack() {
|
||||
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) {
|
||||
// Exception will not be thrown in this case
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final void setSwallowResponse() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final void disableSwallowRequest() {
|
||||
// NO-OP
|
||||
// HTTP/2 has to swallow any input received to ensure that the flow
|
||||
// control windows are correctly tracked.
|
||||
}
|
||||
|
||||
|
||||
@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() {
|
||||
// Should never be called for StreamProcessor as isReadyForRead() is
|
||||
// overridden
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final boolean isReadyForWrite() {
|
||||
return stream.isReadyForWrite();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final void executeDispatches() {
|
||||
Iterator<DispatchType> dispatches = getIteratorAndClearDispatches();
|
||||
/*
|
||||
* Compare with superclass that uses SocketWrapper
|
||||
* A sync is not necessary here as the window sizes are updated with
|
||||
* syncs before the dispatches are executed and it is the window size
|
||||
* updates that need to be complete before the dispatch executes.
|
||||
*/
|
||||
while (dispatches != null && dispatches.hasNext()) {
|
||||
DispatchType dispatchType = dispatches.next();
|
||||
/*
|
||||
* Dispatch on new thread.
|
||||
* Firstly, this avoids a deadlock on the SocketWrapper as Streams
|
||||
* being processed by container threads lock the SocketProcessor
|
||||
* before they lock the SocketWrapper which is the opposite order to
|
||||
* container threads processing via Http2UpgrageHandler.
|
||||
* Secondly, this code executes after a Window update has released
|
||||
* one or more Streams. By dispatching each Stream to a dedicated
|
||||
* thread, those Streams may progress concurrently.
|
||||
*/
|
||||
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
|
||||
public void recycle() {
|
||||
// StreamProcessor instances are not re-used.
|
||||
// Clear fields that can be cleared to aid GC and trigger NPEs if this
|
||||
// is reused
|
||||
setSocketWrapper(null);
|
||||
setAdapter(null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Log getLog() {
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
// NO-OP. Handled by the Http2UpgradeHandler
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public 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()) {
|
||||
// If this is an async request then the request ends when it has
|
||||
// been completed. The AsyncContext is responsible for calling
|
||||
// endRequest() in that case.
|
||||
endRequest();
|
||||
}
|
||||
|
||||
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 boolean flushBufferedWrite() throws IOException {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("streamProcessor.flushBufferedWrite.entry",
|
||||
stream.getConnectionId(), stream.getIdentifier()));
|
||||
}
|
||||
if (stream.flush(false)) {
|
||||
// The buffer wasn't fully flushed so re-register the
|
||||
// stream for write. Note this does not go via the
|
||||
// Response since the write registration state at
|
||||
// that level should remain unchanged. Once the buffer
|
||||
// has been emptied then the code below will call
|
||||
// dispatch() which will enable the
|
||||
// Response to respond to this event.
|
||||
if (stream.isReadyForWrite()) {
|
||||
// Unexpected
|
||||
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()) {
|
||||
// The request has been processed but the request body has not been
|
||||
// fully read. This typically occurs when Tomcat rejects an upload
|
||||
// of some form (e.g. PUT or POST). Need to tell the client not to
|
||||
// send any more data but only if a reset has not already been
|
||||
// triggered.
|
||||
StreamException se = new StreamException(
|
||||
sm.getString("streamProcessor.cancel", stream.getConnectionId(),
|
||||
stream.getIdentifier()), Http2Error.CANCEL, stream.getIdAsInt());
|
||||
handler.sendStreamReset(se);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user