Bug 57661

Summary: Delay sending of 100 continue response until application tries to read request body
Product: Tomcat 9 Reporter: Mark Thomas <markt>
Component: ConnectorsAssignee: Tomcat Developers Mailing List <dev>
Status: RESOLVED FIXED    
Severity: enhancement CC: michaelo, t.oberlies
Priority: P2    
Version: unspecified   
Target Milestone: -----   
Hardware: PC   
OS: All   

Description Mark Thomas 2015-03-04 11:14:02 UTC
Currently Tomcat sends the 100 continue response just before the request is passed to the application for processing. This denies the application the opporutnity to reject the request early based on the request line and/or headers. Dealying the 100 continue response until the application attempts to read the requets body should address this.
Comment 1 Tobias Oberlies 2016-04-23 13:34:27 UTC
It is unfortunate that Tomcat unconditionally send 100 CONTINUE acknowledgements. (In Tomcat 7 this is triggered by the StandardContextValve.)

The "Expect: 100-continue" is important for clients that want to send a large POST request but are unable to read the response before they have sent the complete request body (e.g. the Apache httpclient). These clients could send only headers plus the "Expect: 100-continue" and would then see an error response (e.g. 403). With the current behaviour of Tomcat however, they are asked to send the entity, the server application responds with an error, but the client doesn't see it (assuming the request entity is larger than the swallow size, i.e. approx. 2 MB). Instead the client only sees an exception ("connection reset").
Comment 2 Tobias Oberlies 2016-04-23 13:35:09 UTC
It is unfortunate that Tomcat unconditionally send 100 CONTINUE acknowledgements. (In Tomcat 7 this is triggered by the StandardContextValve.)

The "Expect: 100-continue" is important for clients that want to send a large POST request but are unable to read the response before they have sent the complete request body (e.g. the Apache httpclient). These clients could send only headers plus the "Expect: 100-continue" and would then see an error response (e.g. 403). With the current behaviour of Tomcat however, they are asked to send the entity, the server application responds with an error, but the client doesn't see it (assuming the request entity is larger than the swallow size, i.e. approx. 2 MB). Instead the client only sees an exception ("connection reset").
Comment 3 Tobias Oberlies 2016-04-23 14:04:54 UTC
Sorry for the redundant comments.

I have an addition to my analysis in comment #1:
> ... (e.g. the Apache httpclient). These clients could
> send only headers plus the "Expect: 100-continue" and would then see an
> error response (e.g. 403)

With this enhancement implemented, there would be benefit, but it would be limited. The benefit would be that the client sees the response instead of an exception.

What we wouldn't get with the Apache 4.x httpclient is that the connection can be reused for the next request. The problem is that because the httpclient doesn't send the last chunk of the (to be discarded) entity after seeing a final response to its "Expect: 100-continue" request. This forces the server to close the connection. Apparently, this was only fixed in the 5.x httpclient [1]

[1] https://issues.apache.org/jira/browse/HTTPCORE-411
Comment 4 Michael Osipov 2016-04-24 11:21:07 UTC
To add some additional information based on my findings in Tomcat 6.0.45:
As already described by Mark, Tomcat sends the negative reponse before a client has completely uploaded his request body. I believe that this implementation is fully RFC compliant and reasonable one. Consider uploading hundreds of megabytes and then wait for the denial? What a waste.

I was searching for a bug in Firefox and Tomcat and discovered that Firefox is faulty too. curl perfectly handles the premature end of transfer. I have documented my findings with Tomcat and the expect continue with Firefox here: https://bugzilla.mozilla.org/show_bug.cgi?id=751552#c14. Similar issues: https://bugzilla.mozilla.org/show_bug.cgi?id=914088, https://bugzilla.mozilla.org/show_bug.cgi?id=729496
Comment 5 Mark Thomas 2020-03-13 16:36:05 UTC
See https://github.com/eclipse-ee4j/servlet-api/issues/307 for a description of what other containers do.

Options appear to be:
a) container sends it after auth (current Tomcat behaviour)
b) container sends it when an InputStream / Reader is obtained
c) container sends it when an InputStream / Reader is first used

I'm currently leaning towards adding an option to select between a) and b) but as an Enum so additional options could be added later.
Comment 6 Michael Osipov 2020-03-13 18:53:08 UTC
(In reply to Mark Thomas from comment #5)
> See https://github.com/eclipse-ee4j/servlet-api/issues/307 for a description
> of what other containers do.
> 
> Options appear to be:
> a) container sends it after auth (current Tomcat behaviour)
> b) container sends it when an InputStream / Reader is obtained
> c) container sends it when an InputStream / Reader is first used
> 
> I'm currently leaning towards adding an option to select between a) and b)
> but as an Enum so additional options could be added later.

Not only auth, on any status != 2xx. You may remember my redirect example on the mailing list. At no point a filter should need to obtain/peek/consume the input stream if a decision has to done based on headers only.
Comment 7 Malay 2020-08-04 17:56:03 UTC
(In reply to Mark Thomas from comment #5)
I ran into this recently and ended up implementing option (b) locally:
b) container sends it when an InputStream / Reader is obtained

I'd be happy to prepare my local changes as a PR if there is a willingness to move forward with this solution.

Thanks!
Comment 8 Michael Osipov 2020-08-04 18:01:36 UTC
Please note that state changing actions do not necessary require a body, e.g., DELETE or generic POST with command in the URL. If Tomcat would wait until obtaining input this would completely defeat Expect Continue support.
Comment 9 Christopher Schultz 2020-08-04 20:29:11 UTC
Why send 100-continue if you don't expect to send a request entity? The whole point of 100-continue is to request permission from the server to send a (usually large) request entity.
Comment 10 Michael Osipov 2020-08-04 20:30:53 UTC
(In reply to Christopher Schultz from comment #9)
> Why send 100-continue if you don't expect to send a request entity? The
> whole point of 100-continue is to request permission from the server to send
> a (usually large) request entity.

because a client impl may does this by default. I haven't veryfied Apache HttpClient not to send the header when not HttpEntity is attached.
Comment 11 Malay 2020-08-04 23:37:15 UTC
I took a closer look at my implementation and it is actually option (c):
c) container sends it when an InputStream / Reader is first used

The approach that I'm taking is to only send the '100 continue' response when the application reads the request body, there is no additional blocking involved, so it will not interfere with handling of requests with no content.

Regarding requests like DELETE that do not contain content, it is not allowed for those requests to contain the "Expect: 100-continue" header, from RFC 7231 5.1.1:
A client MUST NOT generate a 100-continue expectation in a request
      that does not include a message body.

From what I can tell, Apache HttpComponents will not add the "Expect: 100-continue" header if the content body size is zero.
Comment 12 Michael Osipov 2020-08-05 05:54:40 UTC
(In reply to Malay from comment #11)
> I took a closer look at my implementation and it is actually option (c):
> c) container sends it when an InputStream / Reader is first used
> 
> The approach that I'm taking is to only send the '100 continue' response
> when the application reads the request body, there is no additional blocking
> involved, so it will not interfere with handling of requests with no content.
> 
> Regarding requests like DELETE that do not contain content, it is not
> allowed for those requests to contain the "Expect: 100-continue" header,
> from RFC 7231 5.1.1:
> A client MUST NOT generate a 100-continue expectation in a request
>       that does not include a message body.
> 
> From what I can tell, Apache HttpComponents will not add the "Expect:
> 100-continue" header if the content body size is zero.

Thanks for citing. I did not have this in mind.
Comment 13 Malay 2020-08-06 02:15:41 UTC
I posted PR 332 https://github.com/apache/tomcat/pull/332 with my implementation. Please let me know if this is the right approach, I thought of several ways to implement this and decided on this approach because it allows StandardContextValve to still be the brains behind sending the 100 Continue response. Thanks!
Comment 14 Mark Thomas 2020-09-05 16:14:56 UTC
Fixed in:
- master for 10.0.0-M8 onwards
- 9.0.x for 9.0.38 onwards
- 8.5.x for 8.5.58 onwards