fix: логировать SESSION_INIT и реальный wire-seq в дампе трафика
This commit is contained in:
+63
-30
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
@@ -36,6 +37,7 @@ class Api {
|
|||||||
/// ради registerHandler/pushStream).
|
/// ради registerHandler/pushStream).
|
||||||
final PacketDispatcher _dispatcher = PacketDispatcher();
|
final PacketDispatcher _dispatcher = PacketDispatcher();
|
||||||
StreamSubscription<(int, Map<String, dynamic>)>? _pushSub;
|
StreamSubscription<(int, Map<String, dynamic>)>? _pushSub;
|
||||||
|
StreamSubscription<WireLogEvent>? _wireLogSub;
|
||||||
|
|
||||||
SessionState _sessionState = SessionState.disconnected;
|
SessionState _sessionState = SessionState.disconnected;
|
||||||
final _stateController = StreamController<SessionState>.broadcast();
|
final _stateController = StreamController<SessionState>.broadcast();
|
||||||
@@ -80,7 +82,6 @@ class Api {
|
|||||||
int _reconnectAttempts = 0;
|
int _reconnectAttempts = 0;
|
||||||
bool _autoReconnect = false;
|
bool _autoReconnect = false;
|
||||||
int _sessionEpoch = 0;
|
int _sessionEpoch = 0;
|
||||||
int _seq = 0;
|
|
||||||
bool? _lastInteractive;
|
bool? _lastInteractive;
|
||||||
|
|
||||||
static const Duration _connectWatchdogTimeout = Duration(seconds: 75);
|
static const Duration _connectWatchdogTimeout = Duration(seconds: 75);
|
||||||
@@ -90,8 +91,6 @@ class Api {
|
|||||||
|
|
||||||
int get sessionEpoch => _sessionEpoch;
|
int get sessionEpoch => _sessionEpoch;
|
||||||
|
|
||||||
int _nextSeq() => _seq = (_seq + 1) & 0xFFFF;
|
|
||||||
|
|
||||||
// Публичное API
|
// Публичное API
|
||||||
|
|
||||||
/// Подключается к серверу, шлёт хэндшейк, запускает пинг.
|
/// Подключается к серверу, шлёт хэндшейк, запускает пинг.
|
||||||
@@ -130,7 +129,7 @@ class Api {
|
|||||||
}
|
}
|
||||||
if (gen != _connectGen) return;
|
if (gen != _connectGen) return;
|
||||||
|
|
||||||
final options = await _buildSessionOptions(endpoint);
|
final (session, wireLog) = await _buildSessionOptions(endpoint);
|
||||||
if (gen != _connectGen) return;
|
if (gen != _connectGen) return;
|
||||||
|
|
||||||
logger.i(
|
logger.i(
|
||||||
@@ -145,8 +144,10 @@ class Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final session = options;
|
|
||||||
_session = session;
|
_session = session;
|
||||||
|
// Подписываемся на wire-лог ядра ДО connect(), чтобы поймать пакеты
|
||||||
|
// SESSION_INIT-хендшейка (иначе они уходят до listen и теряются).
|
||||||
|
_wireLogSub = wireLog.listen(_onWireLog);
|
||||||
_pushSub = session.pushesMap().listen(_onPush);
|
_pushSub = session.pushesMap().listen(_onPush);
|
||||||
TrafficMonitor.instance.recordEvent(
|
TrafficMonitor.instance.recordEvent(
|
||||||
'connect',
|
'connect',
|
||||||
@@ -270,32 +271,21 @@ class Api {
|
|||||||
if (session == null) {
|
if (session == null) {
|
||||||
throw StateError('Нет соединения (${Opcode.name(opcode)})');
|
throw StateError('Нет соединения (${Opcode.name(opcode)})');
|
||||||
}
|
}
|
||||||
final seq = _nextSeq();
|
// Лог запроса/ответа ведётся из wire-лога ядра (_onWireLog) по настоящему
|
||||||
DebugSessionLog.instance.recordRequest(opcode, seq, payload);
|
// проводному seq, поэтому здесь ничего не пишем.
|
||||||
TrafficMonitor.instance.recordOutgoing(opcode, payload, seq, 0);
|
final KolibriResponse resp = await session
|
||||||
|
.requestMapFull(opcode, Map<String, dynamic>.from(payload))
|
||||||
final KolibriResponse resp;
|
.timeout(
|
||||||
try {
|
ServerConfig.requestTimeout,
|
||||||
resp = await session
|
onTimeout: () =>
|
||||||
.requestMapFull(opcode, Map<String, dynamic>.from(payload))
|
throw TimeoutException('${Opcode.name(opcode)} таймаут'),
|
||||||
.timeout(
|
);
|
||||||
ServerConfig.requestTimeout,
|
|
||||||
onTimeout: () =>
|
|
||||||
throw TimeoutException('${Opcode.name(opcode)} таймаут'),
|
|
||||||
);
|
|
||||||
} catch (e, st) {
|
|
||||||
DebugSessionLog.instance.recordError(seq, e);
|
|
||||||
Error.throwWithStackTrace(e, st);
|
|
||||||
}
|
|
||||||
|
|
||||||
final packet = Packet(
|
final packet = Packet(
|
||||||
cmd: resp.cmd,
|
cmd: resp.cmd,
|
||||||
seq: seq,
|
|
||||||
opcode: resp.opcode,
|
opcode: resp.opcode,
|
||||||
payload: resp.payload,
|
payload: resp.payload,
|
||||||
);
|
);
|
||||||
DebugSessionLog.instance.recordResponse(seq, packet.cmd, packet.payload);
|
|
||||||
TrafficMonitor.instance.recordIncoming(packet, 0);
|
|
||||||
|
|
||||||
if (packet.isError) {
|
if (packet.isError) {
|
||||||
if (isSessionExpiredPayload(packet.payload)) {
|
if (isSessionExpiredPayload(packet.payload)) {
|
||||||
@@ -303,7 +293,6 @@ class Api {
|
|||||||
messageFromErrorPayload(packet.payload),
|
messageFromErrorPayload(packet.payload),
|
||||||
);
|
);
|
||||||
_sessionExpiredController.add(ex);
|
_sessionExpiredController.add(ex);
|
||||||
DebugSessionLog.instance.recordError(seq, ex);
|
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
final text = _serverErrorText(packet.payload);
|
final text = _serverErrorText(packet.payload);
|
||||||
@@ -312,7 +301,6 @@ class Api {
|
|||||||
messageFromErrorPayload(packet.payload),
|
messageFromErrorPayload(packet.payload),
|
||||||
errorKey: resp.errorKey,
|
errorKey: resp.errorKey,
|
||||||
);
|
);
|
||||||
DebugSessionLog.instance.recordError(seq, err);
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
return packet;
|
return packet;
|
||||||
@@ -369,7 +357,7 @@ class Api {
|
|||||||
|
|
||||||
/// Строит устройство-поля и создаёт сессию ядра. Заодно заполняет
|
/// Строит устройство-поля и создаёт сессию ядра. Заодно заполняет
|
||||||
/// [_userAgent] и [_deviceId] для геттеров.
|
/// [_userAgent] и [_deviceId] для геттеров.
|
||||||
Future<KolibriSession> _buildSessionOptions(
|
Future<(KolibriSession, Stream<WireLogEvent>)> _buildSessionOptions(
|
||||||
({String host, int port}) endpoint,
|
({String host, int port}) endpoint,
|
||||||
) async {
|
) async {
|
||||||
final deviceInfo = DeviceInfoPlugin();
|
final deviceInfo = DeviceInfoPlugin();
|
||||||
@@ -477,7 +465,7 @@ class Api {
|
|||||||
final insecureTls = await TlsConfig.isInsecureAllowed();
|
final insecureTls = await TlsConfig.isInsecureAllowed();
|
||||||
final proxy = await _buildProxyUrl();
|
final proxy = await _buildProxyUrl();
|
||||||
|
|
||||||
return openSession(
|
return openSessionWithWireLog(
|
||||||
host: endpoint.host,
|
host: endpoint.host,
|
||||||
port: endpoint.port,
|
port: endpoint.port,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
@@ -519,10 +507,53 @@ class Api {
|
|||||||
opcode: event.$1,
|
opcode: event.$1,
|
||||||
payload: event.$2,
|
payload: event.$2,
|
||||||
);
|
);
|
||||||
TrafficMonitor.instance.recordIncoming(packet, 0);
|
// Учёт трафика пушей ведётся из wire-лога ядра (_onWireLog).
|
||||||
_dispatcher.dispatch(packet);
|
_dispatcher.dispatch(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Единый источник лога трафика: ядро отдаёт сюда каждый пакет обеих сторон —
|
||||||
|
/// включая SESSION_INIT-хендшейк и пинги — с настоящим проводным seq. Раньше
|
||||||
|
/// лог вёлся вручную из [sendRequest] по локальному счётчику, из-за чего
|
||||||
|
/// хендшейк/пинги в дамп не попадали, а seq был смещён относительно провода.
|
||||||
|
void _onWireLog(WireLogEvent e) {
|
||||||
|
final payload = _decodeWireJson(e.json);
|
||||||
|
final cmd = _wireCmdCode(e.cmd);
|
||||||
|
if (e.direction == 'out') {
|
||||||
|
DebugSessionLog.instance.recordRequest(e.opcode, e.seq, payload);
|
||||||
|
TrafficMonitor.instance.recordOutgoing(e.opcode, payload, e.seq, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Входящие: ответы матчатся по seq, пуши идут только в монитор трафика.
|
||||||
|
if (e.cmd != 'push') {
|
||||||
|
DebugSessionLog.instance.recordResponse(e.seq, cmd, payload);
|
||||||
|
}
|
||||||
|
TrafficMonitor.instance.recordIncoming(
|
||||||
|
Packet(cmd: cmd, seq: e.seq, opcode: e.opcode, payload: payload),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _wireCmdCode(String cmd) {
|
||||||
|
switch (cmd) {
|
||||||
|
case 'ok':
|
||||||
|
return CmdType.ok;
|
||||||
|
case 'not_found':
|
||||||
|
return CmdType.notFound;
|
||||||
|
case 'error':
|
||||||
|
return CmdType.error;
|
||||||
|
default:
|
||||||
|
return CmdType.request; // 'request' и 'push'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static dynamic _decodeWireJson(String json) {
|
||||||
|
try {
|
||||||
|
return jsonDecode(json);
|
||||||
|
} catch (_) {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _setSessionState(SessionState state) {
|
void _setSessionState(SessionState state) {
|
||||||
if (_sessionState == state) return;
|
if (_sessionState == state) return;
|
||||||
_sessionState = state;
|
_sessionState = state;
|
||||||
@@ -573,6 +604,8 @@ class Api {
|
|||||||
_livenessTimer = null;
|
_livenessTimer = null;
|
||||||
_pushSub?.cancel();
|
_pushSub?.cancel();
|
||||||
_pushSub = null;
|
_pushSub = null;
|
||||||
|
_wireLogSub?.cancel();
|
||||||
|
_wireLogSub = null;
|
||||||
_lastInteractive = null;
|
_lastInteractive = null;
|
||||||
final session = _session;
|
final session = _session;
|
||||||
_session = null;
|
_session = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user