Skip to content

Commit cd46d48

Browse files
perf: reduce overhead on requested uri and eTag generation
1 parent 647bb03 commit cd46d48

8 files changed

Lines changed: 51 additions & 31 deletions

File tree

packages/serinus/lib/src/contexts/route_context.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,7 @@ class RouteContext<T extends RouteHandlerSpec> {
4242
final ModuleScope moduleScope;
4343

4444
/// The [providers] property contains the providers of the module.
45-
late final Map<Type, Provider> providers = {
46-
for (var provider in moduleScope.unifiedProviders)
47-
provider.runtimeType: provider,
48-
};
45+
Map<Type, Provider> providers = {};
4946

5047
/// The [values] property contains the values from ValueProviders.
5148
late final Map<ValueToken, Object?> values = Map.unmodifiable(
@@ -125,7 +122,12 @@ class RouteContext<T extends RouteHandlerSpec> {
125122
this.queryParameters = const {},
126123
this.hooksServices = const {},
127124
this.exceptionFilters = const {},
128-
});
125+
}) {
126+
providers = {
127+
for (var provider in moduleScope.unifiedProviders)
128+
provider.runtimeType: provider,
129+
};
130+
}
129131

130132
/// Initializes the metadata for the route context.
131133
Future<Map<String, Metadata>> initMetadata(ExecutionContext context) async {

packages/serinus/lib/src/core/middlewares/middleware.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class _ShelfMiddleware extends Middleware {
9494
}
9595
return shelf.Request(
9696
argsHost.request.method.toString(),
97-
argsHost.request.uri,
97+
argsHost.request.requestedUri,
9898
body: argsHost.request.body.toString(),
9999
headers: Map<String, Object>.from(argsHost.request.headers.values),
100100
context: {'shelf.io.connection_info': argsHost.request.clientInfo!},

packages/serinus/lib/src/exceptions/serinus_exception.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'dart:io';
2-
import 'dart:typed_data';
32

43
import '../extensions/string_extensions.dart';
54
import '../mixins/mixins.dart';
@@ -40,7 +39,7 @@ class SerinusException with JsonObject implements HttpException {
4039
@override
4140
Map<String, dynamic> toJson() {
4241
return {
43-
'message': Uint8List.fromList(message.codeUnits).tryParse() ?? message,
42+
'message': message.codeUnits.tryParse() ?? message,
4443
'statusCode': statusCode,
4544
'uri': uri != null ? uri!.path : 'No Uri',
4645
};

packages/serinus/lib/src/extensions/string_extensions.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import 'dart:convert';
2-
import 'dart:typed_data';
32

43
/// The [jsonDecoder] is used to decode JSON strings.
54
final jsonDecoder = Utf8Decoder().fuse(const JsonDecoder());
65

76
/// The extension [JsonString] is used to add functionalities to the [String] class.
8-
extension JsonString on Uint8List {
7+
extension JsonString on List<int> {
98
/// This method is used to parse a [String] to a [Map<String, dynamic>].
109
dynamic tryParse() {
1110
try {

packages/serinus/lib/src/http/internal_request.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ abstract class IncomingMessage {
3636
/// The uri of the request.
3737
Uri get uri;
3838

39+
/// The requested uri of the request. It is the absolute and complete uri of the request.
40+
Uri get requestedUri;
41+
3942
/// The method of the request.
4043
String get method;
4144

@@ -163,11 +166,14 @@ class InternalRequest extends IncomingMessage {
163166
String get id => _id ??= 'req-${original.hashCode ^ ++_idCounter}';
164167

165168
// Cache the parsed URI to avoid repeated Uri.parse work on hot paths.
166-
late final Uri _uri = original.requestedUri;
169+
late final Uri _uri = original.uri;
167170

168171
@override
169172
String get path => _uri.path;
170173

174+
@override
175+
Uri get requestedUri => original.requestedUri;
176+
171177
@override
172178
Uri get uri => _uri;
173179

packages/serinus/lib/src/http/request.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@ class Request {
3636
/// The path of the request.
3737
String get path => _original.path;
3838

39-
/// The uri of the request.
39+
/// The uri of the request. It is the path of the request as received in the HTTP line.
4040
Uri get uri => _original.uri;
4141

42+
/// The requested uri of the request. It is the absolute and complete uri of the request.
43+
Uri get requestedUri => _original.requestedUri;
44+
4245
/// The method of the request.
4346
HttpMethod get method => HttpMethod.parse(_original.method);
4447

packages/serinus/lib/src/utils/wrapped_response.dart

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import 'dart:convert';
22
import 'dart:io';
33
import 'dart:typed_data';
44

5-
import 'package:crypto/crypto.dart';
6-
75
import '../extensions/object_extensions.dart';
86

97
/// Shared JSON encoder to avoid repeated allocations.
@@ -14,11 +12,14 @@ class WrappedResponse {
1412
/// The wrapped data.
1513
Object? _data;
1614

15+
List<int>? _encodedBytes;
16+
1717
/// Get the wrapped data.
1818
Object? get data => _data;
1919

2020
set data(Object? value) {
2121
_data = value;
22+
_encodedBytes = null;
2223
isEncoded = false;
2324
}
2425

@@ -30,39 +31,54 @@ class WrappedResponse {
3031

3132
/// Convert the data to bytes.
3233
List<int> toBytes() {
34+
final encodedBytes = _encodedBytes;
35+
if (encodedBytes != null) {
36+
return encodedBytes;
37+
}
3338
if (data == null) {
34-
return Uint8List(0);
39+
return _encodedBytes = Uint8List(0);
3540
}
3641
if (isEncoded && data is List<int>) {
37-
return data as List<int>;
42+
return _encodedBytes = data as List<int>;
3843
}
3944
if (data is Uint8List) {
40-
return data as Uint8List;
45+
return _encodedBytes = data as Uint8List;
4146
}
4247
// Primitive types
4348
if (data is String || data is num || data is bool) {
44-
return data?.toBytes() ?? Uint8List(0);
49+
return _encodedBytes = data?.toBytes() ?? Uint8List(0);
4550
}
4651
// File
4752
if (data is File) {
48-
return (data as File).readAsBytesSync();
53+
return _encodedBytes = (data as File).readAsBytesSync();
4954
}
5055
// If data was JSON-serializable but wasn't encoded earlier, fall back to encoding here.
5156
if (data!.canBeJson()) {
52-
return sharedJsonUtf8Encoder.convert(data);
57+
return _encodedBytes = sharedJsonUtf8Encoder.convert(data);
5358
}
5459
// Fallback: string representation
55-
return utf8.encode(data.toString()) as Uint8List? ?? Uint8List(0);
60+
return _encodedBytes = utf8.encode(data.toString());
5661
}
5762

5863
/// Get the ETag for the response data.
5964
String get eTag {
6065
final bytes = toBytes();
6166
if (bytes.isEmpty) {
62-
return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'; // ETag for empty response
67+
return 'W/"0-0"';
68+
}
69+
final hash = _fastHash(bytes);
70+
return 'W/"${bytes.length.toRadixString(16)}-${hash.toRadixString(16)}"';
71+
}
72+
73+
int _fastHash(List<int> bytes) {
74+
const int offsetBasis = 0x811c9dc5;
75+
const int fnvPrime = 0x01000193;
76+
77+
var hash = offsetBasis;
78+
for (final byte in bytes) {
79+
hash ^= byte;
80+
hash = (hash * fnvPrime) & 0xffffffff;
6381
}
64-
// Simple ETag generation using a hash of the bytes
65-
final hash = base64Encode(sha1.convert(bytes).bytes).substring(0, 27);
66-
return '"${bytes.length}-$hash"';
82+
return hash;
6783
}
6884
}

packages/serinus_microservices/lib/transporters/grpc/grpc_transport.dart

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class GrpcTransport extends TransportAdapter<Server, GrpcOptions> {
8484

8585
@override
8686
Future<void> init(ApplicationConfig config) async {
87-
_messagesResolver = GrpcMessageResolver(
87+
messagesResolver = GrpcMessageResolver(
8888
config,
8989
(services) {
9090
server = Server.create(
@@ -171,9 +171,4 @@ class GrpcTransport extends TransportAdapter<Server, GrpcOptions> {
171171
});
172172
return controller.stream;
173173
}
174-
175-
MessagesResolver? _messagesResolver;
176-
177-
@override
178-
MessagesResolver? get messagesResolver => _messagesResolver;
179174
}

0 commit comments

Comments
 (0)