Fabian,

Tomcat's behaviour is as expected and as per spec.

The content-length header is used to determine the end of the request
body. HTTP/1.1 allows pipe-linign requests. Whatever bytes on the wire
are seen next will be treated as the next request.

Mark


On 25/06/2020 23:08, Fabian Morgan wrote:
> Hi --
> 
> While testing various scenarios in Tomcat 9.0.30, I’ve found Tomcat returns
> different responses when the same request is issued twice in a row.  I have
> three such scenarios (all related) to illustrate.  I used Postman to issue
> the requests.
> 
> First, here is some environment information:
> 
> Operating System: Mac OS Mojave 10.14.6
> 
> Http Client: Postman 7.24.0
> 
> Relevant Automatic/Hidden headers for Postman:
> 
> Cache-Control: no-cache
> 
> Accept: */*
> 
> Accept-Encoding: gzip, deflate, br
> 
> Connection: keep-alive
> 
> Java version: 1.8.0_221
> 
> All of these scenarios are on fresh install of Tomcat 9.0.30 with default
> port of 8080.
> 
> Note: In each of the following scenarios, the steps must be done fairly
> quickly one right after the other with no delay.  Please also stop and
> restart Tomcat in between each scenario.
> 
> Steps for First Scenario:
> 
>    1.
> 
>    In Postman, issue PUT request to invalid url, such as
>    http://localhost:8080/thisisnotvalid.  Ensure Content-Length header is
>    sent with value 12345.  Ensure the request has a request body that is a
>    file attached with size >= 26545 bytes.  In Postman, I marked it with
>    binary radiobutton.  I receive response with 405 (Method Not Allowed)
>    status and HTML in the body.
>    2.
> 
>    In Postman, issue GET request to http://localhost:8080/thisisnotvalid.
>    Ensure Content-Length header is sent with value 12345.  The request must
>    NOT have a body (in Postman I marked it with none radiobutton).  I receive
>    response with 404 (Not Found) status and HTML in the body.
>    3.
> 
>    In Postman, issue GET request to http://localhost:8080/thisisnotvalid.
>    Ensure Content-Length header is sent with value 12345.  Ensure the request
>    has a request body that is a file attached with size >= 26545 bytes (yes on
>    a GET request).  In Postman, I marked it with binary radiobutton.  NOTE: I
>    receive 400 (Bad Request) response and HTML in the body.  This is NOT
>    expected.
>    4.
> 
>    Issue same request in (3) again, and now I receive response with 404
>    (Not Found) status and HTML in the body as expected.  Continuing to issue
>    the request again seems to return 404 response as expected hereafter.
> 
> 
> Note that after step (3), I see the following exception trace in
> catalina.out:
> 
> org.apache.coyote.http11.Http11Processor.service Error parsing HTTP request
> header
> 
>  Note: further occurrences of HTTP request parsing errors will be logged at
> DEBUG level.
> 
> java.lang.IllegalArgumentException: Invalid character found in method name.
> HTTP method names must be tokens
> 
> at
> org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:415)
> 
> at
> org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:260)
> 
> at
> org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
> 
> at
> org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860)
> 
> at
> org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1598)
> 
> at
> org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
> 
> at
> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
> 
> at
> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
> 
> at
> org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
> 
> at java.lang.Thread.run(Thread.java:748)
> 
> Steps for Second Scenario:
> 
>    1.
> 
>    In Postman, issue GET request to invalid url, such as
>    http://localhost:8080/thisisnotvalid.  Ensure Content-Length header is
>    sent with value 1.  Ensure the request has a request body that is a text
>    file attached containing 4 a’s in it as the only content (yes on a GET
>    request).  I receive response with 404 (Not Found) status and HTML in the
>    body.
>    2.
> 
>    Issue same request in (1) again, and now the server responds with 501
>    (Not Implemented) status and HTML in the body.  This is NOT expected.
>    3.
> 
>    Issue same request in (1) again, and now it responds again with 404
>    error as expected. Continuing to issue the same request will continue to
>    alternate server responding with 404 and 501.
> 
> 
> Note: The alternating responses don’t occur when Content-Length header is
> not present.
> 
> Note: The following lines can be seen in localhost_access_log:
> 
> 0:0:0:0:0:0:0:1 - - [25/Jun/2020:13:51:17 -0700] "GET /thisisnotvalid
> HTTP/1.1" 404 723
> 
> 0:0:0:0:0:0:0:1 - - [25/Jun/2020:13:51:18 -0700] "aaaGET /thisisnotvalid
> HTTP/1.1" 501 731
> 
> 0:0:0:0:0:0:0:1 - - [25/Jun/2020:13:51:21 -0700] "GET /thisisnotvalid
> HTTP/1.1" 404 723
> 
> 0:0:0:0:0:0:0:1 - - [25/Jun/2020:13:51:22 -0700] "aaaGET /thisisnotvalid
> HTTP/1.1" 501 731
> 
> 
> Steps for Third Scenario:
> 
>    1.
> 
>    In Postman, issue GET request to invalid url, such as
>    http://localhost:8080/thisisnotvalid.  Ensure Content-Length header is
>    sent with value 12345.  The request must NOT have a body (in Postman I
>    marked it with none radiobutton).  I receive response with 404 (Not Found)
>    status and HTML in the body.
>    2.
> 
>    Issue same request in (1) again, and now the server does not respond.
>    This is NOT expected.
>    3.
> 
>    Issue same request in (1) again, and now it responds again with 404
>    error as expected. Continuing to issue the same request will continue to
>    alternate server not responding and server returning 404 error.
> 
> 
> Note: The alternating responses don’t seem to occur when Content-Length
> header is not present.
> 
> I’ve debugged the code numerous times, and while not claiming to fully
> understand it, the following proposed change seems to alleviate the
> unexpected results from the scenarios above.
> 
> Proposed change:
> 
>    1.
> 
>    In conf/server.xml, change the original 8080 Connector configuration to
>    use a custom protocol class.
> 
>    The original Connector configuration is:
>    <Connector port="8080" protocol="HTTP/1.1"
> 
>           connectionTimeout="20000"
> 
>           redirectPort="8443" />
> 
> 
> The updated Connector configuration:
> <Connector port="8080"
> protocol="org.apache.coyote.http11.MyHttp11Nio2Protocol"
> 
>           connectionTimeout="20000" />
> 
> 
>    1.
> 
>    Create a Java web application with a module called mytomcat.  In the src
>    folder, create org.apache.coyote.http11 package with three classes:
>    MyHttp11InputBuffer, MyHttp11Nio2Protocol, and MyHttp11Processor.  Note:
>    The overridden endRequest() method in MyHttp11InputBuffer seems to
>    alleviate the unexpected results.  Note that MyHttp11Processor is
>    essentially the same as Http11Processor (it was decompiled by the IDE from
>    the jar), except one difference: the inputBuffer in the constructor is set
>    to an instance of MyHttp11InputBuffer.  The source for these files are
>    below at the end of the message.
> 
>    2.
> 
>    Ensure the class files for the source files in step (2) get placed in
>    
> PATHTOAPACHE/apache-tomcat-9.0.30/webapps/reproducer/WEB-INF/mytomcat_classes.
>    PATHTOAPACHE must be replaced with the parent directory of
>    apache-tomcat-9.0.30 folder, and the folders in the path must be created as
>    necessary.
>    3.
> 
>    Edit conf/catalina.properties to update common.loader property.  Add
>    
> "PATHTOAPACHE/apache-tomcat-9.0.30/webapps/reproducer/WEB-INF/mytomcat_classes"
>    at the end and save.  PATHTOAPACHE must be substituted as in step (3).
>    4.
> 
>    Shutdown and restart Tomcat.
> 
> 
> With the changes above, rerunning the scenarios now give expected results.
> 
> Scenario One with proposed change:
> 
>    1.
> 
>    I receive response with 405 (Method Not Allowed) status and HTML in the
>    body
>    2.
> 
>    I receive response with 404 (Not Found) status and HTML in the body.
>    3.
> 
>    I receive response with 404 (Not Found) status and HTML in the body.
>    Note that this result is updated
>    4.
> 
>    I receive response with 404 (Not Found) status and HTML in the body.
> 
> 
> Scenario Two with proposed change:
> 
>    1.
> 
>    I receive response with 404 (Not Found) status and HTML in the body.
>    2.
> 
>    I receive response with 404 (Not Found) status and HTML in the body.
>    Note that this result is updated
>    3.
> 
>    I receive response with 404 (Not Found) status and HTML in the body.
> 
> 
> Scenario Three with proposed change:
> 
>    1.
> 
>    I receive response with 404 (Not Found) status and HTML in the body.
>    2.
> 
>    I receive response with 404 (Not Found) status and HTML in the body.
>    Note that this result is updated
>    3.
> 
>    I receive response with 404 (Not Found) status and HTML in the body.
> 
> 
> Would you please give guidance on whether the scenarios outlined are indeed
> unexpected, whether the proposed change is appropriate, etc?
> 
> Thanks,
> 
> Fabian
> 
> ==========================
> 
> Source file for MyHttp11InputBuffer:
> 
> package org.apache.coyote.http11;
> 
> import org.apache.coyote.Request;
> import org.apache.tomcat.util.http.parser.HttpParser;
> 
> import java.io.IOException;
> 
> public class MyHttp11InputBuffer extends Http11InputBuffer {
> private final MyHttp11Processor processor;
> 
> public MyHttp11InputBuffer(MyHttp11Processor processor,
> Request request,
> int headerBufferSize,
> boolean rejectIllegalHeaderName,
> HttpParser httpParser) {
> super(request, headerBufferSize, rejectIllegalHeaderName, httpParser);
> 
> this.processor = processor;
> }
> 
> @Override
> void endRequest() throws IOException {
> getByteBuffer().limit(0).position(0);
> }
> }
> ==========================
> 
> Source file for MyHttp11Nio2Protocol:
> 
> package org.apache.coyote.http11;
> 
> import org.apache.coyote.Processor;
> 
> public class MyHttp11Nio2Protocol extends Http11Nio2Protocol {
>     @Override
>     protected Processor createProcessor() {
>         return new MyHttp11Processor(this, this.adapter);
>     }
> }
> 
> ==========================
> 
> Source file for MyHttp11Processor:
> 
> //
> // Source code recreated from a .class file by IntelliJ IDEA
> // (powered by Fernflower decompiler)
> //
> 
> package org.apache.coyote.http11;
> 
> import java.io.IOException;
> import java.io.InterruptedIOException;
> import java.nio.ByteBuffer;
> import java.util.ArrayList;
> import java.util.HashSet;
> import java.util.Iterator;
> import java.util.List;
> import java.util.Set;
> import java.util.regex.Pattern;
> import org.apache.coyote.AbstractProcessor;
> import org.apache.coyote.ActionCode;
> import org.apache.coyote.Adapter;
> import org.apache.coyote.ErrorState;
> import org.apache.coyote.Request;
> import org.apache.coyote.RequestInfo;
> import org.apache.coyote.UpgradeProtocol;
> import org.apache.coyote.UpgradeToken;
> import org.apache.coyote.http11.filters.BufferedInputFilter;
> import org.apache.coyote.http11.filters.ChunkedInputFilter;
> import org.apache.coyote.http11.filters.ChunkedOutputFilter;
> import org.apache.coyote.http11.filters.GzipOutputFilter;
> import org.apache.coyote.http11.filters.IdentityInputFilter;
> import org.apache.coyote.http11.filters.IdentityOutputFilter;
> import org.apache.coyote.http11.filters.SavedRequestInputFilter;
> import org.apache.coyote.http11.filters.VoidInputFilter;
> import org.apache.coyote.http11.filters.VoidOutputFilter;
> import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
> import org.apache.juli.logging.Log;
> import org.apache.juli.logging.LogFactory;
> import org.apache.tomcat.ContextBind;
> import org.apache.tomcat.InstanceManager;
> 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.FastHttpDateFormat;
> import org.apache.tomcat.util.http.MimeHeaders;
> import org.apache.tomcat.util.http.parser.HttpParser;
> import org.apache.tomcat.util.http.parser.TokenList;
> import org.apache.tomcat.util.log.UserDataHelper.Mode;
> import org.apache.tomcat.util.net.SendfileDataBase;
> import org.apache.tomcat.util.net.SendfileKeepAliveState;
> import org.apache.tomcat.util.net.SendfileState;
> import org.apache.tomcat.util.net.SocketWrapperBase;
> import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
> import org.apache.tomcat.util.res.StringManager;
> 
> public class MyHttp11Processor extends AbstractProcessor {
>     private static final Log log = LogFactory.getLog(Http11Processor.class);
>     private static final StringManager sm =
> StringManager.getManager(Http11Processor.class);
>     private final AbstractHttp11Protocol<?> protocol;
>     private final Http11InputBuffer inputBuffer;
>     private final Http11OutputBuffer outputBuffer;
>     private final HttpParser httpParser;
>     private int pluggableFilterIndex = 2147483647;
>     private volatile boolean keepAlive = true;
>     private boolean openSocket = false;
>     private boolean readComplete = true;
>     private boolean http11 = true;
>     private boolean http09 = false;
>     private boolean contentDelimitation = true;
>     private UpgradeToken upgradeToken = null;
>     private SendfileDataBase sendfileData = null;
> 
>     public MyHttp11Processor(AbstractHttp11Protocol<?> protocol, Adapter
> adapter) {
>         super(adapter);
>         this.protocol = protocol;
>         this.httpParser = new HttpParser(protocol.getRelaxedPathChars(),
> protocol.getRelaxedQueryChars());
>         this.inputBuffer = new MyHttp11InputBuffer(this, this.request,
> protocol.getMaxHttpHeaderSize(), protocol.getRejectIllegalHeaderName(),
> this.httpParser);
>         this.request.setInputBuffer(this.inputBuffer);
>         this.outputBuffer = new Http11OutputBuffer(this.response,
> protocol.getMaxHttpHeaderSize());
>         this.response.setOutputBuffer(this.outputBuffer);
>         this.inputBuffer.addFilter(new
> IdentityInputFilter(protocol.getMaxSwallowSize()));
>         this.outputBuffer.addFilter(new IdentityOutputFilter());
>         this.inputBuffer.addFilter(new
> ChunkedInputFilter(protocol.getMaxTrailerSize(),
> protocol.getAllowedTrailerHeadersInternal(),
> protocol.getMaxExtensionSize(), protocol.getMaxSwallowSize()));
>         this.outputBuffer.addFilter(new ChunkedOutputFilter());
>         this.inputBuffer.addFilter(new VoidInputFilter());
>         this.outputBuffer.addFilter(new VoidOutputFilter());
>         this.inputBuffer.addFilter(new BufferedInputFilter());
>         this.outputBuffer.addFilter(new GzipOutputFilter());
>         this.pluggableFilterIndex = this.inputBuffer.getFilters().length;
>     }
> 
>     private static boolean statusDropsConnection(int status) {
>         return status == 400 || status == 408 || status == 411 || status ==
> 413 || status == 414 || status == 500 || status == 503 || status == 501;
>     }
> 
>     private void addInputFilter(InputFilter[] inputFilters, String
> encodingName) {
>         if (!encodingName.equals("identity")) {
>             if (encodingName.equals("chunked")) {
>                 this.inputBuffer.addActiveFilter(inputFilters[1]);
>                 this.contentDelimitation = true;
>             } else {
>                 for(int i = this.pluggableFilterIndex; i <
> inputFilters.length; ++i) {
>                     if
> (inputFilters[i].getEncodingName().toString().equals(encodingName)) {
>                         this.inputBuffer.addActiveFilter(inputFilters[i]);
>                         return;
>                     }
>                 }
> 
>                 this.response.setStatus(501);
>                 this.setErrorState(ErrorState.CLOSE_CLEAN, (Throwable)null);
>                 if (log.isDebugEnabled()) {
> 
> log.debug(sm.getString("http11processor.request.prepare") + " Unsupported
> transfer encoding [" + encodingName + "]");
>                 }
>             }
>         }
> 
>     }
> 
>     public SocketState service(SocketWrapperBase<?> socketWrapper) throws
> IOException {
>         RequestInfo rp = this.request.getRequestProcessor();
>         rp.setStage(1);
>         this.setSocketWrapper(socketWrapper);
>         this.keepAlive = true;
>         this.openSocket = false;
>         this.readComplete = true;
>         boolean keptAlive = false;
> 
>         SendfileState sendfileState;
>         for(sendfileState = SendfileState.DONE;
> !this.getErrorState().isError() && this.keepAlive && !this.isAsync() &&
> this.upgradeToken == null && sendfileState == SendfileState.DONE &&
> !this.protocol.isPaused(); sendfileState =
> this.processSendfile(socketWrapper)) {
>             try {
>                 if (!this.inputBuffer.parseRequestLine(keptAlive,
> this.protocol.getConnectionTimeout(), this.protocol.getKeepAliveTimeout()))
> {
>                     if (this.inputBuffer.getParsingRequestLinePhase() ==
> -1) {
>                         return SocketState.UPGRADING;
>                     }
> 
>                     if (this.handleIncompleteRequestLineRead()) {
>                         break;
>                     }
>                 }
> 
>                 if (this.protocol.isPaused()) {
>                     this.response.setStatus(503);
>                     this.setErrorState(ErrorState.CLOSE_CLEAN,
> (Throwable)null);
>                 } else {
>                     keptAlive = true;
> 
> this.request.getMimeHeaders().setLimit(this.protocol.getMaxHeaderCount());
>                     if (!this.inputBuffer.parseHeaders()) {
>                         this.openSocket = true;
>                         this.readComplete = false;
>                         break;
>                     }
> 
>                     if (!this.protocol.getDisableUploadTimeout()) {
> 
> socketWrapper.setReadTimeout((long)this.protocol.getConnectionUploadTimeout());
>                     }
>                 }
>             } catch (IOException var13) {
>                 if (log.isDebugEnabled()) {
>                     log.debug(sm.getString("http11processor.header.parse"),
> var13);
>                 }
> 
>                 this.setErrorState(ErrorState.CLOSE_CONNECTION_NOW, var13);
>                 break;
>             } catch (Throwable var14) {
>                 ExceptionUtils.handleThrowable(var14);
>                 Mode logMode = this.userDataHelper.getNextMode();
>                 if (logMode != null) {
>                     String message =
> sm.getString("http11processor.header.parse");
>                     switch(logMode) {
>                         case INFO_THEN_DEBUG:
>                             message = message +
> sm.getString("http11processor.fallToDebug");
>                         case INFO:
>                             log.info(message, var14);
>                             break;
>                         case DEBUG:
>                             log.debug(message, var14);
>                     }
>                 }
> 
>                 this.response.setStatus(400);
>                 this.setErrorState(ErrorState.CLOSE_CLEAN, var14);
>             }
> 
>             if (isConnectionToken(this.request.getMimeHeaders(),
> "upgrade")) {
>                 String requestedProtocol =
> this.request.getHeader("Upgrade");
>                 UpgradeProtocol upgradeProtocol =
> this.protocol.getUpgradeProtocol(requestedProtocol);
>                 if (upgradeProtocol != null &&
> upgradeProtocol.accept(this.request)) {
>                     this.response.setStatus(101);
>                     this.response.setHeader("Connection", "Upgrade");
>                     this.response.setHeader("Upgrade", requestedProtocol);
>                     this.action(ActionCode.CLOSE, (Object)null);
>                     this.getAdapter().log(this.request, this.response, 0L);
>                     InternalHttpUpgradeHandler upgradeHandler =
> upgradeProtocol.getInternalUpgradeHandler(socketWrapper, this.getAdapter(),
> this.cloneRequest(this.request));
>                     UpgradeToken upgradeToken = new
> UpgradeToken(upgradeHandler, (ContextBind)null, (InstanceManager)null);
>                     this.action(ActionCode.UPGRADE, upgradeToken);
>                     return SocketState.UPGRADING;
>                 }
>             }
> 
>             if (this.getErrorState().isIoAllowed()) {
>                 rp.setStage(2);
> 
>                 try {
>                     this.prepareRequest();
>                 } catch (Throwable var12) {
>                     ExceptionUtils.handleThrowable(var12);
>                     if (log.isDebugEnabled()) {
> 
> log.debug(sm.getString("http11processor.request.prepare"), var12);
>                     }
> 
>                     this.response.setStatus(500);
>                     this.setErrorState(ErrorState.CLOSE_CLEAN, var12);
>                 }
>             }
> 
>             int maxKeepAliveRequests =
> this.protocol.getMaxKeepAliveRequests();
>             if (maxKeepAliveRequests == 1) {
>                 this.keepAlive = false;
>             } else if (maxKeepAliveRequests > 0 &&
> socketWrapper.decrementKeepAlive() <= 0) {
>                 this.keepAlive = false;
>             }
> 
>             if (this.getErrorState().isIoAllowed()) {
>                 try {
>                     rp.setStage(3);
>                     this.getAdapter().service(this.request, this.response);
>                     if (this.keepAlive && !this.getErrorState().isError()
> && !this.isAsync() && statusDropsConnection(this.response.getStatus())) {
>                         this.setErrorState(ErrorState.CLOSE_CLEAN,
> (Throwable)null);
>                     }
>                 } catch (InterruptedIOException var9) {
>                     this.setErrorState(ErrorState.CLOSE_CONNECTION_NOW,
> var9);
>                 } catch (HeadersTooLargeException var10) {
> 
> log.error(sm.getString("http11processor.request.process"), var10);
>                     if (this.response.isCommitted()) {
>                         this.setErrorState(ErrorState.CLOSE_NOW, var10);
>                     } else {
>                         this.response.reset();
>                         this.response.setStatus(500);
>                         this.setErrorState(ErrorState.CLOSE_CLEAN, var10);
>                         this.response.setHeader("Connection", "close");
>                     }
>                 } catch (Throwable var11) {
>                     ExceptionUtils.handleThrowable(var11);
> 
> log.error(sm.getString("http11processor.request.process"), var11);
>                     this.response.setStatus(500);
>                     this.setErrorState(ErrorState.CLOSE_CLEAN, var11);
>                     this.getAdapter().log(this.request, this.response, 0L);
>                 }
>             }
> 
>             rp.setStage(4);
>             if (!this.isAsync()) {
>                 this.endRequest();
>             }
> 
>             rp.setStage(5);
>             if (this.getErrorState().isError()) {
>                 this.response.setStatus(500);
>             }
> 
>             if (!this.isAsync() || this.getErrorState().isError()) {
>                 this.request.updateCounters();
>                 if (this.getErrorState().isIoAllowed()) {
>                     this.inputBuffer.nextRequest();
>                     this.outputBuffer.nextRequest();
>                 }
>             }
> 
>             if (!this.protocol.getDisableUploadTimeout()) {
>                 int connectionTimeout =
> this.protocol.getConnectionTimeout();
>                 if (connectionTimeout > 0) {
>                     socketWrapper.setReadTimeout((long)connectionTimeout);
>                 } else {
>                     socketWrapper.setReadTimeout(0L);
>                 }
>             }
> 
>             rp.setStage(6);
>         }
> 
>         rp.setStage(7);
>         if (!this.getErrorState().isError() && !this.protocol.isPaused()) {
>             if (this.isAsync()) {
>                 return SocketState.LONG;
>             } else if (this.isUpgrade()) {
>                 return SocketState.UPGRADING;
>             } else if (sendfileState == SendfileState.PENDING) {
>                 return SocketState.SENDFILE;
>             } else if (this.openSocket) {
>                 return this.readComplete ? SocketState.OPEN :
> SocketState.LONG;
>             } else {
>                 return SocketState.CLOSED;
>             }
>         } else {
>             return SocketState.CLOSED;
>         }
>     }
> 
>     protected final void setSocketWrapper(SocketWrapperBase<?>
> socketWrapper) {
>         super.setSocketWrapper(socketWrapper);
>         this.inputBuffer.init(socketWrapper);
>         this.outputBuffer.init(socketWrapper);
>     }
> 
>     private Request cloneRequest(Request source) throws IOException {
>         Request dest = new Request();
>         dest.decodedURI().duplicate(source.decodedURI());
>         dest.method().duplicate(source.method());
>         dest.getMimeHeaders().duplicate(source.getMimeHeaders());
>         dest.requestURI().duplicate(source.requestURI());
>         dest.queryString().duplicate(source.queryString());
>         return dest;
>     }
> 
>     private boolean handleIncompleteRequestLineRead() {
>         this.openSocket = true;
>         if (this.inputBuffer.getParsingRequestLinePhase() > 1) {
>             if (this.protocol.isPaused()) {
>                 this.response.setStatus(503);
>                 this.setErrorState(ErrorState.CLOSE_CLEAN, (Throwable)null);
>                 return false;
>             }
> 
>             this.readComplete = false;
>         }
> 
>         return true;
>     }
> 
>     private void checkExpectationAndResponseStatus() {
>         if (this.request.hasExpectation() && (this.response.getStatus() <
> 200 || this.response.getStatus() > 299)) {
>             this.inputBuffer.setSwallowInput(false);
>             this.keepAlive = false;
>         }
> 
>     }
> 
>     private void prepareRequest() throws IOException {
>         this.http11 = true;
>         this.http09 = false;
>         this.contentDelimitation = false;
>         if (this.protocol.isSSLEnabled()) {
>             this.request.scheme().setString("https");
>         }
> 
>         MessageBytes protocolMB = this.request.protocol();
>         if (protocolMB.equals("HTTP/1.1")) {
>             protocolMB.setString("HTTP/1.1");
>         } else if (protocolMB.equals("HTTP/1.0")) {
>             this.http11 = false;
>             this.keepAlive = false;
>             protocolMB.setString("HTTP/1.0");
>         } else if (protocolMB.equals("")) {
>             this.http09 = true;
>             this.http11 = false;
>             this.keepAlive = false;
>         } else {
>             this.http11 = false;
>             this.response.setStatus(505);
>             this.setErrorState(ErrorState.CLOSE_CLEAN, (Throwable)null);
>             if (log.isDebugEnabled()) {
>                 log.debug(sm.getString("http11processor.request.prepare") +
> " Unsupported HTTP version \"" + protocolMB + "\"");
>             }
>         }
> 
>         MimeHeaders headers = this.request.getMimeHeaders();
>         MessageBytes connectionValueMB = headers.getValue("Connection");
>         if (connectionValueMB != null && !connectionValueMB.isNull()) {
>             Set<String> tokens = new HashSet();
>             TokenList.parseTokenList(headers.values("Connection"), tokens);
>             if (tokens.contains("close")) {
>                 this.keepAlive = false;
>             } else if (tokens.contains("keep-alive")) {
>                 this.keepAlive = true;
>             }
>         }
> 
>         if (this.http11) {
>             MessageBytes expectMB = headers.getValue("expect");
>             if (expectMB != null && !expectMB.isNull()) {
>                 if
> (expectMB.toString().trim().equalsIgnoreCase("100-continue")) {
>                     this.inputBuffer.setSwallowInput(false);
>                     this.request.setExpectation(true);
>                 } else {
>                     this.response.setStatus(417);
>                     this.setErrorState(ErrorState.CLOSE_CLEAN,
> (Throwable)null);
>                 }
>             }
>         }
> 
>         Pattern restrictedUserAgents =
> this.protocol.getRestrictedUserAgentsPattern();
>         MessageBytes hostValueMB;
>         if (restrictedUserAgents != null && (this.http11 ||
> this.keepAlive)) {
>             hostValueMB = headers.getValue("user-agent");
>             if (hostValueMB != null && !hostValueMB.isNull()) {
>                 String userAgentValue = hostValueMB.toString();
>                 if (restrictedUserAgents.matcher(userAgentValue).matches())
> {
>                     this.http11 = false;
>                     this.keepAlive = false;
>                 }
>             }
>         }
> 
>         hostValueMB = null;
> 
>         try {
>             hostValueMB = headers.getUniqueValue("host");
>         } catch (IllegalArgumentException var16) {
>             this.badRequest("http11processor.request.multipleHosts");
>         }
> 
>         if (this.http11 && hostValueMB == null) {
>             this.badRequest("http11processor.request.noHostHeader");
>         }
> 
>         ByteChunk uriBC = this.request.requestURI().getByteChunk();
>         byte[] uriB = uriBC.getBytes();
>         int pos;
>         if (uriBC.startsWithIgnoreCase("http", 0)) {
>             pos = 4;
>             if (uriBC.startsWithIgnoreCase("s", pos)) {
>                 ++pos;
>             }
> 
>             if (uriBC.startsWith("://", pos)) {
>                 pos += 3;
>                 int uriBCStart = uriBC.getStart();
>                 int slashPos = uriBC.indexOf('/', pos);
>                 int atPos = uriBC.indexOf('@', pos);
>                 if (slashPos > -1 && atPos > slashPos) {
>                     atPos = -1;
>                 }
> 
>                 if (slashPos == -1) {
>                     slashPos = uriBC.getLength();
>                     this.request.requestURI().setBytes(uriB, uriBCStart +
> 6, 1);
>                 } else {
>                     this.request.requestURI().setBytes(uriB, uriBCStart +
> slashPos, uriBC.getLength() - slashPos);
>                 }
> 
>                 if (atPos != -1) {
>                     while(pos < atPos) {
>                         byte c = uriB[uriBCStart + pos];
>                         if (!HttpParser.isUserInfo(c)) {
> 
> this.badRequest("http11processor.request.invalidUserInfo");
>                             break;
>                         }
> 
>                         ++pos;
>                     }
> 
>                     pos = atPos + 1;
>                 }
> 
>                 if (this.http11) {
>                     if (hostValueMB != null &&
> !hostValueMB.getByteChunk().equals(uriB, uriBCStart + pos, slashPos - pos))
> {
>                         if (this.protocol.getAllowHostHeaderMismatch()) {
>                             hostValueMB = headers.setValue("host");
>                             hostValueMB.setBytes(uriB, uriBCStart + pos,
> slashPos - pos);
>                         } else {
> 
> this.badRequest("http11processor.request.inconsistentHosts");
>                         }
>                     }
>                 } else {
>                     try {
>                         hostValueMB = headers.setValue("host");
>                         hostValueMB.setBytes(uriB, uriBCStart + pos,
> slashPos - pos);
>                     } catch (IllegalStateException var15) {
>                     }
>                 }
>             } else {
>                 this.badRequest("http11processor.request.invalidScheme");
>             }
>         }
> 
>         for(pos = uriBC.getStart(); pos < uriBC.getEnd(); ++pos) {
>             if (!this.httpParser.isAbsolutePathRelaxed(uriB[pos])) {
>                 this.badRequest("http11processor.request.invalidUri");
>                 break;
>             }
>         }
> 
>         InputFilter[] inputFilters = this.inputBuffer.getFilters();
>         if (this.http11) {
>             MessageBytes transferEncodingValueMB =
> headers.getValue("transfer-encoding");
>             if (transferEncodingValueMB != null) {
>                 List<String> encodingNames = new ArrayList();
> 
> TokenList.parseTokenList(headers.values("transfer-encoding"),
> encodingNames);
>                 Iterator var24 = encodingNames.iterator();
> 
>                 while(var24.hasNext()) {
>                     String encodingName = (String)var24.next();
>                     this.addInputFilter(inputFilters, encodingName);
>                 }
>             }
>         }
> 
>         long contentLength = -1L;
> 
>         try {
>             contentLength = this.request.getContentLengthLong();
>         } catch (NumberFormatException var13) {
> 
> this.badRequest("http11processor.request.nonNumericContentLength");
>         } catch (IllegalArgumentException var14) {
> 
> this.badRequest("http11processor.request.multipleContentLength");
>         }
> 
>         if (contentLength >= 0L) {
>             if (this.contentDelimitation) {
>                 headers.removeHeader("content-length");
>                 this.request.setContentLength(-1L);
>             } else {
>                 this.inputBuffer.addActiveFilter(inputFilters[0]);
>                 this.contentDelimitation = true;
>             }
>         }
> 
>         this.parseHost(hostValueMB);
>         if (!this.contentDelimitation) {
>             this.inputBuffer.addActiveFilter(inputFilters[2]);
>             this.contentDelimitation = true;
>         }
> 
>         if (!this.getErrorState().isIoAllowed()) {
>             this.getAdapter().log(this.request, this.response, 0L);
>         }
> 
>     }
> 
>     private void badRequest(String errorKey) {
>         this.response.setStatus(400);
>         this.setErrorState(ErrorState.CLOSE_CLEAN, (Throwable)null);
>         if (log.isDebugEnabled()) {
>             log.debug(sm.getString(errorKey));
>         }
> 
>     }
> 
>     protected final void prepareResponse() throws IOException {
>         boolean entityBody = true;
>         this.contentDelimitation = false;
>         OutputFilter[] outputFilters = this.outputBuffer.getFilters();
>         if (this.http09) {
>             this.outputBuffer.addActiveFilter(outputFilters[0]);
>             this.outputBuffer.commit();
>         } else {
>             int statusCode = this.response.getStatus();
>             if (statusCode < 200 || statusCode == 204 || statusCode == 205
> || statusCode == 304) {
>                 this.outputBuffer.addActiveFilter(outputFilters[2]);
>                 entityBody = false;
>                 this.contentDelimitation = true;
>                 if (statusCode == 205) {
>                     this.response.setContentLength(0L);
>                 } else {
>                     this.response.setContentLength(-1L);
>                 }
>             }
> 
>             MessageBytes methodMB = this.request.method();
>             if (methodMB.equals("HEAD")) {
>                 this.outputBuffer.addActiveFilter(outputFilters[2]);
>                 this.contentDelimitation = true;
>             }
> 
>             if (this.protocol.getUseSendfile()) {
>                 this.prepareSendfile(outputFilters);
>             }
> 
>             boolean useCompression = false;
>             if (entityBody && this.sendfileData == null) {
>                 useCompression = this.protocol.useCompression(this.request,
> this.response);
>             }
> 
>             MimeHeaders headers = this.response.getMimeHeaders();
>             if (entityBody || statusCode == 204) {
>                 String contentType = this.response.getContentType();
>                 if (contentType != null) {
>                     headers.setValue("Content-Type").setString(contentType);
>                 }
> 
>                 String contentLanguage = this.response.getContentLanguage();
>                 if (contentLanguage != null) {
> 
> headers.setValue("Content-Language").setString(contentLanguage);
>                 }
>             }
> 
>             long contentLength = this.response.getContentLengthLong();
>             boolean connectionClosePresent = isConnectionToken(headers,
> "close");
>             if (this.http11 && this.response.getTrailerFields() != null) {
>                 this.outputBuffer.addActiveFilter(outputFilters[1]);
>                 this.contentDelimitation = true;
>                 headers.addValue("Transfer-Encoding").setString("chunked");
>             } else if (contentLength != -1L) {
>                 headers.setValue("Content-Length").setLong(contentLength);
>                 this.outputBuffer.addActiveFilter(outputFilters[0]);
>                 this.contentDelimitation = true;
>             } else if (this.http11 && entityBody &&
> !connectionClosePresent) {
>                 this.outputBuffer.addActiveFilter(outputFilters[1]);
>                 this.contentDelimitation = true;
>                 headers.addValue("Transfer-Encoding").setString("chunked");
>             } else {
>                 this.outputBuffer.addActiveFilter(outputFilters[0]);
>             }
> 
>             if (useCompression) {
>                 this.outputBuffer.addActiveFilter(outputFilters[3]);
>             }
> 
>             if (headers.getValue("Date") == null) {
> 
> headers.addValue("Date").setString(FastHttpDateFormat.getCurrentDate());
>             }
> 
>             if (entityBody && !this.contentDelimitation) {
>                 this.keepAlive = false;
>             }
> 
>             this.checkExpectationAndResponseStatus();
>             if (this.keepAlive && statusDropsConnection(statusCode)) {
>                 this.keepAlive = false;
>             }
> 
>             int keepAliveTimeout;
>             if (!this.keepAlive) {
>                 if (!connectionClosePresent) {
>                     headers.addValue("Connection").setString("close");
>                 }
>             } else if (!this.getErrorState().isError()) {
>                 if (!this.http11) {
>                     headers.addValue("Connection").setString("keep-alive");
>                 }
> 
>                 if (this.protocol.getUseKeepAliveResponseHeader()) {
>                     boolean connectionKeepAlivePresent =
> isConnectionToken(this.request.getMimeHeaders(), "keep-alive");
>                     if (connectionKeepAlivePresent) {
>                         keepAliveTimeout =
> this.protocol.getKeepAliveTimeout();
>                         if (keepAliveTimeout > 0) {
>                             String value = "timeout=" +
> (long)keepAliveTimeout / 1000L;
>                             headers.setValue("Keep-Alive").setString(value);
>                             if (this.http11) {
>                                 MessageBytes connectionHeaderValue =
> headers.getValue("Connection");
>                                 if (connectionHeaderValue == null) {
> 
> headers.addValue("Connection").setString("keep-alive");
>                                 } else {
> 
> connectionHeaderValue.setString(connectionHeaderValue.getString() + ", " +
> "keep-alive");
>                                 }
>                             }
>                         }
>                     }
>                 }
>             }
> 
>             String server = this.protocol.getServer();
>             if (server == null) {
>                 if (this.protocol.getServerRemoveAppProvidedValues()) {
>                     headers.removeHeader("server");
>                 }
>             } else {
>                 headers.setValue("Server").setString(server);
>             }
> 
>             try {
>                 this.outputBuffer.sendStatus();
>                 keepAliveTimeout = headers.size();
>                 int i = 0;
> 
>                 while(true) {
>                     if (i >= keepAliveTimeout) {
>                         this.outputBuffer.endHeaders();
>                         break;
>                     }
> 
>                     this.outputBuffer.sendHeader(headers.getName(i),
> headers.getValue(i));
>                     ++i;
>                 }
>             } catch (Throwable var14) {
>                 ExceptionUtils.handleThrowable(var14);
>                 this.outputBuffer.resetHeaderBuffer();
>                 throw var14;
>             }
> 
>             this.outputBuffer.commit();
>         }
>     }
> 
>     private static boolean isConnectionToken(MimeHeaders headers, String
> token) throws IOException {
>         MessageBytes connection = headers.getValue("Connection");
>         if (connection == null) {
>             return false;
>         } else {
>             Set<String> tokens = new HashSet();
>             TokenList.parseTokenList(headers.values("Connection"), tokens);
>             return tokens.contains(token);
>         }
>     }
> 
>     private void prepareSendfile(OutputFilter[] outputFilters) {
>         String fileName =
> (String)this.request.getAttribute("org.apache.tomcat.sendfile.filename");
>         if (fileName == null) {
>             this.sendfileData = null;
>         } else {
>             this.outputBuffer.addActiveFilter(outputFilters[2]);
>             this.contentDelimitation = true;
>             long pos =
> (Long)this.request.getAttribute("org.apache.tomcat.sendfile.start");
>             long end =
> (Long)this.request.getAttribute("org.apache.tomcat.sendfile.end");
>             this.sendfileData =
> this.socketWrapper.createSendfileData(fileName, pos, end - pos);
>         }
> 
>     }
> 
>     protected void populatePort() {
>         this.request.action(ActionCode.REQ_LOCALPORT_ATTRIBUTE,
> this.request);
>         this.request.setServerPort(this.request.getLocalPort());
>     }
> 
>     protected boolean flushBufferedWrite() throws IOException {
>         if (this.outputBuffer.hasDataToWrite() &&
> this.outputBuffer.flushBuffer(false)) {
>             this.outputBuffer.registerWriteInterest();
>             return true;
>         } else {
>             return false;
>         }
>     }
> 
>     protected SocketState dispatchEndRequest() {
>         if (!this.keepAlive) {
>             return SocketState.CLOSED;
>         } else {
>             this.endRequest();
>             this.inputBuffer.nextRequest();
>             this.outputBuffer.nextRequest();
>             return this.socketWrapper.isReadPending() ? SocketState.LONG :
> SocketState.OPEN;
>         }
>     }
> 
>     protected Log getLog() {
>         return log;
>     }
> 
>     private void endRequest() {
>         if (this.getErrorState().isError()) {
>             this.inputBuffer.setSwallowInput(false);
>         } else {
>             this.checkExpectationAndResponseStatus();
>         }
> 
>         if (this.getErrorState().isIoAllowed()) {
>             try {
>                 this.inputBuffer.endRequest();
>             } catch (IOException var4) {
>                 this.setErrorState(ErrorState.CLOSE_CONNECTION_NOW, var4);
>             } catch (Throwable var5) {
>                 ExceptionUtils.handleThrowable(var5);
>                 this.response.setStatus(500);
>                 this.setErrorState(ErrorState.CLOSE_NOW, var5);
>                 log.error(sm.getString("http11processor.request.finish"),
> var5);
>             }
>         }
> 
>         if (this.getErrorState().isIoAllowed()) {
>             try {
>                 this.action(ActionCode.COMMIT, (Object)null);
>                 this.outputBuffer.end();
>             } catch (IOException var2) {
>                 this.setErrorState(ErrorState.CLOSE_CONNECTION_NOW, var2);
>             } catch (Throwable var3) {
>                 ExceptionUtils.handleThrowable(var3);
>                 this.setErrorState(ErrorState.CLOSE_NOW, var3);
>                 log.error(sm.getString("http11processor.response.finish"),
> var3);
>             }
>         }
> 
>     }
> 
>     protected final void finishResponse() throws IOException {
>         this.outputBuffer.end();
>     }
> 
>     protected final void ack() {
>         if (!this.response.isCommitted() && this.request.hasExpectation()) {
>             this.inputBuffer.setSwallowInput(true);
> 
>             try {
>                 this.outputBuffer.sendAck();
>             } catch (IOException var2) {
>                 this.setErrorState(ErrorState.CLOSE_CONNECTION_NOW, var2);
>             }
>         }
> 
>     }
> 
>     protected final void flush() throws IOException {
>         this.outputBuffer.flush();
>     }
> 
>     protected final int available(boolean doRead) {
>         return this.inputBuffer.available(doRead);
>     }
> 
>     protected final void setRequestBody(ByteChunk body) {
>         InputFilter savedBody = new SavedRequestInputFilter(body);
>         Http11InputBuffer internalBuffer =
> (Http11InputBuffer)this.request.getInputBuffer();
>         internalBuffer.addActiveFilter(savedBody);
>     }
> 
>     protected final void setSwallowResponse() {
>         this.outputBuffer.responseFinished = true;
>     }
> 
>     protected final void disableSwallowRequest() {
>         this.inputBuffer.setSwallowInput(false);
>     }
> 
>     protected final void sslReHandShake() throws IOException {
>         if (this.sslSupport != null) {
>             InputFilter[] inputFilters = this.inputBuffer.getFilters();
> 
> ((BufferedInputFilter)inputFilters[3]).setLimit(this.protocol.getMaxSavePostSize());
>             this.inputBuffer.addActiveFilter(inputFilters[3]);
>             this.socketWrapper.doClientAuth(this.sslSupport);
> 
>             try {
>                 Object sslO = this.sslSupport.getPeerCertificateChain();
>                 if (sslO != null) {
> 
> this.request.setAttribute("javax.servlet.request.X509Certificate", sslO);
>                 }
>             } catch (IOException var3) {
>                 log.warn(sm.getString("http11processor.socket.ssl"), var3);
>             }
>         }
> 
>     }
> 
>     protected final boolean isRequestBodyFullyRead() {
>         return this.inputBuffer.isFinished();
>     }
> 
>     protected final void registerReadInterest() {
>         this.socketWrapper.registerReadInterest();
>     }
> 
>     protected final boolean isReadyForWrite() {
>         return this.outputBuffer.isReady();
>     }
> 
>     public UpgradeToken getUpgradeToken() {
>         return this.upgradeToken;
>     }
> 
>     protected final void doHttpUpgrade(UpgradeToken upgradeToken) {
>         this.upgradeToken = upgradeToken;
>         this.outputBuffer.responseFinished = true;
>     }
> 
>     public ByteBuffer getLeftoverInput() {
>         return this.inputBuffer.getLeftover();
>     }
> 
>     public boolean isUpgrade() {
>         return this.upgradeToken != null;
>     }
> 
>     protected boolean isTrailerFieldsReady() {
>         return this.inputBuffer.isChunking() ?
> this.inputBuffer.isFinished() : true;
>     }
> 
>     protected boolean isTrailerFieldsSupported() {
>         if (!this.http11) {
>             return false;
>         } else {
>             return !this.response.isCommitted() ? true :
> this.outputBuffer.isChunking();
>         }
>     }
> 
>     private SendfileState processSendfile(SocketWrapperBase<?>
> socketWrapper) {
>         this.openSocket = this.keepAlive;
>         SendfileState result = SendfileState.DONE;
>         if (this.sendfileData != null && !this.getErrorState().isError()) {
>             if (this.keepAlive) {
>                 if (this.available(false) == 0) {
>                     this.sendfileData.keepAliveState =
> SendfileKeepAliveState.OPEN;
>                 } else {
>                     this.sendfileData.keepAliveState =
> SendfileKeepAliveState.PIPELINED;
>                 }
>             } else {
>                 this.sendfileData.keepAliveState =
> SendfileKeepAliveState.NONE;
>             }
> 
>             result = socketWrapper.processSendfile(this.sendfileData);
>             switch(result) {
>                 case ERROR:
>                     if (log.isDebugEnabled()) {
> 
> log.debug(sm.getString("http11processor.sendfile.error"));
>                     }
> 
>                     this.setErrorState(ErrorState.CLOSE_CONNECTION_NOW,
> (Throwable)null);
>                 default:
>                     this.sendfileData = null;
>             }
>         }
> 
>         return result;
>     }
> 
>     public final void recycle() {
>         this.getAdapter().checkRecycled(this.request, this.response);
>         super.recycle();
>         this.inputBuffer.recycle();
>         this.outputBuffer.recycle();
>         this.upgradeToken = null;
>         this.socketWrapper = null;
>         this.sendfileData = null;
>         this.sslSupport = null;
>     }
> 
>     public void pause() {
>     }
> }
> 

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org
For additional commands, e-mail: users-h...@tomcat.apache.org

Reply via email to