CodeSampleX

Beispiel

shelf 1.4.2: Handle shelf request URL parsing, subpath rewrites with change, single-subscription body buffering, and pipeline error handling

Verifiziertes Beispiel für pub shelf 1.4.2: Handle shelf request URL parsing, subpath rewrites with change, single-subscription body buffering, and pipeline…

sha256:8b87dbe3a6d2ab33ba4b807d61e35686efe22f1481d942766d8e1190783f14fe

Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen. Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen. MIT-0

Ausführungsbelege

Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.

Beleggrundlage
Signierter Vertrag bestanden
Verifizierungsbelege
2
Signaturschlüssel, die es gebaut haben
2
Deklarierte Umgebung dart linux x64 dart dart pub

Umgebungen der Verifizierungsläufe

Umgebung Contract Stufen Lauf
dart 3 · linux debian/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · pub@1
2026-08-16
dart 3 · linux debian/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · pub@1
2026-08-18

Fall

HOW
Ziel
Handle shelf request URL parsing, subpath rewrites with change, single-subscription body buffering, and pipeline error handling
Pakete
Symbole
  • Request
  • Request.url
  • Request.requestedUri
  • Request.handlerPath
  • Request.change
  • Request.readAsString
  • Response
  • Response.ok
  • Pipeline
  • createMiddleware
  • Cascade
  • HijackException
Umgebung
dart
Erstellt
2026-08-16T12:20:28Z

Contract

  1. assert request.url is a relative Uri with no scheme, host, or leading slash, that handlerPath defaults to '/', that requestedUri.path equals handlerPath + url.path, that change(path:) consumes path segments from url.path into handlerPath rather than appending, that change(path:) with a leading slash or unmatched prefix throws ArgumentError, and that Request constructor rejects a non-empty handlerPath missing a trailing slash
  2. assert reading a request body with readAsString drains the single-subscription Body stream so a second read throws StateError synchronously, that Request.change without body preserves the drained body so downstream handlers fail, and that passing body to change restores a fresh readable stream
  3. assert createMiddleware requestHandler short-circuit passes the response through its own responseHandler, that an exception caught by errorHandler does not run responseHandler on the error response, and that HijackException bypasses errorHandler completely to unwind the call stack
  4. assert Cascade dispatches to subsequent handlers on matching 404 status codes, but returns a 500 response immediately without cascading
  5. assert Response headers are case-insensitive on lookup while preserving original key casing in keys, and that string bodies automatically populate content-type and content-length whereas stream bodies do not

Dateien

  • NOTES.md
  • csx.json
  • pubspec.lock
  • pubspec.yaml
  • test/contract.dart

Quellartefakt herunterladen (tar.gz)

Quelltext

NOTES.md
# shelf Request, Middleware, and Cascade Contracts

`search_known_solution` query for `shelf` found existing sample `sha256:2651162215727e626baac2f69d7b7e043707968fea0f9218aa9dda0648549e95` covering `shelf_router` parameter extraction and route matching. This sample focuses on core `shelf` Request URI invariants, path mounting via `change()`, stream body consumption, middleware error handling, and cascade semantics.

## Failure Mode

Code expecting `request.url.path` to start with a leading slash or `request.change(path: ...)` to append path segments fails loudly with runtime `ArgumentError` exceptions and broken route matching. Code reading request bodies multiple times without restoring them via `request.change(body: ...)` crashes downstream middleware and handlers with synchronous `StateError` exceptions.
csx.json
{"case":{"believed":"A shelf Request's url property is an absolute Uri whose path starts with a leading slash matching requestedUri.path, and change(path:) appends subpaths.","caseId":"case:sha256:f7be8df38545cba8e4a524e100a031b3da20ba389ff48f05ee510567c0f4c1e7","contract":["assert request.url is a relative Uri with no scheme, host, or leading slash, that handlerPath defaults to '/', that requestedUri.path equals handlerPath + url.path, that change(path:) consumes path segments from url.path into handlerPath rather than appending, that change(path:) with a leading slash or unmatched prefix throws ArgumentError, and that Request constructor rejects a non-empty handlerPath missing a trailing slash","assert reading a request body with readAsString drains the single-subscription Body stream so a second read throws StateError synchronously, that Request.change without body preserves the drained body so downstream handlers fail, and that passing body to change restores a fresh readable stream","assert createMiddleware requestHandler short-circuit passes the response through its own responseHandler, that an exception caught by errorHandler does not run responseHandler on the error response, and that HijackException bypasses errorHandler completely to unwind the call stack","assert Cascade dispatches to subsequent handlers on matching 404 status codes, but returns a 500 response immediately without cascading","assert Response headers are case-insensitive on lookup while preserving original key casing in keys, and that string bodies automatically populate content-type and content-length whereas stream bodies do not"],"goal":"Handle shelf request URL parsing, subpath rewrites with change, single-subscription body buffering, and pipeline error handling","kind":"HOW","packages":["pkg:pub/shelf@1.4.2"],"schemaVersion":1,"symbols":["Request","Request.url","Request.requestedUri","Request.handlerPath","Request.change","Request.readAsString","Response","Response.ok","Pipeline","createMiddleware","Cascade","HijackException"]},"contractCommand":["dart","test/contract.dart"],"environment":{"arch":"x64","ecosystem":"pub","executionContext":"dart","language":"dart","os":"linux","packageManager":"pub","runtime":"dart","schemaVersion":1},"license":"MIT-0","packages":["pkg:pub/shelf@1.4.2"],"schemaVersion":1,"symbols":["Request","Request.url","Request.requestedUri","Request.handlerPath","Request.change","Request.readAsString","Response","Response.ok","Pipeline","createMiddleware","Cascade","HijackException"],"verifierAdapter":"pub@1"}
pubspec.lock
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
  async:
    dependency: transitive
    description:
      name: async
      sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
      url: "https://pub.dev"
    source: hosted
    version: "2.13.1"
  collection:
    dependency: transitive
    description:
      name: collection
      sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
      url: "https://pub.dev"
    source: hosted
    version: "1.19.1"
  http_parser:
    dependency: transitive
    description:
      name: http_parser
      sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
      url: "https://pub.dev"
    source: hosted
    version: "4.1.2"
  meta:
    dependency: transitive
    description:
      name: meta
      sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
      url: "https://pub.dev"
    source: hosted
    version: "1.19.0"
  path:
    dependency: transitive
    description:
      name: path
      sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
      url: "https://pub.dev"
    source: hosted
    version: "1.9.1"
  shelf:
    dependency: "direct main"
    description:
      name: shelf
      sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
      url: "https://pub.dev"
    source: hosted
    version: "1.4.2"
  source_span:
    dependency: transitive
    description:
      name: source_span
      sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
      url: "https://pub.dev"
    source: hosted
    version: "1.10.2"
  stack_trace:
    dependency: transitive
    description:
      name: stack_trace
      sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
      url: "https://pub.dev"
    source: hosted
    version: "1.12.1"
  stream_channel:
    dependency: transitive
    description:
      name: stream_channel
      sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
      url: "https://pub.dev"
    source: hosted
    version: "2.1.4"
  string_scanner:
    dependency: transitive
    description:
      name: string_scanner
      sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
      url: "https://pub.dev"
    source: hosted
    version: "1.4.1"
  term_glyph:
    dependency: transitive
    description:
      name: term_glyph
      sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
      url: "https://pub.dev"
    source: hosted
    version: "1.2.2"
  typed_data:
    dependency: transitive
    description:
      name: typed_data
      sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
      url: "https://pub.dev"
    source: hosted
    version: "1.4.0"
sdks:
  dart: ">=3.5.0 <4.0.0"
pubspec.yaml
name: shelf_contract
description: Proving core shelf Request, Response, Pipeline, and Middleware behavior contracts in Dart.
publish_to: none

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  shelf: 1.4.2
test/contract.dart
import 'dart:async';
import 'package:shelf/shelf.dart';

void main() async {
  // ---------------------------------------------------------------------------
  // Contract 1: Request.url vs Request.requestedUri vs Request.handlerPath
  // A competent model expects request.url.path to be an absolute path starting
  // with a leading slash matching requestedUri.path. In reality, request.url
  // is a relative Uri without scheme/host whose path never starts with a slash,
  // request.handlerPath defaults to '/', and request.change(path: ...) consumes
  // path segments from url.path into handlerPath rather than appending to url.
  // ---------------------------------------------------------------------------
  {
    final req = Request(
      'GET',
      Uri.parse('http://example.com:8080/api/v1/users?page=2'),
    );

    // requestedUri is absolute and holds full host, port, path with leading slash, and query.
    assert(req.requestedUri.toString() == 'http://example.com:8080/api/v1/users?page=2');
    assert(req.requestedUri.path == '/api/v1/users');
    assert(req.requestedUri.isAbsolute == true);
    assert(req.requestedUri.hasScheme == true);
    assert(req.requestedUri.host == 'example.com');
    assert(req.requestedUri.port == 8080);

    // url is relative to handlerPath, has NO leading slash, no scheme, and no host.
    assert(req.handlerPath == '/');
    assert(req.url.toString() == 'api/v1/users?page=2');
    assert(req.url.path == 'api/v1/users');
    assert(req.url.isAbsolute == false);
    assert(req.url.hasScheme == false);
    assert(req.url.host == '');

    // requestedUri.path is exactly handlerPath + url.path (or '/' when url.path is empty).
    assert('${req.handlerPath}${req.url.path}' == req.requestedUri.path);

    // change(path: 'api') consumes 'api' from url.path and moves it into handlerPath.
    final reqMounted = req.change(path: 'api');
    assert(reqMounted.handlerPath == '/api/');
    assert(reqMounted.url.path == 'v1/users');
    assert(reqMounted.url.toString() == 'v1/users?page=2');
    assert(reqMounted.requestedUri.toString() == req.requestedUri.toString());

    // change(path: '/api') with a leading slash is rejected with ArgumentError.
    var threwLeadingSlash = false;
    try {
      req.change(path: '/api');
    } on ArgumentError {
      threwLeadingSlash = true;
    }
    assert(threwLeadingSlash, 'change(path:) with leading slash must throw ArgumentError');

    // change(path: 'wrong') where url does not begin with that segment throws ArgumentError.
    var threwNotFound = false;
    try {
      req.change(path: 'wrong');
    } on ArgumentError {
      threwNotFound = true;
    }
    assert(threwNotFound, 'change(path:) with non-matching segment must throw ArgumentError');

    // handlerPath in constructor must end with '/' if non-empty, otherwise throws ArgumentError.
    var threwBadHandlerPath = false;
    try {
      Request('GET', Uri.parse('http://example.com/api/users'), handlerPath: '/api');
    } on ArgumentError {
      threwBadHandlerPath = true;
    }
    assert(threwBadHandlerPath, 'handlerPath without trailing slash must throw ArgumentError');
  }

  // ---------------------------------------------------------------------------
  // Contract 2: Request/Response bodies are single-subscription streams.
  // Calling readAsString() or read() twice throws StateError synchronously.
  // Request.change() without body shares the drained body reference.
  // ---------------------------------------------------------------------------
  {
    final req = Request(
      'POST',
      Uri.parse('http://example.com/items'),
      body: '{"name":"widget"}',
    );

    // First read drains the body stream.
    final firstRead = await req.readAsString();
    assert(firstRead == '{"name":"widget"}');

    // Second read throws StateError immediately (synchronous throw).
    var threwSecondRead = false;
    try {
      req.readAsString();
    } on StateError catch (e) {
      threwSecondRead = true;
      assert(e.message.contains('The "read" method can only be called once'));
    }
    assert(threwSecondRead, 'Second readAsString() call must throw StateError');

    // Calling change() passes along the drained body unless explicitly replaced.
    final forwardedReq = req.change(headers: {'x-forwarded': 'true'});
    var threwForwardedRead = false;
    try {
      forwardedReq.readAsString();
    } on StateError {
      threwForwardedRead = true;
    }
    assert(threwForwardedRead, 'change() without body must preserve drained StateError');

    // Passing body to change() replaces the body with a fresh readable stream.
    final restoredReq = req.change(body: '{"name":"widget"}');
    final restoredBody = await restoredReq.readAsString();
    assert(restoredBody == '{"name":"widget"}');
  }

  // ---------------------------------------------------------------------------
  // Contract 3: Pipeline and createMiddleware behavior.
  // Short-circuiting requestHandler runs the middleware's responseHandler.
  // Handled errors from errorHandler do NOT run responseHandler.
  // HijackException bypasses errorHandler completely.
  // ---------------------------------------------------------------------------
  {
    final trace = <String>[];

    final mw = createMiddleware(
      requestHandler: (request) {
        trace.add('request');
        if (request.url.path == 'short-circuit') {
          return Response.ok('blocked');
        }
        return null;
      },
      responseHandler: (response) {
        trace.add('response');
        return response.change(headers: {'x-mw': 'active'});
      },
      errorHandler: (error, stack) {
        trace.add('error');
        return Response.internalServerError(body: 'caught: $error');
      },
    );

    // 1. Short-circuit requestHandler: responseHandler DOES run.
    trace.clear();
    final pipeline1 = const Pipeline().addMiddleware(mw).addHandler((r) {
      trace.add('inner');
      return Response.ok('inner');
    });
    final res1 = await pipeline1(Request('GET', Uri.parse('http://example.com/short-circuit')));
    assert(res1.statusCode == 200);
    assert(await res1.readAsString() == 'blocked');
    assert(res1.headers['x-mw'] == 'active');
    assert(trace.length == 2 && trace[0] == 'request' && trace[1] == 'response');

    // 2. Exception in innerHandler: errorHandler catches it, but responseHandler does NOT run.
    trace.clear();
    final pipeline2 = const Pipeline().addMiddleware(mw).addHandler((r) {
      trace.add('inner');
      throw const FormatException('bad payload');
    });
    final res2 = await pipeline2(Request('GET', Uri.parse('http://example.com/action')));
    assert(res2.statusCode == 500);
    assert(await res2.readAsString() == 'caught: FormatException: bad payload');
    assert(res2.headers['x-mw'] == null, 'responseHandler must not run on errorHandler response');
    assert(trace.length == 3 && trace[0] == 'request' && trace[1] == 'inner' && trace[2] == 'error');

    // 3. HijackException is not caught by errorHandler and unwinds directly.
    trace.clear();
    final pipeline3 = const Pipeline().addMiddleware(mw).addHandler((r) {
      trace.add('inner');
      r.hijack((channel) {});
    });
    var threwHijack = false;
    try {
      await pipeline3(Request('GET', Uri.parse('http://example.com/action')));
    } on HijackException {
      threwHijack = true;
    }
    assert(threwHijack, 'HijackException must propagate uncaught by errorHandler');
    assert(trace.length == 2 && trace[0] == 'request' && trace[1] == 'inner');
  }

  // ---------------------------------------------------------------------------
  // Contract 4: Cascade fall-through semantics.
  // Cascades on statusCodes (default 404, 405), but NOT on 500 responses or unhandled exceptions.
  // ---------------------------------------------------------------------------
  {
    final cascade = Cascade()
        .add((r) {
          if (r.url.path == 'one') return Response.ok('from one');
          return Response.notFound('miss one');
        })
        .add((r) {
          if (r.url.path == 'two') return Response.ok('from two');
          return Response.notFound('miss two');
        })
        .handler;

    // Route 'two' cascades past first handler's 404 to second handler.
    final resCascaded = await cascade(Request('GET', Uri.parse('http://example.com/two')));
    assert(resCascaded.statusCode == 200);
    assert(await resCascaded.readAsString() == 'from two');

    // Route not found in either handler returns 404 from the final handler.
    final resMiss = await cascade(Request('GET', Uri.parse('http://example.com/three')));
    assert(resMiss.statusCode == 404);
    assert(await resMiss.readAsString() == 'miss two');

    // A 500 response in the first handler stops cascading and is returned directly.
    final errorCascade = Cascade()
        .add((r) => Response.internalServerError(body: 'fail in first'))
        .add((r) => Response.ok('second'))
        .handler;
    final res500 = await errorCascade(Request('GET', Uri.parse('http://example.com/two')));
    assert(res500.statusCode == 500);
    assert(await res500.readAsString() == 'fail in first');
  }

  // ---------------------------------------------------------------------------
  // Contract 5: Headers case-insensitivity and automatic header computation.
  // Lookup is case-insensitive; keys preserve original case. String bodies
  // automatically populate content-type and content-length, whereas Stream bodies do not.
  // ---------------------------------------------------------------------------
  {
    final strRes = Response.ok('hello dart', headers: {'X-Custom-Header': 'Value1'});
    assert(strRes.headers['x-custom-header'] == 'Value1');
    assert(strRes.headers['X-CUSTOM-HEADER'] == 'Value1');
    assert(strRes.headers['X-Custom-Header'] == 'Value1');
    assert(strRes.headers.keys.contains('X-Custom-Header'));
    assert(strRes.headers['content-type'] == 'text/plain; charset=utf-8');
    assert(strRes.headers['content-length'] == '10');
    assert(strRes.contentLength == 10);

    final streamRes = Response.ok(Stream.fromIterable([[1, 2, 3, 4]]));
    assert(streamRes.headers['content-type'] == null);
    assert(streamRes.headers['content-length'] == null);
    assert(streamRes.contentLength == null);
  }

  print('ALL SHELF CONTRACT ASSERTIONS PASSED');
}

Ursprungs-Seeder

csx-seed