-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(dynamite_runtime): split dynamite_client into separate libra…
…ries Signed-off-by: Nikolas Rimikis <[email protected]>
- Loading branch information
Showing
8 changed files
with
486 additions
and
491 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
/// The dynamite client to handle http connections. | ||
library http_client; | ||
|
||
export 'src/dynamite_client.dart'; | ||
export 'src/client/authentication.dart'; | ||
export 'src/client/client.dart'; | ||
export 'src/client/exception.dart'; | ||
export 'src/client/response.dart'; | ||
export 'src/http_extensions.dart'; |
64 changes: 64 additions & 0 deletions
64
packages/dynamite/dynamite_runtime/lib/src/client/authentication.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import 'dart:convert'; | ||
|
||
import 'package:meta/meta.dart'; | ||
|
||
/// Base dynamite authentication. | ||
@immutable | ||
sealed class DynamiteAuthentication { | ||
/// Creates a new authentication. | ||
const DynamiteAuthentication({ | ||
required this.type, | ||
required this.scheme, | ||
}); | ||
|
||
/// The base type of the authentication. | ||
final String type; | ||
|
||
/// The used authentication HTTP scheme. | ||
final String? scheme; | ||
|
||
/// The authentication headers added to a request. | ||
Map<String, String> get headers; | ||
} | ||
|
||
/// Basic http authentication with username and password. | ||
class DynamiteHttpBasicAuthentication extends DynamiteAuthentication { | ||
/// Creates a new http basic authentication. | ||
const DynamiteHttpBasicAuthentication({ | ||
required this.username, | ||
required this.password, | ||
}) : super( | ||
type: 'http', | ||
scheme: 'basic', | ||
); | ||
|
||
/// The username. | ||
final String username; | ||
|
||
/// The password. | ||
final String password; | ||
|
||
@override | ||
Map<String, String> get headers => { | ||
'Authorization': 'Basic ${base64.encode(utf8.encode('$username:$password'))}', | ||
}; | ||
} | ||
|
||
/// Http bearer authentication with a token. | ||
class DynamiteHttpBearerAuthentication extends DynamiteAuthentication { | ||
/// Creates a new http bearer authentication. | ||
const DynamiteHttpBearerAuthentication({ | ||
required this.token, | ||
}) : super( | ||
type: 'http', | ||
scheme: 'bearer', | ||
); | ||
|
||
/// The authentication token. | ||
final String token; | ||
|
||
@override | ||
Map<String, String> get headers => { | ||
'Authorization': 'Bearer $token', | ||
}; | ||
} |
126 changes: 126 additions & 0 deletions
126
packages/dynamite/dynamite_runtime/lib/src/client/client.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import 'dart:async'; | ||
import 'dart:typed_data'; | ||
|
||
import 'package:cookie_jar/cookie_jar.dart'; | ||
import 'package:dynamite_runtime/http_client.dart'; | ||
import 'package:dynamite_runtime/src/utils/debug_mode.dart'; | ||
import 'package:dynamite_runtime/src/utils/uri.dart'; | ||
import 'package:http/http.dart' as http; | ||
|
||
/// A client for making network requests. | ||
/// | ||
/// See: | ||
/// * [DynamiteResponse] as the response returned by an operation. | ||
/// * [DynamiteRawResponse] as the raw response that can be serialized. | ||
/// * [DynamiteApiException] as the exception that can be thrown in operations | ||
/// * [DynamiteAuthentication] for providing authentication methods. | ||
class DynamiteClient { | ||
/// Creates a new dynamite network client. | ||
/// | ||
/// If [httpClient] is not provided a default one will be created. | ||
/// The [baseURL] will be normalized, removing any trailing `/`. | ||
DynamiteClient( | ||
Uri baseURL, { | ||
this.baseHeaders, | ||
http.Client? httpClient, | ||
this.cookieJar, | ||
this.authentications, | ||
}) : httpClient = httpClient ?? http.Client(), | ||
baseURL = baseURL.normalizeEmptyPath() { | ||
if (baseURL.queryParametersAll.isNotEmpty) { | ||
throw UnsupportedError('Dynamite can not work with a baseURL containing query parameters.'); | ||
} | ||
} | ||
|
||
/// The base server url used to build the request uri. | ||
/// | ||
/// See `https://swagger.io/docs/specification/api-host-and-base-path` for | ||
/// further information. | ||
final Uri baseURL; | ||
|
||
/// The base headers added to each request. | ||
final Map<String, String>? baseHeaders; | ||
|
||
/// The base http client. | ||
final http.Client httpClient; | ||
|
||
/// The optional cookie jar to persist the response cookies. | ||
final CookieJar? cookieJar; | ||
|
||
/// The available authentications for this client. | ||
/// | ||
/// The first one matching the required authentication type will be used. | ||
final List<DynamiteAuthentication>? authentications; | ||
|
||
/// Makes a request against a given [path]. | ||
/// | ||
/// The query parameters of the [baseURL] are added. | ||
/// The [path] is resolved against the path of the [baseURL]. | ||
/// All [baseHeaders] are added to the request. | ||
Future<http.StreamedResponse> executeRequest( | ||
String method, | ||
String path, { | ||
Map<String, String>? headers, | ||
Uint8List? body, | ||
Set<int>? validStatuses, | ||
}) { | ||
final uri = Uri.parse('$baseURL$path'); | ||
|
||
return executeRawRequest( | ||
method, | ||
uri, | ||
headers: headers, | ||
body: body, | ||
validStatuses: validStatuses, | ||
); | ||
} | ||
|
||
/// Executes a HTTP request against give full [uri]. | ||
Future<http.StreamedResponse> executeRawRequest( | ||
String method, | ||
Uri uri, { | ||
Map<String, String>? headers, | ||
Uint8List? body, | ||
Set<int>? validStatuses, | ||
}) async { | ||
final request = http.Request(method, uri); | ||
|
||
if (baseHeaders != null) { | ||
request.headers.addAll(baseHeaders!); | ||
} | ||
|
||
if (headers != null) { | ||
request.headers.addAll(headers); | ||
} | ||
|
||
if (body != null) { | ||
request.bodyBytes = body; | ||
} | ||
|
||
if (cookieJar != null) { | ||
final cookies = await cookieJar!.loadForRequest(uri); | ||
if (cookies.isNotEmpty) { | ||
request.headers['cookie'] = cookies.join('; '); | ||
} | ||
} | ||
|
||
final response = await httpClient.send(request); | ||
|
||
final cookieHeader = response.headersSplitValues['set-cookie']; | ||
if (cookieHeader != null && cookieJar != null) { | ||
final cookies = cookieHeader.map(Cookie.fromSetCookieValue).toList(); | ||
await cookieJar!.saveFromResponse(uri, cookies); | ||
} | ||
|
||
if (validStatuses?.contains(response.statusCode) ?? true) { | ||
return response; | ||
} else { | ||
if (kDebugMode) { | ||
final result = await http.Response.fromStream(response); | ||
throw DynamiteStatusCodeException.fromResponse(result); | ||
} else { | ||
throw DynamiteStatusCodeException(response.statusCode); | ||
} | ||
} | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
packages/dynamite/dynamite_runtime/lib/src/client/exception.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import 'package:dynamite_runtime/src/utils/debug_mode.dart'; | ||
import 'package:http/http.dart' as http; | ||
import 'package:meta/meta.dart'; | ||
|
||
/// The exception thrown by operations of a `DynamiteClient`. | ||
/// | ||
/// | ||
/// See: | ||
/// * [DynamiteStatusCodeException] as the exception thrown when the response | ||
/// returns an invalid status code. | ||
@immutable | ||
sealed class DynamiteApiException extends http.ClientException { | ||
DynamiteApiException(super.message, super.uri); | ||
} | ||
|
||
/// An exception caused by an invalid status code in a response. | ||
class DynamiteStatusCodeException extends DynamiteApiException { | ||
/// Creates a new dynamite exception with the given information. | ||
DynamiteStatusCodeException( | ||
this.statusCode, [ | ||
Uri? url, | ||
]) : super('Invalid status code $statusCode.', url); | ||
|
||
/// Creates a new Exception from the given [response] for debugging. | ||
/// | ||
/// Awaits the response and tries to decode the `response` into a string. | ||
/// Do not use this in production for performance reasons. | ||
factory DynamiteStatusCodeException.fromResponse(http.Response response) { | ||
assert(kDebugMode, 'Do not use in production for performance reasons.'); | ||
|
||
String body; | ||
try { | ||
body = response.body; | ||
} on FormatException { | ||
body = 'binary'; | ||
} | ||
|
||
return DynamiteStatusCodeException._( | ||
response.statusCode, | ||
response.headers, | ||
body, | ||
); | ||
} | ||
|
||
DynamiteStatusCodeException._( | ||
this.statusCode, | ||
Map<String, Object?> headers, | ||
String body, [ | ||
Uri? url, | ||
]) : super('Invalid status code $statusCode, $statusCode, headers: $headers, body: $body', url); | ||
|
||
/// The returned status code when the exception was thrown. | ||
final int statusCode; | ||
} |
Oops, something went wrong.