mirror of
https://github.com/foss42/apidash.git
synced 2025-12-01 02:07:00 +08:00
streamHttpRequest: replaced string buffering with chunk_expansion
This commit is contained in:
@@ -68,11 +68,9 @@ class HttpResponseModel with _$HttpResponseModel {
|
||||
}, response.headers);
|
||||
MediaType? mediaType = getMediaTypeFromHeaders(responseHeaders);
|
||||
|
||||
//TODO: Review Effectiveness
|
||||
final body = decodeBytes(
|
||||
response.bodyBytes,
|
||||
response.headers['content-type']!,
|
||||
);
|
||||
final body = (mediaType?.subtype == kSubTypeJson)
|
||||
? utf8.decode(response.bodyBytes)
|
||||
: response.body;
|
||||
|
||||
return HttpResponseModel(
|
||||
statusCode: response.statusCode,
|
||||
|
||||
@@ -27,8 +27,7 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest(
|
||||
defaultUriScheme: defaultUriScheme,
|
||||
noSSL: noSSL,
|
||||
);
|
||||
final output = await stream.last;
|
||||
|
||||
final output = await stream.first;
|
||||
return (output?.$2, output?.$3, output?.$4);
|
||||
|
||||
// if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||
@@ -194,7 +193,7 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
||||
StreamSubscription<List<int>?>? subscription;
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
cleanup() async {
|
||||
Future<void> cleanup() async {
|
||||
stopwatch.stop();
|
||||
await subscription?.cancel();
|
||||
httpClientManager.closeClient(requestId);
|
||||
@@ -202,17 +201,22 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
||||
controller.close();
|
||||
}
|
||||
|
||||
Future<void> handleError(dynamic error) async {
|
||||
Future<void> addCancelledMessage() async {
|
||||
if (!controller.isClosed) {
|
||||
controller.add((null, null, null, kMsgRequestCancelled));
|
||||
}
|
||||
httpClientManager.removeCancelledRequest(requestId);
|
||||
await cleanup();
|
||||
}
|
||||
|
||||
Future<void> addErrorMessage(dynamic error) async {
|
||||
await Future.microtask(() {});
|
||||
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||
if (!controller.isClosed) {
|
||||
controller.add((null, null, null, kMsgRequestCancelled));
|
||||
}
|
||||
httpClientManager.removeCancelledRequest(requestId);
|
||||
await addCancelledMessage();
|
||||
} else {
|
||||
controller.add((null, null, null, error.toString()));
|
||||
await cleanup();
|
||||
}
|
||||
await cleanup();
|
||||
}
|
||||
|
||||
controller.onCancel = () async {
|
||||
@@ -221,11 +225,7 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
||||
};
|
||||
|
||||
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||
if (!controller.isClosed) {
|
||||
controller.add((null, null, null, kMsgRequestCancelled));
|
||||
}
|
||||
httpClientManager.removeCancelledRequest(requestId);
|
||||
controller.close();
|
||||
await addCancelledMessage();
|
||||
return controller.stream;
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
||||
);
|
||||
|
||||
if (uri == null) {
|
||||
await handleError(uriError ?? 'Invalid URL');
|
||||
await addErrorMessage(uriError ?? 'Invalid URL');
|
||||
return controller.stream;
|
||||
}
|
||||
|
||||
@@ -248,7 +248,6 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
||||
requestModel: requestModel,
|
||||
apiType: apiType,
|
||||
);
|
||||
//----------------- Response Handling ---------------------
|
||||
|
||||
HttpResponse getResponseFromBytes(List<int> bytes) {
|
||||
return HttpResponse.bytes(
|
||||
@@ -262,54 +261,46 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
||||
);
|
||||
}
|
||||
|
||||
final buffer = StringBuffer();
|
||||
final contentType =
|
||||
streamedResponse.headers['content-type']?.toString() ?? '';
|
||||
final contentLength =
|
||||
int.tryParse(streamedResponse.headers['content-length'] ?? '') ?? -1;
|
||||
int receivedBytes = 0;
|
||||
final chunkList = <List<int>>[];
|
||||
bool hasEmitted = false;
|
||||
|
||||
subscription = streamedResponse.stream.listen(
|
||||
(bytes) {
|
||||
(bytes) async {
|
||||
if (controller.isClosed) return;
|
||||
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||
return await addCancelledMessage();
|
||||
}
|
||||
|
||||
final isStreaming = kStreamingResponseTypes.contains(contentType);
|
||||
|
||||
if (isStreaming) {
|
||||
//For Streaming responses, output every response
|
||||
final response = getResponseFromBytes(bytes);
|
||||
controller.add((true, response, stopwatch.elapsed, null));
|
||||
return;
|
||||
}
|
||||
|
||||
//For non Streaming events, add output to buffer
|
||||
receivedBytes += bytes.length;
|
||||
buffer.write(decodeBytes(bytes, contentType));
|
||||
|
||||
if (!hasEmitted &&
|
||||
contentLength > 0 &&
|
||||
receivedBytes >= contentLength &&
|
||||
!controller.isClosed) {
|
||||
final response = getResponseFromBytes(buffer.toString().codeUnits);
|
||||
controller.add((false, response, stopwatch.elapsed, null));
|
||||
hasEmitted = true;
|
||||
} else {
|
||||
chunkList.add(bytes);
|
||||
}
|
||||
},
|
||||
onDone: () {
|
||||
//handle cases where response is larger than a TCP packet and cuts mid-way
|
||||
onDone: () async {
|
||||
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||
return await addCancelledMessage();
|
||||
}
|
||||
if (!hasEmitted && !controller.isClosed) {
|
||||
final response = getResponseFromBytes(buffer.toString().codeUnits);
|
||||
final allBytes = chunkList.expand((x) => x).toList();
|
||||
final response = getResponseFromBytes(allBytes);
|
||||
final isStreaming = kStreamingResponseTypes.contains(contentType);
|
||||
controller.add((isStreaming, response, stopwatch.elapsed, null));
|
||||
}
|
||||
cleanup();
|
||||
await cleanup();
|
||||
},
|
||||
onError: handleError,
|
||||
onError: addErrorMessage,
|
||||
);
|
||||
|
||||
return controller.stream;
|
||||
} catch (e) {
|
||||
await handleError(e);
|
||||
await addErrorMessage(e);
|
||||
return controller.stream;
|
||||
}
|
||||
}
|
||||
@@ -328,7 +319,7 @@ Future<http.StreamedResponse> makeStreamedRequest({
|
||||
|
||||
//----------------- Request Creation ---------------------
|
||||
//Handling HTTP Multipart Requests
|
||||
if (requestModel == APIType.rest && isMultipart && hasBody) {
|
||||
if (apiType == APIType.rest && isMultipart && hasBody) {
|
||||
final multipart = http.MultipartRequest(
|
||||
requestModel.method.name.toUpperCase(),
|
||||
uri,
|
||||
|
||||
@@ -51,59 +51,3 @@ Future<http.Response> convertStreamedResponse(
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Stream<String?> streamTextResponse(
|
||||
// http.StreamedResponse streamedResponse,
|
||||
// ) async* {
|
||||
// try {
|
||||
// if (streamedResponse.statusCode != 200) {
|
||||
// final errorText = await streamedResponse.stream.bytesToString();
|
||||
// throw Exception('${streamedResponse.statusCode}\n$errorText');
|
||||
// }
|
||||
// final utf8Stream = streamedResponse.stream.transform(utf8.decoder);
|
||||
// await for (final chunk in utf8Stream) {
|
||||
// yield chunk;
|
||||
// }
|
||||
// } catch (e) {
|
||||
// rethrow;
|
||||
// }
|
||||
// }
|
||||
|
||||
String getCharset(String contentType) {
|
||||
final match = RegExp(
|
||||
r'charset=([^\s;]+)',
|
||||
caseSensitive: false,
|
||||
).firstMatch(contentType);
|
||||
return match?.group(1)?.toLowerCase() ?? 'utf-8'; // default to utf-8
|
||||
}
|
||||
|
||||
String decodeBytes(List<int> bytes, String contentType) {
|
||||
String _decodeUtf16(List<int> bytes, Endian endianness) {
|
||||
final byteData = ByteData.sublistView(Uint8List.fromList(bytes));
|
||||
final codeUnits = <int>[];
|
||||
for (int i = 0; i + 1 < byteData.lengthInBytes; i += 2) {
|
||||
codeUnits.add(byteData.getUint16(i, endianness));
|
||||
}
|
||||
return String.fromCharCodes(codeUnits);
|
||||
}
|
||||
|
||||
final cSet = getCharset(contentType);
|
||||
switch (cSet) {
|
||||
case 'utf-8':
|
||||
case 'utf8':
|
||||
return utf8.decode(bytes, allowMalformed: true);
|
||||
case 'utf-16':
|
||||
case 'utf-16le':
|
||||
return _decodeUtf16(bytes, Endian.little);
|
||||
case 'utf-16be':
|
||||
return _decodeUtf16(bytes, Endian.big);
|
||||
case 'iso-8859-1':
|
||||
case 'latin1':
|
||||
return latin1.decode(bytes);
|
||||
case 'us-ascii':
|
||||
case 'ascii':
|
||||
return ascii.decode(bytes);
|
||||
default:
|
||||
return utf8.decode(bytes, allowMalformed: true); //UTF8
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user