WF 8.0 HTTP Upgrade help needed

classic Classic list List threaded Threaded
17 messages Options
PB
Reply | Threaded
Open this post in threaded view
|

WF 8.0 HTTP Upgrade help needed

PB
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
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

Heiko Braun
At a first glance, I'd say your while{} block never returns. Is ServletInputStream.isFinished() what you've been looking for, instead of isReady()

On 28 Mar 2014, at 16:26, PB <[hidden email]> wrote:

while (in.isReady()) {



_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
PB
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

PB
Hi,

I dare to say that my code is correct ;) The problem is that it is NEVER called - this condition (even if it's wrong, however it's not) is never checked. When I remove this line:

out.setWriteListener(new EchoWriteListener(queue, out));

it seems to work. However, I have no writer, so it should be rather called SwallowListener... It's not my goal.

Maybe I should initialize WriteListener from the ReadListener after the first read? Or maybe, when I want to send back the response I should do this directly from the read listener? e.g. https://java.net/projects/tyrus/sources/source-code-repository/content/trunk/containers/servlet/src/main/java/org/glassfish/tyrus/servlet/TyrusHttpUpgradeHandler.java

Thanks,
Przemyslaw


On Fri, Mar 28, 2014 at 5:14 PM, Heiko Braun <[hidden email]> wrote:
At a first glance, I'd say your while{} block never returns. Is ServletInputStream.isFinished() what you've been looking for, instead of isReady()

On 28 Mar 2014, at 16:26, PB <[hidden email]> wrote:

while (in.isReady()) {




_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

Jim McGuinness
So here's what I'm getting (my source code is attached) ...


__Telnet__

dador-iMac:~ dador$ telnet 10.0.1.14 8080

Trying 10.0.1.14...

Connected to 10.0.1.14.

Escape character is '^]'.

GET /wildfly-debug/upgrade HTTP/1.1

Connection: upgrade


HTTP/1.1 101 Switching Protocols

Connection: Upgrade

X-Powered-By: Undertow 1

Server: Wildfly 8

Content-Length: 0

Date: Fri, 28 Mar 2014 20:22:36 GMT


the quick brown fox blah, blah, blah

^]

telnet> quit

Connection closed.

dador-iMac:~ dador$ 



__console___

15:22:36,825 INFO  [stdout] (default task-15) servlet doGet() received 'upgrade'

15:22:46,488 INFO  [stdout] (default I/O-3) listener onDataAvailable() called

15:22:46,488 INFO  [stdout] (default I/O-3) listener read 'the quick brown fox blah, blah, blah'; successfully offered to queue

15:22:46,489 INFO  [stdout] (default I/O-3) listener read ''; successfully offered to queue

15:22:56,824 INFO  [stdout] (default I/O-3) listener onDataAvailable() called

15:22:56,824 INFO  [stdout] (default I/O-3) listener onAllDataRead() called

15:22:56,824 INFO  [stdout] (default I/O-3) here is data queued ...

15:22:56,825 INFO  [stdout] (default I/O-3) the quick brown fox blah, blah, blah

15:22:56,825 INFO  [stdout] (default I/O-3) 

15:22:56,825 INFO  [stdout] (default I/O-3) 

15:22:56,825 INFO  [stdout] (default I/O-3) now do something



So the queue is getting the data as it's being piped in (the blanks in the queued data are telnet line feeds). But I have to send a signal to the servlet that all of the data has been sent (I simply close the telnet connection). Then the listener's onAllDataRead() method gets called.

So maybe this is a configuration issue. By the way, it made a difference for me in the telnet session when I specified the Connection as "upgrade" versus "Upgrade".

Just a suggestion, but you may also want to take a look at the non-blocking I/O if you input stream is a long one.

Good luck,

--Jim.


On Fri, Mar 28, 2014 at 3:05 PM, PB <[hidden email]> wrote:
Hi,

I dare to say that my code is correct ;) The problem is that it is NEVER called - this condition (even if it's wrong, however it's not) is never checked. When I remove this line:

out.setWriteListener(new EchoWriteListener(queue, out));

it seems to work. However, I have no writer, so it should be rather called SwallowListener... It's not my goal.

Maybe I should initialize WriteListener from the ReadListener after the first read? Or maybe, when I want to send back the response I should do this directly from the read listener? e.g. https://java.net/projects/tyrus/sources/source-code-repository/content/trunk/containers/servlet/src/main/java/org/glassfish/tyrus/servlet/TyrusHttpUpgradeHandler.java

Thanks,
Przemyslaw


On Fri, Mar 28, 2014 at 5:14 PM, Heiko Braun <[hidden email]> wrote:
At a first glance, I'd say your while{} block never returns. Is ServletInputStream.isFinished() what you've been looking for, instead of isReady()

On 28 Mar 2014, at 16:26, PB <[hidden email]> wrote:

while (in.isReady()) {




_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev


_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev

EchoHandler.java (964 bytes) Download Attachment
EchoReadListener.java (1K) Download Attachment
UpgradeServlet.java (1K) Download Attachment
PB
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

PB
Thanks for tips but actually it does not change anything :)

Still there is only the reader that is non-blocking. If you check my updated example you will see that in the EchoHandler class I added a writer task which is the only way I found at the moment to have an asynchronous (yet BLOCKING) way of sending responses to the (telnet) client.

What I noticed is that Read and Write listeners are kind of mutually exclusive i.e. when I set a WriteListener and ReadListener, the read part will be NEVER called. When I set only ReadListener it's OK but then I'm not able to write ServletOutputStream in a non-blocking way. I checked the Servlet 3.1 specification, which documents Upgrade part very poorly and they do not say anything about such limitations. I could try to set the WriterListener from within ReadListener but subsequent calls to setWriteListener throw IllegalStateException, which does not follow strictly the specification:

Section 3.7:
"A subsequent call to setReadListener in the scope of the current request is illegal and an IllegalStateException MUST be thrown."

Section 5.3 (symmetrical to 3.7 but regarding the write):
says nothing about IllegalStateExceptionin in case of subsequent calls to setWriteListener

There was a presentation during JavaOne 2013 showing the upgrade mechanism, but of course Oracle guys did not show the good pieces: https://www.youtube.com/watch?v=LX8tchFSpnA

In conclusion, I am able to read in a non-blocking manner and write in a blocking way. Anyone has an example how to use non-blocking read and write streams?

Cheers,
Przemyslaw




On Fri, Mar 28, 2014 at 9:31 PM, Jim McGuinness <[hidden email]> wrote:
So here's what I'm getting (my source code is attached) ...


__Telnet__

dador-iMac:~ dador$ telnet 10.0.1.14 8080

Trying 10.0.1.14...

Connected to 10.0.1.14.

Escape character is '^]'.

GET /wildfly-debug/upgrade HTTP/1.1

Connection: upgrade


HTTP/1.1 101 Switching Protocols

Connection: Upgrade

X-Powered-By: Undertow 1

Server: Wildfly 8

Content-Length: 0

Date: Fri, 28 Mar 2014 20:22:36 GMT


the quick brown fox blah, blah, blah

^]

telnet> quit

Connection closed.

dador-iMac:~ dador$ 



__console___

15:22:36,825 INFO  [stdout] (default task-15) servlet doGet() received 'upgrade'

15:22:46,488 INFO  [stdout] (default I/O-3) listener onDataAvailable() called

15:22:46,488 INFO  [stdout] (default I/O-3) listener read 'the quick brown fox blah, blah, blah'; successfully offered to queue

15:22:46,489 INFO  [stdout] (default I/O-3) listener read ''; successfully offered to queue

15:22:56,824 INFO  [stdout] (default I/O-3) listener onDataAvailable() called

15:22:56,824 INFO  [stdout] (default I/O-3) listener onAllDataRead() called

