Files
apidash/packages/better_networking/lib/services/http_service.dart

388 lines
12 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:seed/seed.dart';
import '../consts.dart';
import '../extensions/extensions.dart';
import '../models/models.dart';
import '../utils/utils.dart';
import 'http_client_manager.dart';
typedef HttpResponse = http.Response;
final httpClientManager = HttpClientManager();
Future<(HttpResponse?, Duration?, String?)> sendHttpRequest(
String requestId,
APIType apiType,
HttpRequestModel requestModel, {
SupportedUriSchemes defaultUriScheme = kDefaultUriScheme,
bool noSSL = false,
}) async {
final stream = await streamHttpRequest(
requestId,
apiType,
requestModel,
defaultUriScheme: defaultUriScheme,
noSSL: noSSL,
);
final output = await stream.last;
return (output?.$2, output?.$3, output?.$4);
// if (httpClientManager.wasRequestCancelled(requestId)) {
// httpClientManager.removeCancelledRequest(requestId);
// }
// final client = httpClientManager.createClient(requestId, noSSL: noSSL);
// (Uri?, String?) uriRec = getValidRequestUri(
// requestModel.url,
// requestModel.enabledParams,
// defaultUriScheme: defaultUriScheme,
// );
// if (uriRec.$1 != null) {
// Uri requestUrl = uriRec.$1!;
// Map<String, String> headers = requestModel.enabledHeadersMap;
// bool overrideContentType = false;
// HttpResponse? response;
// String? body;
// try {
// Stopwatch stopwatch = Stopwatch()..start();
// if (apiType == APIType.rest) {
// var isMultiPartRequest =
// requestModel.bodyContentType == ContentType.formdata;
// if (kMethodsWithBody.contains(requestModel.method)) {
// var requestBody = requestModel.body;
// if (requestBody != null &&
// !isMultiPartRequest &&
// requestBody.isNotEmpty) {
// body = requestBody;
// if (requestModel.hasContentTypeHeader) {
// overrideContentType = true;
// } else {
// headers[HttpHeaders.contentTypeHeader] =
// requestModel.bodyContentType.header;
// }
// }
// if (isMultiPartRequest) {
// var multiPartRequest = http.MultipartRequest(
// requestModel.method.name.toUpperCase(),
// requestUrl,
// );
// multiPartRequest.headers.addAll(headers);
// for (var formData in requestModel.formDataList) {
// if (formData.type == FormDataType.text) {
// multiPartRequest.fields.addAll({formData.name: formData.value});
// } else {
// multiPartRequest.files.add(
// await http.MultipartFile.fromPath(
// formData.name,
// formData.value,
// ),
// );
// }
// }
// http.StreamedResponse multiPartResponse = await client.send(
// multiPartRequest,
// );
// stopwatch.stop();
// http.Response convertedMultiPartResponse =
// await convertStreamedResponse(multiPartResponse);
// return (convertedMultiPartResponse, stopwatch.elapsed, null);
// }
// }
// switch (requestModel.method) {
// case HTTPVerb.get:
// response = await client.get(requestUrl, headers: headers);
// break;
// case HTTPVerb.head:
// response = await client.head(requestUrl, headers: headers);
// break;
// case HTTPVerb.post:
// case HTTPVerb.put:
// case HTTPVerb.patch:
// case HTTPVerb.delete:
// case HTTPVerb.options:
// final request = prepareHttpRequest(
// url: requestUrl,
// method: requestModel.method.name.toUpperCase(),
// headers: headers,
// body: body,
// overrideContentType: overrideContentType,
// );
// final streamed = await client.send(request);
// response = await http.Response.fromStream(streamed);
// break;
// }
// }
// if (apiType == APIType.graphql) {
// var requestBody = getGraphQLBody(requestModel);
// if (requestBody != null) {
// var contentLength = utf8.encode(requestBody).length;
// if (contentLength > 0) {
// body = requestBody;
// headers[HttpHeaders.contentLengthHeader] = contentLength.toString();
// if (!requestModel.hasContentTypeHeader) {
// headers[HttpHeaders.contentTypeHeader] = ContentType.json.header;
// }
// }
// }
// response = await client.post(requestUrl, headers: headers, body: body);
// }
// stopwatch.stop();
// return (response, stopwatch.elapsed, null);
// } catch (e) {
// if (httpClientManager.wasRequestCancelled(requestId)) {
// return (null, null, kMsgRequestCancelled);
// }
// return (null, null, e.toString());
// } finally {
// httpClientManager.closeClient(requestId);
// }
// } else {
// return (null, null, uriRec.$2);
// }
}
void cancelHttpRequest(String? requestId) {
httpClientManager.cancelRequest(requestId);
}
http.Request prepareHttpRequest({
required Uri url,
required String method,
required Map<String, String> headers,
required String? body,
bool overrideContentType = false,
}) {
var request = http.Request(method, url);
if (headers.getValueContentType() != null) {
request.headers[HttpHeaders.contentTypeHeader] = headers
.getValueContentType()!;
if (!overrideContentType) {
headers.removeKeyContentType();
}
}
if (body != null) {
request.body = body;
headers[HttpHeaders.contentLengthHeader] = request.bodyBytes.length
.toString();
}
request.headers.addAll(headers);
return request;
}
typedef HttpStreamOutput = (
bool? streamOutput,
HttpResponse? resp,
Duration? dur,
String? err,
)?;
Future<Stream<HttpStreamOutput>> streamHttpRequest(
String requestId,
APIType apiType,
HttpRequestModel requestModel, {
SupportedUriSchemes defaultUriScheme = kDefaultUriScheme,
bool noSSL = false,
}) async {
final controller = StreamController<HttpStreamOutput>();
StreamSubscription<List<int>?>? subscription;
final stopwatch = Stopwatch()..start();
cleanup() async {
stopwatch.stop();
await subscription?.cancel();
httpClientManager.closeClient(requestId);
await Future.microtask(() {});
controller.close();
}
Future<void> handleError(dynamic error) async {
await Future.microtask(() {});
if (httpClientManager.wasRequestCancelled(requestId)) {
if (!controller.isClosed) {
controller.add((null, null, null, kMsgRequestCancelled));
}
httpClientManager.removeCancelledRequest(requestId);
} else {
controller.add((null, null, null, error.toString()));
}
await cleanup();
}
controller.onCancel = () async {
await subscription?.cancel();
httpClientManager.cancelRequest(requestId);
};
if (httpClientManager.wasRequestCancelled(requestId)) {
if (!controller.isClosed) {
controller.add((null, null, null, kMsgRequestCancelled));
}
httpClientManager.removeCancelledRequest(requestId);
controller.close();
return controller.stream;
}
final client = httpClientManager.createClient(requestId, noSSL: noSSL);
final (uri, uriError) = getValidRequestUri(
requestModel.url,
requestModel.enabledParams,
defaultUriScheme: defaultUriScheme,
);
if (uri == null) {
await handleError(uriError ?? 'Invalid URL');
return controller.stream;
}
try {
final streamedResponse = await makeStreamedRequest(
client: client,
uri: uri,
requestModel: requestModel,
apiType: apiType,
);
//----------------- Response Handling ---------------------
HttpResponse getResponseFromBytes(List<int> bytes) {
return HttpResponse.bytes(
bytes,
streamedResponse.statusCode,
request: streamedResponse.request,
headers: streamedResponse.headers,
isRedirect: streamedResponse.isRedirect,
persistentConnection: streamedResponse.persistentConnection,
reasonPhrase: streamedResponse.reasonPhrase,
);
}
final buffer = StringBuffer();
final contentType =
streamedResponse.headers['content-type']?.toString() ?? '';
final contentLength =
int.tryParse(streamedResponse.headers['content-length'] ?? '') ?? -1;
int receivedBytes = 0;
bool hasEmitted = false;
subscription = streamedResponse.stream.listen(
(bytes) {
if (controller.isClosed) return;
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;
}
},
onDone: () {
//handle cases where response is larger than a TCP packet and cuts mid-way
if (!hasEmitted && !controller.isClosed) {
final response = getResponseFromBytes(buffer.toString().codeUnits);
final isStreaming = kStreamingResponseTypes.contains(contentType);
controller.add((isStreaming, response, stopwatch.elapsed, null));
}
cleanup();
},
onError: handleError,
);
return controller.stream;
} catch (e) {
await handleError(e);
return controller.stream;
}
}
Future<http.StreamedResponse> makeStreamedRequest({
required http.Client client,
required Uri uri,
required HttpRequestModel requestModel,
required APIType apiType,
}) async {
final headers = requestModel.enabledHeadersMap;
final hasBody = kMethodsWithBody.contains(requestModel.method);
final isMultipart = requestModel.bodyContentType == ContentType.formdata;
http.StreamedResponse streamedResponse;
//----------------- Request Creation ---------------------
//Handling HTTP Multipart Requests
if (requestModel == APIType.rest && isMultipart && hasBody) {
final multipart = http.MultipartRequest(
requestModel.method.name.toUpperCase(),
uri,
)..headers.addAll(headers);
for (final data in requestModel.formDataList) {
if (data.type == FormDataType.text) {
multipart.fields[data.name] = data.value;
} else {
multipart.files.add(
await http.MultipartFile.fromPath(data.name, data.value),
);
}
}
streamedResponse = await client.send(multipart);
} else if (apiType == APIType.graphql) {
// Handling GraphQL Requests
var requestBody = getGraphQLBody(requestModel);
String? body;
if (requestBody != null) {
var contentLength = utf8.encode(requestBody).length;
if (contentLength > 0) {
body = requestBody;
headers[HttpHeaders.contentLengthHeader] = contentLength.toString();
if (!requestModel.hasContentTypeHeader) {
headers[HttpHeaders.contentTypeHeader] = ContentType.json.header;
}
}
}
final request = http.Request('POST', uri)
..headers.addAll(headers)
..body = body ?? '';
streamedResponse = await client.send(request);
} else {
//Handling regular REST Requests
String? body;
bool overrideContentType = false;
if (hasBody && requestModel.body?.isNotEmpty == true) {
body = requestModel.body;
if (!requestModel.hasContentTypeHeader) {
headers[HttpHeaders.contentTypeHeader] =
requestModel.bodyContentType.header;
} else {
overrideContentType = true;
}
}
final request = prepareHttpRequest(
url: uri,
method: requestModel.method.name.toUpperCase(),
headers: headers,
body: body,
overrideContentType: overrideContentType,
);
streamedResponse = await client.send(request);
}
return streamedResponse;
}