/* * 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;
finalvoid process(SocketEvent event) { try { // Note: The regular processor uses the socketWrapper lock, but using that here triggers a deadlock
processLock.lock(); try { // HTTP/2 equivalent of AbstractConnectionHandler#process() without the // socket <-> processor mapping
SocketState state = SocketState.CLOSED; try {
state = process(socketWrapper, event);
if (state == SocketState.LONG) {
handler.getProtocol().getHttp11Protocol().addWaitingProcessor(this);
} elseif (state == SocketState.CLOSED) {
handler.getProtocol().getHttp11Protocol().removeWaitingProcessor(this); 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 on this stream (reset).
StreamException se = new StreamException(sm.getString("streamProcessor.cancel", stream.getConnectionId(),
stream.getIdAsString()), Http2Error.NO_ERROR, stream.getIdAsInt());
stream.close(se);
} elseif (!getErrorState().isConnectionIoAllowed()) {
ConnectionException ce = new ConnectionException(
sm.getString("streamProcessor.error.connection", stream.getConnectionId(),
stream.getIdAsString()),
Http2Error.INTERNAL_ERROR);
stream.close(ce);
} elseif (!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.close() will call recycle so only need it here
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, e);
stream.close(ce);
state = SocketState.CLOSED;
} finally { if (state == SocketState.CLOSED) {
recycle();
}
}
} finally {
processLock.unlock();
}
} finally {
handler.executeQueuedStream();
}
}
// 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. staticvoid prepareHeaders(Request coyoteRequest, Response coyoteResponse, boolean noSendfile,
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));
// Compression can't be used with sendfile // Need to check for compression (and set headers appropriately) before // adding headers below if (noSendfile && 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());
}
// 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 { // Disable response body if (stream != null) {
stream.configureVoidOutputFilter();
} 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());
}
}
@Override protectedfinalvoid ack(ContinueResponseTiming continueResponseTiming) { // Only try and send the ACK for ALWAYS or if the timing of the request // to send the ACK matches the current configuration. 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 protectedfinalvoid setRequestBody(ByteChunk body) {
stream.getInputBuffer().insertReplayedBody(body); try {
stream.receivedEndOfStream();
} catch (ConnectionException e) { // Exception will not be thrown in this case
}
}
@Override protectedfinalvoid disableSwallowRequest() { // NO-OP // HTTP/2 has to swallow any input received to ensure that the flow // control windows are correctly tracked.
}
@Override protectedfinalvoid registerReadInterest() { // Should never be called for StreamProcessor as isReadyForRead() is // overridden thrownew UnsupportedOperationException();
}
@Override protectedfinalvoid 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 publicfinalvoid recycle() { // StreamProcessor instances are not re-used.
// Calling removeRequestProcessor even though the RequestProcesser was // never added will add the values from the RequestProcessor to the // running total for the GlobalRequestProcessor
RequestGroupInfo global = handler.getProtocol().getGlobal(); if (global != null) {
global.removeRequestProcessor(request.getRequestProcessor());
}
// Clear fields that can be cleared to aid GC and trigger NPEs if this // is reused
setSocketWrapper(null);
}
/* * In HTTP/1.1 some aspects of the request are validated as the request is parsed and the request rejected * immediately with a 400 response. These checks are performed in Http11InputBuffer. Because, in Tomcat's HTTP/2 * implementation, incoming frames are processed on one thread while the corresponding request/response is processed * on a separate thread, rejecting invalid requests is more involved. * * One approach would be to validate the request during parsing, note any validation errors and then generate a 400 * response once processing moves to the separate request/response thread. This would require refactoring to track * the validation errors. * * A second approach, and the one currently adopted, is to perform the validation shortly after processing of the * received request passes to the separate thread and to generate a 400 response if validation fails. * * The checks performed below are based on the checks in Http11InputBuffer.
*/ privateboolean validateRequest() {
HttpParser httpParser = new HttpParser(handler.getProtocol().getHttp11Protocol().getRelaxedPathChars(),
handler.getProtocol().getHttp11Protocol().getRelaxedQueryChars());
// Method name must be a token
String method = request.method().toString(); if (!HttpParser.isToken(method)) { returnfalse;
}
// Scheme must adhere to RFC 3986
String scheme = request.scheme().toString(); if (!HttpParser.isScheme(scheme)) { returnfalse;
}
// Invalid character in request target // (other checks such as valid %nn happen later)
ByteChunk bc = request.requestURI().getByteChunk(); for (int i = bc.getStart(); i < bc.getEnd(); i++) { if (httpParser.isNotRequestTargetRelaxed(bc.getBuffer()[i])) { returnfalse;
}
}
// Ensure the query string doesn't contain invalid characters. // (other checks such as valid %nn happen later)
String qs = request.queryString().toString(); if (qs != null) { for (char c : qs.toCharArray()) { if (!httpParser.isQueryRelaxed(c)) { returnfalse;
}
}
}
// HTTP header names must be tokens. // Stream#emitHeader() checks that all the pseudo headers appear first.
MimeHeaders headers = request.getMimeHeaders();
Enumeration<String> names = headers.names(); while (names.hasMoreElements()) {
String name = names.nextElement(); if (!H2_PSEUDO_HEADERS_REQUEST.contains(name) && !HttpParser.isToken(name)) { returnfalse;
}
}
returntrue;
}
@Override protectedfinalboolean flushBufferedWrite() throws IOException { if (log.isDebugEnabled()) {
log.debug(sm.getString("streamProcessor.flushBufferedWrite.entry", stream.getConnectionId(),
stream.getIdAsString()));
} 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 thrownew IllegalStateException();
} returntrue;
} returnfalse;
}
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 und die Messung sind noch experimentell.