Dubhe

feat(request): add request listener

  1 +import 'dart:async';
  2 +import 'dart:convert';
  3 +import 'dart:io';
  4 +
  5 +import 'package:auto_track/auto_track/track/track.dart';
  6 +
  7 +import '../../utils/request_model.dart';
  8 +import '../page_view/page_stack.dart';
  9 +
  10 +class HttpClientRequestWithChecker implements HttpClientRequest {
  11 + final HttpClientRequest _realRequest;
  12 + final Stopwatch _stopwatch;
  13 + final Page? pageInfoData;
  14 +
  15 + HttpClientRequestWithChecker(
  16 + this._realRequest, this._stopwatch, this.pageInfoData);
  17 +
  18 + @override
  19 + bool get bufferOutput => _realRequest.bufferOutput;
  20 +
  21 + @override
  22 + int get contentLength => _realRequest.contentLength;
  23 +
  24 + @override
  25 + Encoding get encoding => _realRequest.encoding;
  26 +
  27 + @override
  28 + bool get followRedirects => _realRequest.followRedirects;
  29 +
  30 + @override
  31 + int get maxRedirects => _realRequest.maxRedirects;
  32 +
  33 + @override
  34 + bool get persistentConnection => _realRequest.persistentConnection;
  35 +
  36 + @override
  37 + void add(List<int> data) {
  38 + _realRequest.add(data);
  39 + }
  40 +
  41 + @override
  42 + void addError(Object error, [StackTrace? stackTrace]) {
  43 + _realRequest.addError(error, stackTrace);
  44 + }
  45 +
  46 + @override
  47 + Future addStream(Stream<List<int>> stream) {
  48 + return _realRequest.addStream(stream);
  49 + }
  50 +
  51 + @override
  52 + Future<HttpClientResponse> close() async {
  53 + return _realRequest.close().then((HttpClientResponse response) {
  54 + _checkResponse(_realRequest, response);
  55 + return response;
  56 + }).catchError((dynamic error, dynamic stackTrace) {}, test: (error) {
  57 + _stopwatch.stop();
  58 + String message;
  59 + if (error is HttpException) {
  60 + message = error.message;
  61 + } else {
  62 + message = error.toString();
  63 + }
  64 + Track.instance.reportHttpRequest(RequestModel(
  65 + uri: _realRequest.uri,
  66 + method: method,
  67 + pageId: pageInfoData?.pageInfo?.pageKey ?? "",
  68 + requestHeaders: _realRequest.headers,
  69 + message: message,
  70 + status: -1,
  71 + spent: _stopwatch.elapsedMilliseconds));
  72 + return false;
  73 + });
  74 + }
  75 +
  76 + @override
  77 + HttpConnectionInfo? get connectionInfo => _realRequest.connectionInfo;
  78 +
  79 + @override
  80 + List<Cookie> get cookies => _realRequest.cookies;
  81 +
  82 + @override
  83 + Future<HttpClientResponse> get done async {
  84 + return close();
  85 + }
  86 +
  87 + @override
  88 + Future flush() {
  89 + return _realRequest.flush();
  90 + }
  91 +
  92 + @override
  93 + HttpHeaders get headers => _realRequest.headers;
  94 +
  95 + @override
  96 + String get method => _realRequest.method;
  97 +
  98 + @override
  99 + Uri get uri => _realRequest.uri;
  100 +
  101 + @override
  102 + void write(Object? obj) {
  103 + _realRequest.write(obj);
  104 + }
  105 +
  106 + @override
  107 + void writeAll(Iterable objects, [String separator = '']) {
  108 + _realRequest.writeAll(objects, separator);
  109 + }
  110 +
  111 + @override
  112 + void writeCharCode(int charCode) {
  113 + _realRequest.writeCharCode(charCode);
  114 + }
  115 +
  116 + @override
  117 + void writeln([Object? obj = '']) {
  118 + _realRequest.writeln(obj);
  119 + }
  120 +
  121 + void _checkResponse(HttpClientRequest request, HttpClientResponse response) {
  122 + String message = 'status ${response.statusCode}';
  123 + message = '$message: ${response.reasonPhrase}';
  124 +
  125 + _stopwatch.stop();
  126 +
  127 + Track.instance.reportHttpRequest(RequestModel(
  128 + uri: _realRequest.uri,
  129 + method: method,
  130 + pageId: pageInfoData?.pageInfo?.pageKey ?? "",
  131 + requestHeaders: request.headers,
  132 + responseHeaders: response.headers,
  133 + message: message,
  134 + status: response.statusCode,
  135 + spent: _stopwatch.elapsedMilliseconds));
  136 + }
  137 +
  138 + @override
  139 + set bufferOutput(bool bufferOutput) {
  140 + _realRequest.bufferOutput = bufferOutput;
  141 + }
  142 +
  143 + @override
  144 + set contentLength(int contentLength) {
  145 + _realRequest.contentLength = contentLength;
  146 + }
  147 +
  148 + @override
  149 + set encoding(Encoding encoding) {
  150 + _realRequest.encoding = encoding;
  151 + }
  152 +
  153 + @override
  154 + set followRedirects(bool followRedirects) {
  155 + _realRequest.followRedirects = followRedirects;
  156 + }
  157 +
  158 + @override
  159 + set maxRedirects(int maxRedirects) {
  160 + _realRequest.maxRedirects = maxRedirects;
  161 + }
  162 +
  163 + @override
  164 + set persistentConnection(bool persistentConnection) {
  165 + _realRequest.persistentConnection = persistentConnection;
  166 + }
  167 +
  168 + @override
  169 + void abort([Object? exception, StackTrace? stackTrace]) {
  170 + _realRequest.abort(exception, stackTrace);
  171 + }
  172 +}
  173 +
  174 +class HttpClientWithChecker implements HttpClient {
  175 + final HttpClient _realClient;
  176 +
  177 + Uri? url;
  178 + String? method;
  179 +
  180 + HttpClientWithChecker(this._realClient);
  181 +
  182 + @override
  183 + set connectionFactory(
  184 + Future<ConnectionTask<Socket>> Function(
  185 + Uri url, String? proxyHost, int? proxyPort)?
  186 + f) {
  187 + // TODO: add impl here
  188 + assert(false);
  189 + }
  190 +
  191 + @override
  192 + set keyLog(Function(String line)? callback) {
  193 + // TODO: add impl here
  194 + assert(false);
  195 + }
  196 +
  197 + @override
  198 + bool get autoUncompress => _realClient.autoUncompress;
  199 +
  200 + @override
  201 + set autoUncompress(bool value) => _realClient.autoUncompress = value;
  202 +
  203 + @override
  204 + Duration? get connectionTimeout => _realClient.connectionTimeout;
  205 +
  206 + @override
  207 + set connectionTimeout(Duration? value) =>
  208 + _realClient.connectionTimeout = value;
  209 +
  210 + @override
  211 + Duration get idleTimeout => _realClient.idleTimeout;
  212 +
  213 + @override
  214 + set idleTimeout(Duration value) => _realClient.idleTimeout = value;
  215 +
  216 + @override
  217 + int? get maxConnectionsPerHost => _realClient.maxConnectionsPerHost;
  218 +
  219 + @override
  220 + set maxConnectionsPerHost(int? value) =>
  221 + _realClient.maxConnectionsPerHost = value;
  222 +
  223 + @override
  224 + String? get userAgent => _realClient.userAgent;
  225 +
  226 + @override
  227 + set userAgent(String? value) => _realClient.userAgent = value;
  228 +
  229 + @override
  230 + void addCredentials(
  231 + Uri url, String realm, HttpClientCredentials credentials) =>
  232 + _realClient.addCredentials(url, realm, credentials);
  233 +
  234 + @override
  235 + void addProxyCredentials(String host, int port, String realm,
  236 + HttpClientCredentials credentials) =>
  237 + _realClient.addProxyCredentials(host, port, realm, credentials);
  238 +
  239 + @override
  240 + set authenticate(
  241 + Future<bool> Function(Uri url, String scheme, String? realm)? f) =>
  242 + _realClient.authenticate = f;
  243 +
  244 + @override
  245 + set authenticateProxy(
  246 + Future<bool> Function(
  247 + String host, int port, String scheme, String? realm)?
  248 + f) =>
  249 + _realClient.authenticateProxy = f;
  250 +
  251 + @override
  252 + set badCertificateCallback(
  253 + bool Function(X509Certificate cert, String host, int port)?
  254 + callback) =>
  255 + _realClient.badCertificateCallback = callback;
  256 +
  257 + @override
  258 + void close({bool force = false}) => _realClient.close(force: force);
  259 +
  260 + @override
  261 + Future<HttpClientRequest> delete(String host, int port, String path) =>
  262 + _realClient.delete(host, port, path);
  263 +
  264 + @override
  265 + Future<HttpClientRequest> deleteUrl(Uri url) => _realClient.deleteUrl(url);
  266 +
  267 + @override
  268 + set findProxy(String Function(Uri url)? f) => _realClient.findProxy = f;
  269 +
  270 + @override
  271 + Future<HttpClientRequest> get(String host, int port, String path) =>
  272 + _realClient.get(host, port, path);
  273 +
  274 + @override
  275 + Future<HttpClientRequest> getUrl(Uri url) =>
  276 + _addCheck(_realClient.getUrl(url), 'get', url);
  277 +
  278 + @override
  279 + Future<HttpClientRequest> head(String host, int port, String path) =>
  280 + _realClient.head(host, port, path);
  281 +
  282 + @override
  283 + Future<HttpClientRequest> headUrl(Uri url) =>
  284 + _addCheck(_realClient.headUrl(url), 'head', url);
  285 +
  286 + @override
  287 + Future<HttpClientRequest> patch(String host, int port, String path) =>
  288 + _realClient.patch(host, port, path);
  289 +
  290 + @override
  291 + Future<HttpClientRequest> patchUrl(Uri url) =>
  292 + _addCheck(_realClient.patchUrl(url), 'patch', url);
  293 +
  294 + @override
  295 + Future<HttpClientRequest> post(String host, int port, String path) =>
  296 + _realClient.post(host, port, path);
  297 +
  298 + @override
  299 + Future<HttpClientRequest> postUrl(Uri url) =>
  300 + _addCheck(_realClient.postUrl(url), 'post', url);
  301 +
  302 + @override
  303 + Future<HttpClientRequest> put(String host, int port, String path) =>
  304 + _realClient.put(host, port, path);
  305 +
  306 + @override
  307 + Future<HttpClientRequest> putUrl(Uri url) =>
  308 + _addCheck(_realClient.putUrl(url), 'put', url);
  309 +
  310 + @override
  311 + Future<HttpClientRequest> open(
  312 + String method, String host, int port, String path) {
  313 + const int hashMark = 0x23;
  314 + const int questionMark = 0x3f;
  315 + int fragmentStart = path.length;
  316 + int queryStart = path.length;
  317 + for (int i = path.length - 1; i >= 0; i--) {
  318 + final char = path.codeUnitAt(i);
  319 + if (char == hashMark) {
  320 + fragmentStart = i;
  321 + queryStart = i;
  322 + } else if (char == questionMark) {
  323 + queryStart = i;
  324 + }
  325 + }
  326 + String? query;
  327 + if (queryStart < fragmentStart) {
  328 + query = path.substring(queryStart + 1, fragmentStart);
  329 + path = path.substring(0, queryStart);
  330 + }
  331 + final Uri uri =
  332 + Uri(scheme: 'http', host: host, port: port, path: path, query: query);
  333 + return _addCheck(_realClient.open(method, host, port, path), method, uri);
  334 + }
  335 +
  336 + @override
  337 + Future<HttpClientRequest> openUrl(String method, Uri url) =>
  338 + _addCheck(_realClient.openUrl(method, url), method, url);
  339 +
  340 + Future<HttpClientRequest> _addCheck(
  341 + Future<HttpClientRequest> request, String method, Uri url) {
  342 + final Stopwatch stopwatch = Stopwatch()..start();
  343 + final Page? pageInfoData = PageStack.instance.getCurrentPage();
  344 + return request
  345 + .then((HttpClientRequest request) =>
  346 + HttpClientRequestWithChecker(request, stopwatch, pageInfoData))
  347 + .catchError((dynamic error, dynamic stackTrace) {}, test: (error) {
  348 + String message = error.toString();
  349 + if (error is SocketException) {
  350 + message = error.message;
  351 + }
  352 + Track.instance.reportHttpRequest(RequestModel(
  353 + uri: url,
  354 + method: method,
  355 + pageId: pageInfoData?.pageInfo?.pageKey ?? "",
  356 + requestHeaders: null,
  357 + message: message,
  358 + status: -1,
  359 + spent: stopwatch.elapsedMilliseconds));
  360 + return false;
  361 + });
  362 + }
  363 +}
  364 +
  365 +class _DefaultHttpOverrides extends HttpOverrides {}
  366 +
  367 +class AutoTrackHttpOverrides extends HttpOverrides {
  368 + HttpOverrides? _currentOverrides;
  369 +
  370 + AutoTrackHttpOverrides(this._currentOverrides) : super() {
  371 + _currentOverrides ??= _DefaultHttpOverrides();
  372 + }
  373 +
  374 + @override
  375 + HttpClient createHttpClient(SecurityContext? context) {
  376 + return HttpClientWithChecker(_currentOverrides!.createHttpClient(context));
  377 + }
  378 +
  379 + @override
  380 + String findProxyFromEnvironment(Uri url, Map<String, String>? environment) {
  381 + return _currentOverrides!.findProxyFromEnvironment(url, environment);
  382 + }
  383 +}
  384 +
  1 +class RequestModel {
  2 + RequestModel({
  3 + required this.uri,
  4 + required this.method,
  5 + required this.message,
  6 + required this.status,
  7 + required this.spent,
  8 + required this.pageId,
  9 + this.requestBody,
  10 + this.requestHeaders,
  11 + this.responseHeaders,
  12 + });
  13 +
  14 + Uri uri;
  15 + String method;
  16 + String message;
  17 + String pageId;
  18 + int status;
  19 + int spent;
  20 + dynamic requestBody;
  21 + dynamic requestHeaders;
  22 + dynamic responseHeaders;
  23 +
  24 + Map<String, dynamic> toMap() {
  25 + return {
  26 + 'uri': uri,
  27 + 'method': method,
  28 + 'message': message,
  29 + 'pageId': pageId,
  30 + 'status': status,
  31 + 'spent': spent,
  32 + 'requestBody': requestBody,
  33 + 'requestHeaders': requestHeaders,
  34 + 'responseHeaders': responseHeaders,
  35 + };
  36 + }
  37 +}