You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tomcat.apache.org by Christopher Schultz <ch...@christopherschultz.net> on 2023/04/12 18:31:00 UTC

Getting started with Websocket

All,

I'm finally dipping my toes into Websocket-based communication with my 
Tomcat-based applications. Is it possible to do everything with "real" 
code and not any annotations?

I was looking for something like the Servlet Async model where you take 
an existing request and put it into async mode:

         final AsyncContext ac = request.startAsync(request, response);

         ac.start(new Runnable() {

             @Override
             public void run() {
                 // Do some stuff

                 // Write the response
                 ac.complete();
             }
         });

All the tutorials I see use annotations to set up the Websocket 
endpoint, etc.

Is it possible and/or recommended to use pure-code registration of such 
endpoints and/or post-HTTP-request upgrade?

I'm wanting to do things like verify that the request belongs to an 
authenticated user with certain privileges, etc. and only then allow 
that user to connect using Websocket to trade data across the wire.

How can I relate a Websocket session to an authenticated user? Can I 
communicate back and forth between the Websocket world and the 
HTTP-based world like websocket.session <-> HttpSession?

If I want to send a fire-and-forget message to the client from the 
server, can I just:

session.getAsyncRemote().sendText("hello world");

and ignore the Future<?> object returned, or will I need to verify that 
message was sent before attempting to send another one?

Thanks,
-chris

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


AW: Getting started with Websocket

Posted by "Thomas Hoffmann (Speed4Trade GmbH)" <Th...@speed4trade.com.INVALID>.
Hello Chris,

> -----Ursprüngliche Nachricht-----
> Von: Christopher Schultz <ch...@christopherschultz.net>
> Gesendet: Mittwoch, 12. April 2023 20:31
> An: Tomcat Users List <us...@tomcat.apache.org>
> Betreff: Getting started with Websocket
> 
> All,
> 
> I'm finally dipping my toes into Websocket-based communication with my
> Tomcat-based applications. Is it possible to do everything with "real"
> code and not any annotations?
> 
> I was looking for something like the Servlet Async model where you take an
> existing request and put it into async mode:
> 
>          final AsyncContext ac = request.startAsync(request, response);
> 
>          ac.start(new Runnable() {
> 
>              @Override
>              public void run() {
>                  // Do some stuff
> 
>                  // Write the response
>                  ac.complete();
>              }
>          });
> 
> All the tutorials I see use annotations to set up the Websocket endpoint, etc.
> 
> Is it possible and/or recommended to use pure-code registration of such
> endpoints and/or post-HTTP-request upgrade?
> 
> I'm wanting to do things like verify that the request belongs to an authenticated
> user with certain privileges, etc. and only then allow that user to connect using
> Websocket to trade data across the wire.
> 
> How can I relate a Websocket session to an authenticated user? Can I
> communicate back and forth between the Websocket world and the HTTP-
> based world like websocket.session <-> HttpSession?
> 
> If I want to send a fire-and-forget message to the client from the server, can I
> just:
> 
> session.getAsyncRemote().sendText("hello world");
> 
> and ignore the Future<?> object returned, or will I need to verify that message
> was sent before attempting to send another one?
> 
> Thanks,
> -chris
> 
I used the simple example below.
You need to add a kind of hashmap per user for example. Its just a POC.

public class SseServlet extends HttpServlet
{

    private static final long serialVersionUID = 1L;

    @Override
    public void destroy()
    {
        //
    }

