Describe the bug
Description
I'm encountering an issue when trying to implement a custom logger middleware that reads and logs the request body (e.g., as JSON). The Request object's body stream (accessed via readAsString() or similar) can only be read once, as per the Dart HTTP server constraints. This means that if the middleware reads the body for logging, subsequent handlers (like a login endpoint) cannot read it again, leading to a "Bad state: The 'read' method can only be called once on a Request/Response object" error.
This makes it impossible to log request bodies in middleware without either:
- Duplicating the read logic in every handler (inefficient).
- Buffering/cloning the body stream in the middleware and providing a re-readable version (which isn't currently supported out-of-the-box).
Minimal Reproducible Example
Here's a minimal setup to reproduce:
main.dart (app setup):
import 'package:relic/relic.dart';
import 'dart:convert';
// Extension for bodyAsJson (from utils/extensions.dart)
extension RequestExtension on Request {
Future<Map<String, dynamic>> get bodyAsJson async {
final body = await this.readAsString();
return jsonDecode(body) as Map<String, dynamic>;
}
}
// Main
void main() {
final app = Router();
app.use('/', customLogger()); // Apply logger middleware globally
app.post('/login', login); // Handler that also reads body
final server = Relic();
server.use('/', app);
server.serve();
}
// Example handler (login)
Future<Response> login(Request req) async {
try {
final body = await req.bodyAsJson; // Fails here if middleware read it first
final email = body['email'] as String?;
final password = body['password'] as String?;
if (email == null || password == null) {
return Response.badRequest(
body: Body.fromString(jsonEncode({
'success': false,
'message': 'Email and password are required',
})),
);
}
// Custom Middleware (Issue Occurred In _message Func)
Middleware customLogger({final Logger? logger}) {
return (final next) {
final localLogger = logger ?? logMessage;
return (final req) async {
final startTime = DateTime.now();
final watch = Stopwatch()..start();
try {
final result = await next(req);
final msg = switch (result) {
final Response rc => '${rc.statusCode}',
final Hijack _ => 'hijacked',
final WebSocketUpgrade _ => 'connected',
};
localLogger(_message(startTime, req, watch.elapsed, msg));
return result;
} catch (error, stackTrace) {
localLogger(
_errorMessage(startTime, req, watch.elapsed, error),
type: LoggerType.error,
stackTrace: stackTrace,
);
rethrow;
}
};
};
}
String _formatQuery(final String query) {
return query == '' ? '' : '?$query';
}
// (Issue Occurred Here)
String _message(
final DateTime requestTime,
final Request request,
final Duration elapsedTime,
final String message,
) {
final method = request.method.value;
final url = request.url;
final body = request.bodyAsJson; /* Throws a Bad state: The 'read' method can only be called once on a Request/Response object */
return '${requestTime.toIso8601String()} '
'${elapsedTime.toString().padLeft(15)} '
'${method.padRight(7)} [$message] ' // 7 - longest standard HTTP method
'${url.path}${_formatQuery(url.query)}'
'${body} ${request.headers}';
}
String _errorMessage(
final DateTime requestTime,
final Request request,
final Duration elapsedTime,
final Object error,
) {
return _message(requestTime, request, elapsedTime, 'ERROR: $error');
}
// Simulate auth logic...
return Response.ok(
body: Body.fromString(jsonEncode({'success': true})),
);
} catch (e) {
return Response.internalServerError(
body: Body.fromString(jsonEncode({
'success': false,
'message': 'Login failed: $e',
})),
);
}
}
To reproduce
Steps to Reproduce:
- Set up a simple Relic app with a custom logger middleware that reads request.bodyAsJson.
- Define a handler (e.g., /login) that also reads request.bodyAsJson.
- Send a POST request to /login with a JSON body (e.g., {"email": "test@example.com", "password": "pass"}).
- Observe the error in the handler because the middleware already consumed the body stream.
Expected behavior
Body should be readable anywhere if needed.
Library version
1.0.0
Platform information
Relic
Additional context
No response
How experienced are you with this library?
None
Are you interested in working on a PR for this?
Describe the bug
Description
I'm encountering an issue when trying to implement a custom logger middleware that reads and logs the request body (e.g., as JSON). The Request object's body stream (accessed via readAsString() or similar) can only be read once, as per the Dart HTTP server constraints. This means that if the middleware reads the body for logging, subsequent handlers (like a login endpoint) cannot read it again, leading to a "Bad state: The 'read' method can only be called once on a Request/Response object" error.
This makes it impossible to log request bodies in middleware without either:
Minimal Reproducible Example
Here's a minimal setup to reproduce:
main.dart (app setup):
To reproduce
Steps to Reproduce:
Expected behavior
Body should be readable anywhere if needed.
Library version
1.0.0
Platform information
Relic
Additional context
No response
How experienced are you with this library?
None
Are you interested in working on a PR for this?