[wildfly-dev] WF 8.0 HTTP Upgrade help needed
Przemyslaw Bielicki
pbielicki at gmail.com
Fri Apr 4 03:30:27 EDT 2014
cool, thx a lot - that should do the trick :)
On Fri, Apr 4, 2014 at 9:27 AM, Stuart Douglas
<stuart.w.douglas at gmail.com>wrote:
>
>
>
> On Fri, Apr 4, 2014 at 6:19 PM, Przemyslaw Bielicki <pbielicki at gmail.com>wrote:
>
>> thanks a lot - it seems to work but:
>>
>> 1. Synchronized is bad :) anyway, you wrote that there is only one thread
>> so synchronization is not needed
>>
>
> oops, that was a copy paste error, it is not needed.
>
>
>> 2. Main issue: it's not bi-directional full-duplex (like WebSockets) i.e.
>> you are not able to receive and send data at the same time, it's still
>> synchronous
>>
>
> Web sockets is exactly the same. There is generally only one thread, and
> it handles both read and write notifications. If you are worried about the
> read thread just reading constantly and the write thread never getting a go
> then just put a limit on how much data can be queued before it is written
> out.
>
> At one point XNIO did have separate read and write threads, however it
> ended up being much slower as you end up needing to synchronise. A single
> IO thread is faster, you just have to be careful of how much you buffer on
> the read side before writing.
>
> Stuart
>
>
>>
>> many thanks anyway - nice try :)
>>
>>
>> On Fri, Apr 4, 2014 at 9:02 AM, Stuart Douglas <
>> stuart.w.douglas at gmail.com> wrote:
>>
>>> 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 at gmail.com <mailto:stuart.w.douglas at gmail.com>> wrote:
>>>>
>>>>
>>>>
>>>> Przemyslaw Bielicki wrote:
>>>>
>>>> I tried - exactly the same results.
>>>>
>>>> Another weird observation is that ServletOutputStream.isReady()
>>>> is
>>>> returning true even after the connection is closed
>>>> (ServletInputStream.__isFinished() is correctly returning
>>>> true).
>>>>
>>>>
>>>> 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 WriteListener
>>>> 6. WriteListener.onWritePossible(__) gets called and I process
>>>>
>>>> the data -
>>>> I clean the queue
>>>> 7. As long as I'm in WriteListener.onWritePossible(__)
>>>>
>>>> (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 called
>>>> 8. Only when I leave WriteListener.onWritePossible(__) method
>>>> the
>>>>
>>>> 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 but
>>>> WriteListener.onWritePossible(__) is never called again. When I
>>>>
>>>> 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
>>>> if {@link javax.servlet.__ServletOutputStream#isReady()} method
>>>>
>>>> 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 at gmail.com <mailto:stuart.w.douglas at gmail.com>
>>>> <mailto:stuart.w.douglas at __gmail.com
>>>>
>>>> <mailto:stuart.w.douglas at gmail.com>>> wrote:
>>>>
>>>> Can you try with the latest development build of Wildfly
>>>> (from
>>>> https://ci.jboss.org/hudson/____job/WildFly-latest-master/
>>>> <https://ci.jboss.org/hudson/__job/WildFly-latest-master/>
>>>>
>>>> <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
>>>>
>>>> ("upgrade".equalsIgnoreCase(____req.getHeader("Connection"))) {
>>>> req.upgrade(EchoHandler.class)____;
>>>>
>>>>
>>>> }
>>>> }
>>>> }
>>>>
>>>> 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
>>>>
>>>> ___________________________________________________
>>>>
>>>> wildfly-dev mailing list
>>>> wildfly-dev at lists.jboss.org <mailto:wildfly-dev at lists.jboss.org
>>>> >
>>>> <mailto:wildfly-dev at lists.__jboss.org
>>>> <mailto:wildfly-dev at 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 at lists.jboss.org
>>>> https://lists.jboss.org/mailman/listinfo/wildfly-dev
>>>>
>>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/wildfly-dev/attachments/20140404/b61cccbf/attachment-0001.html
More information about the wildfly-dev
mailing list