Hi everyone,
I'm trying to use the new async servlet mechanism introduced in 3.0 to
serve a large file over a long period of time without occupying a
thread. There is example code for doing this from the Jetty site. I have
attached a slightly modified and simplified version here:
@WebServlet(asyncSupported = true, name = "PrinceOfDarkness",
urlPatterns = "/prince")
public final class DataRateLimitedServlet extends HttpServlet {
private int buffersize = 8192;
private long pauseNS = TimeUnit.MILLISECONDS.toNanos(100);
ScheduledThreadPoolExecutor scheduler;
@Override
public void init() throws ServletException {
// Create and start a shared scheduler.
scheduler = new ScheduledThreadPoolExecutor(pool);
}
@Override
public void destroy() {
scheduler.shutdown();
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/x-data");
// If we have a file path and this is a jetty response, we can
use the JettyStream impl
ServletOutputStream out = response.getOutputStream();
// Jetty API was not used, so lets try the standards approach
// Can we find the content as an input stream
InputStream content = new
FileInputStream("/home/me/PRINCE_OF_DARKNESS.iso");
if (content == null) {
response.sendError(404);
return;
}
// Set a StandardStream as he write listener to write the
content asynchronously
out.setWriteListener(new StandardDataStream(content,
request.startAsync(), out));
}
/**
* A standard API Stream writer
*/
private final class StandardDataStream implements WriteListener,
Runnable {
private final InputStream content;
private final AsyncContext async;
private final ServletOutputStream out;
private StandardDataStream(InputStream content, AsyncContext
async, ServletOutputStream out) {
this.content = content;
this.async = async;
this.out = out;
}
@Override
public void onWritePossible() throws IOException {
// If we are able to write
if (out.isReady()) {
// Allocated a copy buffer for each write, so as to not
hold while paused
// TODO put these buffers into a pool
byte[] buffer = new byte[buffersize];
// read some content into the copy buffer
int len = content.read(buffer);
// If we are at EOF
if (len < 0) {
// complete the async lifecycle
async.complete();
return;
}
// write out the copy buffer. This will be an
asynchronous write
// and will always return immediately without
blocking. If a subsequent
// call to out.isReady() returns false, then this
onWritePossible method
// will be called back when a write is possible.
out.write(buffer, 0, len);
// Schedule a timer callback to pause writing. Because
isReady() is not called,
// a onWritePossible callback is no scheduled.
scheduler.schedule(this, pauseNS, TimeUnit.NANOSECONDS);
}
}
@Override
public void run() {
try {
// When the pause timer wakes up, call
onWritePossible. Either isReady() will return
// true and another chunk of content will be written,
or it will return false and the
// onWritePossible() callback will be scheduled when a
write is next possible.
onWritePossible();
} catch (Exception e) {
onError(e);
}
}
@Override
public void onError(Throwable t) {
getServletContext().log("Async Error", t);
async.complete();
}
}
}
I'm using this to serve a big file. But it doesn't get to serve a big
file. It gives this exception (Tomcat 8.0.32):
ApplicationContext.log Async Error
java.lang.IllegalStateException: not in non blocking mode.
at org.apache.coyote.Response.isReady(Response.java:609)
at
org.apache.catalina.connector.OutputBuffer.isReady(OutputBuffer.java:663)
What is weird is that it gets this after serving exactly 2457600 (300 *
8192) bytes, or at 2531328 (309 * 8192) bytes of the file.
Either I'm doing something wrong, or there's a major bug in the way
Tomcat is handling async output.
Btw Jetty is showing exactly the same behavior, failing at 300*8192 bytes.
Any ideas? AsyncContext seems like a great idea but is it used in the
real world? If so, how?
Thanks