fire_http 0.0.1
fire_http: ^0.0.1 copied to clipboard
A client of HTTP/1.1 or HTTP/2.
example/main.dart
// ignore_for_file: avoid_print
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:fire_http/fire_http.dart';
import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart' as mime;
Future<void> main(List<String> arguments) async {
// the host of client
const host = 'https://catfact.ninja';
// the path of request
const path = '/fact';
// create HttpClient with HTTP/1.1
final client1 = HttpClient(client: Http1Client(), headers: {}, host: host);
// create HttpClient with HTTP/2
final client2 = HttpClient(client: Http2Client(), headers: {}, host: host);
// request with HTTP/1.1 Client
final resClient1 = await client1.get(path);
// request with HTTP/2 Client
final resClient2 = await client2.get(path);
// print all responses
print('\n====== FIRE HTTP =====\n');
print('=> response Client1: \n${(resClient1 as Response).body}\n');
print('=> response Client2: \n${(resClient2 as Response).body}');
}
/// [HttpClient] is a sample Client Wrapper that can be
/// used to [Http1Client] or [Http2Client]
class HttpClient {
// client for the app server
BaseHttpClient client;
// client for outside of app server
BaseHttpClient? clientOutsideAppServer;
// specific host of the client
String host;
// to set the required headers
Map<String, String> headers;
HttpClient({
required this.client,
required this.host,
required this.headers,
}) : super() {
// auto set up the client
setUpClient(clientParam: client);
}
// constant of error code: certificate already registered
static const _tlsCertAlreadyRegisteredErrorCode = 184549481;
/// [setUpClient] to set up the client anytime
void setUpClient({
required BaseHttpClient clientParam,
}) {
final newClient = clientParam.setUp(
// set null, since use this free url
securityContext: null,
// securityContext: SecurityContext(),
);
clientOutsideAppServer = newClient;
client = newClient;
}
/// [setUpCertPinning] to set up the certificate pinning anytime
void setUpCertPinning({
required String certBase64,
String? certAltBase64,
}) {
final securityContext = SecurityContext();
// set up the securityContext with the certBase64 and certAltBase64
void setUpCert(String cert) {
final certBytes = base64Decode(cert);
try {
securityContext.setTrustedCertificatesBytes(certBytes);
} catch (e) {
// return nothing when certificate already registered
if (e is TlsException &&
e.osError?.errorCode == _tlsCertAlreadyRegisteredErrorCode) {
// Cert already registered, so ignore the error.
return;
}
rethrow;
}
}
setUpCert(certBase64);
if (certAltBase64 != null) {
setUpCert(certAltBase64);
}
final newClient = client.setUp(
securityContext: securityContext,
);
client = newClient;
clientOutsideAppServer = newClient;
}
// parse the path with host
Uri getParsedUrl(String path) => Uri.parse('$host$path');
// generate required request headers
Map<String, String> generateRequestHeader([
Map<String, String> overrideHeader = const {},
]) {
return {
...headers,
...overrideHeader,
};
}
///
/// Sends an HTTP GET request to the given URL.
///
Future<dynamic> get(
String path, {
Map<String, String> overrideHeader = const {},
}) async {
final allHeaders = generateRequestHeader(overrideHeader);
final parsedUrl = getParsedUrl(path);
final response = await client.get(parsedUrl, headers: allHeaders);
return response;
}
///
/// Sends an HTTP POST request to the given URL.
///
Future<dynamic> post(
String path,
dynamic data, {
Map<String, String> overrideHeader = const {},
}) async {
final allHeaders = generateRequestHeader(overrideHeader);
final parsedUrl = getParsedUrl(path);
final encodedBody = utf8.encode(jsonEncode(data as Map<String, String>));
final response = await client.post(
parsedUrl,
headers: allHeaders,
body: encodedBody,
);
print('HTTP response\n'
'Method: POST\n'
'Url: $parsedUrl\n'
'Code: ${response.statusCode}\n'
'Data: ${response.body}');
return response;
}
///
/// Sends an HTTP PATCH request to the given URL.
///
Future<dynamic> patch(
String path,
dynamic data, {
Map<String, String> overrideHeader = const {},
}) async {
final allHeaders = generateRequestHeader(overrideHeader);
final parsedUrl = getParsedUrl(path);
final encodedBody = utf8.encode(jsonEncode(data as Map<String, String>));
final response = await client.patch(
parsedUrl,
headers: allHeaders,
body: encodedBody,
);
return response;
}
///
/// Sends an HTTP PUT request to the given URL.
///
Future<dynamic> put(
String path,
dynamic data, {
Map<String, String> overrideHeader = const {},
}) async {
final allHeaders = generateRequestHeader(overrideHeader);
final parsedUrl = getParsedUrl(path);
final encodedBody = utf8.encode(jsonEncode(data as Map<String, String>));
final response = await client.put(
parsedUrl,
headers: allHeaders,
body: encodedBody,
);
print('HTTP response\n'
'Method: PUT\n'
'Url: $parsedUrl\n'
'Data: ${response.body}');
return response;
}
///
/// Sends an HTTP DELETE request to the given URL.
///
Future<dynamic> delete(
String path, {
Map<String, String> overrideHeader = const {},
dynamic body,
}) async {
final allHeaders = generateRequestHeader(overrideHeader);
final parsedUrl = getParsedUrl(path);
final encodedBody = utf8.encode(jsonEncode(body as Map<String, String>));
final response = await client.delete(
parsedUrl,
headers: allHeaders,
body: encodedBody,
);
print('HTTP response\n'
'Method: DELETE\n'
'Url: $parsedUrl\n'
'Data: ${response.body}');
return response;
}
///
/// Sends an HTTP GET request to the given URL to download file.
///
Future<Response> downloadFile(
String path, {
Map<String, String> overrideHeader = const {},
}) async {
final allHeaders = generateRequestHeader(overrideHeader);
final parsedUrl = getParsedUrl(path);
final response = await client.get(
parsedUrl,
headers: allHeaders,
);
return response;
}
///
/// Sends an HTTP request to the given URL to upload file.
///
Future<dynamic> uploadFile(
String path,
Map<String, File> mapFile,
Map<String, dynamic> data, {
Map<String, String> overrideHeader = const {},
HttpMethodType method = HttpMethodType.post,
Completer<Stream<double>>? uploadProgressCompleter,
Completer<Stream<double>>? downloadProgressCompleter,
dynamic signPayload = 'null',
}) async {
final parsedUrl = getParsedUrl(path);
final Map<String, String> inputHeaders = {
...overrideHeader,
HttpConstants.accept: HttpConstants.jsonContentType,
HttpConstants.contentType: HttpConstants.multiPartFormDataType,
HttpConstants.authorization: headers[HttpConstants.authorization] ?? '',
};
final allHeaders = generateRequestHeader(inputHeaders);
final request = MultipartRequest(
HttpMethod(type: method).toString(),
parsedUrl,
);
data.forEach((key, value) => request.fields[key] = value);
request.headers.addAll(allHeaders);
for (final element in mapFile.entries) {
final mimeTypeData = mime.lookupMimeType(
element.value.path,
headerBytes: [0xFF, 0xD8],
)?.split('/');
final field = await element.value.readAsBytes();
final multiPartFile = MultipartFile.fromBytes(
element.key,
field,
filename: element.value.path.split('/').last,
contentType: MediaType(mimeTypeData![0], mimeTypeData[1]),
);
request.files.add(multiPartFile);
}
final streamResponse = await client
.send(
request,
uploadStreamCompleter: uploadProgressCompleter,
)
.then((value) {
if (downloadProgressCompleter != null) {
/// Invoke onDownloadStreamCreated callback
/// to give download progress stream to caller
final responseLength = value.contentLength ?? 0;
int downloadDataCount = 0;
final downloadStream = value.stream.transform<double>(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
downloadDataCount += data.length;
sink.add(downloadDataCount / responseLength);
},
handleError: (error, stack, sink) {
sink.addError(error, stack);
},
handleDone: (sink) {
sink.close();
},
),
);
downloadProgressCompleter.complete(downloadStream);
}
return value;
});
return Response.fromStream(streamResponse);
}
///
/// Sends an HTTP GET request to the given URL to
/// download file outside our server.
///
Future<dynamic> downloadFileFromOtherServer(
String url, {
Map<String, String> overrideHeader = const {},
}) async {
final requestHeader = generateRequestHeader(overrideHeader);
final uri = Uri.parse(url);
final response = await clientOutsideAppServer!.get(
uri,
headers: requestHeader,
);
return response;
}
/// Function to create HTTP request
/// with upload and download stream completer.
/// So caller can pass the completer
/// to get stream they want
Future<dynamic> streamedRequest({
required HttpMethodType method,
required String path,
Map<String, dynamic>? body,
Map<String, String> overrideHeader = const {},
Completer<Stream<double>>? uploadProgressCompleter,
Completer<Stream<double>>? downloadProgressCompleter,
}) async {
final allHeaders = generateRequestHeader(overrideHeader);
final request = StreamedRequest(
HttpMethod(type: method).toString(),
getParsedUrl(path),
)..headers.addAll(allHeaders);
final stringifyJson = body != null ? jsonEncode(body) : 'null';
request.contentLength = stringifyJson.length;
await Stream.fromIterable(stringifyJson.split(''))
.transform(utf8.encoder)
.transform<double>(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
request.sink.add(data);
},
handleError: (error, stack, sink) {
request.sink.addError(error, stack);
sink.addError(error, stack);
},
handleDone: (sink) {
request.sink.close();
sink.close();
},
),
)
.toList();
final streamResponse = await client
.send(
request,
uploadStreamCompleter: uploadProgressCompleter,
)
.then((value) async {
if (downloadProgressCompleter != null) {
/// Invoke onDownloadStreamCreated callback
/// to give download progress stream to caller
final responseLength = value.contentLength ?? 0;
int downloadDataCount = 0;
final downloadStream = value.stream.transform<double>(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
downloadDataCount += data.length;
sink.add(downloadDataCount / responseLength);
},
handleError: (error, stack, sink) {
sink.addError(error, stack);
},
handleDone: (sink) {
sink.close();
},
),
);
downloadProgressCompleter.complete(downloadStream);
}
return value;
});
return Response.fromStream(streamResponse);
}
}