devtools
devtools copied to clipboard
Normal socket wrongly named WebSocket
Just helped another user who got rather confused about seeing "WebSocket" traffic when just making normal HTTP calls. This issue is therefore created in hope of making DevTools less confusing when using it for monitoring network traffic.
The following program will repeatable make use of a fresh HttpClient
to make a HTTP request:
import 'dart:io';
Future<void> main(List<String> args) async {
// ignore: literal_only_boolean_expressions
while (true) {
final client = HttpClient();
await Future<void>.delayed(const Duration(seconds: 10));
final req = await client.getUrl(Uri.parse('https://example.com/'));
await req.close();
client.close();
}
}
In the "Network" overview we will see the following:
The problem is that we are not making any WebSocket connections. Instead, these events comes from normal socket events send by the Dart VM when the HttpClient are preparing the raw socket connection to the web server (later used for the HTTP communication). These events are of the type SocketStatistic
and documented here:
https://pub.dev/documentation/vm_service/latest/vm_service/SocketStatistic-class.html
(The reason why the HTTP event comes first is properly because it is sorted by the start time of the event. Since the event contains the time it takes for creating the socket, the HTTP statistic object is properly created before even attempting to create a socket).
Since WebSocket has a very specific meaning in the context of web communication, it is not correct to use the term for normal sockets (or the type "ws"). In fact, we will also see "WebSocket" when just creating a simple server communicating over normal sockets without any use of HTTP:
import 'dart:convert';
import 'dart:io';
Future<void> main(List<String> args) async {
final serverSocket = await ServerSocket.bind('localhost', 1337);
serverSocket.listen((socket) async {
print('Received: ${utf8.decode(await socket.single)}');
await socket.close();
});
// ignore: literal_only_boolean_expressions
while (true) {
await Future<void>.delayed(const Duration(seconds: 10));
final socket = await Socket.connect('localhost', 1337);
socket.writeln('Hello World');
await socket.flush();
await socket.close();
}
}
So in short, I think "WebSocket" should be renamed to "Socket" in the "Network" tab. Also, it would properly make sense if you can toggle if you want to see the sockets in your overview since it is really not that interesting for a lot of people if they just need to know which HTTP request are being sent.
It is indeed very confusing, especially when trying to debug websocket traffic.
This confusing naming convention remains as of Oct 7, 2022
Wow, this was opened three years ago and no comment or fix. I observe a lot of them even while we are reusing he same HttpClient
This came up in a recent blog post: https://blog.burkharts.net/everything-you-always-wanted-to-know-about-httpclients#heading-what-are-these-weird-websocket-requests-in-the-dart-network-tool
Would it make sense to make this a P2?
@bkonyi and @brianquinlan the network traffic in question comes from the VmService's getSocketProfile
RPC. What are your thoughts WRT to how this data should actually be classified if it is not web socket traffic? Do you have a proposed solution to this issue?
I'm not sure when this was changed but I recall that some time in the past we didn't see this WebSocket requests which are in reality the connection part of a normal HTTP request but the request time was shown in the actual HTTP request entry in the list. that's how it should be again.
@bkonyi and @brianquinlan the network traffic in question comes from the VmService's
getSocketProfile
RPC. What are your thoughts WRT to how this data should actually be classified if it is not web socket traffic? Do you have a proposed solution to this issue?
From https://github.com/flutter/devtools/issues/8010 it looks like all network requests are being classified as WebSocket requests. I don't know if DevTools or VmService is doing the wrong thing here.
DevTools is just reading the data we get from the VM service. @derekxu16 or @bkonyi can you take a look at the vm_service side of this and see if anything has changed recently that could be causing this? I know there was some protocol-side work on network related code in the last ~6 months or so.
@kenzieschmoll
DevTools is just reading the data we get from the VM service
Technically true but I think DevTools are at fault here.
If we start from this method that gets called with list of SocketStatistic
(which are of the type TCP or UDP based on the documentation https://pub.dev/documentation/vm_service/latest/vm_service/SocketStatistic/socketType.html . For me, it look very much like those SocketStatistic
objects are statistic about raw sockets and not websocket)):
/// Update or add all [requests] and [sockets] to the current requests.
///
/// If the entry already exists then it will be modified in place, otherwise
/// a new [HttpProfileRequest] will be added to the end of the requests lists.
///
/// [notifyListeners] will only be called once all [requests] and [sockets]
/// have be updated or added.
void updateOrAddAll({
required List<HttpProfileRequest> requests,
required List<SocketStatistic> sockets,
required int timelineMicrosOffset,
}) {
_updateOrAddRequests(requests);
_updateWebSocketRequests(sockets, timelineMicrosOffset);
notifyListeners();
}
https://github.com/flutter/devtools/blob/8935f2839931ef2b05f615fd498d3ab39266231e/packages/devtools_app/lib/src/screens/network/network_controller.dart#L381-L416
But for some reason, we just send those sockets to this _updateWebSocketRequests
method:
void _updateWebSocketRequests(
List<SocketStatistic> sockets,
int timelineMicrosOffset,
) {
for (final socket in sockets) {
final webSocket = WebSocket(socket, timelineMicrosOffset);
if (_requestsById.containsKey(webSocket.id)) {
final existingRequest = _requestsById[webSocket.id];
if (existingRequest is WebSocket) {
existingRequest.update(webSocket);
} else {
// If we override an entry that is not a Websocket then that means
// the ids of the requestMapping entries may collide with other types
// of requests.
assert(existingRequest is WebSocket);
}
} else {
value.add(webSocket);
// The new [sockets] may contain web sockets with the same ids as ones we
// already have, so we remove the current web sockets and replace them with
// updated data.
_requestsById[webSocket.id] = webSocket;
}
}
}
https://github.com/flutter/devtools/blob/8935f2839931ef2b05f615fd498d3ab39266231e/packages/devtools_app/lib/src/screens/network/network_controller.dart#L418-L443
Where we end up creating WebSocket
objects (not the type from dart:io
but its own type in DevTools). If we take a look at this WebSocket
class we can see where the problems in the GUI comes from:
class WebSocket extends NetworkRequest {
WebSocket(this._socket, this._timelineMicrosBase);
// ...
@override
String get contentType => 'websocket';
@override
String get type => 'ws';
String get socketType => _socket.socketType;
@override
String get uri => _socket.address;
@override
int get port => _socket.port;
// ...
// TODO(kenz): is this always GET? Chrome DevTools shows GET in the response
// headers for web socket traffic.
@override
String get method => 'GET';
// TODO(kenz): is this always 101? Chrome DevTools lists "101" for WS status
// codes with a tooltip of "101 Web Socket Protocol Handshake"
@override
String get status => '101';
// ...
}
https://github.com/flutter/devtools/blob/8935f2839931ef2b05f615fd498d3ab39266231e/packages/devtools_app/lib/src/screens/network/network_model.dart#L85-L187
So this type will always just call these events for ws
or websocket
. Even if they originally comes from SocketStatistic
that have nothing to do with websockets.
My guess is that this mistake got implemented with https://github.com/flutter/devtools/pull/2191 where the code looks like it mixing up what is socket and what a websocket is. Maybe the confusion happen because the developers was more focused on web development where the word "socket" more easily gets translated to "websocket".
But I can't find any traces of SocketStatistic
have ever meant websocket (tried look though vm_service). Yes, there can be a websocket inside a normal socket but to then conclude all sockets must be websockets... that is wrong. :)
@julemand101 yep, that's correct! SocketStatistic
is reporting details about Socket
objects, not WebSocket
, so the above code is making a bad assumption.
@bkonyi do we have any information from the VM about web socket traffic? From the devtools side, we request two things from the VM:
- an HttpProfile
- a SocketProfile
Can one or both of these profiles contain web socket traffic? and if so, how is web socket traffic distinguished from other types of network traffic within those profile(s)?
Looking forward to solving it, the problem right now is very difficult for development debugging