Skip to content

Commit

Permalink
WebSocketStream: Use one WebSocketError object to error everything
Browse files Browse the repository at this point in the history
Error readable, writable and wss.closed with the same WebSocketError
object.

Also add wpts for this and other close error scenarios.

Bug: 41470216
Change-Id: I0c68390ba4e7bef9db6a19516ac4afc77ed290e2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5321157
Reviewed-by: Nidhi Jaju <[email protected]>
Commit-Queue: Adam Rice <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1266302}
  • Loading branch information
ricea authored and pull[bot] committed Mar 7, 2024
1 parent 5861d83 commit 1430650
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 0 deletions.
24 changes: 24 additions & 0 deletions websockets/handlers/passive-close-abort_wsh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c) 2024 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""
Wait for a Close frame from the client and then close the connection without
sending a Close frame in return.
"""

from mod_pywebsocket.handshake import AbortedByUserException


def web_socket_do_extra_handshake(request):
pass


def web_socket_transfer_data(request):
while True:
if request.ws_stream.receive_message() is None:
return


def web_socket_passive_closing_handshake(request):
raise AbortedByUserException('abrupt close')
44 changes: 44 additions & 0 deletions websockets/handlers/remote-close_wsh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright (c) 2024 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""
Perform a server-initiated close according to the parameters passed in the
query string. Supported parameters:
* code=INT: The close code to send in the close frame. If omitted the Close
frame will have an empty body.
* reason=TEXT: The reason to be sent in the close frame. Only sent if `code` is
set.
* abrupt=1: Close the connection without sending a Close frame.
Example: /remote-close?code=1000&reason=Done
"""

import urllib

from mod_pywebsocket.handshake import AbortedByUserException


def web_socket_do_extra_handshake(request):
pass


def web_socket_transfer_data(request):
parts = urllib.parse.urlsplit(request.uri)
parameters = urllib.parse.parse_qs(parts.query)
if 'abrupt' in parameters:
# Send a ping frame to make sure this isn't misinterpreted as a
# handshake failure.
request.ws_stream.send_ping('ping')
# Rudely close the connection.
raise AbortedByUserException('Abort the connection')
code = None
reason = None
if 'code' in parameters:
code = int(parameters['code'][0])
reason = parameters.get('reason', [''])[0]
request.ws_stream.close_connection(code, reason)
10 changes: 10 additions & 0 deletions websockets/stream/tentative/close.any.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// META: script=../../constants.sub.js
// META: script=resources/url-constants.js
// META: global=window,worker
// META: variant=?default
// META: variant=?wss
// META: variant=?wpt_flags=h2

Expand Down Expand Up @@ -108,6 +109,15 @@ promise_test(async () => {
'one second should have elapsed');
}, 'writer close() promise should not resolve until handshake completes');

promise_test(async t => {
const wss = new WebSocketStream(`${BASEURL}/passive-close-abort`);
await wss.opened;
wss.close({closeCode: 4000, reason: 'because'});
const error = await wss.closed.then(t.unreached_func('closed should reject'), e => e);
assert_equals(error.constructor, WebSocketError, 'error should be WebSocketError');
assert_equals(error.closeCode, 1006, 'close code should be Abnormal Closure');
}, 'incomplete closing handshake should be considered unclean close');

const abortOrCancel = [
{
method: 'abort',
Expand Down
74 changes: 74 additions & 0 deletions websockets/stream/tentative/remote-close.any.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// META: script=../../constants.sub.js
// META: script=resources/url-constants.js
// META: global=window,worker
// META: variant=?default
// META: variant=?wss
// META: variant=?wpt_flags=h2

'use strict';

promise_test(async t => {
const wss = new WebSocketStream(`${BASEURL}/remote-close?code=1000`);
const { readable, writable } = await wss.opened;
const { closeCode, reason } = await wss.closed;
assert_equals(closeCode, 1000, 'code should be 1000');
assert_equals(reason, '', 'reason should be empty');
const { value, done } = await readable.getReader().read();
assert_true(done, 'readable should be closed');
await promise_rejects_dom(t, 'InvalidStateError', writable.getWriter().ready,
'writable should be errored');
}, 'clean close should be clean');

promise_test(async () => {
const wss = new WebSocketStream(`${BASEURL}/remote-close`);
const { closeCode, reason } = await wss.closed;
assert_equals(closeCode, 1005, 'code should be No Status Rcvd');
assert_equals(reason, '', 'reason should be empty');
}, 'close frame with no body should result in status code 1005');

promise_test(async () => {
const wss = new WebSocketStream(`${BASEURL}/remote-close?code=4000&reason=robot`);
const { closeCode, reason } = await wss.closed;
assert_equals(closeCode, 4000, 'code should be 4000');
assert_equals(reason, 'robot', 'reason should be set');
}, 'reason should be passed through');

promise_test(async () => {
const wss = new WebSocketStream(`${BASEURL}/remote-close?code=4000&` +
'reason=%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88');
const { reason } = await wss.closed;
assert_equals(reason, 'ロボット', 'reason should be set');
}, 'UTF-8 reason should work');

promise_test(async t => {
const wss = new WebSocketStream(`${BASEURL}/remote-close?code=4567`);
const { writable } = await wss.opened;
const veryLargeMessage = new Uint8Array(20 * 1024 * 1024); // 20MB.
const writePromise = writable.getWriter().write(veryLargeMessage);
const closedError = await wss.closed.then(t.unreached_func('closed should reject'), e => e);
assert_equals(closedError.constructor, WebSocketError, 'error should be WebSocketError');
assert_equals(closedError.closeCode, 4567, 'closeCode should be set');
promise_rejects_js(t, WebSocketError, writePromise, 'write() should reject');
}, 'close with unwritten data should not be considered clean');

promise_test(async t => {
const wss = new WebSocketStream(`${BASEURL}/remote-close?code=4222&reason=remote`);
await wss.opened;
wss.close({closeCode: 4111, reason: 'local'});
const { closeCode, reason } = await wss.closed;
assert_equals(closeCode, 4222, 'remote code should be used');
assert_equals(reason, 'remote', 'remote reason should be used');
}, 'remote code and reason should be used');

promise_test(async t => {
const wss = new WebSocketStream(`${BASEURL}/remote-close?abrupt=1`);
const { readable, writable } = await wss.opened;
const closedError = await wss.closed.then(t.unreached_func('closed should reject'), e => e);
assert_equals(closedError.constructor, WebSocketError, 'error should be a WebSocketError');
assert_equals(closedError.name, 'WebSocketError', 'error name should be WebSocketError');
assert_equals(closedError.closeCode, 1006, 'code should be Abnormal Closure');
await promise_rejects_exactly(t, closedError, readable.getReader().read(),
'readable should be errored with the same object');
await promise_rejects_exactly(t, closedError, writable.getWriter().ready,
'writable should be errored with the same object');
}, 'abrupt close should give an error');

0 comments on commit 1430650

Please sign in to comment.