diff --git a/packages/dart_frog/lib/src/_internal.dart b/packages/dart_frog/lib/src/_internal.dart index bea82913f..d1f503e3e 100644 --- a/packages/dart_frog/lib/src/_internal.dart +++ b/packages/dart_frog/lib/src/_internal.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'package:dart_frog/dart_frog.dart'; import 'package:dart_frog/src/body_parsers/body_parsers.dart'; import 'package:http_methods/http_methods.dart' show isHttpMethod; +import 'package:meta/meta.dart'; import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf_io.dart' as shelf_io; diff --git a/packages/dart_frog/lib/src/response.dart b/packages/dart_frog/lib/src/response.dart index 52cc4da07..ca5a5466c 100644 --- a/packages/dart_frog/lib/src/response.dart +++ b/packages/dart_frog/lib/src/response.dart @@ -18,17 +18,32 @@ class Response { encoding: encoding, ), ); + Response._(this._response); /// Create a [Response] with a stream of bytes. + /// + /// If [bufferOutput] is `true` (the default), streamed responses will be + /// buffered to improve performance. If `false`, all chunks will be pushed + /// over the wire as they're received. Note that, disabling buffering of the + /// output can result in very poor performance, when writing many small + /// chunks. + /// + /// See also: + /// + /// * [HttpResponse.bufferOutput](https://api.flutter.dev/flutter/dart-io/HttpResponse/bufferOutput.html) Response.stream({ int statusCode = 200, Stream>? body, Map? headers, + bool bufferOutput = true, }) : this._( shelf.Response( statusCode, body: body, headers: headers, + context: !bufferOutput + ? {Response.shelfBufferOutputContextKey: bufferOutput} + : null, ), ); @@ -80,7 +95,15 @@ class Response { encoding: encoding, ); - Response._(this._response); + /// A [shelf.Response.context] key used to determine if if the + /// [HttpResponse.bufferOutput] should be enabled or disabled. + /// + /// See also: + /// + /// * [shelf_io library](https://pub.dev/documentation/shelf/latest/shelf_io/shelf_io-library.html) + /// * [HttpResponse.bufferOutput](https://api.flutter.dev/flutter/dart-io/HttpResponse/bufferOutput.html) + @visibleForTesting + static const shelfBufferOutputContextKey = 'shelf.io.buffer_output'; shelf.Response _response; @@ -91,6 +114,12 @@ class Response { /// The returned map is unmodifiable. Map get headers => _response.headers; + /// Extra context that can be used by for middleware and handlers. + /// + /// The value is immutable. + @visibleForTesting + Map get context => _response.context; + /// Returns a [Stream] representing the body. Stream> bytes() => _response.read(); diff --git a/packages/dart_frog/test/src/response_test.dart b/packages/dart_frog/test/src/response_test.dart index 0e9e48b87..c8c70a39f 100644 --- a/packages/dart_frog/test/src/response_test.dart +++ b/packages/dart_frog/test/src/response_test.dart @@ -107,6 +107,46 @@ void main() { final response = Response.stream(body: stream); expect(response.bytes(), emits(equals(bytes))); }); + + group('bufferOutput', () { + test('is omitted by default', () { + final response = Response.stream( + body: const Stream.empty(), + // ignore: avoid_redundant_argument_values + bufferOutput: true, + ); + + expect( + response.context, + isNot(contains(Response.shelfBufferOutputContextKey)), + reason: + '''The context should not have the '${Response.shelfBufferOutputContextKey}' key.''', + ); + }); + + test('can be disabled', () { + final response = Response.stream( + body: const Stream.empty(), + bufferOutput: false, + ); + + expect( + response.context, + contains(Response.shelfBufferOutputContextKey), + reason: + '''The context should have the '${Response.shelfBufferOutputContextKey}' key.''', + ); + + final bufferOutput = + response.context[Response.shelfBufferOutputContextKey]; + expect( + bufferOutput, + isFalse, + reason: + '''The '${Response.shelfBufferOutputContextKey}' should be 'false' when disabled.''', + ); + }); + }); }); group('formData', () {