Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

The WebSocketStream Interface#48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
ricea wants to merge33 commits intomain
base:main
Choose a base branch
Loading
fromadd-websocketstream
Open

Conversation

@ricea
Copy link
Collaborator

@ricearicea commentedJul 14, 2023
edited by pr-previewbot
Loading

Specify the WebSocketStream API. This exposes backpressure to
JavaScript, avoiding pathological behavior when one side of the
connection is not keeping up. It also provides a modern API using
Streams and Promises to interoperate cleanly with the modern web
ecosystem.

See the explainer at
https://github.com/ricea/websocketstream-explainer/blob/master/README.md
for examples and motivation.

(SeeWHATWG Working Mode: Changes for more details.)


Preview |Diff

@ricearicea added do not merge yetPull request must not be merged per rationale in comment topic: websockets labelsJul 14, 2023
Copy link

@LamzinLamzin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Hi Adam,

Thanks for the PR! Looks good in general. However, I left a few comments.

Thanks,
Oleh

@ricea
Copy link
CollaboratorAuthor

The algorithms are complete now.

@nidhijaju@domenic PTAL

The PR Preview is unhappy for some reason, but it builds locally.

@ricearicea changed the title[WIP] The WebSocketStream InterfaceThe WebSocketStream InterfaceJan 28, 2024
@ricearicea removed the do not merge yetPull request must not be merged per rationale in comment labelJan 28, 2024
Temporarily force-link "writable stream writer" until it can be exportedfrom the streams standard.
This feature is not worth the complexity so it is being removed.
…cancel()"Put back mention of passing close code or reason to abort() or cancel(),in preparation for updating it to use WebSocketError.
Some algorithms that are common to WebSocket and WebSocketStream havebeen moved to a new section to be shared.
@dead-claudia
Copy link

Quick question: wouldn't it be better to model it this way?

  • ws = new WebSocketStream(url, options?): Open WebSocket
  • void await ws.ready: Wait for ready, fails with aOperationErrorDOMException on premature close.
  • void await ws.closed: Wait for closure
  • ws.readable: Receive messages like now, reads fail with aNotReadableErrorDOMException on close.
  • ws.writable: Send messages like now, writes fail with aNetworkErrorDOMException on close. Can be written to before the connection is established, to allow for writing messages immediately upon open.
  • To close, just close the writer. The current values ofcloseCode andcloseReason are used for the socket close frame.
  • On server close, setscloseCode andcloseReason accordingly.

WebIDL:

dictionary WebSocketStreamOptions {  sequence<USVString> protocols;  AbortSignalsignal;};interfaceWebSocketStream {constructor(USVString url,              optional WebSocketStreamOptions options);  readonly attribute ReadableStream readable;  readonly attribute WritableStream writable;  readonly attribute sequence<DOMString> extensions;  readonly attribute DOMString protocol;  readonly attribute Promise<undefined> opened;  readonly attribute Promise<undefined> closed;  [Clamp] attributeunsignedshort closeCode;  attribute USVString closeReason;};

This has a few other added benefits as well:

  1. It's closer tofetch and itsresponse = await fetch(request) processing model. Notably, you can write to requests infetch before the request is even issued.
  2. Everything just references a shared object of a single type. Easier for developers to wrap their minds around and easier for implementors to work with.
  3. No need for a dedicatedclose method, and custom close codes and reasons even work withpipeThrough.

Note: I dropped the URL field. Userland code can just employ aMap if they truly need to care about it.

@annevkannevk added the needs implementer interestMoving the issue forward requires implementers to express interest labelJan 29, 2024
Copy link
Member

@domenicdomenic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Only got a little ways through before I had to wrap up for the day.

Also apply backpressure on connection, and linkify "exists".
@ricea
Copy link
CollaboratorAuthor

  • ws.readable: Receive messages like now, reads fail with aNotReadableErrorDOMException on close.

I think always throwing an exception on close would lead to poor ergonomics. For example, even a simple function like

constwss=newWebSocketStream('/messages');forawait(constmessageofwss.readable){constdiv=newHTMLDivElement();div.textContent=message;body.appendChild(div);}

would have to be wrapped in atry { ... } catch { ... } block to deal with theNotReadableError exception.

  • ws.writable: Send messages like now, writes fail with aNetworkErrorDOMException on close. Can be written to before the connection is established, to allow for writing messages immediately upon open.

This is what WebTransport does with datagrams, but it adds a lot of complexity to the standard and implementation. In the case of WebSockets, no messages can be sent until the handshake response has been received anyway, so there's no performance benefit in queuing them early. I consciously chose not to give out the streams until they were useful to avoid the need to think about these issues.

  • To close, just close the writer. The current values ofcloseCode andcloseReason are used for the socket close frame.

This means you have to pass around the writer to any place that needs to be able to close the WebSocket. I have the impression that most users of WebSockets don't bother with the close codes but for those who do it feels awkward:

wss.closeCode=1000;wss.closeReason='Thank you';writer.close();
  • On server close, setscloseCode andcloseReason accordingly.

I'm not comfortable that they can be changed asynchronously outside of script:

  1. If their timing was unlucky, a developer could overwrite the value sent by the server when trying to close the connection from the user agent, and then there'd be no way to recover it.
  2. closeCode andcloseReason will change before any reaction towss.closed is executed, meaning that script could synchronously observe the fact the connection was closed before it had been informed. This sort of racy behaviour leads to hard-to-understand bugs.

This has a few other added benefits as well:

  1. It's closer tofetch and itsresponse = await fetch(request) processing model. Notably, you can write to requests infetch before the request is even issued.

In the case of HTTP, it's necessary to send the request body first, so it's not an exact parallel.fetch() doesn't make the response body available for reading until it actually has a response, which I think is the right choice.

  1. Everything just references a shared object of a single type. Easier for developers to wrap their minds around and easier for implementors to work with.

It's a bit easier to implement, yes, but in my opinion it's harder to use. Developers won't have to think about the various dictionaries in my design, they can just copy the calling patterns from examples.

Implementing the construction of aRequest from aRequestInit to match the fetch standard is a nightmare, but from the developer's perspective it's just an option bag, it doesn't even require thought.

WebSocketError is a bit of a wart, but I suspect most developers will never have to construct one.

  1. No need for a dedicatedclose method, and custom close codes and reasons even work withpipeThrough.

You can actually pass aWebSocketError throughpipeThrough, but I doubt anyone will.

Note: I dropped the URL field. Userland code can just employ aMap if they truly need to care about it.

I'm not sure of the usefulness of the URL field, but it's very easy to implement, and maybe if it saves someone from needing to use aMap it is worth it.

dead-claudia reacted with thumbs up emojijcayzac reacted with heart emoji

Copy link
Member

@domenicdomenic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Didn't yet get to the last two sections, but it's all looking quite nice so far; no substantial comments.

riceaand others added3 commitsJanuary 31, 2024 15:14
Co-authored-by: Domenic Denicola <d@domenic.me>
Co-authored-by: Domenic Denicola <d@domenic.me>
Co-authored-by: Domenic Denicola <d@domenic.me>
Copy link
Member

@domenicdomenic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

LGTM with nits, nice work!

riceaand others added7 commitsFebruary 21, 2024 15:38
Co-authored-by: Domenic Denicola <d@domenic.me>
Co-authored-by: Domenic Denicola <d@domenic.me>
Co-authored-by: Domenic Denicola <d@domenic.me>
Co-authored-by: Domenic Denicola <d@domenic.me>
Don't nest a link inside another link.
@jhugard
Copy link

Any thoughts on taking this as an opportunity to facilitate adding custom HTTP UPGRADE request headers?

For example, web clients currently must provide any auth token as a query parameter on the URL. This conflicts with security best practice, which recommend using an Authorization header to avoid accidental logging of security related info.

@ricea
Copy link
CollaboratorAuthor

Any thoughts on taking this as an opportunity to facilitate adding custom HTTP UPGRADE request headers?

