Commit 2871bb82 authored by Christian Schudt's avatar Christian Schudt

Prevent WritePendingException during delivery of BOSH packets.

This exception caused serious trouble, e.g.:
- Clients got an invalid/incomplete packet, which breaks them.
- Openfire logged related exceptions like org.jivesoftware.openfire.http.HttpConnectionClosedException: The http connection is no longer available to deliver content

It was caused by calling complete() although the previous write() has not completed (due to its async nature).

I also restructured that the Content-Length header is only set once.

Fixes OF-989.
See also https://community.igniterealtime.org/thread/57622
parent 25d492d4
...@@ -294,12 +294,13 @@ public class HttpBindServlet extends HttpServlet { ...@@ -294,12 +294,13 @@ public class HttpBindServlet extends HttpServlet {
} }
final byte[] byteContent = content.getBytes(StandardCharsets.UTF_8); final byte[] byteContent = content.getBytes(StandardCharsets.UTF_8);
// BOSH communication should not use Chunked encoding.
// This is prevented by explicitly setting the Content-Length header.
response.setContentLength(byteContent.length);
if (async) { if (async) {
response.getOutputStream().setWriteListener(new WriteListenerImpl(context, byteContent)); response.getOutputStream().setWriteListener(new WriteListenerImpl(context, byteContent));
} else { } else {
// BOSH communication should not use Chunked encoding.
// This is prevented by explicitly setting the Content-Length header.
context.getResponse().setContentLength(byteContent.length);
context.getResponse().getOutputStream().write(byteContent); context.getResponse().getOutputStream().write(byteContent);
context.getResponse().getOutputStream().flush(); context.getResponse().getOutputStream().flush();
context.complete(); context.complete();
...@@ -433,11 +434,12 @@ public class HttpBindServlet extends HttpServlet { ...@@ -433,11 +434,12 @@ public class HttpBindServlet extends HttpServlet {
} }
} }
static class WriteListenerImpl implements WriteListener { private static class WriteListenerImpl implements WriteListener {
private final AsyncContext context; private final AsyncContext context;
private final byte[] data; private final byte[] data;
private final String remoteAddress; private final String remoteAddress;
private volatile boolean written;
public WriteListenerImpl(AsyncContext context, byte[] data) { public WriteListenerImpl(AsyncContext context, byte[] data) {
this.context = context; this.context = context;
...@@ -447,14 +449,22 @@ public class HttpBindServlet extends HttpServlet { ...@@ -447,14 +449,22 @@ public class HttpBindServlet extends HttpServlet {
@Override @Override
public void onWritePossible() throws IOException { public void onWritePossible() throws IOException {
// This method may be invoked multiple times and by different threads, e.g. when writing large byte arrays.
Log.trace("Data can be written to [" + remoteAddress + "]"); Log.trace("Data can be written to [" + remoteAddress + "]");
ServletOutputStream servletOutputStream = context.getResponse().getOutputStream();
// BOSH communication should not use Chunked encoding. while (servletOutputStream.isReady()) {
// This is prevented by explicitly setting the Content-Length header. // Make sure a write/complete operation is only done, if no other write is pending, i.e. if isReady() == true
context.getResponse().setContentLength(data.length); // Otherwise WritePendingException is thrown.
if (!written) {
context.getResponse().getOutputStream().write(data); written = true;
context.complete(); servletOutputStream.write(data);
// After this write isReady() may return false, indicating the write is not finished.
// In this case onWritePossible() is invoked again as soon as the isReady() == true again,
// in which case we would only complete the request.
} else {
context.complete();
}
}
} }
@Override @Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment