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 {
}
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) {
response.getOutputStream().setWriteListener(new WriteListenerImpl(context, byteContent));
} 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().flush();
context.complete();
......@@ -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 byte[] data;
private final String remoteAddress;
private volatile boolean written;
public WriteListenerImpl(AsyncContext context, byte[] data) {
this.context = context;
......@@ -447,15 +449,23 @@ public class HttpBindServlet extends HttpServlet {
@Override
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 + "]");
// BOSH communication should not use Chunked encoding.
// This is prevented by explicitly setting the Content-Length header.
context.getResponse().setContentLength(data.length);
context.getResponse().getOutputStream().write(data);
ServletOutputStream servletOutputStream = context.getResponse().getOutputStream();
while (servletOutputStream.isReady()) {
// Make sure a write/complete operation is only done, if no other write is pending, i.e. if isReady() == true
// Otherwise WritePendingException is thrown.
if (!written) {
written = true;
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
public void onError(Throwable throwable) {
......
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