    @Override
    public void service(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException
    {
        // https://web.dev/eventsource-basics/
        // https://www.howopensource.com/2016/01/java-sse-chat-example/
        if ("text/event-stream".equals(req.getHeader("Accept")))
        {
            AsyncContext actx = req.startAsync();
            actx.setTimeout(0);

            System.out.println("new connection");

            res.setContentType("text/event-stream");
            res.setHeader("Cache-Control", "no-cache");
            res.setHeader("Connection", "keep-alive");
            res.setCharacterEncoding("UTF-8");

            Thread t = new Thread(new SseSender(actx, new Random().nextInt()));
            t.start();
            //save actx and use it when we need sent data to the client.
        }
    }



    class SseSender implements Runnable
    {
        private AsyncContext ctx;
        private int iRnd;
        private boolean bRunning = true;

        public SseSender(final AsyncContext p, final int i)
        {
            this.ctx = p;
            this.iRnd = i;

            // final String id = UUID.randomUUID().toString();

            ctx.addListener(new AsyncListener()
            {
                @Override
                public void onComplete(final AsyncEvent event) throws IOException {
                    System.out.println(iRnd + " completed");
                    bRunning = false;
                    // stop thread or remove ctx from list
                }
                @Override
                public void onError(final AsyncEvent event) throws IOException {
                    System.out.println(iRnd + " error");
                    bRunning = false;
                    // stop thread or remove ctx from list
                }
                @Override
                public void onStartAsync(final AsyncEvent event) throws IOException {
                    // Do nothing
                }
                @Override
                public void onTimeout(final AsyncEvent event) throws IOException {
                    bRunning = false;
                    System.out.println(iRnd + " timed out");
                    // stop thread or remove ctx from list
                }
            });
        }

        @Override
        public void run()
        {

            while (bRunning)
            {
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }

                if (bRunning)
                {
                    try
                    {
                        sendMessage(ctx.getResponse().getWriter(), 123, "test " + Integer.toString(iRnd));
                    }
                    catch (Exception e)
                    {
                        bRunning = false;
                        System.out.println("Exception on context " + iRnd);
                    }
                }
            }
            //ctx.complete();
        }

        private void sendMessage(final PrintWriter writer, final long id, final String message)
        {
            writer.print("id: ");
            writer.println(id);
            writer.print("data: ");
            writer.println(message);
            writer.println();
            writer.println("event");
            writer.println("eventname-xx");
            writer.flush();
        }
    }
}


The js-File:

let peer = null;
var source = null;

onconnect = function (ev) {
    console.log("on-connect");
    let port = ev.ports[0];
    port.onmessage = (e) => {
        if (e.data.type=='start' && e.data.port){
            peer=e.data.port;
        } else if (e.data.type=='msg' && peer){
            setInterval(()=>{
                peer.postMessage({type:'msg',msg:'greetings!'});
            },2000);
        }
    }
    
	if (source == null)
	{
		source = new EventSource('/inventar/sse/events?user=th');
	} 
	
	source.addEventListener('message', function(e) {
	console.log(e.data); // Notification
	}, false);
	
	source.addEventListener('open', function(e) {
	console.log("open");
	}, false);
	
	source.addEventListener('error', function(e) {
	if (e.readyState == EventSource.CLOSED) {
	console.log("closed");
	}
	}, false);  
	
    port.start();
}


And the html:

<html>
<head>
<script>

var worker = new SharedWorker('./test.js');
worker.port.start();

</script>
</head>
<body>
ok
</body>
</html>

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Getting started with Websocket

Posted by Mark Thomas <ma...@apache.org>.
On 12/04/2023 20:39, Christopher Schultz wrote:
> Mark,
> 
> On 4/12/23 15:21, Mark Thomas wrote:
>> On 12/04/2023 19:31, Christopher Schultz wrote:
>>> All,
>>>
>>> I'm finally dipping my toes into Websocket-based communication with 
>>> my Tomcat-based applications. Is it possible to do everything with 
>>> "real" code and not any annotations?
>>>
>>> I was looking for something like the Servlet Async model where you 
>>> take an existing request and put it into async mode:
>>>
>>>          final AsyncContext ac = request.startAsync(request, response);
>>>
>>>          ac.start(new Runnable() {
>>>
>>>              @Override
>>>              public void run() {
>>>                  // Do some stuff
>>>
>>>                  // Write the response
>>>                  ac.complete();
>>>              }
>>>          });
>>>
>>> All the tutorials I see use annotations to set up the Websocket 
>>> endpoint, etc.
>>>
>>> Is it possible and/or recommended to use pure-code registration of 
>>> such endpoints and/or post-HTTP-request upgrade?
>>
>> Configure an endpoint with code:
>> ServerContainer.addEndpoint(ServerEndpointConfig)
>>
>> or
>>
>> Upgrade an existing connection to WebSocket:
>> ServerContainer.upgradeHttpToWebSocket(...)
> 
> ACK
> 
> How do I get a reference to a ServerContainer instance?

