diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 35bc4aa0c..c2b09f38d 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -267,20 +267,16 @@ class CollectionStateNotifier ref.read(codePaneVisibleStateProvider.notifier).state = false; final defaultUriScheme = ref.read(settingsProvider).defaultUriScheme; - if (requestId == null || state == null) { - return; - } - RequestModel? requestModel = state![requestId]; + if (requestId == null || state == null) return; - if (requestModel?.httpRequestModel == null) { - return; - } + RequestModel? requestModel = state![requestId]; + if (requestModel?.httpRequestModel == null) return; APIType apiType = requestModel!.apiType; HttpRequestModel substitutedHttpRequestModel = getSubstitutedHttpRequestModel(requestModel.httpRequestModel!); - // set current model's isWorking to true and update state + // Set current model's isWorking to true and update state var map = {...state!}; map[requestId] = requestModel.copyWith( isWorking: true, @@ -298,6 +294,7 @@ class CollectionStateNotifier ); late final RequestModel newRequestModel; + if (responseRec.$1 == null) { newRequestModel = requestModel.copyWith( responseStatus: -1, @@ -305,17 +302,20 @@ class CollectionStateNotifier isWorking: false, ); } else { - final httpResponseModel = baseHttpResponseModel.fromResponse( + final httpResponseModel = baseHttpResponseModel.fromDioResponse( response: responseRec.$1!, time: responseRec.$2!, ); - int statusCode = responseRec.$1!.statusCode; + + final int statusCode = responseRec.$1?.statusCode ?? -1; + newRequestModel = requestModel.copyWith( responseStatus: statusCode, message: kResponseCodeReasons[statusCode], httpResponseModel: httpResponseModel, isWorking: false, ); + String newHistoryId = getNewUuid(); HistoryRequestModel model = HistoryRequestModel( historyId: newHistoryId, @@ -335,7 +335,7 @@ class CollectionStateNotifier ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(model); } - // update state with response data + // Update state with response data map = {...state!}; map[requestId] = newRequestModel; state = map; diff --git a/packages/apidash_core/lib/models/http_response_model.dart b/packages/apidash_core/lib/models/http_response_model.dart index 914aaa577..417756f9d 100644 --- a/packages/apidash_core/lib/models/http_response_model.dart +++ b/packages/apidash_core/lib/models/http_response_model.dart @@ -6,6 +6,7 @@ import 'package:collection/collection.dart' show mergeMaps; import 'package:http/http.dart'; import 'package:http_parser/http_parser.dart'; import '../extensions/extensions.dart'; +import 'package:dio/dio.dart' as dio; import '../utils/utils.dart'; import '../consts.dart'; @@ -85,4 +86,31 @@ class HttpResponseModel with _$HttpResponseModel { time: time, ); } + + HttpResponseModel fromDioResponse({ + required dio.Response response, + Duration? time, + }) { + final headers = {}; + response.headers.forEach((k, v) { + headers[k] = v.join(','); + }); + + MediaType? mediaType = getMediaTypeFromHeaders(headers); + final body = (mediaType?.subtype == kSubTypeJson) + ? jsonEncode(response.data) + : response.data.toString(); + + return HttpResponseModel( + statusCode: response.statusCode, + headers: headers, + requestHeaders: response.requestOptions.headers.map((k, v) => MapEntry(k, v.toString())), + body: body, + formattedBody: formatBody(body, mediaType), + bodyBytes: response.data is List + ? Uint8List.fromList(response.data) + : utf8.encode(body), + time: time, + ); + } } diff --git a/packages/apidash_core/lib/services/http_client_manager.dart b/packages/apidash_core/lib/services/http_client_manager.dart index 7b815413f..9c3ee9139 100644 --- a/packages/apidash_core/lib/services/http_client_manager.dart +++ b/packages/apidash_core/lib/services/http_client_manager.dart @@ -1,65 +1,65 @@ import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:dio/io.dart'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart' as http; -import 'package:http/io_client.dart'; - -http.Client createHttpClientWithNoSSL() { - var ioClient = HttpClient() - ..badCertificateCallback = - (X509Certificate cert, String host, int port) => true; - return IOClient(ioClient); -} class HttpClientManager { static final HttpClientManager _instance = HttpClientManager._internal(); static const int _maxCancelledRequests = 100; - final Map _clients = {}; + + final Map _clients = {}; + final Map _cancelTokens = {}; final Set _cancelledRequests = {}; - factory HttpClientManager() { - return _instance; - } + factory HttpClientManager() => _instance; HttpClientManager._internal(); - http.Client createClient( - String requestId, { - bool noSSL = false, - }) { - final client = - (noSSL && !kIsWeb) ? createHttpClientWithNoSSL() : http.Client(); - _clients[requestId] = client; - return client; + Dio createClient(String requestId, {bool noSSL = false}) { + final dio = Dio(); + + if (noSSL && !kIsWeb) { + (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () { + final client = HttpClient(); + client.badCertificateCallback = (cert, host, port) => true; + return client; + }; + } + + final cancelToken = CancelToken(); + _clients[requestId] = dio; + _cancelTokens[requestId] = cancelToken; + + return dio; } + CancelToken? getCancelToken(String requestId) => _cancelTokens[requestId]; + void cancelRequest(String? requestId) { - if (requestId != null && _clients.containsKey(requestId)) { - _clients[requestId]?.close(); + if (requestId != null) { + _cancelTokens[requestId]?.cancel("Request cancelled"); _clients.remove(requestId); - + _cancelTokens.remove(requestId); _cancelledRequests.add(requestId); + if (_cancelledRequests.length > _maxCancelledRequests) { _cancelledRequests.remove(_cancelledRequests.first); } } } - bool wasRequestCancelled(String requestId) { - return _cancelledRequests.contains(requestId); - } + bool wasRequestCancelled(String requestId) => + _cancelledRequests.contains(requestId); void removeCancelledRequest(String requestId) { _cancelledRequests.remove(requestId); } void closeClient(String requestId) { - if (_clients.containsKey(requestId)) { - _clients[requestId]?.close(); - _clients.remove(requestId); - } + _clients.remove(requestId); + _cancelTokens.remove(requestId); } - bool hasActiveClient(String requestId) { - return _clients.containsKey(requestId); - } + bool hasActiveClient(String requestId) => + _clients.containsKey(requestId); } diff --git a/packages/apidash_core/lib/services/http_service.dart b/packages/apidash_core/lib/services/http_service.dart index 73f82d9a3..70af04537 100644 --- a/packages/apidash_core/lib/services/http_service.dart +++ b/packages/apidash_core/lib/services/http_service.dart @@ -1,18 +1,21 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; -import 'package:http/http.dart' as http; + +import 'package:apidash_core/apidash_core.dart' as http; +import 'package:dio/dio.dart'; import 'package:seed/seed.dart'; + import '../consts.dart'; import '../models/models.dart'; import '../utils/utils.dart'; import 'http_client_manager.dart'; -typedef HttpResponse = http.Response; +typedef DioHttpResponse = Response; // from dio +typedef HttpHttpResponse = http.Response; // from http final httpClientManager = HttpClientManager(); -Future<(HttpResponse?, Duration?, String?)> sendHttpRequest( +Future<(Response?, Duration?, String?)> sendHttpRequest( String requestId, APIType apiType, HttpRequestModel requestModel, { @@ -22,110 +25,104 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest( if (httpClientManager.wasRequestCancelled(requestId)) { httpClientManager.removeCancelledRequest(requestId); } - final client = httpClientManager.createClient(requestId, noSSL: noSSL); - (Uri?, String?) uriRec = getValidRequestUri( + final dio = httpClientManager.createClient(requestId, noSSL: noSSL); + final cancelToken = httpClientManager.getCancelToken(requestId); + + final (uri, uriError) = getValidRequestUri( requestModel.url, requestModel.enabledParams, defaultUriScheme: defaultUriScheme, ); - if (uriRec.$1 != null) { - Uri requestUrl = uriRec.$1!; - Map headers = requestModel.enabledHeadersMap; - HttpResponse? response; - String? body; - try { - Stopwatch stopwatch = Stopwatch()..start(); - if (apiType == APIType.rest) { - var isMultiPartRequest = - requestModel.bodyContentType == ContentType.formdata; - - if (kMethodsWithBody.contains(requestModel.method)) { - var requestBody = requestModel.body; - if (requestBody != null && !isMultiPartRequest) { - var contentLength = utf8.encode(requestBody).length; - if (contentLength > 0) { - body = requestBody; - headers[HttpHeaders.contentLengthHeader] = - contentLength.toString(); - if (!requestModel.hasContentTypeHeader) { - headers[HttpHeaders.contentTypeHeader] = - requestModel.bodyContentType.header; - } - } - } - if (isMultiPartRequest) { - var multiPartRequest = http.MultipartRequest( - requestModel.method.name.toUpperCase(), - requestUrl, - ); - multiPartRequest.headers.addAll(headers); - for (var formData in requestModel.formDataList) { - if (formData.type == FormDataType.text) { - multiPartRequest.fields.addAll({formData.name: formData.value}); - } else { - multiPartRequest.files.add( - await http.MultipartFile.fromPath( - formData.name, - formData.value, - ), - ); - } - } - http.StreamedResponse multiPartResponse = - await client.send(multiPartRequest); + if (uri == null) return (null, null, uriError); - stopwatch.stop(); - http.Response convertedMultiPartResponse = - await convertStreamedResponse(multiPartResponse); - return (convertedMultiPartResponse, stopwatch.elapsed, null); - } - } - response = switch (requestModel.method) { - HTTPVerb.get => await client.get(requestUrl, headers: headers), - HTTPVerb.head => response = - await client.head(requestUrl, headers: headers), - HTTPVerb.post => response = - await client.post(requestUrl, headers: headers, body: body), - HTTPVerb.put => response = - await client.put(requestUrl, headers: headers, body: body), - HTTPVerb.patch => response = - await client.patch(requestUrl, headers: headers, body: body), - HTTPVerb.delete => response = - await client.delete(requestUrl, headers: headers, body: body), - }; - } - if (apiType == APIType.graphql) { - var requestBody = getGraphQLBody(requestModel); - if (requestBody != null) { - var contentLength = utf8.encode(requestBody).length; - if (contentLength > 0) { - body = requestBody; - headers[HttpHeaders.contentLengthHeader] = contentLength.toString(); - if (!requestModel.hasContentTypeHeader) { - headers[HttpHeaders.contentTypeHeader] = ContentType.json.header; + try { + final headers = requestModel.enabledHeadersMap; + final stopwatch = Stopwatch()..start(); + Response? response; + + final isMultiPartRequest = + requestModel.bodyContentType == ContentType.formdata; + + if (!requestModel.hasContentTypeHeader) { + headers[HttpHeaders.contentTypeHeader] = + requestModel.bodyContentType.header; + } + + if (apiType == APIType.rest) { + if (kMethodsWithBody.contains(requestModel.method)) { + if (isMultiPartRequest) { + final formData = FormData(); + + for (var formField in requestModel.formDataList) { + if (formField.type == FormDataType.text) { + formData.fields.add(MapEntry(formField.name, formField.value)); + } else { + formData.files.add( + MapEntry( + formField.name, + await MultipartFile.fromFile(formField.value), + ), + ); } } + + response = await dio.request( + uri.toString(), + data: formData, + options: Options( + method: requestModel.method.name.toUpperCase(), + headers: headers, + ), + cancelToken: cancelToken, + ); + } else { + final body = requestModel.body ?? ''; + response = await dio.request( + uri.toString(), + data: body, + options: Options( + method: requestModel.method.name.toUpperCase(), + headers: headers, + ), + cancelToken: cancelToken, + ); } - response = await client.post( - requestUrl, - headers: headers, - body: body, + } else { + response = await dio.request( + uri.toString(), + options: Options( + method: requestModel.method.name.toUpperCase(), + headers: headers, + ), + cancelToken: cancelToken, ); } - stopwatch.stop(); - return (response, stopwatch.elapsed, null); - } catch (e) { - if (httpClientManager.wasRequestCancelled(requestId)) { - return (null, null, kMsgRequestCancelled); + } + + if (apiType == APIType.graphql) { + final body = getGraphQLBody(requestModel); + if (body != null) { + headers[HttpHeaders.contentTypeHeader] = ContentType.json.header; + response = await dio.post( + uri.toString(), + data: body, + options: Options(headers: headers), + cancelToken: cancelToken, + ); } - return (null, null, e.toString()); - } finally { - httpClientManager.closeClient(requestId); } - } else { - return (null, null, uriRec.$2); + + stopwatch.stop(); + return (response, stopwatch.elapsed, null); + } catch (e) { + if (httpClientManager.wasRequestCancelled(requestId)) { + return (null, null, kMsgRequestCancelled); + } + return (null, null, e.toString()); + } finally { + httpClientManager.closeClient(requestId); } } diff --git a/packages/apidash_core/pubspec.yaml b/packages/apidash_core/pubspec.yaml index 13aaea079..3536ff142 100644 --- a/packages/apidash_core/pubspec.yaml +++ b/packages/apidash_core/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: path: ../postman seed: ^0.0.3 xml: ^6.3.0 + dio: ^5.8.0+1 dev_dependencies: flutter_test: diff --git a/pubspec.lock b/pubspec.lock index ba47984ea..d34790e35 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -358,6 +358,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + dio: + dependency: transitive + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" equatable: dependency: transitive description: