w3c / webtransport

WebTransport is a web API for flexible data transport
https://w3c.github.io/webtransport/
Other
838 stars 51 forks source link

Listening to event by event name #597

Closed MuratovBektur closed 4 months ago

MuratovBektur commented 6 months ago

It would be nice if it were possible to listen and send data depending on the type of event (eg ping, pong). This would allow:

1) Do not reinvent your wheel for processing incoming messages depending on the type of message

2) Do not wait until all messages arrive from the other side in order to extract the message type from the incoming data. And this will also allow you to immediately process large data (for example files)

The first option is to add some new method

const transport = new WebTransport(URL);
await transport.ready;

const stream = await transport.createBidirectionalStream();
await writer.writeWithCustomEvent('custom-event', data); // Please change writeWithCustomEvent to something more appropriate.

The second option is to restart the recording method.

const transport = new WebTransport(URL);
await transport.ready;

const stream = await transport.createBidirectionalStream();
await writer.write('custom-event', data);
jan-ivar commented 5 months ago

Hi Bektur, this appears to be question 3 from https://github.com/w3c/webtransport/issues/506.

Was my answer to question 3 in https://github.com/w3c/webtransport/issues/506#issuecomment-1537259616 unsatisfactory? If so I'm happy to resume discussion here. I had some questions there about what you're looking for exactly. If you could answer those it might help me understand what functionality seems missing.

MuratovBektur commented 5 months ago

When I said listening to events I meant something like this https://socket.io/docs/v4/listening-to-events/.

I saw your code. Did I understand correctly that when you enter any character into the input, you create a new connection and start subscribing to listening? If I understand you correctly, you suggest creating a new connection for each type of event, which can significantly increase the load on the server. I suggest adding a transaction ID to the payload for each message. I understand that this may slow down the communication process between the servers and the client a little, but it will reduce the load on the server and make the syntax for sending and receiving event types more explicit.

LPardue commented 5 months ago

An app would open a single WebTransport session that can accomodate multiple comcurrent WebTransport streams. Each stream can be used for separate interactions. Streams creation is very lightweight.

jan-ivar commented 5 months ago

When I said listening to events I meant something like this https://socket.io/docs/v4/listening-to-events/.

Socket.io is a JS library built on-top of WebSocket, where this:

socket.emit("details", {message: "Hello from server", time: Date.now()});

...is implemented by encoding semantics into a custom JSON-like payload format sent over WebSocket as plain text:

42["details",{"message":"Hello from server","time":1599241234567}]

Without it, instead of socket.on("details", processDetails), an app might write something like this instead (not exactly, but for illustrative purposes using a {topic, payload} scheme):

ws.onmessage = ({data}) => {
  const obj = JSON.parse(data);
  if (obj.topic == "details") processDetails(obj.payload);
}

Similarly, we'd expect JS libraries built on-top of WebTransport to provide higher-level abstractions like this. Feel free to write one!

jan-ivar commented 5 months ago

WebTransport is even lower-level than WebSocket, operating on bytes, and lacking even message-based framing inside of streams, and lacking order-guarantees outside of (between) streams. These network-level differences are features.

Applications (or JS libraries) are expected to binary-encode its semantics and handle these differences based on their specific needs, e.g. using a TLV-encoding (#598 might help apps communicate these to servers).

Did I understand correctly that when you enter any character into the input, you create a new connection and start subscribing to listening? If I understand you correctly, you suggest creating a new connection for each type of event, which can significantly increase the load on the server.

No, only a single connection is used. The code (which unfortunately isn't connecting right now due to the echo server being down for some reason), waits for ENTER (13) and then creates a new bidirectional stream using createBidirectionalStream() to transmit the message over. A bidirectional stream is a lightweight concept (basically akin to a transaction id).

The rest of the code is basically the encoding and decoding from/to text (since this example is sending of text):

send.onkeypress = async ({keyCode}) => {
  if (keyCode != 13) return;
  const {writable, readable} = new TextEncoderStream("utf-8");
  const writer = writable.getWriter();
  writer.write(send.value);
  writer.close();

  await readable
    .pipeThrough(await wt.createBidirectionalStream())
    .pipeThrough(new TextDecoderStream("utf-8"))
    .pipeTo(new WritableConsole());
}

You can still do framing the WebSocket way, but this is an alternative request/response pattern that avoids head-of-line blocking and takes advantage of the framing inherent in bidirectional streams (in practice, you'd probably want both).

wilaw commented 4 months ago

@MuratovBektur - @jan-ivar has provide code samples which we feel can meet your use-case. Architecturally, we prefer not to introduce events in to our workflow and framing into the Stream writer. WebTransport provides an intentionally low level API (lower than Websocket) on which complex and flexible use cases can be supported via application-level libraries.