mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-09 18:10:29 +00:00
feat: move native communication to FFI (#1457)
This commit is contained in:
86 files changed
+4558
-886
No files matched your search
@@ -0,0 +1,93 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../../libgopeed_boot.dart';
|
||||
import '../../../ffi/libgopeed_worker.dart';
|
||||
import '../../../../util/util.dart';
|
||||
import '../gopeed_transport.dart';
|
||||
|
||||
GopeedTransport createTransport(GopeedTransportConfig config) => NativeGopeedTransport();
|
||||
|
||||
class NativeGopeedTransport implements GopeedTransport {
|
||||
NativeGopeedTransport()
|
||||
: _externalDio = Dio(
|
||||
BaseOptions(
|
||||
contentType: Headers.jsonContentType,
|
||||
sendTimeout: const Duration(seconds: 5),
|
||||
connectTimeout: const Duration(seconds: 5),
|
||||
receiveTimeout: const Duration(seconds: 120),
|
||||
),
|
||||
);
|
||||
|
||||
final Dio _externalDio;
|
||||
final LibgopeedWorker? _ffiWorker = Util.isDesktop() ? LibgopeedWorker() : null;
|
||||
|
||||
@override
|
||||
Future<dynamic> request(
|
||||
String path, {
|
||||
String method = 'GET',
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
}) async {
|
||||
final request = _RequestParts.from(path, queryParameters);
|
||||
final requestBody = data == null ? '' : jsonEncode(data);
|
||||
final payload = _ffiWorker != null
|
||||
? await _ffiWorker.invoke(method.toUpperCase(), request.path, request.query, requestBody)
|
||||
: await LibgopeedBoot.instance.invoke(
|
||||
method.toUpperCase(),
|
||||
request.path,
|
||||
query: request.query,
|
||||
body: requestBody,
|
||||
);
|
||||
return jsonDecode(payload);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<String>> proxyRequest(String uri, {dynamic data, Options? options}) {
|
||||
return _externalDio.request<String>(
|
||||
uri,
|
||||
data: data,
|
||||
options: (options ?? Options()).copyWith(responseType: ResponseType.plain),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String join(String path) => path;
|
||||
}
|
||||
|
||||
class _RequestParts {
|
||||
const _RequestParts(this.path, this.query);
|
||||
|
||||
final String path;
|
||||
final String query;
|
||||
|
||||
factory _RequestParts.from(String rawPath, Map<String, dynamic>? queryParameters) {
|
||||
var path = rawPath;
|
||||
var query = '';
|
||||
final queryIndex = rawPath.indexOf('?');
|
||||
if (queryIndex >= 0) {
|
||||
path = rawPath.substring(0, queryIndex);
|
||||
query = rawPath.substring(queryIndex + 1);
|
||||
}
|
||||
final extraQuery = _encodeQueryParameters(queryParameters);
|
||||
if (extraQuery.isNotEmpty) {
|
||||
query = query.isEmpty ? extraQuery : '$query&$extraQuery';
|
||||
}
|
||||
return _RequestParts(path, query);
|
||||
}
|
||||
|
||||
static String _encodeQueryParameters(Map<String, dynamic>? queryParameters) {
|
||||
if (queryParameters == null || queryParameters.isEmpty) return '';
|
||||
final segments = <String>[];
|
||||
queryParameters.forEach((key, value) {
|
||||
if (value == null) return;
|
||||
final values = value is Iterable ? value : [value];
|
||||
for (final item in values) {
|
||||
if (item == null) continue;
|
||||
segments.add('${Uri.encodeQueryComponent(key)}=${Uri.encodeQueryComponent(item.toString())}');
|
||||
}
|
||||
});
|
||||
return segments.join('&');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import '../gopeed_transport.dart';
|
||||
|
||||
GopeedTransport createTransport(GopeedTransportConfig config) {
|
||||
throw UnsupportedError('Gopeed transport is unavailable on this platform');
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../gopeed_transport.dart';
|
||||
|
||||
GopeedTransport createTransport(GopeedTransportConfig config) => RestGopeedTransport(config);
|
||||
|
||||
class RestGopeedTransport implements GopeedTransport {
|
||||
RestGopeedTransport(GopeedTransportConfig config)
|
||||
: _dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: '${Uri.base.origin}/',
|
||||
contentType: Headers.jsonContentType,
|
||||
sendTimeout: const Duration(seconds: 5),
|
||||
connectTimeout: const Duration(seconds: 5),
|
||||
receiveTimeout: const Duration(seconds: 120),
|
||||
),
|
||||
) {
|
||||
_dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
if (config.apiToken.isNotEmpty) {
|
||||
options.headers['X-Api-Token'] = config.apiToken;
|
||||
}
|
||||
handler.next(options);
|
||||
},
|
||||
onError: (error, handler) {
|
||||
if (error.response?.statusCode == 401) {
|
||||
config.onUnauthorized?.call();
|
||||
}
|
||||
handler.next(error);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final Dio _dio;
|
||||
|
||||
@override
|
||||
Future<dynamic> request(
|
||||
String path, {
|
||||
String method = 'GET',
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
}) async {
|
||||
final response = await _dio.request<dynamic>(
|
||||
path,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: Options(method: method),
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<String>> proxyRequest(String uri, {dynamic data, Options? options}) {
|
||||
options ??= Options();
|
||||
options.headers ??= {};
|
||||
options.headers!['X-Target-Uri'] = uri;
|
||||
return _dio.request<String>(
|
||||
'/api/web/proxy?t=${DateTime.now().millisecondsSinceEpoch}',
|
||||
data: data,
|
||||
options: options.copyWith(responseType: ResponseType.plain),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String join(String path) {
|
||||
final baseUrl = _dio.options.baseUrl;
|
||||
final cleanBaseUrl = baseUrl.endsWith('/') ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl;
|
||||
final cleanPath = path.startsWith('/') ? path.substring(1) : path;
|
||||
return '$cleanBaseUrl/$cleanPath';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import 'entry/gopeed_transport_stub.dart'
|
||||
if (dart.library.html) 'entry/gopeed_transport_web.dart'
|
||||
if (dart.library.io) 'entry/gopeed_transport_native.dart';
|
||||
|
||||
class GopeedTransportConfig {
|
||||
const GopeedTransportConfig({
|
||||
required this.network,
|
||||
required this.address,
|
||||
required this.apiToken,
|
||||
this.onUnauthorized,
|
||||
});
|
||||
|
||||
final String network;
|
||||
final String address;
|
||||
final String apiToken;
|
||||
final void Function()? onUnauthorized;
|
||||
}
|
||||
|
||||
abstract interface class GopeedTransport {
|
||||
Future<dynamic> request(String path, {String method = 'GET', dynamic data, Map<String, dynamic>? queryParameters});
|
||||
|
||||
Future<Response<String>> proxyRequest(String uri, {dynamic data, Options? options});
|
||||
|
||||
String join(String path);
|
||||
}
|
||||
|
||||
GopeedTransport createGopeedTransport(GopeedTransportConfig config) => createTransport(config);
|
||||
Reference in new issue
Block a user