mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-04-29 23:09:48 +08:00
152 lines
4.9 KiB
Dart
152 lines
4.9 KiB
Dart
/*
|
|
* Copyright 2024 Hongen Wang All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:proxypin/network/util/compress.dart';
|
|
import 'package:proxypin/network/util/logger.dart';
|
|
import 'package:proxypin/utils/har.dart';
|
|
|
|
import '../http/http.dart';
|
|
import 'interceptor.dart';
|
|
import 'manager/report_server_manager.dart';
|
|
|
|
/// Hosts interceptor
|
|
/// @author wanghongen
|
|
class ReportServerInterceptor extends Interceptor {
|
|
Future<ReportServerManager> get reportServerManager async => await ReportServerManager.instance;
|
|
|
|
static HttpClient httpClient = HttpClient();
|
|
|
|
@override
|
|
int get priority => 1000;
|
|
|
|
@override
|
|
Future<HttpRequest?> onRequest(HttpRequest request) async {
|
|
unawaited(_reportRequestIfSplit(request));
|
|
return request;
|
|
}
|
|
|
|
@override
|
|
Future<HttpResponse?> onResponse(HttpRequest request, HttpResponse response) async {
|
|
unawaited(reportServer(request, response));
|
|
return response;
|
|
}
|
|
|
|
@override
|
|
Future<void> onError(HttpRequest? request, error, StackTrace? stackTrace) async {
|
|
if (request != null) {
|
|
unawaited(reportServer(request, null, error: error, stackTrace: stackTrace));
|
|
}
|
|
return;
|
|
}
|
|
|
|
Future<void> _reportRequestIfSplit(HttpRequest request) async {
|
|
String requestUrl = request.requestUrl;
|
|
var manager = await reportServerManager;
|
|
var server = await manager.matchServer(requestUrl);
|
|
if (server == null || !server.splitReport) {
|
|
return;
|
|
}
|
|
var payload = Har.toHarRequest(request);
|
|
await _sendReport(server, payload, requestUrl, phase: "request");
|
|
}
|
|
|
|
Future<void> reportServer(HttpRequest request, HttpResponse? response,
|
|
{dynamic error, StackTrace? stackTrace}) async {
|
|
if (response != null) {
|
|
request.response = response;
|
|
}
|
|
|
|
String requestUrl = request.requestUrl;
|
|
var manager = await reportServerManager;
|
|
var server = await manager.matchServer(requestUrl);
|
|
if (server == null) {
|
|
return;
|
|
}
|
|
|
|
Map payload;
|
|
String? phase;
|
|
if (server.splitReport) {
|
|
if (request.response == null) {
|
|
return;
|
|
}
|
|
payload = Har.toHarResponse(request);
|
|
phase = "response";
|
|
} else {
|
|
payload = Har.toHar(request);
|
|
}
|
|
await _sendReport(server, payload, requestUrl, phase: phase);
|
|
}
|
|
|
|
Future<void> _sendReport(ReportServer server, Map payload, String requestUrl, {String? phase}) async {
|
|
try {
|
|
logger.i("reportServer start: $requestUrl -> ${server.name} (${server.serverUrl})");
|
|
|
|
var serverUrl = (server.serverUrl).trim();
|
|
if (serverUrl.isEmpty) {
|
|
logger.w('reportServer skipped: serverUrl empty for ${server.name}');
|
|
return;
|
|
}
|
|
if (!serverUrl.startsWith('http://') && !serverUrl.startsWith('https://')) {
|
|
serverUrl = 'http://$serverUrl';
|
|
}
|
|
|
|
final uri = Uri.parse(serverUrl);
|
|
|
|
List<int> body = utf8.encode(jsonEncode(payload));
|
|
final compression = server.compression?.toLowerCase();
|
|
if (compression == 'gzip') {
|
|
try {
|
|
body = gzipEncode(body);
|
|
} catch (e) {
|
|
logger.w('reportServer gzip compress failed: $e');
|
|
}
|
|
}
|
|
|
|
final ioReq = await httpClient.postUrl(uri).timeout(const Duration(seconds: 5));
|
|
|
|
final matchedRule = server.name;
|
|
if (matchedRule.isNotEmpty) {
|
|
// URL encode the server name to support non-ASCII characters (e.g., Chinese)
|
|
final encodedName = Uri.encodeComponent(matchedRule);
|
|
ioReq.headers.set('X-Report-Name', encodedName);
|
|
}
|
|
if (phase != null) {
|
|
ioReq.headers.set('X-Report-Phase', phase);
|
|
}
|
|
|
|
ioReq.headers.set(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8');
|
|
if (compression == 'gzip') {
|
|
ioReq.headers.set(HttpHeaders.contentEncodingHeader, 'gzip');
|
|
}
|
|
|
|
ioReq.add(body);
|
|
final ioResp = await ioReq.close().timeout(const Duration(seconds: 30));
|
|
final respText = await ioResp.transform(utf8.decoder).join();
|
|
if (ioResp.statusCode >= 200 && ioResp.statusCode < 300) {
|
|
logger.i('reportServer delivered to ${server.name} (${uri.toString()}), status=${ioResp.statusCode}');
|
|
} else {
|
|
logger.w('reportServer delivery to ${server.name} failed, status=${ioResp.statusCode}, body=$respText');
|
|
}
|
|
} catch (e, st) {
|
|
logger.e("reportServer error $requestUrl", error: e, stackTrace: st);
|
|
}
|
|
}
|
|
}
|