Support for additional segments in websocket endpoints

Hello all,

Currently, there are two mechanisms to register web socket endpoints.

  1. static, where components are known at registration time
  2. dynamic, where components can be registered later through the component manager

See https://extensions.xwiki.org/xwiki/bin/view/Extension/WebSocket%20Integration/#HEnd-pointComponents for me details/

In the case of dynamic endpoints, the web socket path is hardcoded to /websocket/{wiki}/{roleHint}, then a dedicated XWikiEndpointDispatcher static component takes care of mapping the web socket connection attempts to the right component using the value of the roleHint segment.

For my current work to integrate GitHub - yjs/y-websocket: Websocket Connector for Yjs, I wanted to extend the hardcoded /websocket/{wiki}/{roleHint} with an additional segment (representing the “room” in my context, i.e., the real-time session for a given editor). Because this is the expected for the library, with a static URL prefix, and a last variable path for the room id.

While looking for a proper way to support this (XCOMMONS-3370: Allow registering XWikiEndpointDispatchers with an add… by manuelleduc · Pull Request #1387 · xwiki/xwiki-commons · GitHub), we found out several limitations. Mainly that the pattern allowed for URL matching is very limited for web sockets (see Jakarta WebSocket Specification):

The value attribute must be a Java string that is a partial URI or URI-template (level-1), with a leading ‘/’. For a definition of URI-templates, see RFC 6570 (Gregorio et al. 2012)

And see RFC 6570 - URI Template for a definition of “level 1”.

Meaning that we cannot define a “catch-all” url mapping (e.g., /websocket/{wiki}/{roleHint}/**).

Therefore, we are limited to two options.

Option 1: hard-coding an optional additional segment

In this case, both /websocket/{wiki}/{roleHint} and /websocket/{wiki}/{roleHint}/{optionalSegment would be mapped to XWikiEndpointDispatcher.

Cons:

  • the resolved component would be responsible for checking if the correct number of segments is present (which is technically breaking)
  • the change would be limited to one single additional segment, and if we ever have to support two additional segments, we’ll need to hardcode yet another dedicated path

Pros:

  • I have an actual use case where clients’ code is expecting this pattern

Option 2: do nothing

Instead of passing an additional segment, a query parameter can be used.

Cons:

  • breaking the expectation of the client, which is possible more work integrators

Pros:

  • nothing to change server side

I’m -0 for option 1, +0 for option 2.

WDYT?

Same. I’m leaning towards option 2 (hoping it’s not too complex to use the query string) because option 1 is too specific.

Thanks,
Marius

As a follow up, here is how the client needs to be written to connect to the right web socket while having a query parameter.

const doc = new Y.Doc();
new WebsocketProvider("ws://localhost:8080/xwiki/websocket/xwiki", "yjs?room=My.Page", doc)

We need to proceed that way because y-websocket relies on string concatenation to join the URL with the room ID with a /.
This is not pretty but not complex and easy to abstract.

If your plan was to pass a document reference in the URL component, moving the document reference into a query parameter might actually be a good choice. Otherwise, you’ll run into problems when document names contain for example dots that need to be escaped with \ which is disallowed in URLs. See XWIKI-23190 for a similar problem that concerns Live Data where, similarly, a document reference is used in the path.

2 Likes

Thanks for you inputs! I went with option 2.