Chris, Emah, > > On 1/17/18 10:17 AM, emah wrote: > > Chris, > > > > > > Christopher Schultz-2 wrote > >>> I'm running a tomcat 8.5.23 instance on ubuntu 16.04 (spring > >>> boot application with embedded tomcat) configured with 2 > >>> connectors: Http11NioProtocol and AjpNioProtocol. The AJP one > >>> is accessed through an apache2 instance configured with > >>> mod_jk.It all works well in the normal use case. > >>> > >>> The application has code to look for the EOFExceptions during > >>> read e.g. the client aborts the request which works well with > >>> HTTP but not AJP. In my test I'm simulating this by closing > >>> the connection half way through (some headers have been sent > >>> but no body) > >>> > >>> The problem I'm seeing is an inconsistent behaviour for > >>> CoyoteInputStream.read() The HTTP connector throws an > >>> EOFException in this case while the AJP one just returns -1. > >>> > >>> The relevant call stack is this at > >>> org.apache.coyote.ajp.AjpProcessor.refillReadBuffer(AjpProcessor.jav > a: > >> > >>> > >>> > 684) > >>> > >>> > >> at > >>> org.apache.coyote.ajp.AjpProcessor$SocketInputBuffer.doRead(AjpProce > ss > >> > >>> > >>> > or.java:1433) > >>> > >>> > >> at org.apache.coyote.Request.doRead(Request.java:581) > >>> at > >>> org.apache.catalina.connector.InputBuffer.realReadBytes(InputBuffer. > ja > >> > >>> > >>> > va:326) > >>> > >>> > >> at > >>> org.apache.catalina.connector.InputBuffer.checkByteBufferEof(InputBu > ff > >> > >>> > >>> > er.java:642) > >>> > >>> > >> at > >>> org.apache.catalina.connector.InputBuffer.readByte(InputBuffer.java: > 33 > >> > >>> > >>> > 7) > >>> > >>> > >> at > >>> org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStre > am > >> > >>> > >>> > .java:93) > >>> > >>> Now based on this old and unrelated thread > >>> https://mail-archives.apache.org/mod_mbox/tomcat-users/201312.mbox/% > 3C > <https://mail-archives.apache.org/mod_mbox/tomcat-users/201312.mbox/%3C> > > > >>> > >>> > >> 15FF6F04-B4C9-4D9B-B1B3-5C10CA955AEE@ > > > >> %3E > >>> > >>> > >> I understand that the AJP connector is perfectly capable of > >> raising an > >>> EOFException but it's just not doing that for me. > >>> > >>> The documentation > >>> https://tomcat.apache.org/tomcat-8.0-doc/config/ajp.html does > >>> not suggest I need to do anything special. > >>> > >>> I guess it's possible that this has something to do with the > >>> way apache2 talks to the AJP connector. Any help is > >>> appreciated. > >> > >> Interesting. > >> > >> If your servlet simply reads the InputStream like this, you > >> don't get an EOFException? > >> > >> ServletInputStream in = request.getInputStream(); for(;;) > >> in.read(); > >> > >> What part of the Servlet specification or Servlet API leads you > >> to believe that EOFException should be thrown when the request > >> has been completely read (or has been truncated, and no further > >> data is available )? > >> > >> I don't see anything to suggest that such behavior is either > >> required or expected. > >> > >> In fact, I'm surprised that the HTTP connector throws an > >> EOFException instead of simply returning -1 like the API says it > >> should. > >> > >> - -chris > > > > Let me first correct something I've said earlier: > > > > HTTP Connector will actually throw an IOException when ssl unwrap > > fails (rather than an EOFException) in at > > org.apache.tomcat.util.net.SecureNioChannel.read(SecureNioChannel.java > :618) > > > > > > > at > > org.apache.tomcat.util.net.NioBlockingSelector.read(NioBlockingSelecto > r.java:173) > > > > > > > at > > org.apache.tomcat.util.net.NioSelectorPool.read(NioSelectorPool.java:2 > 35) > > > > > > > at > > org.apache.tomcat.util.net.NioSelectorPool.read(NioSelectorPool.java:2 > 16) > > > > > > > at > > org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer > (NioEndpoint.java:1241) > > > > > > > at > > org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoi > nt.java:1190) > > > > > > > at > > org.apache.coyote.http11.Http11InputBuffer.fill(Http11InputBuffer.java > :717) > > > > > > > at > > org.apache.coyote.http11.Http11InputBuffer.access$300(Http11InputBuffe > r.java:40) > > > > > > > at > > org.apache.coyote.http11.Http11InputBuffer$SocketInputBuffer.doRead(Ht > tp11InputBuffer.java:1072) > > > > > > > at > > org.apache.coyote.http11.filters.IdentityInputFilter.doRead(IdentityIn > putFilter.java:140) > > > > > > > at > > org.apache.coyote.http11.Http11InputBuffer.doRead(Http11InputBuffer.ja > va:261) > > > > > > > at org.apache.coyote.Request.doRead(Request.java:581) > > at > > org.apache.catalina.connector.InputBuffer.realReadBytes(InputBuffer.ja > va:326) > > > > > > > at > > org.apache.catalina.connector.InputBuffer.checkByteBufferEof(InputBuff > er.java:642) > > > > > > > at > > org.apache.catalina.connector.InputBuffer.readByte(InputBuffer.java:33 > 7) > > > > > > > at > > org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream > .java:93) > > > > > > > while the AJP Connector returns -1. > > > > You're right I can't expect an EOFException, the API just mentions > > generic IOException. I guess my expectation comes from the fact > > that the HTTP Connector throws an IOException on both read and > > write while the AJP one only throws a java.io.IOException: Broken > > pipe exception on write but not on read where it returns -1. > > > > I don't know the networking API well enough to say the behaviour > > is wrong, just hoping I can get the AJP Connector to signal (via > > some IOException) on read that something is unexpected. > > I'd actually say that Tomcat is being inconsistent, here, and that the > EOFException should be "fixed" so that it returns -1 from a read instead > . > > It would be best for you to handle the read=-1 situation in the same > way that you handle EOFException. You could even perform a read and > throw an EOFExceptin yourself if you get a -1 unexpectedly. >
Forget about EOFException, that was my mistake in the first email (and subject). I'm interested in improved handling of aborted connections (at least most of them). That's my end goal. read=-1 solely does not provide sufficient information to be able to distinguish between malformed request bodies and actual aborted connections. That's why the ServletInputStream#readLine API says: Returns: an integer specifying the actual number of bytes read, or -1 if the end of the stream is reached Throws: IOException - if an input or output exception has occurred I understand that detecting aborted connections is not always possible because of the missed handshakes but given that apache2 and tomcat are most likely in the same local network and assuming the protocol closes sockets properly, as long as apache2 detects it tomcat should also. I therefore think my expectation for an IOException when trying to read is justified. Do you still think otherwise? > > > Here's the relevant apache log [Wed Jan 17 14:42:41 2018] > > [23791:140542260176640] [info] ajp_service::jk_ajp_common.c > > (2773): (directory_service) sending request to tomcat failed > > (unrecoverable), because of client read error (attempt=1) [Wed Jan > > 17 14:42:41 2018] [23791:140542260176640] [debug] > > ajp_reset_endpoint::jk_ajp_common.c (851): (directory_service) > > resetting endpoint with socket 23 (socket shutdown) [Wed Jan 17 > > 14:42:41 2018] [23791:140542260176640] [debug] > > ajp_abort_endpoint::jk_ajp_common.c (821): (directory_service) > > aborting endpoint with socket 23 [Wed Jan 17 14:42:41 2018] > > [23791:140542260176640] [debug] jk_shutdown_socket::jk_connect.c > > (932): About to shutdown socket 23 [127.0.0.1:42538 -> > > 127.0.0.1:15443] [Wed Jan 17 14:42:41 2018] > > [23791:140542260176640] [debug] jk_is_input_event::jk_connect.c > > (1383): timeout during poll on socket 23 [127.0.0.1:42538 -> > > 127.0.0.1:15443] (timeout=100) [Wed Jan 17 14:42:41 2018] > > [23791:140542260176640] [debug] jk_shutdown_socket::jk_connect.c > > (1016): Shutdown socket 23 [127.0.0.1:42538 -> 127.0.0.1:15443] and > > read 0 lingering bytes in 0 sec. [Wed Jan 17 14:42:41 2018] > > [23791:140542260176640] [debug] ajp_done::jk_ajp_common.c (3282): > > recycling connection pool for worker directory_service and socket > > -1 [Wed Jan 17 14:42:41 2018] [23791:140542260176640] [debug] > > jk_handler::mod_jk.c (2921): Consumed 0 bytes of remaining request > > data for worker=directory_service [Wed Jan 17 14:42:41 2018] > > [23791:140542260176640] [info] jk_handler::mod_jk.c (2984): > > Aborting connection for worker=directory_service > > > > And tomcat log [DEBUG] [ajp-nio-127.0.0.1-15443-exec-7] > > 14:42:41.067 AjpNioProtocol - Processing socket > > [org.apache.tomcat.util.net.NioChannel@517a280d:java.nio.channels.Sock > etChannel[connected > > > > > > > local=/127.0.0.1:15443 remote=/127.0.0.1:42538]] with status [OPEN_READ] > > [DEBUG] [ajp-nio-127.0.0.1-15443-exec-7] 14:42:41.068 > > AjpNioProtocol - Found processor [null] for socket > > [org.apache.tomcat.util.net.NioChannel@517a280d:java.nio.channels.Sock > etChannel[connected > > > > > > > local=/127.0.0.1:15443 remote=/127.0.0.1:42538]] > > [DEBUG] [ajp-nio-127.0.0.1-15443-exec-7] 14:42:41.068 > > AjpNioProtocol - Popped processor > > [org.apache.coyote.ajp.AjpProcessor@1f4c1d25] from cache [DEBUG] > > [ajp-nio-127.0.0.1-15443-exec-7] 14:46:38.078 AjpMessage - Received > > 979 18 [DEBUG] [ajp-nio-127.0.0.1-15443-exec-7] 14:46:39.578 > > AjpMessage - Received 2 18 // read data is processed here but > > there's no IOException on read. When I try to write however I get a > > java.io.IOException: Broken pipe [DEBUG] > > [ajp-nio-127.0.0.1-15443-exec-7] 14:46:40.717 AjpProcessor - > > Socket: > > [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@1be2c90:org.a > pache.tomcat.util.net.NioChannel@517a280d:java.nio.channels.SocketChanne > l[connected > > > > The "IOException: Broken pipe" is exactly what you'd expect when > writing to a closed stream, so it's not surprising to me that's happenin > g. > > But you are more concerned about the reads than the writes, right? > > Yes, I am concerned about reads. Writes do work as expected raising IOExceptions. Geroge