Blame |
Last modification |
View Log
| Download
| RSS feed
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nonblocking;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.AsyncContext;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* This doesn't do anything particularly useful - it just writes a series of
* numbers to the response body while demonstrating how to perform non-blocking
* writes.
*/
public class NumberWriter
extends HttpServlet
{
private static final long serialVersionUID = 1L
;
@
Override
protected void doGet
(HttpServletRequest req, HttpServletResponse resp
)
throws ServletException,
IOException {
resp.
setContentType("text/plain");
resp.
setCharacterEncoding("UTF-8");
// Non-blocking IO requires async
AsyncContext ac = req.
startAsync();
// Use a single listener for read and write. Listeners often need to
// share state to coordinate reads and writes and this is much easier as
// a single object.
@
SuppressWarnings("unused")
NumberWriterListener listener =
new NumberWriterListener
(
ac, req.
getInputStream(), resp.
getOutputStream());
}
/**
* Keep in mind that each call may well be on a different thread to the
* previous call. Ensure that changes in values will be visible across
* threads. There should only ever be one container thread at a time calling
* the listener.
*/
private static class NumberWriterListener
implements ReadListener,
WriteListener
{
private static final int LIMIT =
10000;
private final AsyncContext ac
;
private final ServletInputStream sis
;
private final ServletOutputStream sos
;
private final AtomicInteger counter =
new AtomicInteger(0);
private volatile boolean readFinished =
false;
private byte[] buffer =
new byte[8192];
private NumberWriterListener
(AsyncContext ac, ServletInputStream sis,
ServletOutputStream sos
) {
this.
ac = ac
;
this.
sis = sis
;
this.
sos = sos
;
// In Tomcat, the order the listeners are set controls the order
// that the first calls are made. In this case, the read listener
// will be called before the write listener.
sis.
setReadListener(this);
sos.
setWriteListener(this);
}
@
Override
public void onDataAvailable
() throws IOException {
// There should be no data to read
int read =
0;
// Loop as long as there is data to read. If isReady() returns false
// the socket will be added to the poller and onDataAvailable() will
// be called again as soon as there is more data to read.
while (sis.
isReady() && read
> -
1) {
read = sis.
read(buffer
);
if (read
> 0) {
throw new IOException("Data was present in input stream");
}
}
}
@
Override
public void onAllDataRead
() throws IOException {
readFinished =
true;
// If sos is not ready to write data, the call to isReady() will
// register the socket with the poller which will trigger a call to
// onWritePossible() when the socket is ready to have data written
// to it.
if (sos.
isReady()) {
onWritePossible
();
}
}
@
Override
public void onWritePossible
() throws IOException {
if (readFinished
) {
int i = counter.
get();
boolean ready =
true;
while (i
< LIMIT
&& ready
) {
i = counter.
incrementAndGet();
String msg =
String.
format("%1$020d\n",
Integer.
valueOf(i
));
sos.
write(msg.
getBytes(StandardCharsets.
UTF_8));
ready = sos.
isReady();
}
if (i == LIMIT
) {
ac.
complete();
}
}
}
@
Override
public void onError
(Throwable throwable
) {
// Should probably log the throwable
ac.
complete();
}
}
}