15:22:56,824 INFO  [stdout] (default I/O-3) here is data queued ...

15:22:56,825 INFO  [stdout] (default I/O-3) the quick brown fox blah, blah, blah

15:22:56,825 INFO  [stdout] (default I/O-3) 

15:22:56,825 INFO  [stdout] (default I/O-3) 

15:22:56,825 INFO  [stdout] (default I/O-3) now do something



So the queue is getting the data as it's being piped in (the blanks in the queued data are telnet line feeds). But I have to send a signal to the servlet that all of the data has been sent (I simply close the telnet connection). Then the listener's onAllDataRead() method gets called.

So maybe this is a configuration issue. By the way, it made a difference for me in the telnet session when I specified the Connection as "upgrade" versus "Upgrade".

Just a suggestion, but you may also want to take a look at the non-blocking I/O if you input stream is a long one.

Good luck,

--Jim.


On Fri, Mar 28, 2014 at 3:05 PM, PB <[hidden email]> wrote:
Hi,

I dare to say that my code is correct ;) The problem is that it is NEVER called - this condition (even if it's wrong, however it's not) is never checked. When I remove this line:

out.setWriteListener(new EchoWriteListener(queue, out));

it seems to work. However, I have no writer, so it should be rather called SwallowListener... It's not my goal.

Maybe I should initialize WriteListener from the ReadListener after the first read? Or maybe, when I want to send back the response I should do this directly from the read listener? e.g. https://java.net/projects/tyrus/sources/source-code-repository/content/trunk/containers/servlet/src/main/java/org/glassfish/tyrus/servlet/TyrusHttpUpgradeHandler.java

Thanks,
Przemyslaw


On Fri, Mar 28, 2014 at 5:14 PM, Heiko Braun <[hidden email]> wrote:
At a first glance, I'd say your while{} block never returns. Is ServletInputStream.isFinished() what you've been looking for, instead of isReady()

On 28 Mar 2014, at 16:26, PB <[hidden email]> wrote:

while (in.isReady()) {




_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev



_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev

EchoHandler.java (1K) Download Attachment
EchoReadListener.java (1K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

Stuart Douglas
In reply to this post by PB
Can you try with the latest development build of Wildfly (from
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
> [hidden email]
> https://lists.jboss.org/mailman/listinfo/wildfly-dev
_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
PB
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

PB
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.

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 <[hidden email]> wrote:
Can you try with the latest development build of Wildfly (from 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
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev


_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev

EchoHandler.java (1K) Download Attachment
EchoReadListener.java (1K) Download Attachment
EchoWriteListener.java (1K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

Rémy Maucherat
On 03/04/2014 15:21, 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.
>
> 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?)
This looks as expected (although Tomcat 8 should concurrently call
read/write in upgraded mode, as a proprietary extension to be able to
implement Websockets on top of Servlets 3.1, besides that Servlet 3.1 is
still not concurrent, so at most one thread processing a request a given
time).

Non blocking IO means it allows avoiding to block on IO, it doesn't
imply anything else. Servlets 3.1 is actually async IO (similar to NIO2,
rather than NIO1), since non blocking IO is unusable as is.

Rémy

_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
PB
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

PB
As I showed  I'm not able to read and write at the same time using non-blocking IO, as promised by Servlet 3.1. I'm able to read using non-blocking IO but my writes are blocking and I'm looking for the solution.

Regarding concurrent access, HTTP Upgrade promises that the upgraded protocol will be full-duplex, so I should be able to read and write concurrently but I'm not.

Another comment is that Servlets 3.1 is actually async ONLY from the server point of view - the client is still blocked because HTTP 1.1 is synchronous (we have to wait for HTTP 2.0 to solve this). Here Upgrade should help as it is a pure TCP, so I'm the master of my protocol and I'm no longer bound to HTTP - I can be as asynchronous as I want. Unfortunately Servlet 3.1 API does not respect this.

It looks like the specification is not meeting the promises given....


On Thu, Apr 3, 2014 at 4:41 PM, Rémy Maucherat <[hidden email]> wrote:
On 03/04/2014 15:21, 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.
>
> 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?)
This looks as expected (although Tomcat 8 should concurrently call
read/write in upgraded mode, as a proprietary extension to be able to
implement Websockets on top of Servlets 3.1, besides that Servlet 3.1 is
still not concurrent, so at most one thread processing a request a given
time).

Non blocking IO means it allows avoiding to block on IO, it doesn't
imply anything else. Servlets 3.1 is actually async IO (similar to NIO2,
rather than NIO1), since non blocking IO is unusable as is.

Rémy

_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev


_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

Jason T. Greene
Let me play with your use case, although it will be a few hours before I can get to it.

There are some limitations to the spec, but they are manageable. As an example the write is actually written for you in a non blocking fashion. It's basically an async deferred write. 

The undertow http handler API is, in my opinion a much better fit for pure non-blocking / reactive applications. 

On Apr 3, 2014, at 9:52 AM, Przemyslaw Bielicki <[hidden email]> wrote:

As I showed  I'm not able to read and write at the same time using non-blocking IO, as promised by Servlet 3.1. I'm able to read using non-blocking IO but my writes are blocking and I'm looking for the solution.

Regarding concurrent access, HTTP Upgrade promises that the upgraded protocol will be full-duplex, so I should be able to read and write concurrently but I'm not.

Another comment is that Servlets 3.1 is actually async ONLY from the server point of view - the client is still blocked because HTTP 1.1 is synchronous (we have to wait for HTTP 2.0 to solve this). Here Upgrade should help as it is a pure TCP, so I'm the master of my protocol and I'm no longer bound to HTTP - I can be as asynchronous as I want. Unfortunately Servlet 3.1 API does not respect this.

It looks like the specification is not meeting the promises given....


On Thu, Apr 3, 2014 at 4:41 PM, Rémy Maucherat <[hidden email]> wrote:
On 03/04/2014 15:21, 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.
>
> 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?)
This looks as expected (although Tomcat 8 should concurrently call
read/write in upgraded mode, as a proprietary extension to be able to
implement Websockets on top of Servlets 3.1, besides that Servlet 3.1 is
still not concurrent, so at most one thread processing a request a given
time).

Non blocking IO means it allows avoiding to block on IO, it doesn't
imply anything else. Servlets 3.1 is actually async IO (similar to NIO2,
rather than NIO1), since non blocking IO is unusable as is.

Rémy

_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev

_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev

_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
PB
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

PB

Hi Jason,

that would be great. I appreciate.

Take your time, it's not super urgent but I would like to have a complete example of the full-duplex upgrade mechanism using standard API. Then I would be more than interested in seeing how I can employ undertow to do the job :)

Cheers,
Przemyslaw

3 kwi 2014 20:31 "Jason T. Greene" <[hidden email]> napisał(a):
Let me play with your use case, although it will be a few hours before I can get to it.

There are some limitations to the spec, but they are manageable. As an example the write is actually written for you in a non blocking fashion. It's basically an async deferred write. 

The undertow http handler API is, in my opinion a much better fit for pure non-blocking / reactive applications. 

On Apr 3, 2014, at 9:52 AM, Przemyslaw Bielicki <[hidden email]> wrote:

As I showed  I'm not able to read and write at the same time using non-blocking IO, as promised by Servlet 3.1. I'm able to read using non-blocking IO but my writes are blocking and I'm looking for the solution.

Regarding concurrent access, HTTP Upgrade promises that the upgraded protocol will be full-duplex, so I should be able to read and write concurrently but I'm not.

Another comment is that Servlets 3.1 is actually async ONLY from the server point of view - the client is still blocked because HTTP 1.1 is synchronous (we have to wait for HTTP 2.0 to solve this). Here Upgrade should help as it is a pure TCP, so I'm the master of my protocol and I'm no longer bound to HTTP - I can be as asynchronous as I want. Unfortunately Servlet 3.1 API does not respect this.

It looks like the specification is not meeting the promises given....


On Thu, Apr 3, 2014 at 4:41 PM, Rémy Maucherat <[hidden email]> wrote:
On 03/04/2014 15:21, 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.
>
> 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?)
This looks as expected (although Tomcat 8 should concurrently call
read/write in upgraded mode, as a proprietary extension to be able to
implement Websockets on top of Servlets 3.1, besides that Servlet 3.1 is
still not concurrent, so at most one thread processing a request a given
time).

Non blocking IO means it allows avoiding to block on IO, it doesn't
imply anything else. Servlets 3.1 is actually async IO (similar to NIO2,
rather than NIO1), since non blocking IO is unusable as is.

Rémy

_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev

_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev

_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

Stuart Douglas
In reply to this post by PB


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
> <[hidden email] <mailto:[hidden email]>> 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/>).
>
>     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
>         [hidden email] <mailto:[hidden email]>
>         https://lists.jboss.org/__mailman/listinfo/wildfly-dev
>         <https://lists.jboss.org/mailman/listinfo/wildfly-dev>
>
>
_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
PB
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