I'm not going to add it in the initial PR.

I've considered adding it initially for WebSocketStream so we can experiment with a smaller and more adaptable user base. But we'd have to add it to the old WebSocket API too eventually. This is getting off-topic for this PR so please take future discussion to#16 (or don't -- that issue has already reached a length where most people won't read the whole thing).

WebIDL requires explicit defaults for optional arguments.Co-authored-by: Philip Jägenstedt <philip@foolip.org>
@dontcallmedom-bot
Copy link

This issue was mentioned inWEBRTCWG-2024-05-21 (Page 38)

@lucacasonato
Copy link
Member

Chrome has shipped this since 124 in stable. Deno is interested in shipping too. Is there any interest in this API from Gecko or WebKit? cc@annevk and@saschanaz

@saschanaz
Copy link
Member

This interface is quite similar to WebTransport but still is different in a few ways, I wonder it could be nicer if it was more similar so that potential migration to webtransport would be easier for devs? (e.g. have createBidirectionalStream to get the stream, even though there will only be a single pair of streams. Too hacky maybe? Also kinda interesting that WT somehow doesn't have the url attribute.)

The name is misleading btw, WebSocketStream is not really a stream but an interface to eventually get one.

The official position will be announced inmozilla/standards-positions#970.

@annevk
Copy link
Member

@dead-claudia
Copy link

Okay, I revisited#48 (comment), addressed the feedback, and came up with a replacement that also uses streams a bit better.

  • stream = new WebSocketStream(url, options?): Open WebSocket
  • stream.readable: Receive messages via a readable stream. Comes withcloseCode andcloseReason properties to read those after close.
  • stream.writable: Send messages via a writable stream. Comes withcloseCode andcloseReason properties to set those for whenever the stream is closed.
  • await stream.readable/writable.negotiated: Wait for the WebSocket negotiation to complete and populatestream.readable/writable.protocol andstream.readable/writable.extensions.
    • This promise rejects on open or negotiation failure.
  • Both streams close on socket disconnect and server close, as you'd expect.
  • Transfer steps for the subtype would require also transmitting negotiation info and close codes/reasons across the transfer boundary.

Here's the WebIDL:

dictionary WebSocketStreamOptions {    sequence<USVString> protocols = [];    AbortSignalsignal;};[Exposed=(Window,Worker)]interfaceWebSocketReadableStream : ReadableStream {    readonly attribute Promise<void> negotiated;    readonly attribute USVString protocol;    readonly attribute USVString extensions;    readonly attributeshort closeCode;    readonly attribute USVString closeReason;};[Exposed=(Window,Worker)]interfaceWebSocketWritableStream : WritableStream {    readonly attribute Promise<void> negotiated;    readonly attribute USVString protocol;    readonly attribute USVString extensions;    [Clamp] attributeshort closeCode;    attribute USVString closeReason;};[Exposed=(Window,Worker)]interfaceWebSocketStream {constructor(USVString url, optional WebSocketStreamOptions options = {});    readonly attribute WebSocketReadableStream readable;    readonly attribute WebSocketWritableStream writable;};

This fully separates readers and writers, so you don't need to pass the parent duplex stream around at all to set close codes. The reader and writer operate entirely independently, to the point you can even send them to different threads (and browsers may be able to optimize that to not require an intermediate controlling thread). For high-throughput web sockets, this could offer a major speed-up opportunity.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@foolipfoolipfoolip left review comments

@annevkannevkannevk left review comments

@domenicdomenicdomenic approved these changes

+2 more reviewers

@LamzinLamzinLamzin left review comments

@nidhijajunidhijajunidhijaju left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Labels

needs implementer interestMoving the issue forward requires implementers to express interesttopic: websockets

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

12 participants

@ricea@dead-claudia@jhugard@dontcallmedom-bot@lucacasonato@saschanaz@annevk@foolip@domenic@Lamzin@nidhijaju

[8]ページ先頭

©2009-2025 Movatter.jp