ServletContext attribute:
jakarta.websocket.server.ServerContainer

>>> I'm wanting to do things like verify that the request belongs to an 
>>> authenticated user with certain privileges, etc. and only then allow 
>>> that user to connect using Websocket to trade data across the wire.
>>
>> There are various ways to do that. Either the upgrade call above or a 
>> custom ServerEndpointConfig.Configurator.modifyHandshake. The former 
>> has more access to the Servlet objects so is probably what you want.
> 
> I think so.
> 
>>> How can I relate a Websocket session to an authenticated user? Can I 
>>> communicate back and forth between the Websocket world and the 
>>> HTTP-based world like websocket.session <-> HttpSession?
>>
>> Not easily. The lifecycles of the sessions diverge at the point of 
>> upgrade. It is possible for the HttpSession to expire but the 
>> WebvSocket session doesn't get notified.
>>
>> You really need to set up everything at the point of the upgrade 
>> including retaining a reference to the HttpSession if you wish.
>>
>> There are various discussions of this - and how to keep them in sync - 
>> on line.
>>
>> It is less than ideal but there is no simple fix.
> 
> Sounds reasonable. So one thing I could to is inject a reference to the 
> HttpSession during the upgrade process. I would just have to be very 
> careful about accessing things in that session just in case it expires. 
> I could also copy whatever I need.
> 
> In this particular use-case, I don't actually need that, but I was 
> curious about the future.
> 
> It looks like there is a significant hole in these APIs that the WG 
> needs to think about fixing in a generalizable way.

Yes. So far the independent lifecycles have made this rather tricky. 
Solutions tend to require creating dependencies between the Servlet and 
WebSocket specs neither really wants.

A lot of the discussion (or references to it) can be found at 
https://github.com/jakartaee/websocket/issues/175

>>> If I want to send a fire-and-forget message to the client from the 
>>> server, can I just:
>>>
>>> session.getAsyncRemote().sendText("hello world");
>>>
>>> and ignore the Future<?> object returned, or will I need to verify 
>>> that message was sent before attempting to send another one?
>>
>> No. You need to make sure the previous message completed before you 
>> send the next one.
> 
> So I would probably then want to use:
> 
> session.getBasicRemote().sendText("hello world");
> 
> and then move-on with my life?

You'd need to wait before you moved on because that blocks but yes.

Note that the whole wait for the last message to finish before you send 
the next is a grey area that might get cleaned up in the next version of 
the spec.

> What about a ws Session which can be accessed by multiple threads that 
> might want to write? How do I coordinate them so that they don't 
> conflict? Would a simple:
> 
> synchronized(session) {
>    session.getBasicRemote().sendText("Message N");
> }
> 
> be sufficient?

Sufficient? Yes. Efficient? Possibly not ;)

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Getting started with Websocket

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Mark,

On 4/12/23 15:21, Mark Thomas wrote:
> On 12/04/2023 19:31, Christopher Schultz wrote:
>> All,
>>
>> I'm finally dipping my toes into Websocket-based communication with my 
>> Tomcat-based applications. Is it possible to do everything with "real" 
>> code and not any annotations?
>>
>> I was looking for something like the Servlet Async model where you 
>> take an existing request and put it into async mode:
>>
>>          final AsyncContext ac = request.startAsync(request, response);
>>
>>          ac.start(new Runnable() {
>>
>>              @Override
>>              public void run() {
>>                  // Do some stuff
>>
>>                  // Write the response
>>                  ac.complete();
>>              }
>>          });
>>
>> All the tutorials I see use annotations to set up the Websocket 
>> endpoint, etc.
>>
>> Is it possible and/or recommended to use pure-code registration of 
>> such endpoints and/or post-HTTP-request upgrade?
> 
> Configure an endpoint with code:
> ServerContainer.addEndpoint(ServerEndpointConfig)
> 
> or
> 
> Upgrade an existing connection to WebSocket:
> ServerContainer.upgradeHttpToWebSocket(...)

ACK

How do I get a reference to a ServerContainer instance?

