d968304570
This is a reland of d84f359786
Original change's description:
> Revert commits part of #40548 which still has some design debates and breaks valid local network http traffic
>
> Bug: https://github.com/flutter/flutter/issues/72723
> Change-Id: Ib5f809fccf1fad69add441fd40c463da8dc8be36
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/192953
> Auto-Submit: Xiao Yu <xster@google.com>
> Commit-Queue: Vyacheslav Egorov <vegorov@google.com>
> Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
> Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
Bug: https://github.com/flutter/flutter/issues/72723
Change-Id: I466d888b3f324a996f0bef0463e7ab3df3179c56
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/195485
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
Commit-Queue: Xiao Yu <xster@google.com>
244 lines
7.5 KiB
Dart
244 lines
7.5 KiB
Dart
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
library dart._http;
|
|
|
|
import "dart:async";
|
|
import "dart:collection";
|
|
import "dart:convert";
|
|
import "dart:developer";
|
|
import "dart:io";
|
|
import "dart:math";
|
|
import "dart:typed_data";
|
|
import "dart:isolate";
|
|
|
|
import "package:async_helper/async_helper.dart";
|
|
import "package:expect/expect.dart";
|
|
|
|
import "../../../sdk/lib/internal/internal.dart"
|
|
show Since, valueOfNonNullableParamWithDefault, HttpStatus;
|
|
|
|
part "../../../sdk/lib/_http/crypto.dart";
|
|
part "../../../sdk/lib/_http/embedder_config.dart";
|
|
part "../../../sdk/lib/_http/http_impl.dart";
|
|
part "../../../sdk/lib/_http/http_date.dart";
|
|
part "../../../sdk/lib/_http/http_parser.dart";
|
|
part "../../../sdk/lib/_http/http_headers.dart";
|
|
part "../../../sdk/lib/_http/http_session.dart";
|
|
part "../../../sdk/lib/_http/websocket.dart";
|
|
part "../../../sdk/lib/_http/websocket_impl.dart";
|
|
|
|
class WebSocketFrame {
|
|
WebSocketFrame(int opcode, List<int> data);
|
|
}
|
|
|
|
// Class that when hooked up to the web socket protocol transformer will
|
|
// collect the message and expect it to be equal to the
|
|
// expectedMessage field when fully received.
|
|
class WebSocketMessageCollector {
|
|
List<int>? expectedMessage;
|
|
|
|
int messageCount = 0;
|
|
|
|
var data;
|
|
|
|
void Function()? onClosed;
|
|
|
|
WebSocketMessageCollector(Stream stream,
|
|
[List<int>? this.expectedMessage = null]) {
|
|
stream.listen(onMessageData, onDone: onClosed, onError: onError);
|
|
}
|
|
|
|
void onMessageData(buffer) {
|
|
if (buffer is String) {
|
|
buffer = utf8.encode(buffer);
|
|
}
|
|
Expect.listEquals(expectedMessage!, buffer);
|
|
messageCount++;
|
|
data = buffer;
|
|
}
|
|
|
|
void onError(e, trace) {
|
|
String msg = "Unexpected error $e";
|
|
if (trace != null) msg += "\nStackTrace: $trace";
|
|
Expect.fail(msg);
|
|
}
|
|
}
|
|
|
|
// Web socket constants.
|
|
const int FRAME_OPCODE_TEXT = 1;
|
|
const int FRAME_OPCODE_BINARY = 2;
|
|
|
|
// Function for building a web socket frame.
|
|
List<int> createFrame(bool fin, int opcode, int? maskingKey, List<int> data,
|
|
int offset, int count) {
|
|
int frameSize = 2;
|
|
if (count > 125) frameSize += 2;
|
|
if (count > 65535) frameSize += 6;
|
|
frameSize += count;
|
|
// No masking.
|
|
assert(maskingKey == null);
|
|
List<int> frame = new Uint8List(frameSize);
|
|
int frameIndex = 0;
|
|
frame[frameIndex++] = (fin ? 0x80 : 0x00) | opcode;
|
|
if (count < 126) {
|
|
frame[frameIndex++] = count;
|
|
} else if (count < 65536) {
|
|
frame[frameIndex++] = 126;
|
|
frame[frameIndex++] = count >> 8;
|
|
frame[frameIndex++] = count & 0xFF;
|
|
} else {
|
|
frame[frameIndex++] = 127;
|
|
for (int i = 0; i < 8; i++) {
|
|
frame[frameIndex++] = count >> ((7 - i) * 8) & 0xFF;
|
|
}
|
|
}
|
|
frame.setRange(frameIndex, frameIndex + count, data, offset);
|
|
return frame;
|
|
}
|
|
|
|
// Test processing messages which are sent in a single frame.
|
|
void testFullMessages() {
|
|
void testMessage(int opcode, List<int> message) {
|
|
int messageCount = 0;
|
|
// Use the same web socket protocol transformer for all frames.
|
|
var transformer = new _WebSocketProtocolTransformer();
|
|
var controller = new StreamController<List<int>>(sync: true);
|
|
WebSocketMessageCollector mc = new WebSocketMessageCollector(
|
|
controller.stream.transform(transformer), message);
|
|
|
|
List<int> frame =
|
|
createFrame(true, opcode, null, message, 0, message.length);
|
|
|
|
// Update the transformer with one big chunk.
|
|
messageCount++;
|
|
controller.add(frame);
|
|
mc.onClosed = () {
|
|
Expect.isNotNull(mc.data);
|
|
Expect.equals(0, transformer._state);
|
|
|
|
mc.data = null;
|
|
|
|
// Only run this part on small messages.
|
|
if (message.length < 1000) {
|
|
// Update the transformer one byte at the time.
|
|
messageCount++;
|
|
for (int i = 0; i < frame.length; i++) {
|
|
controller.add(<int>[frame[i]]);
|
|
}
|
|
Expect.equals(0, transformer._state);
|
|
Expect.isNotNull(mc.data);
|
|
mc.data = null;
|
|
|
|
// Update the transformer two bytes at the time.
|
|
messageCount++;
|
|
for (int i = 0; i < frame.length; i += 2) {
|
|
controller.add(frame.sublist(i, min(i + 2, frame.length)));
|
|
}
|
|
Expect.equals(0, transformer._state);
|
|
Expect.isNotNull(mc.data);
|
|
}
|
|
Expect.equals(messageCount, mc.messageCount);
|
|
print("Messages test, messages $messageCount");
|
|
};
|
|
controller.close();
|
|
}
|
|
|
|
void runTest(int from, int to, int step) {
|
|
for (int messageLength = from; messageLength < to; messageLength += step) {
|
|
List<int> message = [for (int i = 0; i < messageLength; i++) i & 0x7F];
|
|
testMessage(FRAME_OPCODE_TEXT, message);
|
|
for (int i = 0; i < messageLength; i++) message[i] = i & 0xFF;
|
|
testMessage(FRAME_OPCODE_BINARY, message);
|
|
}
|
|
}
|
|
|
|
// Test different message sizes.
|
|
runTest(0, 10, 1);
|
|
runTest(120, 130, 1);
|
|
runTest(0, 1000, 100);
|
|
runTest(65534, 65537, 1);
|
|
}
|
|
|
|
// Test processing of frames which are split into fragments.
|
|
void testFragmentedMessages() {
|
|
// Use the same web socket protocol transformer for all frames.
|
|
var transformer = new _WebSocketProtocolTransformer();
|
|
var controller = new StreamController<List<int>>(sync: true);
|
|
WebSocketMessageCollector mc =
|
|
new WebSocketMessageCollector(controller.stream.transform(transformer));
|
|
|
|
int messageCount = 0;
|
|
int frameCount = 0;
|
|
|
|
void testFragmentMessage(int opcode, List<int> message, int fragmentSize) {
|
|
messageCount++;
|
|
int messageIndex = 0;
|
|
int remaining = message.length;
|
|
bool firstFrame = true;
|
|
bool lastFrame = false;
|
|
while (!lastFrame) {
|
|
int payloadSize = min(fragmentSize, remaining);
|
|
lastFrame = payloadSize == remaining;
|
|
List<int> frame = createFrame(lastFrame, firstFrame ? opcode : 0x00, null,
|
|
message, messageIndex, payloadSize);
|
|
frameCount++;
|
|
messageIndex += payloadSize;
|
|
controller.add(frame);
|
|
remaining -= payloadSize;
|
|
firstFrame = false;
|
|
}
|
|
}
|
|
|
|
void testMessageFragmentation(int opcode, List<int> message) {
|
|
mc.expectedMessage = message;
|
|
|
|
// Test with fragmenting the message in different fragment sizes.
|
|
if (message.length <= 10) {
|
|
for (int i = 1; i < 10; i++) {
|
|
testFragmentMessage(opcode, message, i);
|
|
}
|
|
} else {
|
|
testFragmentMessage(opcode, message, 10);
|
|
testFragmentMessage(opcode, message, 100);
|
|
}
|
|
}
|
|
|
|
void runTest(int from, int to, int step) {
|
|
for (int messageLength = from; messageLength < to; messageLength += step) {
|
|
List<int> message = [for (int i = 0; i < messageLength; i++) i & 0x7F];
|
|
testMessageFragmentation(FRAME_OPCODE_TEXT, message);
|
|
for (int i = 0; i < messageLength; i++) message[i] = i & 0xFF;
|
|
testMessageFragmentation(FRAME_OPCODE_BINARY, message);
|
|
}
|
|
}
|
|
|
|
// Test different message sizes.
|
|
runTest(0, 10, 1);
|
|
runTest(120, 130, 1);
|
|
runTest(0, 1000, 100);
|
|
runTest(65534, 65537, 1);
|
|
print("Fragment messages test, messages $messageCount, frames $frameCount");
|
|
Expect.equals(messageCount, mc.messageCount);
|
|
}
|
|
|
|
void testUnmaskedMessage() {
|
|
var transformer = new _WebSocketProtocolTransformer(true);
|
|
var controller = new StreamController<List<int>>(sync: true);
|
|
asyncStart();
|
|
controller.stream.transform(transformer).listen((_) {}, onError: (e) {
|
|
asyncEnd();
|
|
});
|
|
var message = new Uint8List(10);
|
|
List<int> frame =
|
|
createFrame(true, FRAME_OPCODE_BINARY, null, message, 0, message.length);
|
|
controller.add(frame);
|
|
}
|
|
|
|
void main() {
|
|
testFullMessages();
|
|
testFragmentedMessages();
|
|
testUnmaskedMessage();
|
|
}
|