Skip to content

feat: add stream handler for pure Http Streaming#223

Open
francescovallone wants to merge 4 commits into
mainfrom
feat/streaming
Open

feat: add stream handler for pure Http Streaming#223
francescovallone wants to merge 4 commits into
mainfrom
feat/streaming

Conversation

@francescovallone
Copy link
Copy Markdown
Owner

@francescovallone francescovallone commented Feb 28, 2026

Description

Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.

Fixes # (issue)

Type of change

  • ⭐ New feature (non-breaking change which adds functionality)
  • 📑 This change requires a documentation update

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Summary by CodeRabbit

  • New Features

    • Streaming route support: endpoints can transmit data progressively (sample GET /stream emits periodic messages).
  • Enhancements

    • Improved streamed responses: chunked transfer, explicit write/flush and buffering control, plus stronger error handling and safe finalization.
  • Changes

    • Application startup now uses a module-based entrypoint and no longer includes the previous default test/public route.
  • Tests

    • Added tests for stream route registration and handler behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 28, 2026

📝 Walkthrough

Walkthrough

Adds streaming response support: new RestHandler/StreamHandler types, Controller.onStream registration, propagation of a streaming flag through RouteContext, HTTP adapter streaming reply (chunked writes, flush, error handling), and response API extensions (add/flush/toggleBuffering).

Changes

Cohort / File(s) Summary
Controller & Routing
packages/serinus/lib/src/core/controller.dart, packages/serinus/lib/src/routes/routes_explorer.dart
Introduces RestHandler/StreamHandler; ReqResHandler now extends RestHandler<Future<...>,...>; RestRouteHandlerSpec gains streaming and accepts RestHandler; adds Controller.onStream() and duplicate-route guard; explorer propagates streaming into RouteContext.
Route Context & Execution
packages/serinus/lib/src/contexts/route_context.dart, packages/serinus/lib/src/routes/route_execution_context.dart
Adds final bool streaming to RouteContext; route execution dispatches by handler type (ReqResHandler vs StreamHandler) and treats Stream results as streaming flows (no normal processing).
HTTP Adapter & Response
packages/serinus/lib/src/adapters/serinus_http_server.dart, packages/serinus/lib/src/http/internal_response.dart
Adapter now detects Stream response bodies, sets chunked transfer, disables buffering, writes/adds chunks and flushes per item, handles stream errors and ensures closure; OutgoingMessage/InternalResponse gain add(List<int>), flush(), and toggleBuffering(bool).
CLI Example & Tests
packages/serinus/bin/serinus.dart, packages/serinus/test/core/controller_test.dart
Example switched to application entrypoint (createApplication(..., entrypoint: AppModule())) and removed inline public route/provider; tests added to validate onStream() registration and handler typing.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Controller
    participant Routes as RouteRegistry
    participant RouteExec as RouteExecution
    participant HTTP as HTTPAdapter
    participant Resp as Response

    Client->>Controller: register GET /stream via onStream()
    Controller->>Routes: store RestRouteHandlerSpec(streaming: true)
    Note over Client,Resp: later — incoming request
    Client->>RouteExec: GET /stream
    RouteExec->>Routes: match route
    RouteExec->>StreamHandler: call(RequestContext)
    StreamHandler->>RouteExec: returns Stream<item>
    RouteExec->>HTTP: send Stream as body
    HTTP->>Resp: set chunked transfer, toggleBuffering(false)
    loop per emitted item
        StreamHandler->>HTTP: emit item
        HTTP->>Resp: add/write chunk
        HTTP->>Resp: flush()
    end
    HTTP->>Resp: close() (finally)
    Resp->>Client: streamed chunks complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hop on sockets, nibble bytes so fleet,

Chunks tumble out — a tasty streaming treat,
Flush by flush I send the trail,
Routes and streams in a carrot-mail,
Hooray! I danced while packets eat. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description follows the template structure but lacks substantive content in the summary section, providing no details about the changes, motivation, context, or dependencies. Add a meaningful summary of the streaming feature being introduced, explain the motivation and context for this change, and list any dependencies required. Fill in the 'Fixes #' issue reference or note if not applicable.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature addition: introducing a stream handler for pure HTTP streaming, which aligns with the core changes across the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/streaming

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/serinus/lib/src/adapters/serinus_http_server.dart (1)

164-196: Streaming implementation looks solid.

The chunked transfer encoding approach is correct. A couple of observations:

  1. Minor redundancy: Line 167 sets content-type header again, but it's already set at line 161. This is harmless but could be removed.

  2. Good practices observed: Disabling buffering, proper error handling with finally block, and chunk type validation.

♻️ Optional: Remove redundant header assignment
     if (bodyData is Stream) {
       headers[io.HttpHeaders.dateHeader] = _cachedDate;
       response.toggleBuffering(false);
-      headers[io.HttpHeaders.contentTypeHeader] = contentTypeValue;
       headers[io.HttpHeaders.transferEncodingHeader] = 'chunked';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/serinus/lib/src/adapters/serinus_http_server.dart` around lines 164
- 196, The code assigns the Content-Type header twice when handling a Stream
response; remove the redundant headers[io.HttpHeaders.contentTypeHeader] =
contentTypeValue; inside the bodyData is Stream block so the Content-Type is
only set once (keep the initial assignment where headers is first populated),
leaving the rest of the streaming logic (response.toggleBuffering,
transfer-encoding, error handling, flushAndClose) unchanged; look for the
duplicate assignment near the Stream branch that references contentTypeValue and
delete it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/serinus/lib/src/core/controller.dart`:
- Around line 221-242: Add a Dart doc comment for the public method onStream
explaining its purpose and parameters (handler, route, shouldValidateMultipart)
and mention that it registers a streaming route and throws a StateError if a
duplicate route exists; mirror the style and tags from the existing on method
documentation so tools and the pipeline recognize the public API (reference
symbols: onStream, Route, RequestContext, RestRouteHandlerSpec, StreamHandler,
UuidV4).

---

Nitpick comments:
In `@packages/serinus/lib/src/adapters/serinus_http_server.dart`:
- Around line 164-196: The code assigns the Content-Type header twice when
handling a Stream response; remove the redundant
headers[io.HttpHeaders.contentTypeHeader] = contentTypeValue; inside the
bodyData is Stream block so the Content-Type is only set once (keep the initial
assignment where headers is first populated), leaving the rest of the streaming
logic (response.toggleBuffering, transfer-encoding, error handling,
flushAndClose) unchanged; look for the duplicate assignment near the Stream
branch that references contentTypeValue and delete it.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f303bce and 1e82e17.

📒 Files selected for processing (8)
  • packages/serinus/bin/serinus.dart
  • packages/serinus/lib/src/adapters/serinus_http_server.dart
  • packages/serinus/lib/src/contexts/route_context.dart
  • packages/serinus/lib/src/core/controller.dart
  • packages/serinus/lib/src/http/internal_response.dart
  • packages/serinus/lib/src/routes/route_execution_context.dart
  • packages/serinus/lib/src/routes/routes_explorer.dart
  • packages/serinus/test/core/controller_test.dart

Comment thread packages/serinus/lib/src/core/controller.dart
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/serinus/lib/src/core/controller.dart (2)

231-238: Consider extracting duplicate route validation logic.

The route existence check is duplicated across on, onStatic, and onStream methods. This could be extracted into a private helper method to reduce code duplication.

♻️ Optional refactor to extract helper method
+  void _ensureRouteDoesNotExist(Route route) {
+    final routeExists = _routes.values.any(
+      (r) => r.route.path == route.path && r.route.method == route.method,
+    );
+    if (routeExists) {
+      throw StateError(
+        'A route with the same path and method already exists. [${route.path}] [${route.method}]',
+      );
+    }
+  }

Then replace the duplicated blocks in on, onStatic, and onStream with:

_ensureRouteDoesNotExist(route);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/serinus/lib/src/core/controller.dart` around lines 231 - 238,
Duplicate route-existence validation across on, onStatic, and onStream should be
extracted into a private helper to remove repetition; implement a private method
(e.g., _ensureRouteDoesNotExist(RouteDefinition route)) that checks
_routes.values.any((r) => r.route.path == route.path && r.route.method ==
route.method) and throws the existing StateError with the same message when a
duplicate is found, then call _ensureRouteDoesNotExist(route) from on, onStatic,
and onStream instead of duplicating the check.

244-244: Minor: Add trailing comma for Dart style consistency.

Adding a trailing comma after streaming: true aligns with Dart's preferred formatting style and allows for cleaner diffs when adding future parameters.

📝 Proposed fix
       shouldValidateMultipart: shouldValidateMultipart,
-      streaming: true
+      streaming: true,
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/serinus/lib/src/core/controller.dart` at line 244, Add a trailing
comma after the "streaming: true" entry to follow Dart style conventions; locate
the parameter list or map in controller.dart where "streaming: true" is set
(e.g., inside the call or constructor that sets streaming) and append a comma
after true, then run dartfmt/format to ensure consistent formatting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/serinus/lib/src/core/controller.dart`:
- Around line 231-238: Duplicate route-existence validation across on, onStatic,
and onStream should be extracted into a private helper to remove repetition;
implement a private method (e.g., _ensureRouteDoesNotExist(RouteDefinition
route)) that checks _routes.values.any((r) => r.route.path == route.path &&
r.route.method == route.method) and throws the existing StateError with the same
message when a duplicate is found, then call _ensureRouteDoesNotExist(route)
from on, onStatic, and onStream instead of duplicating the check.
- Line 244: Add a trailing comma after the "streaming: true" entry to follow
Dart style conventions; locate the parameter list or map in controller.dart
where "streaming: true" is set (e.g., inside the call or constructor that sets
streaming) and append a comma after true, then run dartfmt/format to ensure
consistent formatting.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e82e17 and 641420a.

📒 Files selected for processing (1)
  • packages/serinus/lib/src/core/controller.dart

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant