mirror of
https://github.com/foss42/apidash.git
synced 2025-08-06 13:51:20 +08:00
Added SSE ability to HTTPS method (fusion)
This commit is contained in:
@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@ -311,7 +313,14 @@ class CollectionStateNotifier
|
||||
state = map;
|
||||
|
||||
bool noSSL = ref.read(settingsProvider).isSSLDisabled;
|
||||
var responseRec = await sendHttpRequest(
|
||||
|
||||
(Response?, Duration?, String?) responseRec;
|
||||
HttpResponseModel? respModel;
|
||||
HistoryRequestModel? historyM;
|
||||
RequestModel? newRequestModel;
|
||||
|
||||
responseRec = (null, null, null);
|
||||
final stream = await streamHttpRequest(
|
||||
requestId,
|
||||
apiType,
|
||||
substitutedHttpRequestModel,
|
||||
@ -319,7 +328,78 @@ class CollectionStateNotifier
|
||||
noSSL: noSSL,
|
||||
);
|
||||
|
||||
late RequestModel newRequestModel;
|
||||
StreamSubscription? sub;
|
||||
final completer = Completer();
|
||||
|
||||
bool isTextStream = false;
|
||||
|
||||
sub = stream.listen((d) async {
|
||||
if (d == null) return;
|
||||
|
||||
isTextStream = ((d.$1 == null && isTextStream) ||
|
||||
(d.$1 == 'text/event-stream' || d.$1 == 'application/x-ndjson'));
|
||||
|
||||
responseRec = (d.$2, d.$3, d.$4);
|
||||
|
||||
if (!isTextStream) {
|
||||
if (completer.isCompleted) return;
|
||||
completer.complete(responseRec);
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
//----------- 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!,
|
||||
);
|
||||
ref
|
||||
.read(historyMetaStateNotifier.notifier)
|
||||
.editHistoryRequest(historyM!);
|
||||
}
|
||||
//----------- MAKE CHANGES --------------
|
||||
|
||||
if (completer.isCompleted) return;
|
||||
completer.complete(responseRec);
|
||||
}, onDone: () {
|
||||
sub?.cancel();
|
||||
}, onError: (e) {
|
||||
print('err: $e');
|
||||
});
|
||||
responseRec = await completer.future;
|
||||
|
||||
if (responseRec.$1 == null) {
|
||||
newRequestModel = requestModel.copyWith(
|
||||
responseStatus: -1,
|
||||
@ -327,7 +407,7 @@ class CollectionStateNotifier
|
||||
isWorking: false,
|
||||
);
|
||||
} else {
|
||||
final httpResponseModel = baseHttpResponseModel.fromResponse(
|
||||
respModel = baseHttpResponseModel.fromResponse(
|
||||
response: responseRec.$1!,
|
||||
time: responseRec.$2!,
|
||||
);
|
||||
@ -335,11 +415,11 @@ class CollectionStateNotifier
|
||||
newRequestModel = requestModel.copyWith(
|
||||
responseStatus: statusCode,
|
||||
message: kResponseCodeReasons[statusCode],
|
||||
httpResponseModel: httpResponseModel,
|
||||
httpResponseModel: respModel,
|
||||
isWorking: false,
|
||||
);
|
||||
String newHistoryId = getNewUuid();
|
||||
HistoryRequestModel model = HistoryRequestModel(
|
||||
historyM = HistoryRequestModel(
|
||||
historyId: newHistoryId,
|
||||
metaData: HistoryMetaModel(
|
||||
historyId: newHistoryId,
|
||||
@ -352,7 +432,7 @@ class CollectionStateNotifier
|
||||
timeStamp: DateTime.now(),
|
||||
),
|
||||
httpRequestModel: substitutedHttpRequestModel,
|
||||
httpResponseModel: httpResponseModel,
|
||||
httpResponseModel: respModel!,
|
||||
preRequestScript: requestModel.preRequestScript,
|
||||
postRequestScript: requestModel.postRequestScript,
|
||||
);
|
||||
@ -372,7 +452,7 @@ class CollectionStateNotifier
|
||||
},
|
||||
);
|
||||
}
|
||||
ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(model);
|
||||
ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(historyM!);
|
||||
}
|
||||
|
||||
// update state with response data
|
||||
|
@ -90,6 +90,21 @@ class HistoryMetaStateNotifier
|
||||
await loadHistoryRequest(id);
|
||||
}
|
||||
|
||||
void editHistoryRequest(HistoryRequestModel model) async {
|
||||
final id = model.historyId;
|
||||
state = {
|
||||
...state ?? {},
|
||||
id: model.metaData,
|
||||
};
|
||||
final existingKeys = state?.keys.toList() ?? [];
|
||||
if (!existingKeys.contains(id)) {
|
||||
hiveHandler.setHistoryIds([...existingKeys, id]);
|
||||
}
|
||||
hiveHandler.setHistoryMeta(id, model.metaData.toJson());
|
||||
await hiveHandler.setHistoryRequest(id, model.toJson());
|
||||
await loadHistoryRequest(id);
|
||||
}
|
||||
|
||||
Future<void> clearAllHistory() async {
|
||||
await hiveHandler.clearAllHistory();
|
||||
ref.read(selectedHistoryIdStateProvider.notifier).state = null;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
@ -54,6 +56,22 @@ class ResponseBody extends StatelessWidget {
|
||||
options.remove(ResponseBodyView.code);
|
||||
}
|
||||
|
||||
// print('reM -> ${responseModel.sseOutput}');
|
||||
|
||||
if (responseModel.sseOutput?.isNotEmpty ?? false) {
|
||||
final modifiedBody = responseModel.sseOutput!.join('\n\n');
|
||||
print(modifiedBody);
|
||||
return ResponseBodySuccess(
|
||||
key: Key("${selectedRequestModel!.id}-response"),
|
||||
mediaType: mediaType,
|
||||
options: options,
|
||||
bytes: utf8.encode(modifiedBody),
|
||||
body: modifiedBody,
|
||||
formattedBody: modifiedBody,
|
||||
highlightLanguage: highlightLanguage,
|
||||
);
|
||||
}
|
||||
|
||||
return ResponseBodySuccess(
|
||||
key: Key("${selectedRequestModel!.id}-response"),
|
||||
mediaType: mediaType,
|
||||
|
@ -53,6 +53,7 @@ class HttpResponseModel with _$HttpResponseModel {
|
||||
String? formattedBody,
|
||||
@Uint8ListConverter() Uint8List? bodyBytes,
|
||||
@DurationConverter() Duration? time,
|
||||
List<String>? sseOutput,
|
||||
}) = _HttpResponseModel;
|
||||
|
||||
factory HttpResponseModel.fromJson(Map<String, Object?> json) =>
|
||||
|
@ -30,6 +30,7 @@ mixin _$HttpResponseModel {
|
||||
Uint8List? get bodyBytes => throw _privateConstructorUsedError;
|
||||
@DurationConverter()
|
||||
Duration? get time => throw _privateConstructorUsedError;
|
||||
List<String>? get sseOutput => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this HttpResponseModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@ -56,6 +57,7 @@ abstract class $HttpResponseModelCopyWith<$Res> {
|
||||
String? formattedBody,
|
||||
@Uint8ListConverter() Uint8List? bodyBytes,
|
||||
@DurationConverter() Duration? time,
|
||||
List<String>? sseOutput,
|
||||
});
|
||||
}
|
||||
|
||||
@ -81,6 +83,7 @@ class _$HttpResponseModelCopyWithImpl<$Res, $Val extends HttpResponseModel>
|
||||
Object? formattedBody = freezed,
|
||||
Object? bodyBytes = freezed,
|
||||
Object? time = freezed,
|
||||
Object? sseOutput = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
@ -112,6 +115,10 @@ class _$HttpResponseModelCopyWithImpl<$Res, $Val extends HttpResponseModel>
|
||||
? _value.time
|
||||
: time // ignore: cast_nullable_to_non_nullable
|
||||
as Duration?,
|
||||
sseOutput: freezed == sseOutput
|
||||
? _value.sseOutput
|
||||
: sseOutput // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
@ -135,6 +142,7 @@ abstract class _$$HttpResponseModelImplCopyWith<$Res>
|
||||
String? formattedBody,
|
||||
@Uint8ListConverter() Uint8List? bodyBytes,
|
||||
@DurationConverter() Duration? time,
|
||||
List<String>? sseOutput,
|
||||
});
|
||||
}
|
||||
|
||||
@ -159,6 +167,7 @@ class __$$HttpResponseModelImplCopyWithImpl<$Res>
|
||||
Object? formattedBody = freezed,
|
||||
Object? bodyBytes = freezed,
|
||||
Object? time = freezed,
|
||||
Object? sseOutput = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_$HttpResponseModelImpl(
|
||||
@ -190,6 +199,10 @@ class __$$HttpResponseModelImplCopyWithImpl<$Res>
|
||||
? _value.time
|
||||
: time // ignore: cast_nullable_to_non_nullable
|
||||
as Duration?,
|
||||
sseOutput: freezed == sseOutput
|
||||
? _value._sseOutput
|
||||
: sseOutput // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -207,8 +220,10 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
|
||||
this.formattedBody,
|
||||
@Uint8ListConverter() this.bodyBytes,
|
||||
@DurationConverter() this.time,
|
||||
final List<String>? sseOutput,
|
||||
}) : _headers = headers,
|
||||
_requestHeaders = requestHeaders,
|
||||
_sseOutput = sseOutput,
|
||||
super._();
|
||||
|
||||
factory _$HttpResponseModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
@ -246,10 +261,19 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
|
||||
@override
|
||||
@DurationConverter()
|
||||
final Duration? time;
|
||||
final List<String>? _sseOutput;
|
||||
@override
|
||||
List<String>? get sseOutput {
|
||||
final value = _sseOutput;
|
||||
if (value == null) return null;
|
||||
if (_sseOutput is EqualUnmodifiableListView) return _sseOutput;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HttpResponseModel(statusCode: $statusCode, headers: $headers, requestHeaders: $requestHeaders, body: $body, formattedBody: $formattedBody, bodyBytes: $bodyBytes, time: $time)';
|
||||
return 'HttpResponseModel(statusCode: $statusCode, headers: $headers, requestHeaders: $requestHeaders, body: $body, formattedBody: $formattedBody, bodyBytes: $bodyBytes, time: $time, sseOutput: $sseOutput)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -268,7 +292,11 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
|
||||
(identical(other.formattedBody, formattedBody) ||
|
||||
other.formattedBody == formattedBody) &&
|
||||
const DeepCollectionEquality().equals(other.bodyBytes, bodyBytes) &&
|
||||
(identical(other.time, time) || other.time == time));
|
||||
(identical(other.time, time) || other.time == time) &&
|
||||
const DeepCollectionEquality().equals(
|
||||
other._sseOutput,
|
||||
_sseOutput,
|
||||
));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@ -282,6 +310,7 @@ class _$HttpResponseModelImpl extends _HttpResponseModel {
|
||||
formattedBody,
|
||||
const DeepCollectionEquality().hash(bodyBytes),
|
||||
time,
|
||||
const DeepCollectionEquality().hash(_sseOutput),
|
||||
);
|
||||
|
||||
/// Create a copy of HttpResponseModel
|
||||
@ -310,6 +339,7 @@ abstract class _HttpResponseModel extends HttpResponseModel {
|
||||
final String? formattedBody,
|
||||
@Uint8ListConverter() final Uint8List? bodyBytes,
|
||||
@DurationConverter() final Duration? time,
|
||||
final List<String>? sseOutput,
|
||||
}) = _$HttpResponseModelImpl;
|
||||
const _HttpResponseModel._() : super._();
|
||||
|
||||
@ -332,6 +362,8 @@ abstract class _HttpResponseModel extends HttpResponseModel {
|
||||
@override
|
||||
@DurationConverter()
|
||||
Duration? get time;
|
||||
@override
|
||||
List<String>? get sseOutput;
|
||||
|
||||
/// Create a copy of HttpResponseModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
@ -21,6 +21,9 @@ _$HttpResponseModelImpl _$$HttpResponseModelImplFromJson(Map json) =>
|
||||
json['bodyBytes'] as List<int>?,
|
||||
),
|
||||
time: const DurationConverter().fromJson((json['time'] as num?)?.toInt()),
|
||||
sseOutput: (json['sseOutput'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$HttpResponseModelImplToJson(
|
||||
@ -33,4 +36,5 @@ Map<String, dynamic> _$$HttpResponseModelImplToJson(
|
||||
'formattedBody': instance.formattedBody,
|
||||
'bodyBytes': const Uint8ListConverter().toJson(instance.bodyBytes),
|
||||
'time': const DurationConverter().toJson(instance.time),
|
||||
'sseOutput': instance.sseOutput,
|
||||
};
|
||||
|
@ -165,14 +165,18 @@ http.Request prepareHttpRequest({
|
||||
return request;
|
||||
}
|
||||
|
||||
Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
|
||||
Future<Stream<(String? cT, HttpResponse? resp, Duration? dur, String? err)?>>
|
||||
streamHttpRequest(
|
||||
String requestId,
|
||||
APIType apiType,
|
||||
HttpRequestModel requestModel, {
|
||||
SupportedUriSchemes defaultUriScheme = kDefaultUriScheme,
|
||||
bool noSSL = false,
|
||||
}) async {
|
||||
final controller = StreamController<(String?, Duration?, String?)?>();
|
||||
final controller =
|
||||
StreamController<
|
||||
(String? cT, HttpResponse? resp, Duration? dur, String? err)?
|
||||
>();
|
||||
StreamSubscription<String?>? subscription;
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
@ -186,10 +190,10 @@ Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
|
||||
Future<void> handleError(dynamic error) async {
|
||||
await Future.microtask(() {});
|
||||
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||
controller.add((null, null, kMsgRequestCancelled));
|
||||
controller.add((null, null, null, kMsgRequestCancelled));
|
||||
httpClientManager.removeCancelledRequest(requestId);
|
||||
} else {
|
||||
controller.add((null, null, error.toString()));
|
||||
controller.add((null, null, null, error.toString()));
|
||||
}
|
||||
await cleanup();
|
||||
}
|
||||
@ -200,7 +204,7 @@ Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
|
||||
};
|
||||
|
||||
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||
controller.add((null, null, kMsgRequestCancelled));
|
||||
controller.add((null, null, null, kMsgRequestCancelled));
|
||||
httpClientManager.removeCancelledRequest(requestId);
|
||||
controller.close();
|
||||
return controller.stream;
|
||||
@ -243,8 +247,25 @@ Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
|
||||
final streamedResponse = await client.send(multipart);
|
||||
final stream = streamTextResponse(streamedResponse);
|
||||
|
||||
print(streamedResponse.headers['content-type']);
|
||||
|
||||
subscription = stream.listen(
|
||||
(data) => controller.add((data, stopwatch.elapsed, null)),
|
||||
(data) => controller.add((
|
||||
streamedResponse.headers['content-type'].toString(),
|
||||
data == null
|
||||
? null
|
||||
: HttpResponse.bytes(
|
||||
utf8.encode(data),
|
||||
streamedResponse.statusCode,
|
||||
request: streamedResponse.request,
|
||||
headers: streamedResponse.headers,
|
||||
isRedirect: streamedResponse.isRedirect,
|
||||
persistentConnection: streamedResponse.persistentConnection,
|
||||
reasonPhrase: streamedResponse.reasonPhrase,
|
||||
),
|
||||
stopwatch.elapsed,
|
||||
null,
|
||||
)),
|
||||
onDone: () => cleanup(),
|
||||
onError: handleError,
|
||||
);
|
||||
@ -279,7 +300,22 @@ Future<Stream<(String?, Duration?, String?)?>> streamHttpRequest(
|
||||
subscription = stream.listen(
|
||||
(data) {
|
||||
if (!controller.isClosed) {
|
||||
controller.add((data, stopwatch.elapsed, null));
|
||||
controller.add((
|
||||
streamedResponse.headers['content-type'].toString(),
|
||||
data == null
|
||||
? null
|
||||
: HttpResponse.bytes(
|
||||
utf8.encode(data),
|
||||
streamedResponse.statusCode,
|
||||
request: streamedResponse.request,
|
||||
headers: streamedResponse.headers,
|
||||
isRedirect: streamedResponse.isRedirect,
|
||||
persistentConnection: streamedResponse.persistentConnection,
|
||||
reasonPhrase: streamedResponse.reasonPhrase,
|
||||
),
|
||||
stopwatch.elapsed,
|
||||
null,
|
||||
));
|
||||
}
|
||||
},
|
||||
onDone: () => cleanup(),
|
||||
|
Reference in New Issue
Block a user