mirror of
https://github.com/foss42/apidash.git
synced 2025-12-02 02:39:19 +08:00
SSE: Stopping/Cancelling implementation
This commit is contained in:
@@ -22,6 +22,7 @@ class RequestModel with _$RequestModel {
|
||||
HttpResponseModel? httpResponseModel,
|
||||
@JsonKey(includeToJson: false) @Default(false) bool isWorking,
|
||||
@JsonKey(includeToJson: false) DateTime? sendingTime,
|
||||
@JsonKey(includeToJson: false) @Default(false) bool isStreaming,
|
||||
String? preRequestScript,
|
||||
String? postRequestScript,
|
||||
}) = _RequestModel;
|
||||
|
||||
@@ -35,6 +35,8 @@ mixin _$RequestModel {
|
||||
bool get isWorking => throw _privateConstructorUsedError;
|
||||
@JsonKey(includeToJson: false)
|
||||
DateTime? get sendingTime => throw _privateConstructorUsedError;
|
||||
@JsonKey(includeToJson: false)
|
||||
bool get isStreaming => throw _privateConstructorUsedError;
|
||||
String? get preRequestScript => throw _privateConstructorUsedError;
|
||||
String? get postRequestScript => throw _privateConstructorUsedError;
|
||||
|
||||
@@ -66,6 +68,7 @@ abstract class $RequestModelCopyWith<$Res> {
|
||||
HttpResponseModel? httpResponseModel,
|
||||
@JsonKey(includeToJson: false) bool isWorking,
|
||||
@JsonKey(includeToJson: false) DateTime? sendingTime,
|
||||
@JsonKey(includeToJson: false) bool isStreaming,
|
||||
String? preRequestScript,
|
||||
String? postRequestScript});
|
||||
|
||||
@@ -99,6 +102,7 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel>
|
||||
Object? httpResponseModel = freezed,
|
||||
Object? isWorking = null,
|
||||
Object? sendingTime = freezed,
|
||||
Object? isStreaming = null,
|
||||
Object? preRequestScript = freezed,
|
||||
Object? postRequestScript = freezed,
|
||||
}) {
|
||||
@@ -147,6 +151,10 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel>
|
||||
? _value.sendingTime
|
||||
: sendingTime // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
isStreaming: null == isStreaming
|
||||
? _value.isStreaming
|
||||
: isStreaming // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
preRequestScript: freezed == preRequestScript
|
||||
? _value.preRequestScript
|
||||
: preRequestScript // ignore: cast_nullable_to_non_nullable
|
||||
@@ -207,6 +215,7 @@ abstract class _$$RequestModelImplCopyWith<$Res>
|
||||
HttpResponseModel? httpResponseModel,
|
||||
@JsonKey(includeToJson: false) bool isWorking,
|
||||
@JsonKey(includeToJson: false) DateTime? sendingTime,
|
||||
@JsonKey(includeToJson: false) bool isStreaming,
|
||||
String? preRequestScript,
|
||||
String? postRequestScript});
|
||||
|
||||
@@ -240,6 +249,7 @@ class __$$RequestModelImplCopyWithImpl<$Res>
|
||||
Object? httpResponseModel = freezed,
|
||||
Object? isWorking = null,
|
||||
Object? sendingTime = freezed,
|
||||
Object? isStreaming = null,
|
||||
Object? preRequestScript = freezed,
|
||||
Object? postRequestScript = freezed,
|
||||
}) {
|
||||
@@ -287,6 +297,10 @@ class __$$RequestModelImplCopyWithImpl<$Res>
|
||||
? _value.sendingTime
|
||||
: sendingTime // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
isStreaming: null == isStreaming
|
||||
? _value.isStreaming
|
||||
: isStreaming // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
preRequestScript: freezed == preRequestScript
|
||||
? _value.preRequestScript
|
||||
: preRequestScript // ignore: cast_nullable_to_non_nullable
|
||||
@@ -315,6 +329,7 @@ class _$RequestModelImpl implements _RequestModel {
|
||||
this.httpResponseModel,
|
||||
@JsonKey(includeToJson: false) this.isWorking = false,
|
||||
@JsonKey(includeToJson: false) this.sendingTime,
|
||||
@JsonKey(includeToJson: false) this.isStreaming = false,
|
||||
this.preRequestScript,
|
||||
this.postRequestScript});
|
||||
|
||||
@@ -350,13 +365,16 @@ class _$RequestModelImpl implements _RequestModel {
|
||||
@JsonKey(includeToJson: false)
|
||||
final DateTime? sendingTime;
|
||||
@override
|
||||
@JsonKey(includeToJson: false)
|
||||
final bool isStreaming;
|
||||
@override
|
||||
final String? preRequestScript;
|
||||
@override
|
||||
final String? postRequestScript;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'RequestModel(id: $id, apiType: $apiType, name: $name, description: $description, requestTabIndex: $requestTabIndex, httpRequestModel: $httpRequestModel, responseStatus: $responseStatus, message: $message, httpResponseModel: $httpResponseModel, isWorking: $isWorking, sendingTime: $sendingTime, preRequestScript: $preRequestScript, postRequestScript: $postRequestScript)';
|
||||
return 'RequestModel(id: $id, apiType: $apiType, name: $name, description: $description, requestTabIndex: $requestTabIndex, httpRequestModel: $httpRequestModel, responseStatus: $responseStatus, message: $message, httpResponseModel: $httpResponseModel, isWorking: $isWorking, sendingTime: $sendingTime, isStreaming: $isStreaming, preRequestScript: $preRequestScript, postRequestScript: $postRequestScript)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -382,6 +400,8 @@ class _$RequestModelImpl implements _RequestModel {
|
||||
other.isWorking == isWorking) &&
|
||||
(identical(other.sendingTime, sendingTime) ||
|
||||
other.sendingTime == sendingTime) &&
|
||||
(identical(other.isStreaming, isStreaming) ||
|
||||
other.isStreaming == isStreaming) &&
|
||||
(identical(other.preRequestScript, preRequestScript) ||
|
||||
other.preRequestScript == preRequestScript) &&
|
||||
(identical(other.postRequestScript, postRequestScript) ||
|
||||
@@ -403,6 +423,7 @@ class _$RequestModelImpl implements _RequestModel {
|
||||
httpResponseModel,
|
||||
isWorking,
|
||||
sendingTime,
|
||||
isStreaming,
|
||||
preRequestScript,
|
||||
postRequestScript);
|
||||
|
||||
@@ -435,6 +456,7 @@ abstract class _RequestModel implements RequestModel {
|
||||
final HttpResponseModel? httpResponseModel,
|
||||
@JsonKey(includeToJson: false) final bool isWorking,
|
||||
@JsonKey(includeToJson: false) final DateTime? sendingTime,
|
||||
@JsonKey(includeToJson: false) final bool isStreaming,
|
||||
final String? preRequestScript,
|
||||
final String? postRequestScript}) = _$RequestModelImpl;
|
||||
|
||||
@@ -467,6 +489,9 @@ abstract class _RequestModel implements RequestModel {
|
||||
@JsonKey(includeToJson: false)
|
||||
DateTime? get sendingTime;
|
||||
@override
|
||||
@JsonKey(includeToJson: false)
|
||||
bool get isStreaming;
|
||||
@override
|
||||
String? get preRequestScript;
|
||||
@override
|
||||
String? get postRequestScript;
|
||||
|
||||
@@ -27,6 +27,7 @@ _$RequestModelImpl _$$RequestModelImplFromJson(Map json) => _$RequestModelImpl(
|
||||
sendingTime: json['sendingTime'] == null
|
||||
? null
|
||||
: DateTime.parse(json['sendingTime'] as String),
|
||||
isStreaming: json['isStreaming'] as bool? ?? false,
|
||||
preRequestScript: json['preRequestScript'] as String?,
|
||||
postRequestScript: json['postRequestScript'] as String?,
|
||||
);
|
||||
|
||||
@@ -270,19 +270,16 @@ class CollectionStateNotifier
|
||||
Future<void> sendRequest() async {
|
||||
final requestId = ref.read(selectedIdStateProvider);
|
||||
ref.read(codePaneVisibleStateProvider.notifier).state = false;
|
||||
|
||||
if (requestId == null || state == null) return;
|
||||
|
||||
RequestModel? requestModel = state![requestId];
|
||||
if (requestModel?.httpRequestModel == null) return;
|
||||
|
||||
final defaultUriScheme = ref.read(settingsProvider).defaultUriScheme;
|
||||
final EnvironmentModel? originalEnvironmentModel =
|
||||
ref.read(selectedEnvironmentModelProvider);
|
||||
|
||||
if (requestId == null || state == null) {
|
||||
return;
|
||||
}
|
||||
RequestModel? requestModel = state![requestId];
|
||||
|
||||
if (requestModel?.httpRequestModel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestModel != null &&
|
||||
!requestModel.preRequestScript.isNullOrEmpty()) {
|
||||
requestModel = await handlePreRequestScript(
|
||||
@@ -303,23 +300,18 @@ class CollectionStateNotifier
|
||||
APIType apiType = requestModel!.apiType;
|
||||
HttpRequestModel substitutedHttpRequestModel =
|
||||
getSubstitutedHttpRequestModel(requestModel.httpRequestModel!);
|
||||
|
||||
// set current model's isWorking to true and update state
|
||||
var map = {...state!};
|
||||
map[requestId] = requestModel.copyWith(
|
||||
isWorking: true,
|
||||
sendingTime: DateTime.now(),
|
||||
);
|
||||
state = map;
|
||||
|
||||
bool noSSL = ref.read(settingsProvider).isSSLDisabled;
|
||||
|
||||
(Response?, Duration?, String?) responseRec;
|
||||
HttpResponseModel? respModel;
|
||||
HistoryRequestModel? historyM;
|
||||
RequestModel? newRequestModel;
|
||||
// Set model to working and streaming
|
||||
state = {
|
||||
...state!,
|
||||
requestId: requestModel.copyWith(
|
||||
isWorking: true,
|
||||
isStreaming: true,
|
||||
sendingTime: DateTime.now(),
|
||||
),
|
||||
};
|
||||
|
||||
responseRec = (null, null, null);
|
||||
final stream = await streamHttpRequest(
|
||||
requestId,
|
||||
apiType,
|
||||
@@ -328,101 +320,98 @@ class CollectionStateNotifier
|
||||
noSSL: noSSL,
|
||||
);
|
||||
|
||||
StreamSubscription? sub;
|
||||
final completer = Completer();
|
||||
|
||||
HttpResponseModel? respModel;
|
||||
HistoryRequestModel? historyM;
|
||||
RequestModel newRequestModel = requestModel;
|
||||
final completer = Completer<(Response?, Duration?, String?)>();
|
||||
bool isTextStream = false;
|
||||
StreamSubscription? sub;
|
||||
|
||||
sub = stream.listen((d) async {
|
||||
if (d == null) return;
|
||||
|
||||
isTextStream = ((d.$1 == null && isTextStream) ||
|
||||
(d.$1 == 'text/event-stream' || d.$1 == 'application/x-ndjson'));
|
||||
final contentType = d.$1;
|
||||
isTextStream = isTextStream ||
|
||||
contentType == 'text/event-stream' ||
|
||||
contentType == 'application/x-ndjson';
|
||||
|
||||
responseRec = (d.$2, d.$3, d.$4);
|
||||
final response = d.$2;
|
||||
final duration = d.$3;
|
||||
final errorMessage = d.$4;
|
||||
|
||||
if (!isTextStream) {
|
||||
if (completer.isCompleted) return;
|
||||
completer.complete(responseRec);
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete((response, duration, errorMessage));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseRec.$1 != null) {
|
||||
responseRec = (
|
||||
HttpResponse(
|
||||
responseRec.$1!.body,
|
||||
responseRec.$1!.statusCode,
|
||||
request: responseRec.$1!.request,
|
||||
headers: {
|
||||
...(responseRec.$1?.headers ?? {}),
|
||||
'content-type': 'text/event-stream'
|
||||
},
|
||||
isRedirect: responseRec.$1!.isRedirect,
|
||||
reasonPhrase: responseRec.$1!.reasonPhrase,
|
||||
persistentConnection: responseRec.$1!.persistentConnection,
|
||||
),
|
||||
responseRec.$2,
|
||||
responseRec.$3,
|
||||
);
|
||||
}
|
||||
respModel = respModel?.copyWith(
|
||||
sseOutput: [
|
||||
...(respModel?.sseOutput ?? []),
|
||||
if (response != null) response.body,
|
||||
],
|
||||
);
|
||||
|
||||
newRequestModel = newRequestModel.copyWith(
|
||||
httpResponseModel: respModel,
|
||||
isStreaming: true,
|
||||
);
|
||||
state = {
|
||||
...state!,
|
||||
requestId: newRequestModel,
|
||||
};
|
||||
unsave();
|
||||
|
||||
//----------- MAKE CHANGES --------------
|
||||
respModel = respModel?.copyWith(sseOutput: [
|
||||
...(respModel?.sseOutput ?? []),
|
||||
responseRec.$1!.body,
|
||||
]);
|
||||
if (respModel != null) {
|
||||
final nRM = newRequestModel!.copyWith(
|
||||
httpResponseModel: respModel,
|
||||
);
|
||||
map = {...state!};
|
||||
map[requestId] = nRM;
|
||||
state = map;
|
||||
unsave();
|
||||
}
|
||||
//Changing History
|
||||
if (historyM != null && respModel != null) {
|
||||
historyM = historyM!.copyWith(
|
||||
httpResponseModel: respModel!,
|
||||
);
|
||||
historyM = historyM!.copyWith(httpResponseModel: respModel!);
|
||||
ref
|
||||
.read(historyMetaStateNotifier.notifier)
|
||||
.editHistoryRequest(historyM!);
|
||||
}
|
||||
//----------- MAKE CHANGES --------------
|
||||
|
||||
if (completer.isCompleted) return;
|
||||
completer.complete(responseRec);
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete((response, duration, errorMessage));
|
||||
}
|
||||
}, onDone: () {
|
||||
sub?.cancel();
|
||||
state = {
|
||||
...state!,
|
||||
requestId: newRequestModel.copyWith(isStreaming: false),
|
||||
};
|
||||
unsave();
|
||||
}, onError: (e) {
|
||||
print('err: $e');
|
||||
print('Stream error: $e');
|
||||
});
|
||||
responseRec = await completer.future;
|
||||
|
||||
if (responseRec.$1 == null) {
|
||||
newRequestModel = requestModel.copyWith(
|
||||
final (response, duration, errorMessage) = await completer.future;
|
||||
|
||||
if (response == null) {
|
||||
newRequestModel = newRequestModel.copyWith(
|
||||
responseStatus: -1,
|
||||
message: responseRec.$3,
|
||||
message: errorMessage,
|
||||
isWorking: false,
|
||||
isStreaming: false,
|
||||
);
|
||||
} else {
|
||||
final statusCode = response.statusCode;
|
||||
respModel = baseHttpResponseModel.fromResponse(
|
||||
response: responseRec.$1!,
|
||||
time: responseRec.$2!,
|
||||
response: response,
|
||||
time: duration,
|
||||
);
|
||||
int statusCode = responseRec.$1!.statusCode;
|
||||
newRequestModel = requestModel.copyWith(
|
||||
|
||||
newRequestModel = newRequestModel.copyWith(
|
||||
responseStatus: statusCode,
|
||||
message: kResponseCodeReasons[statusCode],
|
||||
httpResponseModel: respModel,
|
||||
isWorking: false,
|
||||
);
|
||||
String newHistoryId = getNewUuid();
|
||||
|
||||
final historyId = getNewUuid();
|
||||
historyM = HistoryRequestModel(
|
||||
historyId: newHistoryId,
|
||||
historyId: historyId,
|
||||
metaData: HistoryMetaModel(
|
||||
historyId: newHistoryId,
|
||||
historyId: historyId,
|
||||
requestId: requestId,
|
||||
apiType: requestModel.apiType,
|
||||
name: requestModel.name,
|
||||
@@ -452,13 +441,15 @@ class CollectionStateNotifier
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(historyM!);
|
||||
}
|
||||
|
||||
// update state with response data
|
||||
map = {...state!};
|
||||
map[requestId] = newRequestModel;
|
||||
state = map;
|
||||
// Final state update
|
||||
state = {
|
||||
...state!,
|
||||
requestId: newRequestModel,
|
||||
};
|
||||
|
||||
unsave();
|
||||
}
|
||||
|
||||
@@ -129,8 +129,11 @@ class SendRequestButton extends ConsumerWidget {
|
||||
ref.watch(selectedIdStateProvider);
|
||||
final isWorking = ref.watch(
|
||||
selectedRequestModelProvider.select((value) => value?.isWorking));
|
||||
final isStreaming = ref.watch(
|
||||
selectedRequestModelProvider.select((value) => value?.isStreaming));
|
||||
|
||||
return SendButton(
|
||||
isStreaming: isStreaming ?? false,
|
||||
isWorking: isWorking ?? false,
|
||||
onTap: () {
|
||||
onTap?.call();
|
||||
|
||||
@@ -5,11 +5,13 @@ import 'package:apidash/consts.dart';
|
||||
class SendButton extends StatelessWidget {
|
||||
const SendButton({
|
||||
super.key,
|
||||
required this.isStreaming,
|
||||
required this.isWorking,
|
||||
required this.onTap,
|
||||
this.onCancel,
|
||||
});
|
||||
|
||||
final bool isStreaming;
|
||||
final bool isWorking;
|
||||
final void Function() onTap;
|
||||
final void Function()? onCancel;
|
||||
@@ -17,13 +19,13 @@ class SendButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ADFilledButton(
|
||||
onPressed: isWorking ? onCancel : onTap,
|
||||
isTonal: isWorking ? true : false,
|
||||
items: isWorking
|
||||
? const [
|
||||
onPressed: (isWorking || isStreaming) ? onCancel : onTap,
|
||||
isTonal: (isWorking || isStreaming),
|
||||
items: (isWorking || isStreaming)
|
||||
? [
|
||||
kHSpacer8,
|
||||
Text(
|
||||
kLabelCancel,
|
||||
isStreaming ? 'Stop' : kLabelCancel,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
kHSpacer6,
|
||||
|
||||
@@ -247,8 +247,6 @@ streamHttpRequest(
|
||||
final streamedResponse = await client.send(multipart);
|
||||
final stream = streamTextResponse(streamedResponse);
|
||||
|
||||
print(streamedResponse.headers['content-type']);
|
||||
|
||||
subscription = stream.listen(
|
||||
(data) => controller.add((
|
||||
streamedResponse.headers['content-type'].toString(),
|
||||
|
||||
@@ -17,6 +17,7 @@ void main() {
|
||||
home: Scaffold(
|
||||
body: SendButton(
|
||||
isWorking: false,
|
||||
isStreaming: false,
|
||||
onTap: () => sendPressed = true,
|
||||
onCancel: () => cancelPressed = true,
|
||||
),
|
||||
@@ -46,6 +47,7 @@ void main() {
|
||||
home: Scaffold(
|
||||
body: SendButton(
|
||||
isWorking: true,
|
||||
isStreaming: false,
|
||||
onTap: () => sendPressed = true,
|
||||
onCancel: () => cancelPressed = true,
|
||||
),
|
||||
@@ -74,6 +76,7 @@ void main() {
|
||||
builder: (context, setState) {
|
||||
return Scaffold(
|
||||
body: SendButton(
|
||||
isStreaming: false,
|
||||
isWorking: isWorking,
|
||||
onTap: () => setState(() => isWorking = true),
|
||||
onCancel: () => setState(() => isWorking = false),
|
||||
|
||||
Reference in New Issue
Block a user