isReady() returns true in closed state - why?

2019-08-31 02:07发布

问题:

ServletOutputStream.isReady() javadoc says the following:

Returns: true if a write to this ServletOutputStream will succeed, otherwise returns false.

In spite of that Jetty's ServletOutputStream implementation, HttpOutput seems to behave rather confusing in the case when the stream is in CLOSED state. It returns true:

case CLOSED:
    return true;

Source: HttpOutput.java:1011.

Furthermore, all three write methods in HttpOutput throws EofException when it's CLOSED:

case CLOSED:
    throw new EofException("Closed");

So it seems that write can never succeed. What's the reason behind this behavior?

回答1:

Key Fact: A close call implies a write operation.

The CLOSED internal state indicates that usage of the stream/output is CLOSED to that dispatch (not that the stream itself is actually closed).

How did we get into this state? Something has triggered the ServletOutputStream.close() (and in turn HttpOutput.close()) and now no more writes are allowed on that stream from the current dispatch.

In the CLOSED state, a flush occurs.

  • A flush will commit the response
  • A flush will finish writing the various buffers present on the various layers of the exchange / connection / output.
  • If there is an aggregate buffer (for many small writes) it writes that out.
  • If there is a compression layer (gzip) it will force a flush from there as well.
  • Then all of those buffers also go through the Transfer-Encoding layer (eg: chunked).
  • Then the network write occurs.

The HttpOutput is also the one point of output for all nested requests, such as using include() from RequestDispatcher which will reopen the HttpOutput for use during the include() and then close it again.

Once the HttpOutput is fully and completely flushed/finished (no more dispatches, no more writes, etc), then the final buffer flush is done, transfer-encodings are completed, the HttpOutput is reset, recycled, and returned to the HttpConnection for use with the next exchange.

We could do a better job of javadoc'ing that in the codebase, or at least using constants and variable names that make more sense.

Opened https://github.com/eclipse/jetty.project/issues/2687

Regarding the Jetty EofException (not the JVM EOFException) on write.

Once the ServletOutputStream is CLOSED for usage on the specific dispatch, further calls to write() will result in a Jetty EofException.

There is also a flavor of EofException where your committed response details were violated.

Eg: you declared a response Content-Length of say 40MB, but wrote 41MB, you exceeded the capability of that committed response, this is an IOException. And the Servlet spec tells us to throw an IOException in this case.

Jetty will throw the Jetty internal EofException (which extends IOException) to indicate this specific scenario and abort the connection, breaking any connection persistence you may have wanted.