[wildfly-dev] WF 8.0 HTTP Upgrade help needed

Stuart Douglas stuart.w.douglas at gmail.com
Fri Apr 4 03:27:24 EDT 2014


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/78f60d74/attachment-0001.html 


More information about the wildfly-dev mailing list