Try something like the code below:
public class AsyncEchoUpgradeServlet extends HttpServlet {
@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
req.upgrade(Handler.class);
}
public static class Handler implements HttpUpgradeHandler {
@Override
public void init(final WebConnection wc) {
Listener listener = new Listener(wc);
try {
//we have to set the write listener before the read listener
//otherwise the output stream could be written to before it is
//in async mode
wc.getOutputStream().setWriteListener(listener);
wc.getInputStream().setReadListener(listener);
} catch (IOException e) {
UndertowLogger.REQUEST_IO_LOGGER.ioException(e);
}
}
@Override
public void destroy() {
}
}
private static class Listener implements WriteListener, ReadListener {
private final WebConnection connection;
private final Queue<String> queue = new ArrayDeque<String>();
private Listener(final WebConnection connection) {
this.connection = connection;
}
@Override
public synchronized void onDataAvailable() throws IOException {
byte[] data = new byte[100];
while (connection.getInputStream().isReady()) {
int read;
if ((read = connection.getInputStream().read(data)) != -1) {
queue.add(new String(data, 0, read));
}
onWritePossible();
}
}
@Override
public void onAllDataRead() throws IOException {
}
@Override
public synchronized void onWritePossible() throws IOException {
while (!queue.isEmpty() && connection.getOutputStream().isReady()) {
String data = queue.poll();
connection.getOutputStream().write(data.getBytes());
}
}
@Override
public void onError(final Throwable t) {
}
}
}
Przemyslaw Bielicki wrote:
Hi Stuart,
thx for the explanations. There is a problem anyway - if you set both
read and write listeners from the handler it won't work at all as in the
write listener ServletOutputStream.isReady() will be returning true all
the time (why not? the output is ready to send some data at the same
time in is ready to read some data), and the read listener will be
ignored (as the tread is focused on the writer). The solution here is
not to have while (out.isReady()) loop in the writer but a simple check
if (out.isReady()), but then you have to call onWritePossible yourself.
It means you need to pass a write listener reference to the read
listener - it's smells more than a bit.
I think I have my conclusion, that is: it is impossible to achieve
full-duplex (thus multiplexing) in an upgraded protocol using Servlet
3.1 API as there is "only ever one IO thread per connection" (I didn't
find such limitation in the specification - can you point out the
section in which it is said there should be on thread per connection?).
In order to achieve multiplexing one have to explicitly create a
separate thread dealing with either reading or writing the data.
Also, using Servlet 3.1 it is impossible to achieve non-blocking read
and write and multiplexing on the same connection, even using a separate
thread.
I will contact the specification owners to see their opinion.
Many thanks,
Przemyslaw
On Fri, Apr 4, 2014 at 12:33 AM, Stuart Douglas<stuart.w.douglas@gmail.com <mailto:stuart.w.douglas@gmail.com>> wrote:(ServletInputStream.__isFinished() is correctly returning true).
Przemyslaw Bielicki wrote:
I tried - exactly the same results.
Another weird observation is that ServletOutputStream.isReady() is
returning true even after the connection is closed6. WriteListener.onWritePossible(__) gets called and I process
Here's the scenario that works but I can write back the data
only once:
1. In HttpUpgradeHandler I set only the ReadListener
2. I switch the protocol and send some data
3. ReadListener gets activated i.e. onDataAvailable() is called.
4. I process the input data, read as much as possible and put
the input
into the queue
5. From within ReadListener I set the WriteListener7. As long as I'm in WriteListener.onWritePossible(__)
the data -
I clean the queue8. Only when I leave WriteListener.onWritePossible(__) method the
(while.out.isReady() is constantly returning true, which is a
correct
bahavior) the ReadListener is on-hold. I can send as much data
as I like
but onDataAvailable() is not calledWriteListener.onWritePossible(__) is never called again. When I
ReadListener.onDataAvailable() is called again and I can consume the
input data again.
9. I can process the input data again i.e. put it into the queue butif {@link javax.servlet.__ServletOutputStream#isReady()} method
try to
reset it I get IllegalStateException
Either the specification or implementation seem not very mature....
Wildfly behavior is consistent with the one of Tomcat.
As Remy said this is expected.
Basically there is only ever one IO thread per connection, so only
one method will be active at a time.
The reason why your listener method is not being called again would
become apparent if you look at the javadoc for the read/write listeners:
Subsequently the container will invoke this method if and only<mailto:stuart.w.douglas@__gmail.com
has been called and has returned <code>false</code>.
Basically what this means is that the listener is only invoked if
isReady() returns false at some point. If you have read some data
and then you want to echo it you should call the onWritePossible
method yourself, after you have received the data.
Stuart
At the moment I conclude that the non-blocking write is not
possible in
Servlet 3.1.
I would appreciate if someone can provide an example that
actually works
or explain why the weird behavior I observe is correct (is it?)
Cheers,
Przemyslaw
On Thu, Apr 3, 2014 at 6:18 AM, Stuart Douglas
<stuart.w.douglas@gmail.com <mailto:stuart.w.douglas@gmail.com>https://ci.jboss.org/hudson/____job/WildFly-latest-master/
<mailto:stuart.w.douglas@gmail.com>>> wrote:
Can you try with the latest development build of Wildfly (from
<https://ci.jboss.org/hudson/__job/WildFly-latest-master/>("upgrade".equalsIgnoreCase(____req.getHeader("Connection"))) {
<https://ci.jboss.org/hudson/__job/WildFly-latest-master/
<https://ci.jboss.org/hudson/job/WildFly-latest-master/>>).
There have been some fixes in this area, so your problem
may have
already been fixed.
Stuart
PB wrote:
Hi,
I'm testing the HTTP Upgrade feature of WF 8.0 and I'm
facing
some banal
problem. Basically my ReadListener is NEVER called.
Here's the code:
@WebServlet(urlPatterns = "/upgrade")
public class UpgradeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse
resp) throws ServletException, IOException {
if
req.upgrade(EchoHandler.class)____;___________________________________________________ <mailto:wildfly-dev@lists.__jboss.org
}
}
}
public class EchoHandler implements HttpUpgradeHandler {
@Override
public void init(WebConnection wc) {
try {
ServletInputStream in = wc.getInputStream();
ServletOutputStream out = wc.getOutputStream();
BlockingQueue<String> queue = new
LinkedBlockingQueue<String>();
in.setReadListener(new EchoReadListener(queue,
in));
out.setWriteListener(new
EchoWriteListener(queue, out));
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
public class EchoReadListener implements ReadListener {
@Override
public void onDataAvailable() throws IOException {
while (in.isReady()) {
int length = in.read(buffer);
String input = new String(buffer, 0, length);
if (false == queue.offer(input)) {
System.err.println("'" + input + "' input was
ignored");
}
}
}
I'm connecting to WF using telnet and sending the
upgrade request:
GET /example-webapp/upgrade HTTP/1.1
Host: localhost
Connection: upgrade
Upgrade: echo
and I'm getting correct response:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
X-Powered-By: Undertow 1
Server: Wildfly 8
Content-Length: 0
which means that from now on the protocol between my telnet
client and
WF is pure TCP.
So, I start typing some text, hit Enter and.... nothing
happens.
onDataAvailable() is NEVER called. More so, this makes
WF totally
irresponsive - my whole webapp is dead.
I believe, I'm doing something wrong - any ideas what
exactly?
There is
also a slight chance that Upgrade feature in WF is
f****d :)
Anyway, WF should not block even in case my upgraded
protocol is not
working correctly?
Many thanks,
Przemyslaw
<mailto:wildfly-dev@lists.jboss.org>>
https://lists.jboss.org/____mailman/listinfo/wildfly-dev
<https://lists.jboss.org/__mailman/listinfo/wildfly-dev>
<https://lists.jboss.org/__mailman/listinfo/wildfly-dev
<https://lists.jboss.org/mailman/listinfo/wildfly-dev>>
_______________________________________________
wildfly-dev mailing list
wildfly-dev@lists.jboss.org
https://lists.jboss.org/mailman/listinfo/wildfly-dev