PB
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 <[hidden email]> 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
<[hidden email] <mailto:[hidden email]>> 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/>).


    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
        [hidden email] <mailto:[hidden email]>
        https://lists.jboss.org/__mailman/listinfo/wildfly-dev
        <https://lists.jboss.org/mailman/listinfo/wildfly-dev>




_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

Stuart Douglas
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
> <[hidden email] <mailto:[hidden email]>> 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
>         <[hidden email] <mailto:[hidden email]>
>         <mailto:[hidden email]
>         <mailto:[hidden email]>>> 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
>         [hidden email] <mailto:[hidden email]>
>         <mailto:[hidden email]
>         <mailto:[hidden email]>>
>         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
> [hidden email]
> https://lists.jboss.org/mailman/listinfo/wildfly-dev
_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
PB
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

PB
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
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

many thanks anyway - nice try :)


On Fri, Apr 4, 2014 at 9:02 AM, Stuart Douglas <[hidden email]> 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
<[hidden email] <mailto:[hidden email]>> 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
        <[hidden email] <mailto:[hidden email]>
        <mailto:[hidden email]__gmail.com

        <mailto:[hidden email]>>> 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
        [hidden email] <mailto:[hidden email]>
        <mailto:[hidden email]__jboss.org
        <mailto:[hidden email]>>
        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
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev


_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

Stuart Douglas



On Fri, Apr 4, 2014 at 6:19 PM, Przemyslaw Bielicki <[hidden email]> 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 <[hidden email]> 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
<[hidden email] <mailto:[hidden email]>> 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
        <[hidden email] <mailto:[hidden email]>
        <mailto:[hidden email]__gmail.com

        <mailto:[hidden email]>>> 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
        [hidden email] <mailto:[hidden email]>
        <mailto:[hidden email]__jboss.org
        <mailto:[hidden email]>>
        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
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev



_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev
PB
Reply | Threaded
Open this post in threaded view
|

Re: WF 8.0 HTTP Upgrade help needed

PB
cool, thx a lot - that should do the trick :)



On Fri, Apr 4, 2014 at 9:27 AM, Stuart Douglas <[hidden email]> wrote:



On Fri, Apr 4, 2014 at 6:19 PM, Przemyslaw Bielicki <[hidden email]> 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 <[hidden email]> 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
<[hidden email] <mailto:[hidden email]>> 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
        <[hidden email] <mailto:[hidden email]>
        <mailto:[hidden email]__gmail.com

        <mailto:[hidden email]>>> 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
        [hidden email] <mailto:[hidden email]>
        <mailto:[hidden email]__jboss.org
        <mailto:[hidden email]>>
        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
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev




_______________________________________________
wildfly-dev mailing list
[hidden email]
https://lists.jboss.org/mailman/listinfo/wildfly-dev