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);
|
}, response.headers);
|
||||||
MediaType? mediaType = getMediaTypeFromHeaders(responseHeaders);
|
MediaType? mediaType = getMediaTypeFromHeaders(responseHeaders);
|
||||||
|
|
||||||
//TODO: Review Effectiveness
|
final body = (mediaType?.subtype == kSubTypeJson)
|
||||||
final body = decodeBytes(
|
? utf8.decode(response.bodyBytes)
|
||||||
response.bodyBytes,
|
: response.body;
|
||||||
response.headers['content-type']!,
|
|
||||||
);
|
|
||||||
|
|
||||||
return HttpResponseModel(
|
return HttpResponseModel(
|
||||||
statusCode: response.statusCode,
|
statusCode: response.statusCode,
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest(
|
|||||||
defaultUriScheme: defaultUriScheme,
|
defaultUriScheme: defaultUriScheme,
|
||||||
noSSL: noSSL,
|
noSSL: noSSL,
|
||||||
);
|
);
|
||||||
final output = await stream.last;
|
final output = await stream.first;
|
||||||
|
|
||||||
return (output?.$2, output?.$3, output?.$4);
|
return (output?.$2, output?.$3, output?.$4);
|
||||||
|
|
||||||
// if (httpClientManager.wasRequestCancelled(requestId)) {
|
// if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||||
@@ -194,7 +193,7 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
|||||||
StreamSubscription<List<int>?>? subscription;
|
StreamSubscription<List<int>?>? subscription;
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
cleanup() async {
|
Future<void> cleanup() async {
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
await subscription?.cancel();
|
await subscription?.cancel();
|
||||||
httpClientManager.closeClient(requestId);
|
httpClientManager.closeClient(requestId);
|
||||||
@@ -202,17 +201,22 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
|||||||
controller.close();
|
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(() {});
|
await Future.microtask(() {});
|
||||||
if (httpClientManager.wasRequestCancelled(requestId)) {
|
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||||
if (!controller.isClosed) {
|
await addCancelledMessage();
|
||||||
controller.add((null, null, null, kMsgRequestCancelled));
|
|
||||||
}
|
|
||||||
httpClientManager.removeCancelledRequest(requestId);
|
|
||||||
} else {
|
} else {
|
||||||
controller.add((null, null, null, error.toString()));
|
controller.add((null, null, null, error.toString()));
|
||||||
|
await cleanup();
|
||||||
}
|
}
|
||||||
await cleanup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.onCancel = () async {
|
controller.onCancel = () async {
|
||||||
@@ -221,11 +225,7 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (httpClientManager.wasRequestCancelled(requestId)) {
|
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||||
if (!controller.isClosed) {
|
await addCancelledMessage();
|
||||||
controller.add((null, null, null, kMsgRequestCancelled));
|
|
||||||
}
|
|
||||||
httpClientManager.removeCancelledRequest(requestId);
|
|
||||||
controller.close();
|
|
||||||
return controller.stream;
|
return controller.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
await handleError(uriError ?? 'Invalid URL');
|
await addErrorMessage(uriError ?? 'Invalid URL');
|
||||||
return controller.stream;
|
return controller.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +248,6 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
|||||||
requestModel: requestModel,
|
requestModel: requestModel,
|
||||||
apiType: apiType,
|
apiType: apiType,
|
||||||
);
|
);
|
||||||
//----------------- Response Handling ---------------------
|
|
||||||
|
|
||||||
HttpResponse getResponseFromBytes(List<int> bytes) {
|
HttpResponse getResponseFromBytes(List<int> bytes) {
|
||||||
return HttpResponse.bytes(
|
return HttpResponse.bytes(
|
||||||
@@ -262,54 +261,46 @@ Future<Stream<HttpStreamOutput>> streamHttpRequest(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final buffer = StringBuffer();
|
|
||||||
final contentType =
|
final contentType =
|
||||||
streamedResponse.headers['content-type']?.toString() ?? '';
|
streamedResponse.headers['content-type']?.toString() ?? '';
|
||||||
final contentLength =
|
final chunkList = <List<int>>[];
|
||||||
int.tryParse(streamedResponse.headers['content-length'] ?? '') ?? -1;
|
|
||||||
int receivedBytes = 0;
|
|
||||||
bool hasEmitted = false;
|
bool hasEmitted = false;
|
||||||
|
|
||||||
subscription = streamedResponse.stream.listen(
|
subscription = streamedResponse.stream.listen(
|
||||||
(bytes) {
|
(bytes) async {
|
||||||
if (controller.isClosed) return;
|
if (controller.isClosed) return;
|
||||||
|
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||||
|
return await addCancelledMessage();
|
||||||
|
}
|
||||||
|
|
||||||
final isStreaming = kStreamingResponseTypes.contains(contentType);
|
final isStreaming = kStreamingResponseTypes.contains(contentType);
|
||||||
|
|
||||||
if (isStreaming) {
|
if (isStreaming) {
|
||||||
//For Streaming responses, output every response
|
|
||||||
final response = getResponseFromBytes(bytes);
|
final response = getResponseFromBytes(bytes);
|
||||||
controller.add((true, response, stopwatch.elapsed, null));
|
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;
|
hasEmitted = true;
|
||||||
|
} else {
|
||||||
|
chunkList.add(bytes);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDone: () {
|
onDone: () async {
|
||||||
//handle cases where response is larger than a TCP packet and cuts mid-way
|
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||||
|
return await addCancelledMessage();
|
||||||
|
}
|
||||||
if (!hasEmitted && !controller.isClosed) {
|
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);
|
final isStreaming = kStreamingResponseTypes.contains(contentType);
|
||||||
controller.add((isStreaming, response, stopwatch.elapsed, null));
|
controller.add((isStreaming, response, stopwatch.elapsed, null));
|
||||||
}
|
}
|
||||||
cleanup();
|
await cleanup();
|
||||||
},
|
},
|
||||||
onError: handleError,
|
onError: addErrorMessage,
|
||||||
);
|
);
|
||||||
|
|
||||||
return controller.stream;
|
return controller.stream;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await handleError(e);
|
await addErrorMessage(e);
|
||||||
return controller.stream;
|
return controller.stream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +319,7 @@ Future<http.StreamedResponse> makeStreamedRequest({
|
|||||||
|
|
||||||
//----------------- Request Creation ---------------------
|
//----------------- Request Creation ---------------------
|
||||||
//Handling HTTP Multipart Requests
|
//Handling HTTP Multipart Requests
|
||||||
if (requestModel == APIType.rest && isMultipart && hasBody) {
|
if (apiType == APIType.rest && isMultipart && hasBody) {
|
||||||
final multipart = http.MultipartRequest(
|
final multipart = http.MultipartRequest(
|
||||||
requestModel.method.name.toUpperCase(),
|
requestModel.method.name.toUpperCase(),
|
||||||
uri,
|
uri,
|
||||||
|
|||||||
@@ -51,59 +51,3 @@ Future<http.Response> convertStreamedResponse(
|
|||||||
|
|
||||||
return response;
|
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