mirror of
https://github.com/foss42/apidash.git
synced 2025-06-01 14:58:28 +08:00
feat: add request cancellation functionality
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
import 'package:apidash_core/apidash_core.dart';
|
import 'package:apidash_core/apidash_core.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
import 'providers.dart';
|
import 'providers.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
import '../services/services.dart' show hiveHandler, HiveHandler;
|
import '../services/services.dart' show hiveHandler, HiveHandler;
|
||||||
@ -46,6 +47,7 @@ class CollectionStateNotifier
|
|||||||
final Ref ref;
|
final Ref ref;
|
||||||
final HiveHandler hiveHandler;
|
final HiveHandler hiveHandler;
|
||||||
final baseResponseModel = const HttpResponseModel();
|
final baseResponseModel = const HttpResponseModel();
|
||||||
|
final Map<String, http.Client> _clients = {};
|
||||||
|
|
||||||
bool hasId(String id) => state?.keys.contains(id) ?? false;
|
bool hasId(String id) => state?.keys.contains(id) ?? false;
|
||||||
|
|
||||||
@ -259,9 +261,14 @@ class CollectionStateNotifier
|
|||||||
);
|
);
|
||||||
state = map;
|
state = map;
|
||||||
|
|
||||||
|
// Create an HTTP client and store it to allow cancellation
|
||||||
|
final client = http.Client();
|
||||||
|
_clients[id] = client;
|
||||||
|
|
||||||
(HttpResponse?, Duration?, String?)? responseRec = await request(
|
(HttpResponse?, Duration?, String?)? responseRec = await request(
|
||||||
substitutedHttpRequestModel,
|
substitutedHttpRequestModel,
|
||||||
defaultUriScheme: defaultUriScheme,
|
defaultUriScheme: defaultUriScheme,
|
||||||
|
client: client,
|
||||||
);
|
);
|
||||||
late final RequestModel newRequestModel;
|
late final RequestModel newRequestModel;
|
||||||
if (responseRec.$1 == null) {
|
if (responseRec.$1 == null) {
|
||||||
@ -300,6 +307,10 @@ class CollectionStateNotifier
|
|||||||
ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(model);
|
ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close the client and remove it from the map
|
||||||
|
_clients[id]?.close();
|
||||||
|
_clients.remove(id);
|
||||||
|
|
||||||
// update state with response data
|
// update state with response data
|
||||||
map = {...state!};
|
map = {...state!};
|
||||||
map[id] = newRequestModel;
|
map[id] = newRequestModel;
|
||||||
@ -308,6 +319,24 @@ class CollectionStateNotifier
|
|||||||
ref.read(hasUnsavedChangesProvider.notifier).state = true;
|
ref.read(hasUnsavedChangesProvider.notifier).state = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cancelRequest(String id) {
|
||||||
|
if (_clients.containsKey(id)) {
|
||||||
|
_clients[id]?.close();
|
||||||
|
_clients.remove(id);
|
||||||
|
|
||||||
|
var currentModel = state![id]!;
|
||||||
|
var map = {...state!};
|
||||||
|
map[id] = currentModel.copyWith(
|
||||||
|
isWorking: false,
|
||||||
|
message: 'Request Cancelled',
|
||||||
|
responseStatus: null,
|
||||||
|
httpResponseModel: null,
|
||||||
|
);
|
||||||
|
state = map;
|
||||||
|
ref.read(hasUnsavedChangesProvider.notifier).state = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> clearData() async {
|
Future<void> clearData() async {
|
||||||
ref.read(clearDataStateProvider.notifier).state = true;
|
ref.read(clearDataStateProvider.notifier).state = true;
|
||||||
ref.read(selectedIdStateProvider.notifier).state = null;
|
ref.read(selectedIdStateProvider.notifier).state = null;
|
||||||
|
@ -27,6 +27,14 @@ class ResponsePane extends ConsumerWidget {
|
|||||||
if (responseStatus == null) {
|
if (responseStatus == null) {
|
||||||
return const NotSentWidget();
|
return const NotSentWidget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message == "Request Cancelled") {
|
||||||
|
return ErrorMessage(
|
||||||
|
message: '$message',
|
||||||
|
showIssueButton: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (responseStatus == -1) {
|
if (responseStatus == -1) {
|
||||||
return ErrorMessage(message: '$message. $kUnexpectedRaiseIssue');
|
return ErrorMessage(message: '$message. $kUnexpectedRaiseIssue');
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,11 @@ class SendRequestButton extends ConsumerWidget {
|
|||||||
.read(collectionStateNotifierProvider.notifier)
|
.read(collectionStateNotifierProvider.notifier)
|
||||||
.sendRequest(selectedId!);
|
.sendRequest(selectedId!);
|
||||||
},
|
},
|
||||||
|
onCancel: () {
|
||||||
|
ref
|
||||||
|
.read(collectionStateNotifierProvider.notifier)
|
||||||
|
.cancelRequest(selectedId!);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,23 +7,30 @@ class SendButton extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.isWorking,
|
required this.isWorking,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
|
this.onCancel,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool isWorking;
|
final bool isWorking;
|
||||||
final void Function() onTap;
|
final void Function() onTap;
|
||||||
|
final void Function()? onCancel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FilledButton(
|
return FilledButton(
|
||||||
onPressed: isWorking ? null : onTap,
|
onPressed: isWorking ? onCancel : onTap,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: isWorking
|
children: isWorking
|
||||||
? const [
|
? const [
|
||||||
Text(
|
Text(
|
||||||
kLabelSending,
|
kLabelCancel,
|
||||||
style: kTextStyleButton,
|
style: kTextStyleButton,
|
||||||
),
|
),
|
||||||
|
kHSpacer10,
|
||||||
|
Icon(
|
||||||
|
size: 16,
|
||||||
|
Icons.cancel,
|
||||||
|
)
|
||||||
]
|
]
|
||||||
: const [
|
: const [
|
||||||
Text(
|
Text(
|
||||||
|
@ -140,7 +140,9 @@ class ResponsePaneHeader extends StatelessWidget {
|
|||||||
kHSpacer10,
|
kHSpacer10,
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
"$responseStatus: ${message ?? '-'}",
|
message == 'Request Cancelled'
|
||||||
|
? 'Request Cancelled'
|
||||||
|
: "$responseStatus: ${message ?? '-'}",
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
@ -12,6 +12,7 @@ typedef HttpResponse = http.Response;
|
|||||||
Future<(HttpResponse?, Duration?, String?)> request(
|
Future<(HttpResponse?, Duration?, String?)> request(
|
||||||
HttpRequestModel requestModel, {
|
HttpRequestModel requestModel, {
|
||||||
String defaultUriScheme = kDefaultUriScheme,
|
String defaultUriScheme = kDefaultUriScheme,
|
||||||
|
http.Client? client,
|
||||||
}) async {
|
}) async {
|
||||||
(Uri?, String?) uriRec = getValidRequestUri(
|
(Uri?, String?) uriRec = getValidRequestUri(
|
||||||
requestModel.url,
|
requestModel.url,
|
||||||
@ -23,8 +24,13 @@ Future<(HttpResponse?, Duration?, String?)> request(
|
|||||||
Map<String, String> headers = requestModel.enabledHeadersMap;
|
Map<String, String> headers = requestModel.enabledHeadersMap;
|
||||||
HttpResponse response;
|
HttpResponse response;
|
||||||
String? body;
|
String? body;
|
||||||
|
bool shouldCloseClient = false;
|
||||||
try {
|
try {
|
||||||
Stopwatch stopwatch = Stopwatch()..start();
|
Stopwatch stopwatch = Stopwatch()..start();
|
||||||
|
if (client == null) {
|
||||||
|
client = http.Client();
|
||||||
|
shouldCloseClient = true;
|
||||||
|
}
|
||||||
var isMultiPartRequest =
|
var isMultiPartRequest =
|
||||||
requestModel.bodyContentType == ContentType.formdata;
|
requestModel.bodyContentType == ContentType.formdata;
|
||||||
if (kMethodsWithBody.contains(requestModel.method)) {
|
if (kMethodsWithBody.contains(requestModel.method)) {
|
||||||
@ -68,29 +74,42 @@ Future<(HttpResponse?, Duration?, String?)> request(
|
|||||||
}
|
}
|
||||||
switch (requestModel.method) {
|
switch (requestModel.method) {
|
||||||
case HTTPVerb.get:
|
case HTTPVerb.get:
|
||||||
response = await http.get(requestUrl, headers: headers);
|
response = await client.get(requestUrl, headers: headers);
|
||||||
break;
|
break;
|
||||||
case HTTPVerb.head:
|
case HTTPVerb.head:
|
||||||
response = await http.head(requestUrl, headers: headers);
|
response = await client.head(requestUrl, headers: headers);
|
||||||
break;
|
break;
|
||||||
case HTTPVerb.post:
|
case HTTPVerb.post:
|
||||||
response = await http.post(requestUrl, headers: headers, body: body);
|
response =
|
||||||
|
await client.post(requestUrl, headers: headers, body: body);
|
||||||
break;
|
break;
|
||||||
case HTTPVerb.put:
|
case HTTPVerb.put:
|
||||||
response = await http.put(requestUrl, headers: headers, body: body);
|
response = await client.put(requestUrl, headers: headers, body: body);
|
||||||
break;
|
break;
|
||||||
case HTTPVerb.patch:
|
case HTTPVerb.patch:
|
||||||
response = await http.patch(requestUrl, headers: headers, body: body);
|
response =
|
||||||
|
await client.patch(requestUrl, headers: headers, body: body);
|
||||||
break;
|
break;
|
||||||
case HTTPVerb.delete:
|
case HTTPVerb.delete:
|
||||||
response =
|
response =
|
||||||
await http.delete(requestUrl, headers: headers, body: body);
|
await client.delete(requestUrl, headers: headers, body: body);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
return (response, stopwatch.elapsed, null);
|
return (response, stopwatch.elapsed, null);
|
||||||
|
} on http.ClientException catch (e) {
|
||||||
|
if (e.message.contains('Connection closed') ||
|
||||||
|
e.message.contains('abort')) {
|
||||||
|
return (null, null, 'Request Cancelled');
|
||||||
|
} else {
|
||||||
|
return (null, null, e.toString());
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return (null, null, e.toString());
|
return (null, null, e.toString());
|
||||||
|
} finally {
|
||||||
|
if (shouldCloseClient) {
|
||||||
|
client?.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return (null, null, uriRec.$2);
|
return (null, null, uriRec.$2);
|
||||||
|
Reference in New Issue
Block a user