new change to use intaleq_map sdk 04-16-4
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
List<int> fileToBytes(dynamic data) {
|
||||
if (data is List<int>) {
|
||||
return data;
|
||||
} else {
|
||||
throw const FormatException(
|
||||
'File is not "File" or "String" or "List<int>"');
|
||||
}
|
||||
}
|
||||
|
||||
// void writeOnFile(List<int> bytes) {
|
||||
// var blob = html.Blob(["data"], 'text/plain', 'native');
|
||||
// var anchorElement = html.AnchorElement(
|
||||
// href: html.Url.createObjectUrlFromBlob(blob).toString(),
|
||||
// )
|
||||
// ..setAttribute("download", "data.txt")
|
||||
// ..click();
|
||||
// }
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'dart:async';
|
||||
// ignore: avoid_web_libraries_in_flutter
|
||||
import 'dart:html';
|
||||
|
||||
import '../../certificates/certificates.dart';
|
||||
import '../../exceptions/exceptions.dart';
|
||||
import '../../request/request.dart';
|
||||
import '../../response/response.dart';
|
||||
import '../interface/request_base.dart';
|
||||
import '../utils/body_decoder.dart';
|
||||
|
||||
/// A `dart:html` implementation of `HttpRequestBase`.
|
||||
class HttpRequestImpl implements HttpRequestBase {
|
||||
HttpRequestImpl({
|
||||
bool allowAutoSignedCert = true,
|
||||
List<TrustedCertificate>? trustedCertificates,
|
||||
this.withCredentials = false,
|
||||
String Function(Uri url)? findProxy,
|
||||
});
|
||||
|
||||
/// The currently active XHRs.
|
||||
final _xhrs = <HttpRequest>{};
|
||||
|
||||
///This option requires that you submit credentials for requests
|
||||
///on different sites. The default is false
|
||||
final bool withCredentials;
|
||||
|
||||
@override
|
||||
Duration? timeout;
|
||||
|
||||
/// Sends an HTTP request and asynchronously returns the response.
|
||||
@override
|
||||
Future<Response<T>> send<T>(Request<T> request) async {
|
||||
var bytes = await request.bodyBytes.toBytes();
|
||||
HttpRequest xhr;
|
||||
|
||||
xhr = HttpRequest()
|
||||
..timeout = timeout?.inMilliseconds
|
||||
..open(request.method, '${request.url}', async: true); // check this
|
||||
|
||||
_xhrs.add(xhr);
|
||||
|
||||
xhr
|
||||
..responseType = 'blob'
|
||||
..withCredentials = withCredentials;
|
||||
request.headers.forEach(xhr.setRequestHeader);
|
||||
|
||||
var completer = Completer<Response<T>>();
|
||||
xhr.onLoad.first.then((_) {
|
||||
var blob = xhr.response as Blob? ?? Blob([]);
|
||||
var reader = FileReader();
|
||||
|
||||
reader.onLoad.first.then((_) async {
|
||||
var bodyBytes = BodyBytesStream.fromBytes(reader.result as List<int>);
|
||||
|
||||
final stringBody =
|
||||
await bodyBytesToString(bodyBytes, xhr.responseHeaders);
|
||||
|
||||
String? contentType;
|
||||
|
||||
if (xhr.responseHeaders.containsKey('content-type')) {
|
||||
contentType = xhr.responseHeaders['content-type'];
|
||||
} else {
|
||||
contentType = 'application/json';
|
||||
}
|
||||
// xhr.responseHeaders.containsKey(key)
|
||||
final body = bodyDecoded<T>(
|
||||
request,
|
||||
stringBody,
|
||||
contentType,
|
||||
);
|
||||
|
||||
final response = Response<T>(
|
||||
bodyBytes: bodyBytes,
|
||||
statusCode: xhr.status,
|
||||
request: request,
|
||||
headers: xhr.responseHeaders,
|
||||
statusText: xhr.statusText,
|
||||
body: body,
|
||||
bodyString: stringBody,
|
||||
);
|
||||
completer.complete(response);
|
||||
});
|
||||
|
||||
reader.onError.first.then((error) {
|
||||
completer.completeError(
|
||||
GetHttpException(error.toString(), request.url),
|
||||
StackTrace.current,
|
||||
);
|
||||
});
|
||||
|
||||
reader.readAsArrayBuffer(blob);
|
||||
});
|
||||
|
||||
xhr.onError.first.then((_) {
|
||||
completer.completeError(
|
||||
GetHttpException('XMLHttpRequest error.', request.url),
|
||||
StackTrace.current);
|
||||
});
|
||||
|
||||
xhr.send(bytes);
|
||||
|
||||
try {
|
||||
return await completer.future;
|
||||
} finally {
|
||||
_xhrs.remove(xhr);
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the client and abort all active requests.
|
||||
@override
|
||||
void close() {
|
||||
for (var xhr in _xhrs) {
|
||||
xhr.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'dart:io';
|
||||
|
||||
List<int> fileToBytes(dynamic data) {
|
||||
if (data is File) {
|
||||
return data.readAsBytesSync();
|
||||
} else if (data is String) {
|
||||
if (File(data).existsSync()) {
|
||||
return File(data).readAsBytesSync();
|
||||
} else {
|
||||
throw 'File $data not exists';
|
||||
}
|
||||
} else if (data is List<int>) {
|
||||
return data;
|
||||
} else {
|
||||
throw const FormatException(
|
||||
'File is not "File" or "String" or "List<int>"');
|
||||
}
|
||||
}
|
||||
|
||||
void writeOnFile(List<int> bytes) {}
|
||||
@@ -0,0 +1,99 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import '../../certificates/certificates.dart';
|
||||
import '../../exceptions/exceptions.dart';
|
||||
import '../../request/request.dart';
|
||||
import '../../response/response.dart';
|
||||
import '../interface/request_base.dart';
|
||||
import '../utils/body_decoder.dart';
|
||||
|
||||
/// A `dart:io` implementation of `HttpRequestBase`.
|
||||
class HttpRequestImpl extends HttpRequestBase {
|
||||
io.HttpClient? _httpClient;
|
||||
io.SecurityContext? _securityContext;
|
||||
|
||||
HttpRequestImpl({
|
||||
bool allowAutoSignedCert = true,
|
||||
List<TrustedCertificate>? trustedCertificates,
|
||||
bool withCredentials = false,
|
||||
String Function(Uri url)? findProxy,
|
||||
}) {
|
||||
_httpClient = io.HttpClient();
|
||||
if (trustedCertificates != null) {
|
||||
_securityContext = io.SecurityContext();
|
||||
for (final trustedCertificate in trustedCertificates) {
|
||||
_securityContext!
|
||||
.setTrustedCertificatesBytes(List.from(trustedCertificate.bytes));
|
||||
}
|
||||
}
|
||||
|
||||
_httpClient = io.HttpClient(context: _securityContext);
|
||||
_httpClient!.badCertificateCallback = (_, __, ___) => allowAutoSignedCert;
|
||||
_httpClient!.findProxy = findProxy;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<T>> send<T>(Request<T> request) async {
|
||||
var stream = request.bodyBytes.asBroadcastStream();
|
||||
io.HttpClientRequest? ioRequest;
|
||||
try {
|
||||
_httpClient!.connectionTimeout = timeout;
|
||||
ioRequest = (await _httpClient!.openUrl(request.method, request.url))
|
||||
..followRedirects = request.followRedirects
|
||||
..persistentConnection = request.persistentConnection
|
||||
..maxRedirects = request.maxRedirects
|
||||
..contentLength = request.contentLength ?? -1;
|
||||
request.headers.forEach(ioRequest.headers.set);
|
||||
|
||||
var response = timeout == null
|
||||
? await stream.pipe(ioRequest) as io.HttpClientResponse
|
||||
: await stream.pipe(ioRequest).timeout(timeout!)
|
||||
as io.HttpClientResponse;
|
||||
|
||||
var headers = <String, String>{};
|
||||
response.headers.forEach((key, values) {
|
||||
headers[key] = values.join(',');
|
||||
});
|
||||
|
||||
final bodyBytes = (response);
|
||||
final stringBody = await bodyBytesToString(bodyBytes, headers);
|
||||
|
||||
final body = bodyDecoded<T>(
|
||||
request,
|
||||
stringBody,
|
||||
response.headers.contentType?.mimeType,
|
||||
);
|
||||
|
||||
return Response(
|
||||
headers: headers,
|
||||
request: request,
|
||||
statusCode: response.statusCode,
|
||||
statusText: response.reasonPhrase,
|
||||
bodyBytes: bodyBytes,
|
||||
body: body,
|
||||
bodyString: stringBody,
|
||||
);
|
||||
} on TimeoutException catch (_) {
|
||||
ioRequest?.abort();
|
||||
rethrow;
|
||||
} on io.HttpException catch (error) {
|
||||
throw GetHttpException(error.message, error.uri);
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the HttpClient.
|
||||
@override
|
||||
void close() {
|
||||
if (_httpClient != null) {
|
||||
_httpClient!.close(force: true);
|
||||
_httpClient = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extension FileExt on io.FileSystemEntity {
|
||||
// String get fileName {
|
||||
// return this?.path?.split(io.Platform.pathSeparator)?.last;
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,5 @@
|
||||
void writeOnFile(List<int> bytes) {}
|
||||
|
||||
List<int> fileToBytes(dynamic data) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import '../../certificates/certificates.dart';
|
||||
import '../../request/request.dart';
|
||||
import '../../response/response.dart';
|
||||
import '../interface/request_base.dart';
|
||||
|
||||
class HttpRequestImpl extends HttpRequestBase {
|
||||
HttpRequestImpl({
|
||||
bool allowAutoSignedCert = true,
|
||||
List<TrustedCertificate>? trustedCertificates,
|
||||
bool withCredentials = false,
|
||||
String Function(Uri url)? findProxy,
|
||||
});
|
||||
@override
|
||||
void close() {}
|
||||
|
||||
@override
|
||||
Future<Response<T>> send<T>(Request<T> request) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import '../../request/request.dart';
|
||||
import '../../response/response.dart';
|
||||
|
||||
/// Abstract interface of [HttpRequestImpl].
|
||||
abstract class HttpRequestBase {
|
||||
/// Sends an HTTP [Request].
|
||||
Future<Response<T>> send<T>(Request<T> request);
|
||||
|
||||
/// Closes the [Request] and cleans up any resources associated with it.
|
||||
void close();
|
||||
|
||||
/// Gets and sets the timeout.
|
||||
///
|
||||
/// For mobile, this value will be applied for both connection and request
|
||||
/// timeout.
|
||||
///
|
||||
/// For web, this value will be the request timeout.
|
||||
Duration? timeout;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import '../../request/request.dart';
|
||||
import '../../response/response.dart';
|
||||
import '../interface/request_base.dart';
|
||||
import '../utils/body_decoder.dart';
|
||||
|
||||
typedef MockClientHandler = Future<Response> Function(Request request);
|
||||
|
||||
class MockClient extends HttpRequestBase {
|
||||
/// The handler for than transforms request on response
|
||||
final MockClientHandler _handler;
|
||||
|
||||
/// Creates a [MockClient] with a handler that receives [Request]s and sends
|
||||
/// [Response]s.
|
||||
MockClient(this._handler);
|
||||
|
||||
@override
|
||||
Future<Response<T>> send<T>(Request<T> request) async {
|
||||
var requestBody = await request.bodyBytes.toBytes();
|
||||
var bodyBytes = BodyBytesStream.fromBytes(requestBody);
|
||||
|
||||
var response = await _handler(request);
|
||||
|
||||
final stringBody = await bodyBytesToString(bodyBytes, response.headers!);
|
||||
|
||||
var mimeType = response.headers!.containsKey('content-type')
|
||||
? response.headers!['content-type']
|
||||
: '';
|
||||
|
||||
final body = bodyDecoded<T>(
|
||||
request,
|
||||
stringBody,
|
||||
mimeType,
|
||||
);
|
||||
return Response(
|
||||
headers: response.headers,
|
||||
request: request,
|
||||
statusCode: response.statusCode,
|
||||
statusText: response.statusText,
|
||||
bodyBytes: bodyBytes,
|
||||
body: body,
|
||||
bodyString: stringBody,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../../../../get_core/get_core.dart';
|
||||
import '../../request/request.dart';
|
||||
|
||||
T? bodyDecoded<T>(Request<T> request, String stringBody, String? mimeType) {
|
||||
T? body;
|
||||
dynamic bodyToDecode;
|
||||
|
||||
if (mimeType != null && mimeType.contains('application/json')) {
|
||||
try {
|
||||
bodyToDecode = jsonDecode(stringBody);
|
||||
} on FormatException catch (_) {
|
||||
Get.log('Cannot decode server response to json');
|
||||
bodyToDecode = stringBody;
|
||||
}
|
||||
} else {
|
||||
bodyToDecode = stringBody;
|
||||
}
|
||||
|
||||
try {
|
||||
if (stringBody == '') {
|
||||
body = null;
|
||||
} else if (request.decoder == null) {
|
||||
body = bodyToDecode as T?;
|
||||
} else {
|
||||
body = request.decoder!(bodyToDecode);
|
||||
}
|
||||
} on Exception catch (_) {
|
||||
body = stringBody as T;
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class TrustedCertificate {
|
||||
final List<int> bytes;
|
||||
|
||||
TrustedCertificate(this.bytes);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
class GetHttpException implements Exception {
|
||||
final String message;
|
||||
|
||||
final Uri? uri;
|
||||
|
||||
GetHttpException(this.message, [this.uri]);
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
class GraphQLError {
|
||||
GraphQLError({this.code, this.message});
|
||||
final String? message;
|
||||
final String? code;
|
||||
|
||||
@override
|
||||
String toString() => 'GETCONNECT ERROR:\n\tcode:$code\n\tmessage:$message';
|
||||
}
|
||||
|
||||
class UnauthorizedException implements Exception {
|
||||
@override
|
||||
String toString() {
|
||||
return 'Operation Unauthorized';
|
||||
}
|
||||
}
|
||||
|
||||
class UnexpectedFormat implements Exception {
|
||||
final String message;
|
||||
UnexpectedFormat(this.message);
|
||||
@override
|
||||
String toString() {
|
||||
return 'Unexpected format: $message';
|
||||
}
|
||||
}
|
||||
563
packages/get/lib/get_connect/http/src/http.dart
Normal file
563
packages/get/lib/get_connect/http/src/http.dart
Normal file
@@ -0,0 +1,563 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import '../src/certificates/certificates.dart';
|
||||
import '../src/exceptions/exceptions.dart';
|
||||
import '../src/multipart/form_data.dart';
|
||||
import '../src/request/request.dart';
|
||||
import '../src/response/response.dart';
|
||||
import '../src/status/http_status.dart';
|
||||
import '_http/_stub/_http_request_stub.dart'
|
||||
if (dart.library.io) '_http/_io/_http_request_io.dart'
|
||||
if (dart.library.html) '_http/_html/_http_request_html.dart' as platform;
|
||||
import '_http/interface/request_base.dart';
|
||||
import 'interceptors/get_modifiers.dart';
|
||||
|
||||
typedef Decoder<T> = T Function(dynamic data);
|
||||
|
||||
typedef Progress = Function(double percent);
|
||||
|
||||
class GetHttpClient {
|
||||
String userAgent;
|
||||
String? baseUrl;
|
||||
|
||||
String defaultContentType = 'application/json; charset=utf-8';
|
||||
|
||||
bool followRedirects;
|
||||
int maxRedirects;
|
||||
int maxAuthRetries;
|
||||
|
||||
bool sendUserAgent;
|
||||
|
||||
Decoder? defaultDecoder;
|
||||
|
||||
Duration timeout;
|
||||
|
||||
bool errorSafety = true;
|
||||
|
||||
final HttpRequestBase _httpClient;
|
||||
|
||||
final GetModifier _modifier;
|
||||
|
||||
String Function(Uri url)? findProxy;
|
||||
|
||||
GetHttpClient({
|
||||
this.userAgent = 'getx-client',
|
||||
this.timeout = const Duration(seconds: 8),
|
||||
this.followRedirects = true,
|
||||
this.maxRedirects = 5,
|
||||
this.sendUserAgent = false,
|
||||
this.maxAuthRetries = 1,
|
||||
bool allowAutoSignedCert = false,
|
||||
this.baseUrl,
|
||||
List<TrustedCertificate>? trustedCertificates,
|
||||
bool withCredentials = false,
|
||||
String Function(Uri url)? findProxy,
|
||||
}) : _httpClient = platform.HttpRequestImpl(
|
||||
allowAutoSignedCert: allowAutoSignedCert,
|
||||
trustedCertificates: trustedCertificates,
|
||||
withCredentials: withCredentials,
|
||||
findProxy: findProxy,
|
||||
),
|
||||
_modifier = GetModifier();
|
||||
|
||||
void addAuthenticator<T>(RequestModifier<T> auth) {
|
||||
_modifier.authenticator = auth as RequestModifier;
|
||||
}
|
||||
|
||||
void addRequestModifier<T>(RequestModifier<T> interceptor) {
|
||||
_modifier.addRequestModifier<T>(interceptor);
|
||||
}
|
||||
|
||||
void removeRequestModifier<T>(RequestModifier<T> interceptor) {
|
||||
_modifier.removeRequestModifier(interceptor);
|
||||
}
|
||||
|
||||
void addResponseModifier<T>(ResponseModifier<T> interceptor) {
|
||||
_modifier.addResponseModifier(interceptor);
|
||||
}
|
||||
|
||||
void removeResponseModifier<T>(ResponseModifier<T> interceptor) {
|
||||
_modifier.removeResponseModifier<T>(interceptor);
|
||||
}
|
||||
|
||||
Uri _createUri(String? url, Map<String, dynamic>? query) {
|
||||
if (baseUrl != null) {
|
||||
url = baseUrl! + url!;
|
||||
}
|
||||
final uri = Uri.parse(url!);
|
||||
if (query != null) {
|
||||
return uri.replace(queryParameters: query);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
Future<Request<T>> _requestWithBody<T>(
|
||||
String? url,
|
||||
String? contentType,
|
||||
dynamic body,
|
||||
String method,
|
||||
Map<String, dynamic>? query,
|
||||
Decoder<T>? decoder,
|
||||
Progress? uploadProgress,
|
||||
) async {
|
||||
List<int>? bodyBytes;
|
||||
Stream<List<int>>? bodyStream;
|
||||
final headers = <String, String>{};
|
||||
|
||||
if (sendUserAgent) {
|
||||
headers['user-agent'] = userAgent;
|
||||
}
|
||||
|
||||
if (body is FormData) {
|
||||
bodyBytes = await body.toBytes();
|
||||
headers['content-length'] = bodyBytes.length.toString();
|
||||
headers['content-type'] =
|
||||
'multipart/form-data; boundary=${body.boundary}';
|
||||
} else if (contentType != null &&
|
||||
contentType.toLowerCase() == 'application/x-www-form-urlencoded' &&
|
||||
body is Map) {
|
||||
var parts = [];
|
||||
(body as Map<String, dynamic>).forEach((key, value) {
|
||||
parts.add('${Uri.encodeQueryComponent(key)}='
|
||||
'${Uri.encodeQueryComponent(value.toString())}');
|
||||
});
|
||||
var formData = parts.join('&');
|
||||
bodyBytes = utf8.encode(formData);
|
||||
headers['content-length'] = bodyBytes.length.toString();
|
||||
headers['content-type'] = contentType;
|
||||
} else if (body is Map || body is List) {
|
||||
var jsonString = json.encode(body);
|
||||
|
||||
bodyBytes = utf8.encode(jsonString);
|
||||
headers['content-length'] = bodyBytes.length.toString();
|
||||
headers['content-type'] = contentType ?? defaultContentType;
|
||||
} else if (body is String) {
|
||||
bodyBytes = utf8.encode(body);
|
||||
headers['content-length'] = bodyBytes.length.toString();
|
||||
headers['content-type'] = contentType ?? defaultContentType;
|
||||
} else if (body == null) {
|
||||
headers['content-type'] = contentType ?? defaultContentType;
|
||||
headers['content-length'] = '0';
|
||||
} else {
|
||||
if (!errorSafety) {
|
||||
throw UnexpectedFormat('body cannot be ${body.runtimeType}');
|
||||
}
|
||||
}
|
||||
|
||||
if (bodyBytes != null) {
|
||||
bodyStream = _trackProgress(bodyBytes, uploadProgress);
|
||||
}
|
||||
|
||||
final uri = _createUri(url, query);
|
||||
return Request<T>(
|
||||
method: method,
|
||||
url: uri,
|
||||
headers: headers,
|
||||
bodyBytes: bodyStream,
|
||||
contentLength: bodyBytes?.length ?? 0,
|
||||
followRedirects: followRedirects,
|
||||
maxRedirects: maxRedirects,
|
||||
decoder: decoder,
|
||||
);
|
||||
}
|
||||
|
||||
Stream<List<int>> _trackProgress(
|
||||
List<int> bodyBytes,
|
||||
Progress? uploadProgress,
|
||||
) {
|
||||
var total = 0;
|
||||
var length = bodyBytes.length;
|
||||
|
||||
var byteStream =
|
||||
Stream.fromIterable(bodyBytes.map((i) => [i])).transform<List<int>>(
|
||||
StreamTransformer.fromHandlers(handleData: (data, sink) {
|
||||
total += data.length;
|
||||
if (uploadProgress != null) {
|
||||
var percent = total / length * 100;
|
||||
uploadProgress(percent);
|
||||
}
|
||||
sink.add(data);
|
||||
}),
|
||||
);
|
||||
return byteStream;
|
||||
}
|
||||
|
||||
void _setSimpleHeaders(
|
||||
Map<String, String> headers,
|
||||
String? contentType,
|
||||
) {
|
||||
headers['content-type'] = contentType ?? defaultContentType;
|
||||
if (sendUserAgent) {
|
||||
headers['user-agent'] = userAgent;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<T>> _performRequest<T>(
|
||||
HandlerExecute<T> handler, {
|
||||
bool authenticate = false,
|
||||
int requestNumber = 1,
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
var request = await handler();
|
||||
|
||||
headers?.forEach((key, value) {
|
||||
request.headers[key] = value;
|
||||
});
|
||||
|
||||
if (authenticate) await _modifier.authenticator!(request);
|
||||
final newRequest = await _modifier.modifyRequest<T>(request);
|
||||
|
||||
_httpClient.timeout = timeout;
|
||||
try {
|
||||
var response = await _httpClient.send<T>(newRequest);
|
||||
|
||||
final newResponse =
|
||||
await _modifier.modifyResponse<T>(newRequest, response);
|
||||
|
||||
if (HttpStatus.unauthorized == newResponse.statusCode &&
|
||||
_modifier.authenticator != null &&
|
||||
requestNumber <= maxAuthRetries) {
|
||||
return _performRequest<T>(
|
||||
handler,
|
||||
authenticate: true,
|
||||
requestNumber: requestNumber + 1,
|
||||
headers: newRequest.headers,
|
||||
);
|
||||
} else if (HttpStatus.unauthorized == newResponse.statusCode) {
|
||||
if (!errorSafety) {
|
||||
throw UnauthorizedException();
|
||||
} else {
|
||||
return Response<T>(
|
||||
request: newRequest,
|
||||
headers: newResponse.headers,
|
||||
statusCode: newResponse.statusCode,
|
||||
body: newResponse.body,
|
||||
bodyBytes: newResponse.bodyBytes,
|
||||
bodyString: newResponse.bodyString,
|
||||
statusText: newResponse.statusText,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return newResponse;
|
||||
} on Exception catch (err) {
|
||||
if (!errorSafety) {
|
||||
throw GetHttpException(err.toString());
|
||||
} else {
|
||||
return Response<T>(
|
||||
request: newRequest,
|
||||
headers: null,
|
||||
statusCode: null,
|
||||
body: null,
|
||||
statusText: "$err",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Request<T>> _get<T>(
|
||||
String url,
|
||||
String? contentType,
|
||||
Map<String, dynamic>? query,
|
||||
Decoder<T>? decoder,
|
||||
) {
|
||||
final headers = <String, String>{};
|
||||
_setSimpleHeaders(headers, contentType);
|
||||
final uri = _createUri(url, query);
|
||||
|
||||
return Future.value(Request<T>(
|
||||
method: 'get',
|
||||
url: uri,
|
||||
headers: headers,
|
||||
decoder: decoder ?? (defaultDecoder as Decoder<T>?),
|
||||
contentLength: 0,
|
||||
followRedirects: followRedirects,
|
||||
maxRedirects: maxRedirects,
|
||||
));
|
||||
}
|
||||
|
||||
Future<Request<T>> _request<T>(
|
||||
String? url,
|
||||
String method, {
|
||||
String? contentType,
|
||||
required dynamic body,
|
||||
required Map<String, dynamic>? query,
|
||||
Decoder<T>? decoder,
|
||||
required Progress? uploadProgress,
|
||||
}) {
|
||||
return _requestWithBody<T>(
|
||||
url,
|
||||
contentType,
|
||||
body,
|
||||
method,
|
||||
query,
|
||||
decoder ?? (defaultDecoder as Decoder<T>?),
|
||||
uploadProgress,
|
||||
);
|
||||
}
|
||||
|
||||
Request<T> _delete<T>(
|
||||
String url,
|
||||
String? contentType,
|
||||
Map<String, dynamic>? query,
|
||||
Decoder<T>? decoder,
|
||||
) {
|
||||
final headers = <String, String>{};
|
||||
_setSimpleHeaders(headers, contentType);
|
||||
final uri = _createUri(url, query);
|
||||
|
||||
return Request<T>(
|
||||
method: 'delete',
|
||||
url: uri,
|
||||
headers: headers,
|
||||
decoder: decoder ?? (defaultDecoder as Decoder<T>?),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Response<T>> patch<T>(
|
||||
String url, {
|
||||
dynamic body,
|
||||
String? contentType,
|
||||
Map<String, String>? headers,
|
||||
Map<String, dynamic>? query,
|
||||
Decoder<T>? decoder,
|
||||
Progress? uploadProgress,
|
||||
// List<MultipartFile> files,
|
||||
}) async {
|
||||
try {
|
||||
var response = await _performRequest<T>(
|
||||
() => _request<T>(
|
||||
url,
|
||||
'patch',
|
||||
contentType: contentType,
|
||||
body: body,
|
||||
query: query,
|
||||
decoder: decoder,
|
||||
uploadProgress: uploadProgress,
|
||||
),
|
||||
headers: headers,
|
||||
);
|
||||
return response;
|
||||
} on Exception catch (e) {
|
||||
if (!errorSafety) {
|
||||
throw GetHttpException(e.toString());
|
||||
}
|
||||
return Future.value(Response<T>(
|
||||
statusText: 'Can not connect to server. Reason: $e',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<T>> post<T>(
|
||||
String? url, {
|
||||
dynamic body,
|
||||
String? contentType,
|
||||
Map<String, String>? headers,
|
||||
Map<String, dynamic>? query,
|
||||
Decoder<T>? decoder,
|
||||
Progress? uploadProgress,
|
||||
// List<MultipartFile> files,
|
||||
}) async {
|
||||
try {
|
||||
var response = await _performRequest<T>(
|
||||
() => _request<T>(
|
||||
url,
|
||||
'post',
|
||||
contentType: contentType,
|
||||
body: body,
|
||||
query: query,
|
||||
decoder: decoder,
|
||||
uploadProgress: uploadProgress,
|
||||
),
|
||||
headers: headers,
|
||||
);
|
||||
return response;
|
||||
} on Exception catch (e) {
|
||||
if (!errorSafety) {
|
||||
throw GetHttpException(e.toString());
|
||||
}
|
||||
return Future.value(Response<T>(
|
||||
statusText: 'Can not connect to server. Reason: $e',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<T>> request<T>(
|
||||
String url,
|
||||
String method, {
|
||||
dynamic body,
|
||||
String? contentType,
|
||||
Map<String, String>? headers,
|
||||
Map<String, dynamic>? query,
|
||||
Decoder<T>? decoder,
|
||||
Progress? uploadProgress,
|
||||
}) async {
|
||||
try {
|
||||
var response = await _performRequest<T>(
|
||||
() => _request<T>(
|
||||
url,
|
||||
method,
|
||||
contentType: contentType,
|
||||
query: query,
|
||||
body: body,
|
||||
decoder: decoder,
|
||||
uploadProgress: uploadProgress,
|
||||
),
|
||||
headers: headers,
|
||||
);
|
||||
return response;
|
||||
} on Exception catch (e) {
|
||||
if (!errorSafety) {
|
||||
throw GetHttpException(e.toString());
|
||||
}
|
||||
return Future.value(Response<T>(
|
||||
statusText: 'Can not connect to server. Reason: $e',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<T>> put<T>(
|
||||
String url, {
|
||||
dynamic body,
|
||||
String? contentType,
|
||||
Map<String, String>? headers,
|
||||
Map<String, dynamic>? query,
|
||||
Decoder<T>? decoder,
|
||||
Progress? uploadProgress,
|
||||
}) async {
|
||||
try {
|
||||
var response = await _performRequest<T>(
|
||||
() => _request<T>(
|
||||
url,
|
||||
'put',
|
||||
contentType: contentType,
|
||||
query: query,
|
||||
body: body,
|
||||
decoder: decoder,
|
||||
uploadProgress: uploadProgress,
|
||||
),
|
||||
headers: headers,
|
||||
);
|
||||
return response;
|
||||
} on Exception catch (e) {
|
||||
if (!errorSafety) {
|
||||
throw GetHttpException(e.toString());
|
||||
}
|
||||
return Future.value(Response<T>(
|
||||
statusText: 'Can not connect to server. Reason: $e',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<T>> get<T>(
|
||||
String url, {
|
||||
Map<String, String>? headers,
|
||||
String? contentType,
|
||||
Map<String, dynamic>? query,
|
||||
Decoder<T>? decoder,
|
||||
}) async {
|
||||
try {
|
||||
var response = await _performRequest<T>(
|
||||
() => _get<T>(url, contentType, query, decoder),
|
||||
headers: headers,
|
||||
);
|
||||
return response;
|
||||
} on Exception catch (e) {
|
||||
if (!errorSafety) {
|
||||
throw GetHttpException(e.toString());
|
||||
}
|
||||
return Future.value(Response<T>(
|
||||
statusText: 'Can not connect to server. Reason: $e',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Future<Response<T>> download<T>(
|
||||
// String url,
|
||||
// String path, {
|
||||
// Map<String, String> headers,
|
||||
// String contentType = 'application/octet-stream',
|
||||
// Map<String, dynamic> query,
|
||||
// }) async {
|
||||
// try {
|
||||
// var response = await _performRequest<T>(
|
||||
// () => _get<T>(url, contentType, query, null),
|
||||
// headers: headers,
|
||||
// );
|
||||
// response.bodyBytes.listen((value) {});
|
||||
// return response;
|
||||
// } on Exception catch (e) {
|
||||
// if (!errorSafety) {
|
||||
// throw GetHttpException(e.toString());
|
||||
// }
|
||||
// return Future.value(Response<T>(
|
||||
// statusText: 'Can not connect to server. Reason: $e',
|
||||
// ));
|
||||
// }
|
||||
|
||||
// int byteCount = 0;
|
||||
// int totalBytes = httpResponse.contentLength;
|
||||
|
||||
// Directory appDocDir = await getApplicationDocumentsDirectory();
|
||||
// String appDocPath = appDocDir.path;
|
||||
|
||||
// File file = File(path);
|
||||
|
||||
// var raf = file.openSync(mode: FileMode.write);
|
||||
|
||||
// Completer completer = Completer<String>();
|
||||
|
||||
// httpResponse.listen(
|
||||
// (data) {
|
||||
// byteCount += data.length;
|
||||
|
||||
// raf.writeFromSync(data);
|
||||
|
||||
// if (onDownloadProgress != null) {
|
||||
// onDownloadProgress(byteCount, totalBytes);
|
||||
// }
|
||||
// },
|
||||
// onDone: () {
|
||||
// raf.closeSync();
|
||||
|
||||
// completer.complete(file.path);
|
||||
// },
|
||||
// onError: (e) {
|
||||
// raf.closeSync();
|
||||
// file.deleteSync();
|
||||
// completer.completeError(e);
|
||||
// },
|
||||
// cancelOnError: true,
|
||||
// );
|
||||
|
||||
// return completer.future;
|
||||
// }
|
||||
|
||||
Future<Response<T>> delete<T>(
|
||||
String url, {
|
||||
Map<String, String>? headers,
|
||||
String? contentType,
|
||||
Map<String, dynamic>? query,
|
||||
Decoder<T>? decoder,
|
||||
}) async {
|
||||
try {
|
||||
var response = await _performRequest<T>(
|
||||
() async => _delete<T>(url, contentType, query, decoder),
|
||||
headers: headers,
|
||||
);
|
||||
return response;
|
||||
} on Exception catch (e) {
|
||||
if (!errorSafety) {
|
||||
throw GetHttpException(e.toString());
|
||||
}
|
||||
return Future.value(Response<T>(
|
||||
statusText: 'Can not connect to server. Reason: $e',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void close() {
|
||||
_httpClient.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'dart:async';
|
||||
|
||||
import '../request/request.dart';
|
||||
import '../response/response.dart';
|
||||
|
||||
typedef RequestModifier<T> = FutureOr<Request<T>> Function(Request<T?> request);
|
||||
|
||||
typedef ResponseModifier<T> = FutureOr Function(
|
||||
Request<T?> request, Response<T?> response);
|
||||
|
||||
typedef HandlerExecute<T> = Future<Request<T>> Function();
|
||||
|
||||
class GetModifier<S> {
|
||||
final _requestModifiers = <RequestModifier>[];
|
||||
final _responseModifiers = <ResponseModifier>[];
|
||||
RequestModifier? authenticator;
|
||||
|
||||
void addRequestModifier<T>(RequestModifier<T> interceptor) {
|
||||
_requestModifiers.add(interceptor as RequestModifier);
|
||||
}
|
||||
|
||||
void removeRequestModifier<T>(RequestModifier<T> interceptor) {
|
||||
_requestModifiers.remove(interceptor);
|
||||
}
|
||||
|
||||
void addResponseModifier<T>(ResponseModifier<T> interceptor) {
|
||||
_responseModifiers.add(interceptor as ResponseModifier);
|
||||
}
|
||||
|
||||
void removeResponseModifier<T>(ResponseModifier<T> interceptor) {
|
||||
_requestModifiers.remove(interceptor);
|
||||
}
|
||||
|
||||
Future<Request<T>> modifyRequest<T>(Request<T> request) async {
|
||||
var newRequest = request;
|
||||
if (_requestModifiers.isNotEmpty) {
|
||||
for (var interceptor in _requestModifiers) {
|
||||
newRequest = await interceptor(newRequest) as Request<T>;
|
||||
}
|
||||
}
|
||||
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
Future<Response<T>> modifyResponse<T>(
|
||||
Request<T> request, Response<T> response) async {
|
||||
var newResponse = response;
|
||||
if (_responseModifiers.isNotEmpty) {
|
||||
for (var interceptor in _responseModifiers) {
|
||||
newResponse = await interceptor(request, response) as Response<T>;
|
||||
}
|
||||
}
|
||||
|
||||
return newResponse;
|
||||
}
|
||||
}
|
||||
116
packages/get/lib/get_connect/http/src/multipart/form_data.dart
Normal file
116
packages/get/lib/get_connect/http/src/multipart/form_data.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import '../request/request.dart';
|
||||
import '../utils/utils.dart';
|
||||
import 'multipart_file.dart';
|
||||
|
||||
class FormData {
|
||||
FormData(Map<String, dynamic> map) : boundary = _getBoundary() {
|
||||
map.forEach((key, value) {
|
||||
if (value == null) return;
|
||||
if (value is MultipartFile) {
|
||||
files.add(MapEntry(key, value));
|
||||
} else if (value is List<MultipartFile>) {
|
||||
files.addAll(value.map((e) => MapEntry(key, e)));
|
||||
} else if (value is List) {
|
||||
fields.addAll(value.map((e) => MapEntry(key, e.toString())));
|
||||
} else {
|
||||
fields.add(MapEntry(key, value.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static const int _maxBoundaryLength = 70;
|
||||
|
||||
static String _getBoundary() {
|
||||
final random = Random();
|
||||
var list = List<int>.generate(_maxBoundaryLength - GET_BOUNDARY.length,
|
||||
(_) => boundaryCharacters[random.nextInt(boundaryCharacters.length)],
|
||||
growable: false);
|
||||
return '$GET_BOUNDARY${String.fromCharCodes(list)}';
|
||||
}
|
||||
|
||||
final String boundary;
|
||||
|
||||
/// The form fields to send for this request.
|
||||
final fields = <MapEntry<String, String>>[];
|
||||
|
||||
/// The files to send for this request
|
||||
final files = <MapEntry<String, MultipartFile>>[];
|
||||
|
||||
/// Returns the header string for a field. The return value is guaranteed to
|
||||
/// contain only ASCII characters.
|
||||
String _fieldHeader(String name, String value) {
|
||||
var header =
|
||||
'content-disposition: form-data; name="${browserEncode(name)}"';
|
||||
if (!isPlainAscii(value)) {
|
||||
header = '$header\r\n'
|
||||
'content-type: text/plain; charset=utf-8\r\n'
|
||||
'content-transfer-encoding: binary';
|
||||
}
|
||||
return '$header\r\n\r\n';
|
||||
}
|
||||
|
||||
/// Returns the header string for a file. The return value is guaranteed to
|
||||
/// contain only ASCII characters.
|
||||
String _fileHeader(MapEntry<String, MultipartFile> file) {
|
||||
var header =
|
||||
'content-disposition: form-data; name="${browserEncode(file.key)}"';
|
||||
header = '$header; filename="${browserEncode(file.value.filename)}"';
|
||||
header = '$header\r\n'
|
||||
'content-type: ${file.value.contentType}';
|
||||
return '$header\r\n\r\n';
|
||||
}
|
||||
|
||||
/// The length of the request body from this [FormData]
|
||||
int get length {
|
||||
var length = 0;
|
||||
|
||||
for (final item in fields) {
|
||||
length += '--'.length +
|
||||
_maxBoundaryLength +
|
||||
'\r\n'.length +
|
||||
utf8.encode(_fieldHeader(item.key, item.value)).length +
|
||||
utf8.encode(item.value).length +
|
||||
'\r\n'.length;
|
||||
}
|
||||
|
||||
for (var file in files) {
|
||||
length += '--'.length +
|
||||
_maxBoundaryLength +
|
||||
'\r\n'.length +
|
||||
utf8.encode(_fileHeader(file)).length +
|
||||
file.value.length! +
|
||||
'\r\n'.length;
|
||||
}
|
||||
|
||||
return length + '--'.length + _maxBoundaryLength + '--\r\n'.length;
|
||||
}
|
||||
|
||||
Future<List<int>> toBytes() {
|
||||
return BodyBytesStream(_encode()).toBytes();
|
||||
}
|
||||
|
||||
Stream<List<int>> _encode() async* {
|
||||
const line = [13, 10];
|
||||
final separator = utf8.encode('--$boundary\r\n');
|
||||
final close = utf8.encode('--$boundary--\r\n');
|
||||
|
||||
for (var field in fields) {
|
||||
yield separator;
|
||||
yield utf8.encode(_fieldHeader(field.key, field.value));
|
||||
yield utf8.encode(field.value);
|
||||
yield line;
|
||||
}
|
||||
|
||||
for (final file in files) {
|
||||
yield separator;
|
||||
yield utf8.encode(_fileHeader(file));
|
||||
yield* file.value.stream!;
|
||||
yield line;
|
||||
}
|
||||
yield close;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import '../_http/_stub/_file_decoder_stub.dart'
|
||||
if (dart.library.html) '../_http/_html/_file_decoder_html.dart'
|
||||
if (dart.library.io) '../_http/_io/_file_decoder_io.dart' as decoder;
|
||||
import '../request/request.dart';
|
||||
|
||||
class MultipartFile {
|
||||
MultipartFile(
|
||||
dynamic data, {
|
||||
required this.filename,
|
||||
this.contentType = 'application/octet-stream',
|
||||
}) : _bytes = decoder.fileToBytes(data) {
|
||||
_length = _bytes.length;
|
||||
_stream = BodyBytesStream.fromBytes(_bytes);
|
||||
}
|
||||
|
||||
final List<int> _bytes;
|
||||
|
||||
final String contentType;
|
||||
|
||||
/// This stream will emit the file content of File.
|
||||
Stream<List<int>>? _stream;
|
||||
|
||||
int? _length;
|
||||
|
||||
Stream<List<int>>? get stream => _stream;
|
||||
|
||||
int? get length => _length;
|
||||
|
||||
final String filename;
|
||||
}
|
||||
131
packages/get/lib/get_connect/http/src/request/request.dart
Normal file
131
packages/get/lib/get_connect/http/src/request/request.dart
Normal file
@@ -0,0 +1,131 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import '../http.dart';
|
||||
import '../multipart/form_data.dart';
|
||||
|
||||
class Request<T> {
|
||||
/// Headers attach to this [Request]
|
||||
final Map<String, String> headers;
|
||||
|
||||
/// The [Uri] from request
|
||||
final Uri url;
|
||||
|
||||
final Decoder<T>? decoder;
|
||||
|
||||
/// The Http Method from this [Request]
|
||||
/// ex: `GET`,`POST`,`PUT`,`DELETE`
|
||||
final String method;
|
||||
|
||||
final int? contentLength;
|
||||
|
||||
/// The BodyBytesStream of body from this [Request]
|
||||
final Stream<List<int>> bodyBytes;
|
||||
|
||||
/// When true, the client will follow redirects to resolves this [Request]
|
||||
final bool followRedirects;
|
||||
|
||||
/// The maximum number of redirects if [followRedirects] is true.
|
||||
final int maxRedirects;
|
||||
|
||||
final bool persistentConnection;
|
||||
|
||||
final FormData? files;
|
||||
|
||||
const Request._({
|
||||
required this.method,
|
||||
required this.bodyBytes,
|
||||
required this.url,
|
||||
required this.headers,
|
||||
required this.contentLength,
|
||||
required this.followRedirects,
|
||||
required this.maxRedirects,
|
||||
required this.files,
|
||||
required this.persistentConnection,
|
||||
required this.decoder,
|
||||
});
|
||||
|
||||
factory Request({
|
||||
required Uri url,
|
||||
required String method,
|
||||
required Map<String, String> headers,
|
||||
Stream<List<int>>? bodyBytes,
|
||||
bool followRedirects = true,
|
||||
int maxRedirects = 4,
|
||||
int? contentLength,
|
||||
FormData? files,
|
||||
bool persistentConnection = true,
|
||||
Decoder<T>? decoder,
|
||||
}) {
|
||||
if (followRedirects) {
|
||||
assert(maxRedirects > 0);
|
||||
}
|
||||
return Request._(
|
||||
url: url,
|
||||
method: method,
|
||||
bodyBytes: bodyBytes ??= BodyBytesStream.fromBytes(const []),
|
||||
headers: Map.from(headers),
|
||||
followRedirects: followRedirects,
|
||||
maxRedirects: maxRedirects,
|
||||
contentLength: contentLength,
|
||||
files: files,
|
||||
persistentConnection: persistentConnection,
|
||||
decoder: decoder,
|
||||
);
|
||||
}
|
||||
|
||||
Request<T> copyWith({
|
||||
Uri? url,
|
||||
String? method,
|
||||
Map<String, String>? headers,
|
||||
Stream<List<int>>? bodyBytes,
|
||||
bool? followRedirects,
|
||||
int? maxRedirects,
|
||||
int? contentLength,
|
||||
FormData? files,
|
||||
bool? persistentConnection,
|
||||
Decoder<T>? decoder,
|
||||
bool appendHeader = true,
|
||||
}) {
|
||||
// If appendHeader is set to true, we will merge origin headers with that
|
||||
if (appendHeader && headers != null) {
|
||||
headers.addAll(this.headers);
|
||||
}
|
||||
|
||||
return Request<T>._(
|
||||
url: url ?? this.url,
|
||||
method: method ?? this.method,
|
||||
bodyBytes: bodyBytes ?? this.bodyBytes,
|
||||
headers: headers == null ? this.headers : Map.from(headers),
|
||||
followRedirects: followRedirects ?? this.followRedirects,
|
||||
maxRedirects: maxRedirects ?? this.maxRedirects,
|
||||
contentLength: contentLength ?? this.contentLength,
|
||||
files: files ?? this.files,
|
||||
persistentConnection: persistentConnection ?? this.persistentConnection,
|
||||
decoder: decoder ?? this.decoder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension BodyBytesStream on Stream<List<int>> {
|
||||
static Stream<List<int>> fromBytes(List<int> bytes) =>
|
||||
Stream.fromIterable([bytes]);
|
||||
|
||||
Future<Uint8List> toBytes() {
|
||||
var completer = Completer<Uint8List>();
|
||||
var sink = ByteConversionSink.withCallback(
|
||||
(bytes) => completer.complete(
|
||||
Uint8List.fromList(bytes),
|
||||
),
|
||||
);
|
||||
listen((val) => sink.add(val),
|
||||
onError: completer.completeError,
|
||||
onDone: sink.close,
|
||||
cancelOnError: true);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<String> bytesToString([Encoding encoding = utf8]) =>
|
||||
encoding.decodeStream(this);
|
||||
}
|
||||
270
packages/get/lib/get_connect/http/src/response/response.dart
Normal file
270
packages/get/lib/get_connect/http/src/response/response.dart
Normal file
@@ -0,0 +1,270 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import '../exceptions/exceptions.dart';
|
||||
import '../request/request.dart';
|
||||
import '../status/http_status.dart';
|
||||
|
||||
class GraphQLResponse<T> extends Response<T> {
|
||||
final List<GraphQLError>? graphQLErrors;
|
||||
GraphQLResponse({T? body, this.graphQLErrors}) : super(body: body);
|
||||
GraphQLResponse.fromResponse(Response res)
|
||||
: graphQLErrors = null,
|
||||
super(
|
||||
request: res.request,
|
||||
statusCode: res.statusCode,
|
||||
bodyBytes: res.bodyBytes,
|
||||
bodyString: res.bodyString,
|
||||
statusText: res.statusText,
|
||||
headers: res.headers,
|
||||
body: res.body['data'] as T?);
|
||||
}
|
||||
|
||||
class Response<T> {
|
||||
const Response({
|
||||
this.request,
|
||||
this.statusCode,
|
||||
this.bodyBytes,
|
||||
this.bodyString,
|
||||
this.statusText = '',
|
||||
this.headers = const {},
|
||||
this.body,
|
||||
});
|
||||
|
||||
/// The Http [Request] linked with this [Response].
|
||||
final Request? request;
|
||||
|
||||
/// The response headers.
|
||||
final Map<String, String>? headers;
|
||||
|
||||
/// The status code returned by the server.
|
||||
final int? statusCode;
|
||||
|
||||
/// Human-readable context for [statusCode].
|
||||
final String? statusText;
|
||||
|
||||
/// [HttpStatus] from [Response]. `status.connectionError` is true
|
||||
/// when statusCode is null. `status.isUnauthorized` is true when
|
||||
/// statusCode is equal `401`. `status.isNotFound` is true when
|
||||
/// statusCode is equal `404`. `status.isServerError` is true when
|
||||
/// statusCode is between `500` and `599`.
|
||||
HttpStatus get status => HttpStatus(statusCode);
|
||||
|
||||
/// `hasError` is true when statusCode is not between 200 and 299.
|
||||
bool get hasError => status.hasError;
|
||||
|
||||
/// `isOk` is true when statusCode is between 200 and 299.
|
||||
bool get isOk => !hasError;
|
||||
|
||||
/// `unauthorized` is true when statusCode is equal `401`.
|
||||
bool get unauthorized => status.isUnauthorized;
|
||||
|
||||
/// The response body as a Stream of Bytes.
|
||||
final Stream<List<int>>? bodyBytes;
|
||||
|
||||
/// The response body as a Stream of Bytes.
|
||||
final String? bodyString;
|
||||
|
||||
/// The decoded body of this [Response]. You can access the
|
||||
/// body parameters as Map
|
||||
/// Ex: `body['title'];`
|
||||
final T? body;
|
||||
}
|
||||
|
||||
Future<String> bodyBytesToString(
|
||||
Stream<List<int>> bodyBytes, Map<String, String> headers) {
|
||||
return bodyBytes.bytesToString(_encodingForHeaders(headers));
|
||||
}
|
||||
|
||||
/// Returns the encoding to use for a response with the given headers.
|
||||
///
|
||||
/// Defaults to [utf8] if the headers don't specify a charset or if that
|
||||
/// charset is unknown.
|
||||
Encoding _encodingForHeaders(Map<String, String> headers) =>
|
||||
_encodingForCharset(_contentTypeForHeaders(headers).parameters!['charset']);
|
||||
|
||||
/// Returns the [Encoding] that corresponds to [charset].
|
||||
///
|
||||
/// Returns [fallback] if [charset] is null or if no [Encoding] was found that
|
||||
/// corresponds to [charset].
|
||||
Encoding _encodingForCharset(String? charset, [Encoding fallback = utf8]) {
|
||||
if (charset == null) return fallback;
|
||||
return Encoding.getByName(charset) ?? fallback;
|
||||
}
|
||||
|
||||
/// Returns the MediaType object for the given headers's content-type.
|
||||
///
|
||||
/// Defaults to `application/octet-stream`.
|
||||
HeaderValue _contentTypeForHeaders(Map<String, String> headers) {
|
||||
var contentType = headers['content-type'];
|
||||
if (contentType != null) return HeaderValue.parse(contentType);
|
||||
return HeaderValue('application/octet-stream');
|
||||
}
|
||||
|
||||
class HeaderValue {
|
||||
String _value;
|
||||
Map<String, String?>? _parameters;
|
||||
Map<String, String?>? _unmodifiableParameters;
|
||||
|
||||
HeaderValue([this._value = '', Map<String, String>? parameters]) {
|
||||
if (parameters != null) {
|
||||
_parameters = HashMap<String, String>.from(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
static HeaderValue parse(String value,
|
||||
{String parameterSeparator = ';',
|
||||
String? valueSeparator,
|
||||
bool preserveBackslash = false}) {
|
||||
var result = HeaderValue();
|
||||
result._parse(value, parameterSeparator, valueSeparator, preserveBackslash);
|
||||
return result;
|
||||
}
|
||||
|
||||
String get value => _value;
|
||||
|
||||
void _ensureParameters() {
|
||||
_parameters ??= HashMap<String, String>();
|
||||
}
|
||||
|
||||
Map<String, String?>? get parameters {
|
||||
_ensureParameters();
|
||||
_unmodifiableParameters ??= UnmodifiableMapView(_parameters!);
|
||||
return _unmodifiableParameters;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var stringBuffer = StringBuffer();
|
||||
stringBuffer.write(_value);
|
||||
if (parameters != null && parameters!.isNotEmpty) {
|
||||
_parameters!.forEach((name, value) {
|
||||
stringBuffer
|
||||
..write('; ')
|
||||
..write(name)
|
||||
..write('=')
|
||||
..write(value);
|
||||
});
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
}
|
||||
|
||||
void _parse(String value, String parameterSeparator, String? valueSeparator,
|
||||
bool preserveBackslash) {
|
||||
var index = 0;
|
||||
|
||||
bool done() => index == value.length;
|
||||
|
||||
void bump() {
|
||||
while (!done()) {
|
||||
if (value[index] != ' ' && value[index] != '\t') return;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
String parseValue() {
|
||||
var start = index;
|
||||
while (!done()) {
|
||||
if (value[index] == ' ' ||
|
||||
value[index] == '\t' ||
|
||||
value[index] == valueSeparator ||
|
||||
value[index] == parameterSeparator) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return value.substring(start, index);
|
||||
}
|
||||
|
||||
void expect(String expected) {
|
||||
if (done() || value[index] != expected) {
|
||||
throw StateError('Failed to parse header value');
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
void maybeExpect(String expected) {
|
||||
if (value[index] == expected) index++;
|
||||
}
|
||||
|
||||
void parseParameters() {
|
||||
var parameters = HashMap<String, String?>();
|
||||
_parameters = UnmodifiableMapView(parameters);
|
||||
|
||||
String parseParameterName() {
|
||||
var start = index;
|
||||
while (!done()) {
|
||||
if (value[index] == ' ' ||
|
||||
value[index] == '\t' ||
|
||||
value[index] == '=' ||
|
||||
value[index] == parameterSeparator ||
|
||||
value[index] == valueSeparator) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return value.substring(start, index).toLowerCase();
|
||||
}
|
||||
|
||||
String? parseParameterValue() {
|
||||
if (!done() && value[index] == '"') {
|
||||
var stringBuffer = StringBuffer();
|
||||
index++;
|
||||
while (!done()) {
|
||||
if (value[index] == '\\') {
|
||||
if (index + 1 == value.length) {
|
||||
throw StateError('Failed to parse header value');
|
||||
}
|
||||
if (preserveBackslash && value[index + 1] != '"') {
|
||||
stringBuffer.write(value[index]);
|
||||
}
|
||||
index++;
|
||||
} else if (value[index] == '"') {
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
stringBuffer.write(value[index]);
|
||||
index++;
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
} else {
|
||||
var val = parseValue();
|
||||
return val == '' ? null : val;
|
||||
}
|
||||
}
|
||||
|
||||
while (!done()) {
|
||||
bump();
|
||||
if (done()) return;
|
||||
var name = parseParameterName();
|
||||
bump();
|
||||
if (done()) {
|
||||
parameters[name] = null;
|
||||
return;
|
||||
}
|
||||
maybeExpect('=');
|
||||
bump();
|
||||
if (done()) {
|
||||
parameters[name] = null;
|
||||
return;
|
||||
}
|
||||
var valueParameter = parseParameterValue();
|
||||
if (name == 'charset' && valueParameter != null) {
|
||||
valueParameter = valueParameter.toLowerCase();
|
||||
}
|
||||
parameters[name] = valueParameter;
|
||||
bump();
|
||||
if (done()) return;
|
||||
if (value[index] == valueSeparator) return;
|
||||
expect(parameterSeparator);
|
||||
}
|
||||
}
|
||||
|
||||
bump();
|
||||
_value = parseValue();
|
||||
bump();
|
||||
if (done()) return;
|
||||
maybeExpect(parameterSeparator);
|
||||
parseParameters();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
class HttpStatus {
|
||||
HttpStatus(this.code);
|
||||
|
||||
final int? code;
|
||||
|
||||
static const int continue_ = 100;
|
||||
static const int switchingProtocols = 101;
|
||||
static const int processing = 102;
|
||||
static const int earlyHints = 103;
|
||||
static const int ok = 200;
|
||||
static const int created = 201;
|
||||
static const int accepted = 202;
|
||||
static const int nonAuthoritativeInformation = 203;
|
||||
static const int noContent = 204;
|
||||
static const int resetContent = 205;
|
||||
static const int partialContent = 206;
|
||||
static const int multiStatus = 207;
|
||||
static const int alreadyReported = 208;
|
||||
static const int imUsed = 226;
|
||||
static const int multipleChoices = 300;
|
||||
static const int movedPermanently = 301;
|
||||
static const int found = 302;
|
||||
static const int movedTemporarily = 302; // Common alias for found.
|
||||
static const int seeOther = 303;
|
||||
static const int notModified = 304;
|
||||
static const int useProxy = 305;
|
||||
static const int switchProxy = 306;
|
||||
static const int temporaryRedirect = 307;
|
||||
static const int permanentRedirect = 308;
|
||||
static const int badRequest = 400;
|
||||
static const int unauthorized = 401;
|
||||
static const int paymentRequired = 402;
|
||||
static const int forbidden = 403;
|
||||
static const int notFound = 404;
|
||||
static const int methodNotAllowed = 405;
|
||||
static const int notAcceptable = 406;
|
||||
static const int proxyAuthenticationRequired = 407;
|
||||
static const int requestTimeout = 408;
|
||||
static const int conflict = 409;
|
||||
static const int gone = 410;
|
||||
static const int lengthRequired = 411;
|
||||
static const int preconditionFailed = 412;
|
||||
static const int requestEntityTooLarge = 413;
|
||||
static const int requestUriTooLong = 414;
|
||||
static const int unsupportedMediaType = 415;
|
||||
static const int requestedRangeNotSatisfiable = 416;
|
||||
static const int expectationFailed = 417;
|
||||
static const int imATeapot = 418;
|
||||
static const int misdirectedRequest = 421;
|
||||
static const int unprocessableEntity = 422;
|
||||
static const int locked = 423;
|
||||
static const int failedDependency = 424;
|
||||
static const int tooEarly = 425;
|
||||
static const int upgradeRequired = 426;
|
||||
static const int preconditionRequired = 428;
|
||||
static const int tooManyRequests = 429;
|
||||
static const int requestHeaderFieldsTooLarge = 431;
|
||||
static const int connectionClosedWithoutResponse = 444;
|
||||
static const int unavailableForLegalReasons = 451;
|
||||
static const int clientClosedRequest = 499;
|
||||
static const int internalServerError = 500;
|
||||
static const int notImplemented = 501;
|
||||
static const int badGateway = 502;
|
||||
static const int serviceUnavailable = 503;
|
||||
static const int gatewayTimeout = 504;
|
||||
static const int httpVersionNotSupported = 505;
|
||||
static const int variantAlsoNegotiates = 506;
|
||||
static const int insufficientStorage = 507;
|
||||
static const int loopDetected = 508;
|
||||
static const int notExtended = 510;
|
||||
static const int networkAuthenticationRequired = 511;
|
||||
static const int networkConnectTimeoutError = 599;
|
||||
|
||||
bool get connectionError => code == null;
|
||||
|
||||
bool get isUnauthorized => code == unauthorized;
|
||||
|
||||
bool get isForbidden => code == forbidden;
|
||||
|
||||
bool get isNotFound => code == notFound;
|
||||
|
||||
bool get isServerError =>
|
||||
between(internalServerError, networkConnectTimeoutError);
|
||||
|
||||
bool between(int begin, int end) {
|
||||
return !connectionError && code! >= begin && code! <= end;
|
||||
}
|
||||
|
||||
bool get isOk => between(200, 299);
|
||||
|
||||
bool get hasError => !isOk;
|
||||
}
|
||||
139
packages/get/lib/get_connect/http/src/utils/utils.dart
Normal file
139
packages/get/lib/get_connect/http/src/utils/utils.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
import 'dart:convert';
|
||||
|
||||
bool isTokenChar(int byte) {
|
||||
return byte > 31 && byte < 128 && !SEPARATOR_MAP[byte];
|
||||
}
|
||||
|
||||
bool isValueChar(int byte) {
|
||||
return (byte > 31 && byte < 128) ||
|
||||
(byte == CharCode.SP) ||
|
||||
(byte == CharCode.HT);
|
||||
}
|
||||
|
||||
class CharCode {
|
||||
static const int HT = 9;
|
||||
static const int LF = 10;
|
||||
static const int CR = 13;
|
||||
static const int SP = 32;
|
||||
static const int COMMA = 44;
|
||||
static const int SLASH = 47;
|
||||
static const int ZERO = 48;
|
||||
static const int ONE = 49;
|
||||
static const int COLON = 58;
|
||||
static const int SEMI_COLON = 59;
|
||||
}
|
||||
|
||||
const bool F = false;
|
||||
|
||||
const bool T = true;
|
||||
const SEPARATOR_MAP = [
|
||||
F, F, F, F, F, F, F, F, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
|
||||
F, F, F, F, F, F, F, F, T, F, T, F, F, F, F, F, T, T, F, F, T, F, F, T, //
|
||||
F, F, F, F, F, F, F, F, F, F, T, T, T, T, T, T, T, F, F, F, F, F, F, F, //
|
||||
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, T, T, T, F, F, //
|
||||
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
|
||||
F, F, F, T, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
|
||||
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
|
||||
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
|
||||
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
|
||||
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
|
||||
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F
|
||||
];
|
||||
|
||||
String validateField(String field) {
|
||||
for (var i = 0; i < field.length; i++) {
|
||||
if (!isTokenChar(field.codeUnitAt(i))) {
|
||||
throw FormatException(
|
||||
'Invalid HTTP header field name: ${json.encode(field)}', field, i);
|
||||
}
|
||||
}
|
||||
return field.toLowerCase();
|
||||
}
|
||||
|
||||
// Stream<List<int>> toBodyBytesStream(Stream<List<int>> stream) {
|
||||
// return (stream);
|
||||
// }
|
||||
|
||||
final _asciiOnly = RegExp(r'^[\x00-\x7F]+$');
|
||||
|
||||
final newlineRegExp = RegExp(r'\r\n|\r|\n');
|
||||
|
||||
/// Returns whether [string] is composed entirely of ASCII-compatible
|
||||
/// characters.
|
||||
bool isPlainAscii(String string) => _asciiOnly.hasMatch(string);
|
||||
|
||||
const String GET_BOUNDARY = 'getx-http-boundary-';
|
||||
|
||||
/// Encode [value] like browsers
|
||||
String browserEncode(String value) {
|
||||
return value.replaceAll(newlineRegExp, '%0D%0A').replaceAll('"', '%22');
|
||||
}
|
||||
|
||||
const List<int> boundaryCharacters = <int>[
|
||||
43,
|
||||
95,
|
||||
45,
|
||||
46,
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51,
|
||||
52,
|
||||
53,
|
||||
54,
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
65,
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
69,
|
||||
70,
|
||||
71,
|
||||
72,
|
||||
73,
|
||||
74,
|
||||
75,
|
||||
76,
|
||||
77,
|
||||
78,
|
||||
79,
|
||||
80,
|
||||
81,
|
||||
82,
|
||||
83,
|
||||
84,
|
||||
85,
|
||||
86,
|
||||
87,
|
||||
88,
|
||||
89,
|
||||
90,
|
||||
97,
|
||||
98,
|
||||
99,
|
||||
100,
|
||||
101,
|
||||
102,
|
||||
103,
|
||||
104,
|
||||
105,
|
||||
106,
|
||||
107,
|
||||
108,
|
||||
109,
|
||||
110,
|
||||
111,
|
||||
112,
|
||||
113,
|
||||
114,
|
||||
115,
|
||||
116,
|
||||
117,
|
||||
118,
|
||||
119,
|
||||
120,
|
||||
121,
|
||||
122
|
||||
];
|
||||
Reference in New Issue
Block a user