>> I'm wanting to do things like verify that the request belongs to an 
>> authenticated user with certain privileges, etc. and only then allow 
>> that user to connect using Websocket to trade data across the wire.
> 
> There are various ways to do that. Either the upgrade call above or a 
> custom ServerEndpointConfig.Configurator.modifyHandshake. The former has 
> more access to the Servlet objects so is probably what you want.

I think so.

>> How can I relate a Websocket session to an authenticated user? Can I 
>> communicate back and forth between the Websocket world and the 
>> HTTP-based world like websocket.session <-> HttpSession?
> 
> Not easily. The lifecycles of the sessions diverge at the point of 
> upgrade. It is possible for the HttpSession to expire but the WebvSocket 
> session doesn't get notified.
> 
> You really need to set up everything at the point of the upgrade 
> including retaining a reference to the HttpSession if you wish.
> 
> There are various discussions of this - and how to keep them in sync - 
> on line.
> 
> It is less than ideal but there is no simple fix.

Sounds reasonable. So one thing I could to is inject a reference to the 
HttpSession during the upgrade process. I would just have to be very 
careful about accessing things in that session just in case it expires. 
I could also copy whatever I need.

In this particular use-case, I don't actually need that, but I was 
curious about the future.

It looks like there is a significant hole in these APIs that the WG 
needs to think about fixing in a generalizable way.

>> If I want to send a fire-and-forget message to the client from the 
>> server, can I just:
>>
>> session.getAsyncRemote().sendText("hello world");
>>
>> and ignore the Future<?> object returned, or will I need to verify 
>> that message was sent before attempting to send another one?
> 
> No. You need to make sure the previous message completed before you send 
> the next one.

So I would probably then want to use:

session.getBasicRemote().sendText("hello world");

and then move-on with my life?

What about a ws Session which can be accessed by multiple threads that 
might want to write? How do I coordinate them so that they don't 
conflict? Would a simple:

synchronized(session) {
   session.getBasicRemote().sendText("Message N");
}

be sufficient?

Thanks,
-chris

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Getting started with Websocket

Posted by Mark Thomas <ma...@apache.org>.
On 12/04/2023 19:31, Christopher Schultz wrote:
> All,
> 
> I'm finally dipping my toes into Websocket-based communication with my 
> Tomcat-based applications. Is it possible to do everything with "real" 
> code and not any annotations?
> 
> I was looking for something like the Servlet Async model where you take 
> an existing request and put it into async mode:
> 
>          final AsyncContext ac = request.startAsync(request, response);
> 
>          ac.start(new Runnable() {
> 
>              @Override
>              public void run() {
>                  // Do some stuff
> 
>                  // Write the response
>                  ac.complete();
>              }
>          });
> 
> All the tutorials I see use annotations to set up the Websocket 
> endpoint, etc.
> 
> Is it possible and/or recommended to use pure-code registration of such 
> endpoints and/or post-HTTP-request upgrade?

Configure an endpoint with code:
ServerContainer.addEndpoint(ServerEndpointConfig)

or

Upgrade an existing connection to WebSocket:
ServerContainer.upgradeHttpToWebSocket(...)

> I'm wanting to do things like verify that the request belongs to an 
> authenticated user with certain privileges, etc. and only then allow 
> that user to connect using Websocket to trade data across the wire.

There are various ways to do that. Either the upgrade call above or a 
custom ServerEndpointConfig.Configurator.modifyHandshake. The former has 
more access to the Servlet objects so is probably what you want.

> How can I relate a Websocket session to an authenticated user? Can I 
> communicate back and forth between the Websocket world and the 
> HTTP-based world like websocket.session <-> HttpSession?

Not easily. The lifecycles of the sessions diverge at the point of 
upgrade. It is possible for the HttpSession to expire but the WebvSocket 
session doesn't get notified.

You really need to set up everything at the point of the upgrade 
including retaining a reference to the HttpSession if you wish.

There are various discussions of this - and how to keep them in sync - 
on line.

It is less than ideal but there is no simple fix.

> If I want to send a fire-and-forget message to the client from the 
> server, can I just:
> 
> session.getAsyncRemote().sendText("hello world");
> 
> and ignore the Future<?> object returned, or will I need to verify that 
> message was sent before attempting to send another one?

No. You need to make sure the previous message completed before you send 
the next one.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org