├── .github ├── ISSUE_TEMPLATE │ ├── shelf.md │ ├── shelf_packages_handler.md │ ├── shelf_proxy.md │ ├── shelf_router.md │ ├── shelf_router_generator.md │ ├── shelf_static.md │ ├── shelf_test_handler.md │ └── shelf_web_socket.md ├── dependabot.yml ├── labeler.yml └── workflows │ ├── dart.yml │ ├── no-response.yml │ ├── publish.yaml │ └── pull_request_label.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── mono_repo.yaml ├── pkgs ├── shelf │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ │ └── example.dart │ ├── lib │ │ ├── shelf.dart │ │ ├── shelf_io.dart │ │ └── src │ │ │ ├── body.dart │ │ │ ├── cascade.dart │ │ │ ├── handler.dart │ │ │ ├── headers.dart │ │ │ ├── hijack_exception.dart │ │ │ ├── io_server.dart │ │ │ ├── message.dart │ │ │ ├── middleware.dart │ │ │ ├── middleware │ │ │ ├── add_chunked_encoding.dart │ │ │ └── logger.dart │ │ │ ├── middleware_extensions.dart │ │ │ ├── pipeline.dart │ │ │ ├── request.dart │ │ │ ├── response.dart │ │ │ ├── server.dart │ │ │ ├── server_handler.dart │ │ │ ├── shelf_unmodifiable_map.dart │ │ │ └── util.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ ├── add_chunked_encoding_test.dart │ │ ├── cascade_test.dart │ │ ├── create_middleware_test.dart │ │ ├── headers_test.dart │ │ ├── hijack_test.dart │ │ ├── io_server_test.dart │ │ ├── log_middleware_test.dart │ │ ├── message_change_test.dart │ │ ├── message_test.dart │ │ ├── pipeline_test.dart │ │ ├── request_test.dart │ │ ├── response_test.dart │ │ ├── server_handler_test.dart │ │ ├── shelf_io_test.dart │ │ ├── ssl_certs.dart │ │ └── test_util.dart ├── shelf_packages_handler │ ├── AUTHORS │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── lib │ │ ├── shelf_packages_handler.dart │ │ └── src │ │ │ ├── dir_handler.dart │ │ │ └── package_config_handler.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ └── packages_handler_test.dart ├── shelf_proxy │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ │ └── example.dart │ ├── lib │ │ └── shelf_proxy.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ └── shelf_proxy_test.dart ├── shelf_router │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ │ └── main.dart │ ├── lib │ │ ├── shelf_router.dart │ │ └── src │ │ │ ├── route.dart │ │ │ ├── router.dart │ │ │ └── router_entry.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ ├── route_entry_test.dart │ │ └── router_test.dart ├── shelf_router_generator │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── build.yaml │ ├── dart_test.yaml │ ├── example │ │ ├── main.dart │ │ └── main.g.dart │ ├── lib │ │ ├── builder.dart │ │ └── src │ │ │ └── shelf_router_generator.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ ├── ensure_build_test.dart │ │ ├── server │ │ ├── api.dart │ │ ├── api.g.dart │ │ ├── server.dart │ │ ├── service.dart │ │ ├── service.g.dart │ │ └── unrelatedannotation.dart │ │ └── server_test.dart ├── shelf_static │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ │ ├── example.dart │ │ └── files │ │ │ ├── dart.png │ │ │ ├── favicon.ico │ │ │ └── index.html │ ├── lib │ │ ├── shelf_static.dart │ │ └── src │ │ │ ├── directory_listing.dart │ │ │ ├── static_handler.dart │ │ │ └── util.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ ├── alternative_root_test.dart │ │ ├── basic_file_test.dart │ │ ├── create_file_handler_test.dart │ │ ├── default_document_test.dart │ │ ├── directory_listing_test.dart │ │ ├── get_handler_test.dart │ │ ├── sample_test.dart │ │ ├── symbolic_link_test.dart │ │ └── test_util.dart ├── shelf_test_handler │ ├── AUTHORS │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── lib │ │ ├── shelf_test_handler.dart │ │ └── src │ │ │ ├── expectation.dart │ │ │ ├── handler.dart │ │ │ └── server.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ │ ├── handler_test.dart │ │ └── server_test.dart └── shelf_web_socket │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ └── example.dart │ ├── lib │ ├── shelf_web_socket.dart │ └── src │ │ └── web_socket_handler.dart │ ├── mono_pkg.yaml │ ├── pubspec.yaml │ └── test │ └── web_socket_test.dart └── tool └── ci.sh /.github/ISSUE_TEMPLATE/shelf.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:shelf" 3 | about: "Create a bug or file a feature request against package:shelf." 4 | labels: "package:shelf" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/shelf_packages_handler.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:shelf_packages_handler" 3 | about: "Create a bug or file a feature request against package:shelf_packages_handler." 4 | labels: "package:shelf_packages_handler" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/shelf_proxy.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:shelf_proxy" 3 | about: "Create a bug or file a feature request against package:shelf_proxy." 4 | labels: "package:shelf_proxy" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/shelf_router.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:shelf_router" 3 | about: "Create a bug or file a feature request against package:shelf_router." 4 | labels: "package:shelf_router" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/shelf_router_generator.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:shelf_router_generator" 3 | about: "Create a bug or file a feature request against package:shelf_router_generator." 4 | labels: "package:shelf_router_generator" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/shelf_static.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:shelf_static" 3 | about: "Create a bug or file a feature request against package:shelf_static." 4 | labels: "package:shelf_static" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/shelf_test_handler.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:shelf_test_handler" 3 | about: "Create a bug or file a feature request against package:shelf_test_handler." 4 | labels: "package:shelf_test_handler" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/shelf_web_socket.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "package:shelf_web_socket" 3 | about: "Create a bug or file a feature request against package:shelf_web_socket." 4 | labels: "package:shelf_web_socket" 5 | --- 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file. 2 | # See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | schedule: 9 | interval: monthly 10 | labels: 11 | - autosubmit 12 | groups: 13 | github-actions: 14 | patterns: 15 | - "*" 16 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Configuration for .github/workflows/pull_request_label.yml. 2 | 3 | 'type-infra': 4 | - changed-files: 5 | - any-glob-to-any-file: '.github/**' 6 | 7 | 'package:shelf': 8 | - changed-files: 9 | - any-glob-to-any-file: 'pkgs/shelf/**' 10 | 11 | 'package:shelf_packages_handler': 12 | - changed-files: 13 | - any-glob-to-any-file: 'pkgs/shelf_packages_handler/**' 14 | 15 | 'package:shelf_proxy': 16 | - changed-files: 17 | - any-glob-to-any-file: 'pkgs/shelf_proxy/**' 18 | 19 | 'package:shelf_router': 20 | - changed-files: 21 | - any-glob-to-any-file: 'pkgs/shelf_router/**' 22 | 23 | 'package:shelf_router_generator': 24 | - changed-files: 25 | - any-glob-to-any-file: 'pkgs/shelf_router_generator/**' 26 | 27 | 'package:shelf_static': 28 | - changed-files: 29 | - any-glob-to-any-file: 'pkgs/shelf_static/**' 30 | 31 | 'package:shelf_test_handler': 32 | - changed-files: 33 | - any-glob-to-any-file: 'pkgs/shelf_test_handler/**' 34 | 35 | 'package:shelf_web_socket': 36 | - changed-files: 37 | - any-glob-to-any-file: 'pkgs/shelf_web_socket/**' 38 | -------------------------------------------------------------------------------- /.github/workflows/no-response.yml: -------------------------------------------------------------------------------- 1 | # A workflow to close issues where the author hasn't responded to a request for 2 | # more information; see https://github.com/actions/stale. 3 | 4 | name: No Response 5 | 6 | # Run as a daily cron. 7 | on: 8 | schedule: 9 | # Every day at 8am 10 | - cron: '0 8 * * *' 11 | 12 | # All permissions not specified are set to 'none'. 13 | permissions: 14 | issues: write 15 | pull-requests: write 16 | 17 | jobs: 18 | no-response: 19 | runs-on: ubuntu-latest 20 | if: ${{ github.repository_owner == 'dart-lang' }} 21 | steps: 22 | - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 23 | with: 24 | days-before-stale: -1 25 | days-before-close: 14 26 | stale-issue-label: "needs-info" 27 | close-issue-message: > 28 | Without additional information we're not able to resolve this issue. 29 | Feel free to add more info or respond to any questions above and we 30 | can reopen the case. Thanks for your contribution! 31 | stale-pr-label: "needs-info" 32 | close-pr-message: > 33 | Without additional information we're not able to resolve this PR. 34 | Feel free to add more info or respond to any questions above. 35 | Thanks for your contribution! 36 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | # A CI configuration to auto-publish pub packages. 2 | 3 | name: Publish 4 | 5 | on: 6 | pull_request: 7 | branches: [ master ] 8 | types: [opened, synchronize, reopened, labeled, unlabeled] 9 | push: 10 | tags: [ '[A-z]+-v[0-9]+.[0-9]+.[0-9]+*' ] 11 | 12 | jobs: 13 | publish: 14 | if: ${{ github.repository_owner == 'dart-lang' }} 15 | uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main 16 | permissions: 17 | id-token: write 18 | pull-requests: write 19 | -------------------------------------------------------------------------------- /.github/workflows/pull_request_label.yml: -------------------------------------------------------------------------------- 1 | # This workflow applies labels to pull requests based on the paths that are 2 | # modified in the pull request. 3 | # 4 | # Edit `.github/labeler.yml` to configure labels. For more information, see 5 | # https://github.com/actions/labeler. 6 | 7 | name: Pull Request Labeler 8 | permissions: read-all 9 | 10 | on: 11 | pull_request_target 12 | 13 | jobs: 14 | label: 15 | permissions: 16 | pull-requests: write 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 20 | with: 21 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 22 | sync-labels: true 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dart_tool/ 2 | .packages 3 | pubspec.lock 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### Before you contribute 5 | 6 | Before we can use your code, you must sign the 7 | [Google Individual Contributor License Agreement][CLA] (CLA), which you can do 8 | online. The CLA is necessary mainly because you own the copyright to your 9 | changes, even after your contribution becomes part of our codebase, so we need 10 | your permission to use and distribute your code. We also need to be sure of 11 | various other things—for instance that you'll tell us if you know that your code 12 | infringes on other people's patents. You don't have to sign the CLA until after 13 | you've submitted your code for review and a member has approved it, but you must 14 | do it before we can put your code into our codebase. 15 | 16 | Before you start working on a larger contribution, you should get in touch with 17 | us first through the issue tracker with your idea so that we can help out and 18 | possibly guide you. Coordinating up front makes it much easier to avoid 19 | frustration later on. 20 | 21 | [CLA]: https://cla.developers.google.com/about/google-individual 22 | 23 | ### Code reviews 24 | 25 | All submissions, including submissions by project members, require review. We 26 | recommend [forking the repository][fork], making changes in your fork, and 27 | [sending us a pull request][pr] so we can review the changes and merge them into 28 | this repository. 29 | 30 | [fork]: https://help.github.com/articles/about-forks/ 31 | [pr]: https://help.github.com/articles/creating-a-pull-request/ 32 | 33 | Functional changes will require tests to be added or changed. The tests live in 34 | the `test/` directory, and are run with `pub run test`. If you need to create 35 | new tests, use the existing tests as a guideline for what they should look like. 36 | 37 | Before you send your pull request, make sure all the tests pass! 38 | 39 | ### File headers 40 | 41 | All files in the project must start with the following header. 42 | 43 | // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file 44 | // for details. All rights reserved. Use of this source code is governed by a 45 | // BSD-style license that can be found in the LICENSE file. 46 | 47 | ### The small print 48 | 49 | Contributions made by corporations are covered by a different agreement than the 50 | one above, the 51 | [Software Grant and Corporate Contributor License Agreement][CCLA]. 52 | 53 | [CCLA]: https://developers.google.com/open-source/cla/corporate 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/dart-lang/shelf/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/shelf/actions?query=workflow%3A"Dart+CI"+branch%3Amaster) 2 | 3 | ## About Shelf 4 | 5 | Shelf makes it easy to create and compose web servers and parts of web servers. How? 6 | 7 | - Expose a small set of simple types. 8 | - Map server logic into a simple function: a single argument for the request, the response is the return value. 9 | - Trivially mix and match synchronous and asynchronous processing. 10 | - Flexibility to return a simple string or a byte stream with the same model. 11 | 12 | It was inspired by [Connect](https://github.com/senchalabs/connect) for NodeJS 13 | and [Rack](https://github.com/rack/rack) for Ruby. 14 | 15 | See the [package:shelf readme](pkgs/shelf/) for more information. 16 | 17 | ## Packages 18 | 19 | | Package | Description | Issues | Version | 20 | | --- | --- | --- | --- | 21 | | [shelf](pkgs/shelf/) | A model for web server middleware that encourages composition and easy reuse. | [![issues](https://img.shields.io/badge/shelf-4774bc)][shelf_issues] | [![pub package](https://img.shields.io/pub/v/shelf.svg)](https://pub.dev/packages/shelf) | 22 | | [shelf_packages_handler](pkgs/shelf_packages_handler/) | A shelf handler for serving a `packages/` directory. | [![issues](https://img.shields.io/badge/shelf__packages__handler-4774bc)][shelf_packages_handler_issues] | [![pub package](https://img.shields.io/pub/v/shelf_packages_handler.svg)](https://pub.dev/packages/shelf_packages_handler) | 23 | | [shelf_proxy](pkgs/shelf_proxy/) | A shelf handler for proxying HTTP requests to another server. | [![issues](https://img.shields.io/badge/shelf__proxy-4774bc)][shelf_proxy_issues] | [![pub package](https://img.shields.io/pub/v/shelf_proxy.svg)](https://pub.dev/packages/shelf_proxy) | 24 | | [shelf_router](pkgs/shelf_router/) | A convenient request router for the shelf web-framework, with support for URL-parameters, nested routers and routers generated from source annotations. | [![issues](https://img.shields.io/badge/shelf__router-4774bc)][shelf_router_issues] | [![pub package](https://img.shields.io/pub/v/shelf_router.svg)](https://pub.dev/packages/shelf_router) | 25 | | [shelf_router_generator](pkgs/shelf_router_generator/) | A package:build-compatible builder for generating request routers for the shelf web-framework based on source annotations. | [![issues](https://img.shields.io/badge/shelf__router__generator-4774bc)][shelf_router_generator_issues] | [![pub package](https://img.shields.io/pub/v/shelf_router_generator.svg)](https://pub.dev/packages/shelf_router_generator) | 26 | | [shelf_static](pkgs/shelf_static/) | Static file server support for the shelf package and ecosystem. | [![issues](https://img.shields.io/badge/shelf__static-4774bc)][shelf_static_issues] | [![pub package](https://img.shields.io/pub/v/shelf_static.svg)](https://pub.dev/packages/shelf_static) | 27 | | [shelf_test_handler](pkgs/shelf_test_handler/) | A Shelf handler that makes it easy to test HTTP interactions. | [![issues](https://img.shields.io/badge/shelf__test__handler-4774bc)][shelf_test_handler_issues] | [![pub package](https://img.shields.io/pub/v/shelf_test_handler.svg)](https://pub.dev/packages/shelf_test_handler) | 28 | | [shelf_web_socket](pkgs/shelf_web_socket/) | A shelf handler that wires up a listener for every connection. | [![issues](https://img.shields.io/badge/shelf__web__socket-4774bc)][shelf_web_socket_issues] | [![pub package](https://img.shields.io/pub/v/shelf_web_socket.svg)](https://pub.dev/packages/shelf_web_socket) | 29 | 30 | [shelf_issues]: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf 31 | [shelf_packages_handler_issues]: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_packages_handler 32 | [shelf_proxy_issues]: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_proxy 33 | [shelf_router_issues]: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_router 34 | [shelf_router_generator_issues]: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_router_generator 35 | [shelf_static_issues]: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_static 36 | [shelf_test_handler_issues]: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_test_handler 37 | [shelf_web_socket_issues]: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_web_socket 38 | 39 | ## Publishing automation 40 | 41 | For information about our publishing automation and release process, see 42 | https://github.com/dart-lang/ecosystem/wiki/Publishing-automation. 43 | -------------------------------------------------------------------------------- /mono_repo.yaml: -------------------------------------------------------------------------------- 1 | # See https://github.com/google/mono_repo.dart for details on this file 2 | self_validate: analyze_and_format 3 | 4 | github: 5 | cron: "0 0 * * 0" 6 | 7 | merge_stages: 8 | - analyze_and_format 9 | - unit_test 10 | -------------------------------------------------------------------------------- /pkgs/shelf/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pkgs/shelf/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # https://dart.dev/guides/language/analysis-options 2 | 3 | include: package:dart_flutter_team_lints/analysis_options.yaml 4 | 5 | analyzer: 6 | language: 7 | strict-raw-types: true 8 | 9 | linter: 10 | rules: 11 | - avoid_unused_constructor_parameters 12 | - cancel_subscriptions 13 | - literal_only_boolean_expressions 14 | - missing_whitespace_between_adjacent_strings 15 | - no_adjacent_strings_in_list 16 | - no_runtimeType_toString 17 | - unnecessary_await_in_return 18 | -------------------------------------------------------------------------------- /pkgs/shelf/example/example.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:shelf/shelf.dart'; 6 | import 'package:shelf/shelf_io.dart' as shelf_io; 7 | 8 | void main() async { 9 | var handler = 10 | const Pipeline().addMiddleware(logRequests()).addHandler(_echoRequest); 11 | 12 | var server = await shelf_io.serve(handler, 'localhost', 8080); 13 | 14 | // Enable content compression 15 | server.autoCompress = true; 16 | 17 | print('Serving at http://${server.address.host}:${server.port}'); 18 | } 19 | 20 | Response _echoRequest(Request request) => 21 | Response.ok('Request for "${request.url}"'); 22 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/shelf.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'src/cascade.dart' show Cascade; 6 | export 'src/handler.dart' show Handler; 7 | export 'src/hijack_exception.dart' show HijackException; 8 | export 'src/middleware.dart' show Middleware, createMiddleware; 9 | export 'src/middleware/add_chunked_encoding.dart' show addChunkedEncoding; 10 | export 'src/middleware/logger.dart' show logRequests; 11 | export 'src/middleware_extensions.dart' show MiddlewareExtensions; 12 | export 'src/pipeline.dart' show Pipeline; 13 | export 'src/request.dart' show Request; 14 | export 'src/response.dart' show Response; 15 | export 'src/server.dart' show Server; 16 | // ignore: deprecated_member_use_from_same_package 17 | export 'src/server_handler.dart' show ServerHandler; 18 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/body.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | 8 | import 'message.dart'; 9 | 10 | /// The body of a request or response. 11 | /// 12 | /// This tracks whether the body has been read. It's separate from [Message] 13 | /// because the message may be changed with [Message.change], but each instance 14 | /// should share a notion of whether the body was read. 15 | class Body { 16 | /// The contents of the message body. 17 | /// 18 | /// This will be `null` after [read] is called. 19 | Stream>? _stream; 20 | 21 | /// The encoding used to encode the stream returned by [read], or `null` if no 22 | /// encoding was used. 23 | final Encoding? encoding; 24 | 25 | /// The length of the stream returned by [read], or `null` if that can't be 26 | /// determined efficiently. 27 | final int? contentLength; 28 | 29 | Body._(this._stream, this.encoding, this.contentLength); 30 | 31 | /// Converts [body] to a byte stream and wraps it in a [Body]. 32 | /// 33 | /// [body] may be either a [Body], a [String], a `List`, a 34 | /// `Stream>`, or `null`. If it's a [String], [encoding] will be 35 | /// used to convert it to a `Stream>`. 36 | factory Body(Object? body, [Encoding? encoding]) { 37 | if (body is Body) return body; 38 | 39 | Stream> stream; 40 | int? contentLength; 41 | if (body == null) { 42 | contentLength = 0; 43 | stream = Stream.fromIterable([]); 44 | } else if (body is String) { 45 | if (encoding == null) { 46 | var encoded = utf8.encode(body); 47 | // If the text is plain ASCII, don't modify the encoding. This means 48 | // that an encoding of "text/plain" will stay put. 49 | if (!_isPlainAscii(encoded, body.length)) encoding = utf8; 50 | contentLength = encoded.length; 51 | stream = Stream.fromIterable([encoded]); 52 | } else { 53 | var encoded = encoding.encode(body); 54 | contentLength = encoded.length; 55 | stream = Stream.fromIterable([encoded]); 56 | } 57 | } else if (body is List) { 58 | // Avoid performance overhead from an unnecessary cast. 59 | contentLength = body.length; 60 | stream = Stream.value(body); 61 | } else if (body is List) { 62 | contentLength = body.length; 63 | stream = Stream.value(body.cast()); 64 | } else if (body is Stream>) { 65 | // Avoid performance overhead from an unnecessary cast. 66 | stream = body; 67 | } else if (body is Stream) { 68 | stream = body.cast(); 69 | } else { 70 | throw ArgumentError('Response body "$body" must be a String or a ' 71 | 'Stream.'); 72 | } 73 | 74 | return Body._(stream, encoding, contentLength); 75 | } 76 | 77 | /// Returns whether [bytes] is plain ASCII. 78 | /// 79 | /// [codeUnits] is the number of code units in the original string. 80 | static bool _isPlainAscii(List bytes, int codeUnits) { 81 | // Most non-ASCII code units will produce multiple bytes and make the text 82 | // longer. 83 | if (bytes.length != codeUnits) return false; 84 | 85 | // Non-ASCII code units between U+0080 and U+009F produce 8-bit characters 86 | // with the high bit set. 87 | return bytes.every((byte) => byte & 0x80 == 0); 88 | } 89 | 90 | /// Returns a [Stream] representing the body. 91 | /// 92 | /// Can only be called once. 93 | Stream> read() { 94 | if (_stream == null) { 95 | throw StateError("The 'read' method can only be called once on a " 96 | 'shelf.Request/shelf.Response object.'); 97 | } 98 | var stream = _stream!; 99 | _stream = null; 100 | return stream; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/cascade.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'handler.dart'; 8 | import 'response.dart'; 9 | 10 | /// A typedef for [Cascade._shouldCascade]. 11 | typedef _ShouldCascade = bool Function(Response response); 12 | 13 | /// A helper that calls several handlers in sequence and returns the first 14 | /// acceptable response. 15 | /// 16 | /// By default, a response is considered acceptable if it has a status other 17 | /// than 404 or 405; other statuses indicate that the handler understood the 18 | /// request. 19 | /// 20 | /// If all handlers return unacceptable responses, the final response will be 21 | /// returned. 22 | /// 23 | /// ```dart 24 | /// var handler = new Cascade() 25 | /// .add(webSocketHandler) 26 | /// .add(staticFileHandler) 27 | /// .add(application) 28 | /// .handler; 29 | /// ``` 30 | class Cascade { 31 | /// The function used to determine whether the cascade should continue on to 32 | /// the next handler. 33 | final _ShouldCascade _shouldCascade; 34 | 35 | final Cascade? _parent; 36 | final Handler? _handler; 37 | 38 | /// Creates a new, empty cascade. 39 | /// 40 | /// If [statusCodes] is passed, responses with those status codes are 41 | /// considered unacceptable. If [shouldCascade] is passed, responses for which 42 | /// it returns `true` are considered unacceptable. [statusCodes] and 43 | /// [shouldCascade] may not both be passed. 44 | Cascade({Iterable? statusCodes, bool Function(Response)? shouldCascade}) 45 | : _shouldCascade = _computeShouldCascade(statusCodes, shouldCascade), 46 | _parent = null, 47 | _handler = null { 48 | if (statusCodes != null && shouldCascade != null) { 49 | throw ArgumentError('statusCodes and shouldCascade may not both be ' 50 | 'passed.'); 51 | } 52 | } 53 | 54 | Cascade._(this._parent, this._handler, this._shouldCascade); 55 | 56 | /// Returns a new cascade with [handler] added to the end. 57 | /// 58 | /// [handler] will only be called if all previous handlers in the cascade 59 | /// return unacceptable responses. 60 | Cascade add(Handler handler) => Cascade._(this, handler, _shouldCascade); 61 | 62 | /// Exposes this cascade as a single handler. 63 | /// 64 | /// This handler will call each inner handler in the cascade until one returns 65 | /// an acceptable response, and return that. If no inner handlers return an 66 | /// acceptable response, this will return the final response. 67 | Handler get handler { 68 | final handler = _handler; 69 | if (handler == null) { 70 | throw StateError("Can't get a handler for a cascade with no inner " 71 | 'handlers.'); 72 | } 73 | 74 | return (request) { 75 | if (_parent!._handler == null) return handler(request); 76 | return Future.sync(() => _parent.handler(request)).then((response) { 77 | if (_shouldCascade(response)) return handler(request); 78 | return response; 79 | }); 80 | }; 81 | } 82 | } 83 | 84 | /// Computes the [Cascade._shouldCascade] function based on the user's 85 | /// parameters. 86 | _ShouldCascade _computeShouldCascade( 87 | Iterable? statusCodes, bool Function(Response)? shouldCascade) { 88 | if (shouldCascade != null) return shouldCascade; 89 | statusCodes ??= [404, 405]; 90 | final statusCodeSet = statusCodes.toSet(); 91 | return (response) => statusCodeSet.contains(response.statusCode); 92 | } 93 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | import 'dart:async'; 5 | 6 | import 'request.dart'; 7 | import 'response.dart'; 8 | 9 | /// A function which handles a [Request]. 10 | /// 11 | /// For example a static file handler may read the requested URI from the 12 | /// filesystem and return it as the body of the [Response]. 13 | /// 14 | /// A [Handler] which wraps one or more other handlers to perform pre or post 15 | /// processing is known as a "middleware". 16 | /// 17 | /// A [Handler] may receive a request directly from an HTTP server or it 18 | /// may have been touched by other middleware. Similarly the response may be 19 | /// directly returned by an HTTP server or have further processing done by other 20 | /// middleware. 21 | typedef Handler = FutureOr Function(Request request); 22 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/headers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:collection'; 6 | 7 | import 'package:http_parser/http_parser.dart'; 8 | 9 | import 'util.dart'; 10 | 11 | final _emptyHeaders = Headers._empty(); 12 | 13 | /// Unmodifiable, key-insensitive header map. 14 | class Headers extends UnmodifiableMapView> { 15 | late final Map singleValues = UnmodifiableMapView( 16 | CaseInsensitiveMap.from( 17 | map((key, value) => MapEntry(key, joinHeaderValues(value)!)), 18 | ), 19 | ); 20 | 21 | factory Headers.from(Map>? values) { 22 | if (values == null || values.isEmpty) { 23 | return _emptyHeaders; 24 | } else if (values is Headers) { 25 | return values; 26 | } else { 27 | return Headers._(values.entries); 28 | } 29 | } 30 | 31 | factory Headers.fromEntries( 32 | Iterable>>? entries, 33 | ) { 34 | if (entries == null || (entries is List && entries.isEmpty)) { 35 | return _emptyHeaders; 36 | } else { 37 | return Headers._(entries); 38 | } 39 | } 40 | 41 | Headers._(Iterable>> entries) 42 | : super( 43 | CaseInsensitiveMap.fromEntries( 44 | entries 45 | .where((e) => e.value.isNotEmpty) 46 | .map((e) => MapEntry(e.key, List.unmodifiable(e.value))), 47 | ), 48 | ); 49 | 50 | Headers._empty() : super(const {}); 51 | 52 | factory Headers.empty() => _emptyHeaders; 53 | } 54 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/hijack_exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'request.dart'; 6 | 7 | /// An exception used to indicate that a request has been hijacked. 8 | /// 9 | /// This shouldn't be captured by any code other than the Shelf adapter that 10 | /// created the hijackable request. Middleware that captures exceptions should 11 | /// make sure to pass on HijackExceptions. 12 | /// 13 | /// See also [Request.hijack]. 14 | class HijackException implements Exception { 15 | const HijackException(); 16 | 17 | @override 18 | String toString() => 19 | "A shelf request's underlying data stream was hijacked.\n" 20 | 'This exception is used for control flow and should only be handled by a ' 21 | 'Shelf adapter.'; 22 | } 23 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/io_server.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import '../shelf_io.dart'; 9 | import 'handler.dart'; 10 | import 'server.dart'; 11 | 12 | /// A [Server] backed by a `dart:io` [HttpServer]. 13 | class IOServer implements Server { 14 | /// The underlying [HttpServer]. 15 | final HttpServer server; 16 | 17 | /// Whether [mount] has been called. 18 | bool _mounted = false; 19 | 20 | @override 21 | Uri get url { 22 | if (server.address.isLoopback) { 23 | return Uri(scheme: 'http', host: 'localhost', port: server.port); 24 | } 25 | 26 | // IPv6 addresses in URLs need to be enclosed in square brackets to avoid 27 | // URL ambiguity with the ":" in the address. 28 | if (server.address.type == InternetAddressType.IPv6) { 29 | return Uri( 30 | scheme: 'http', 31 | host: '[${server.address.address}]', 32 | port: server.port); 33 | } 34 | 35 | return Uri(scheme: 'http', host: server.address.address, port: server.port); 36 | } 37 | 38 | /// Calls [HttpServer.bind] and wraps the result in an [IOServer]. 39 | static Future bind(Object address, int port, {int? backlog}) async { 40 | backlog ??= 0; 41 | var server = await HttpServer.bind(address, port, backlog: backlog); 42 | return IOServer(server); 43 | } 44 | 45 | IOServer(this.server); 46 | 47 | @override 48 | void mount(Handler handler) { 49 | if (_mounted) { 50 | throw StateError("Can't mount two handlers for the same server."); 51 | } 52 | _mounted = true; 53 | 54 | serveRequests(server, handler); 55 | } 56 | 57 | @override 58 | Future close() => server.close(); 59 | } 60 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/middleware.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'handler.dart'; 8 | import 'hijack_exception.dart'; 9 | import 'request.dart'; 10 | import 'response.dart'; 11 | 12 | /// A function which creates a new [Handler] by wrapping a [Handler]. 13 | /// 14 | /// You can extend the functions of a [Handler] by wrapping it in 15 | /// [Middleware] that can intercept and process a request before it it sent 16 | /// to a handler, a response after it is sent by a handler, or both. 17 | /// 18 | /// Because [Middleware] consumes a [Handler] and returns a new 19 | /// [Handler], multiple [Middleware] instances can be composed 20 | /// together to offer rich functionality. 21 | /// 22 | /// Common uses for middleware include caching, logging, and authentication. 23 | /// 24 | /// Middleware that captures exceptions should be sure to pass 25 | /// [HijackException]s on without modification. 26 | /// 27 | /// A simple [Middleware] can be created using [createMiddleware]. 28 | typedef Middleware = Handler Function(Handler innerHandler); 29 | 30 | /// Creates a [Middleware] using the provided functions. 31 | /// 32 | /// If provided, [requestHandler] receives a [Request]. It can respond to 33 | /// the request by returning a [Response] or `Future`. 34 | /// [requestHandler] can also return `null` for some or all requests in which 35 | /// case the request is sent to the inner [Handler]. 36 | /// 37 | /// If provided, [responseHandler] is called with the [Response] generated 38 | /// by the inner [Handler]. Responses generated by [requestHandler] are not 39 | /// sent to [responseHandler]. 40 | /// 41 | /// [responseHandler] should return either a [Response] or 42 | /// `Future`. It may return the response parameter it receives or 43 | /// create a new response object. 44 | /// 45 | /// If provided, [errorHandler] receives errors thrown by the inner handler. It 46 | /// does not receive errors thrown by [requestHandler] or [responseHandler], nor 47 | /// does it receive [HijackException]s. It can either return a new response or 48 | /// throw an error. 49 | Middleware createMiddleware({ 50 | FutureOr Function(Request)? requestHandler, 51 | FutureOr Function(Response)? responseHandler, 52 | FutureOr Function(Object error, StackTrace)? errorHandler, 53 | }) { 54 | requestHandler ??= (request) => null; 55 | responseHandler ??= (response) => response; 56 | 57 | FutureOr Function(Object, StackTrace)? onError; 58 | if (errorHandler != null) { 59 | onError = (error, stackTrace) { 60 | if (error is HijackException) throw error; 61 | return errorHandler(error, stackTrace); 62 | }; 63 | } 64 | 65 | return (Handler innerHandler) { 66 | return (request) { 67 | return Future.sync(() => requestHandler!(request)).then((response) { 68 | if (response != null) return response; 69 | 70 | return Future.sync(() => innerHandler(request)) 71 | .then((response) => responseHandler!(response), onError: onError); 72 | }); 73 | }; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/middleware/add_chunked_encoding.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:collection/collection.dart'; 6 | import 'package:http_parser/http_parser.dart'; 7 | 8 | import '../middleware.dart'; 9 | 10 | /// Middleware that adds 11 | /// [chunked transfer coding](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1) 12 | /// to a responses if none of the following conditions are true: 13 | /// 14 | /// * A Content-Length header is provided. 15 | /// * The Content-Type header indicates the MIME type `multipart/byteranges`. 16 | /// * The Transfer-Encoding header already includes the `chunked` coding. 17 | /// 18 | /// This is intended for use by 19 | /// [Shelf adapters](https://pub.dev/packages/shelf#adapters) rather than 20 | /// end-users. 21 | final addChunkedEncoding = createMiddleware(responseHandler: (response) { 22 | if (response.contentLength != null) return response; 23 | if (response.statusCode < 200) return response; 24 | if (response.statusCode == 204) return response; 25 | if (response.statusCode == 304) return response; 26 | if (response.mimeType == 'multipart/byteranges') return response; 27 | 28 | // We only check the last coding here because HTTP requires that the chunked 29 | // encoding be listed last. 30 | var coding = response.headers['transfer-encoding']; 31 | if (coding != null && !equalsIgnoreAsciiCase(coding, 'identity')) { 32 | return response; 33 | } 34 | 35 | return response.change( 36 | headers: {'transfer-encoding': 'chunked'}, 37 | body: chunkedCoding.encoder.bind(response.read())); 38 | }); 39 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/middleware/logger.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:stack_trace/stack_trace.dart'; 8 | 9 | import '../hijack_exception.dart'; 10 | import '../middleware.dart'; 11 | 12 | /// Middleware which prints the time of the request, the elapsed time for the 13 | /// inner handlers, the response's status code and the request URI. 14 | /// 15 | /// If [logger] is passed, it's called for each request. The `msg` parameter is 16 | /// a formatted string that includes the request time, duration, request method, 17 | /// and requested path. When an exception is thrown, it also includes the 18 | /// exception's string and stack trace; otherwise, it includes the status code. 19 | /// The `isError` parameter indicates whether the message is caused by an error. 20 | /// 21 | /// If [logger] is not passed, the message is just passed to [print]. 22 | Middleware logRequests({void Function(String message, bool isError)? logger}) => 23 | (innerHandler) { 24 | final theLogger = logger ?? _defaultLogger; 25 | 26 | return (request) { 27 | var startTime = DateTime.now(); 28 | var watch = Stopwatch()..start(); 29 | 30 | return Future.sync(() => innerHandler(request)).then((response) { 31 | var msg = _message(startTime, response.statusCode, 32 | request.requestedUri, request.method, watch.elapsed); 33 | 34 | theLogger(msg, false); 35 | 36 | return response; 37 | }, onError: (Object error, StackTrace stackTrace) { 38 | if (error is HijackException) throw error; 39 | 40 | var msg = _errorMessage(startTime, request.requestedUri, 41 | request.method, watch.elapsed, error, stackTrace); 42 | 43 | theLogger(msg, true); 44 | 45 | // ignore: only_throw_errors 46 | throw error; 47 | }); 48 | }; 49 | }; 50 | 51 | String _formatQuery(String query) { 52 | return query == '' ? '' : '?$query'; 53 | } 54 | 55 | String _message(DateTime requestTime, int statusCode, Uri requestedUri, 56 | String method, Duration elapsedTime) { 57 | return '${requestTime.toIso8601String()} ' 58 | '${elapsedTime.toString().padLeft(15)} ' 59 | '${method.padRight(7)} [$statusCode] ' // 7 - longest standard HTTP method 60 | '${requestedUri.path}${_formatQuery(requestedUri.query)}'; 61 | } 62 | 63 | String _errorMessage(DateTime requestTime, Uri requestedUri, String method, 64 | Duration elapsedTime, Object error, StackTrace? stack) { 65 | var chain = Chain.current(); 66 | if (stack != null) { 67 | chain = Chain.forTrace(stack) 68 | .foldFrames((frame) => frame.isCore || frame.package == 'shelf') 69 | .terse; 70 | } 71 | 72 | var msg = '$requestTime\t$elapsedTime\t$method\t${requestedUri.path}' 73 | '${_formatQuery(requestedUri.query)}\n$error'; 74 | 75 | return '$msg\n$chain'; 76 | } 77 | 78 | void _defaultLogger(String msg, bool isError) { 79 | if (isError) { 80 | print('[ERROR] $msg'); 81 | } else { 82 | print(msg); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/middleware_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'handler.dart'; 2 | import 'middleware.dart'; 3 | import 'pipeline.dart'; 4 | 5 | /// Extensions on [Middleware] to aid in composing [Middleware] and [Handler]s. 6 | /// 7 | /// These members can be used in place of [Pipeline]. 8 | extension MiddlewareExtensions on Middleware { 9 | /// Merges `this` and [other] into a new [Middleware]. 10 | Middleware addMiddleware(Middleware other) => 11 | (Handler handler) => this(other(handler)); 12 | 13 | /// Merges `this` and [handler] into a new [Handler]. 14 | Handler addHandler(Handler handler) => this(handler); 15 | } 16 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/pipeline.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'handler.dart'; 6 | import 'middleware.dart'; 7 | import 'request.dart'; 8 | 9 | /// A helper that makes it easy to compose a set of [Middleware] and a 10 | /// [Handler]. 11 | /// 12 | /// ```dart 13 | /// var handler = const Pipeline() 14 | /// .addMiddleware(loggingMiddleware) 15 | /// .addMiddleware(cachingMiddleware) 16 | /// .addHandler(application); 17 | /// ``` 18 | /// 19 | /// Note: this package also provides `addMiddleware` and `addHandler` extensions 20 | /// members on [Middleware], which may be easier to use. 21 | class Pipeline { 22 | const Pipeline(); 23 | 24 | /// Returns a new [Pipeline] with [middleware] added to the existing set of 25 | /// [Middleware]. 26 | /// 27 | /// [middleware] will be the last [Middleware] to process a request and 28 | /// the first to process a response. 29 | Pipeline addMiddleware(Middleware middleware) => 30 | _Pipeline(middleware, addHandler); 31 | 32 | /// Returns a new [Handler] with [handler] as the final processor of a 33 | /// [Request] if all of the middleware in the pipeline have passed the request 34 | /// through. 35 | Handler addHandler(Handler handler) => handler; 36 | 37 | /// Exposes this pipeline of [Middleware] as a single middleware instance. 38 | Middleware get middleware => addHandler; 39 | } 40 | 41 | class _Pipeline extends Pipeline { 42 | final Middleware _middleware; 43 | final Middleware _parent; 44 | 45 | _Pipeline(this._middleware, this._parent); 46 | 47 | @override 48 | Handler addHandler(Handler handler) => _parent(_middleware(handler)); 49 | } 50 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/server.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'handler.dart'; 8 | 9 | /// An [adapter][] with a concrete URL. 10 | /// 11 | /// [adapter]: https://github.com/dart-lang/shelf#adapters 12 | /// 13 | /// The most basic definition of "adapter" includes any function that passes 14 | /// incoming requests to a [Handler] and passes its responses to some external 15 | /// client. However, in practice, most adapters are also *servers*—that is, 16 | /// they're serving requests that are made to a certain well-known URL. 17 | /// 18 | /// This interface represents those servers in a general way. It's useful for 19 | /// writing code that needs to know its own URL without tightly coupling that 20 | /// code to a single server implementation. 21 | /// 22 | /// There are two built-in implementations of this interface. You can create a 23 | /// server backed by `dart:io` using `IOServer`, or you can create a server 24 | /// that's backed by a normal [Handler] using `ServerHandler`. 25 | /// 26 | /// Implementations of this interface are responsible for ensuring that the 27 | /// members work as documented. 28 | abstract class Server { 29 | /// The URL of the server. 30 | /// 31 | /// Requests to this URL or any URL beneath it are handled by the handler 32 | /// passed to [mount]. If [mount] hasn't yet been called, the requests wait 33 | /// until it is. If [close] has been called, the handler will not be invoked; 34 | /// otherwise, the behavior is implementation-dependent. 35 | Uri get url; 36 | 37 | /// Mounts [handler] as the base handler for this server. 38 | /// 39 | /// All requests to [url] or and URLs beneath it will be sent to [handler] 40 | /// until [close] is called. 41 | /// 42 | /// Throws a [StateError] if there's already a handler mounted. 43 | void mount(Handler handler); 44 | 45 | /// Closes the server and returns a Future that completes when all resources 46 | /// are released. 47 | /// 48 | /// Once this is called, no more requests will be passed to this server's 49 | /// handler. Otherwise, the cleanup behavior is implementation-dependent. 50 | Future close(); 51 | } 52 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/server_handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:async/async.dart'; 8 | 9 | import 'handler.dart'; 10 | import 'request.dart'; 11 | import 'response.dart'; 12 | import 'server.dart'; 13 | 14 | /// A connected pair of a [Server] and a [Handler]. 15 | /// 16 | /// Requests to the handler are sent to the server's mounted handler once it's 17 | /// available. This is used to expose a virtual [Server] that's actually one 18 | /// part of a larger URL-space. 19 | @Deprecated('Do not use. If you have a use case for this class add a comment ' 20 | 'at https://github.com/dart-lang/shelf/issues/205') 21 | class ServerHandler { 22 | /// The server. 23 | /// 24 | /// Once this has a handler mounted, it's passed all requests to [handler] 25 | /// until this server is closed. 26 | Server get server => _server; 27 | final _HandlerServer _server; 28 | 29 | /// The handler. 30 | /// 31 | /// This passes requests to [server]'s handler. If that handler isn't mounted 32 | /// yet, the requests are handled once it is. 33 | Handler get handler => _onRequest; 34 | 35 | /// Creates a new connected pair of a [Server] with the given [url] and a 36 | /// [Handler]. 37 | /// 38 | /// The caller is responsible for ensuring that requests to [url] or any URL 39 | /// beneath it are handled by [handler]. 40 | /// 41 | /// If [onClose] is passed, it's called when [server] is closed. It may return 42 | /// a [Future] or `null`; its return value is returned by [Server.close]. 43 | ServerHandler(Uri url, {Future? Function()? onClose}) 44 | : _server = _HandlerServer(url, onClose); 45 | 46 | /// Pipes requests to [server]'s handler. 47 | FutureOr _onRequest(Request request) { 48 | if (_server._closeMemo.hasRun) { 49 | throw StateError('Request received after the server was closed.'); 50 | } 51 | 52 | if (_server._handler != null) return _server._handler!(request); 53 | 54 | // Avoid async/await so that the common case of a handler already being 55 | // mounted doesn't involve any extra asynchronous delays. 56 | return _server._onMounted.then((_) => _server._handler!(request)); 57 | } 58 | } 59 | 60 | // ignore: deprecated_member_use_from_same_package 61 | /// The [Server] returned by [ServerHandler]. 62 | class _HandlerServer implements Server { 63 | @override 64 | final Uri url; 65 | 66 | /// The callback to call when [close] is called, or `null`. 67 | final ZoneCallback? _onClose; 68 | 69 | /// The mounted handler. 70 | /// 71 | /// This is `null` until [mount] is called. 72 | Handler? _handler; 73 | 74 | /// A future that fires once [mount] has been called. 75 | Future get _onMounted => _onMountedCompleter.future; 76 | final _onMountedCompleter = Completer(); 77 | 78 | _HandlerServer(this.url, this._onClose); 79 | 80 | @override 81 | void mount(Handler handler) { 82 | if (_handler != null) { 83 | throw StateError("Can't mount two handlers for the same server."); 84 | } 85 | 86 | _handler = handler; 87 | _onMountedCompleter.complete(); 88 | } 89 | 90 | @override 91 | Future close() => _closeMemo.runOnce(() { 92 | return _onClose == null ? null : _onClose(); 93 | }); 94 | final _closeMemo = AsyncMemoizer(); 95 | } 96 | -------------------------------------------------------------------------------- /pkgs/shelf/lib/src/shelf_unmodifiable_map.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:collection'; 6 | 7 | import 'package:http_parser/http_parser.dart'; 8 | 9 | /// A simple wrapper over [UnmodifiableMapView] which avoids re-wrapping itself. 10 | class ShelfUnmodifiableMap extends UnmodifiableMapView { 11 | /// `true` if the key values are already lowercase. 12 | final bool _ignoreKeyCase; 13 | 14 | /// If [source] is a [ShelfUnmodifiableMap] with matching [ignoreKeyCase], 15 | /// then [source] is returned. 16 | /// 17 | /// If [source] is `null` it is treated like an empty map. 18 | /// 19 | /// If [ignoreKeyCase] is `true`, the keys will have case-insensitive access. 20 | /// 21 | /// [source] is copied to a new [Map] to ensure changes to the parameter value 22 | /// after constructions are not reflected. 23 | factory ShelfUnmodifiableMap( 24 | Map? source, { 25 | bool ignoreKeyCase = false, 26 | }) { 27 | if (source is ShelfUnmodifiableMap && 28 | // !ignoreKeyCase: no transformation of the input is required 29 | // source._ignoreKeyCase: the input cannot be transformed any more 30 | (!ignoreKeyCase || source._ignoreKeyCase)) { 31 | return source; 32 | } 33 | 34 | if (source == null || source.isEmpty) { 35 | return const _EmptyShelfUnmodifiableMap(); 36 | } 37 | 38 | if (ignoreKeyCase) { 39 | source = CaseInsensitiveMap.from(source); 40 | } else { 41 | source = Map.from(source); 42 | } 43 | 44 | return ShelfUnmodifiableMap._(source, ignoreKeyCase); 45 | } 46 | 47 | /// Returns an empty [ShelfUnmodifiableMap]. 48 | const factory ShelfUnmodifiableMap.empty() = _EmptyShelfUnmodifiableMap; 49 | 50 | ShelfUnmodifiableMap._(super.source, this._ignoreKeyCase); 51 | } 52 | 53 | /// A const implementation of an empty [ShelfUnmodifiableMap]. 54 | class _EmptyShelfUnmodifiableMap extends MapView 55 | implements ShelfUnmodifiableMap { 56 | @override 57 | bool get _ignoreKeyCase => true; 58 | 59 | const _EmptyShelfUnmodifiableMap() : super(const {}); 60 | 61 | // Override modifier methods that care about the type of key they use so that 62 | // when V is Null, they throw UnsupportedErrors instead of type errors. 63 | @override 64 | void operator []=(String key, Object value) => super[key] = const Object(); 65 | 66 | @override 67 | void addAll(Map other) => super.addAll({}); 68 | 69 | @override 70 | Object putIfAbsent(String key, Object Function() ifAbsent) => 71 | super.putIfAbsent(key, () => const Object()); 72 | 73 | @override 74 | // TODO: https://github.com/dart-lang/sdk/issues/44264 75 | Object remove(Object? key) => super.remove(key)!; 76 | } 77 | -------------------------------------------------------------------------------- /pkgs/shelf/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | # TODO: Get tests passing on windows 16 | - test: --test-randomize-ordering-seed=random -p chrome 17 | os: 18 | - linux 19 | - windows 20 | - test: --test-randomize-ordering-seed=random -p chrome -c dart2wasm 21 | sdk: dev 22 | -------------------------------------------------------------------------------- /pkgs/shelf/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shelf 2 | version: 1.4.3-wip 3 | description: > 4 | A model for web server middleware that encourages composition and easy reuse. 5 | repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf 6 | issue_tracker: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf 7 | 8 | topics: 9 | - server 10 | - shelf 11 | - backend 12 | 13 | environment: 14 | sdk: ^3.4.0 15 | 16 | dependencies: 17 | async: ^2.5.0 18 | collection: ^1.15.0 19 | http_parser: ^4.1.0 20 | path: ^1.8.0 21 | stack_trace: ^1.10.0 22 | stream_channel: ^2.1.0 23 | 24 | dev_dependencies: 25 | dart_flutter_team_lints: ^3.0.0 26 | http: '>=0.13.0 <2.0.0' 27 | test: ^1.16.0 28 | -------------------------------------------------------------------------------- /pkgs/shelf/test/add_chunked_encoding_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:shelf/shelf.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() { 11 | test('adds chunked encoding with no transfer-encoding header', () async { 12 | var response = await _chunkResponse( 13 | Response.ok(Stream.fromIterable(['hi'.codeUnits]))); 14 | expect(response.headers, containsPair('transfer-encoding', 'chunked')); 15 | expect(response.readAsString(), completion(equals('2\r\nhi\r\n0\r\n\r\n'))); 16 | }); 17 | 18 | test('adds chunked encoding with transfer-encoding: identity', () async { 19 | var response = await _chunkResponse(Response.ok( 20 | Stream.fromIterable(['hi'.codeUnits]), 21 | headers: {'transfer-encoding': 'identity'})); 22 | expect(response.headers, containsPair('transfer-encoding', 'chunked')); 23 | expect(response.readAsString(), completion(equals('2\r\nhi\r\n0\r\n\r\n'))); 24 | }); 25 | 26 | test("doesn't add chunked encoding with content length", () async { 27 | var response = await _chunkResponse(Response.ok('hi')); 28 | expect(response.headers, isNot(contains('transfer-encoding'))); 29 | expect(response.readAsString(), completion(equals('hi'))); 30 | }); 31 | 32 | test("doesn't add chunked encoding with status 1xx", () async { 33 | var response = await _chunkResponse( 34 | Response(123, body: const Stream>.empty())); 35 | expect(response.headers, isNot(contains('transfer-encoding'))); 36 | expect(response.read().toList(), completion(isEmpty)); 37 | }); 38 | 39 | test("doesn't add chunked encoding with status 204", () async { 40 | var response = await _chunkResponse( 41 | Response(204, body: const Stream>.empty())); 42 | expect(response.headers, isNot(contains('transfer-encoding'))); 43 | expect(response.read().toList(), completion(isEmpty)); 44 | }); 45 | 46 | test("doesn't add chunked encoding with status 304", () async { 47 | var response = await _chunkResponse( 48 | Response(204, body: const Stream>.empty())); 49 | expect(response.headers, isNot(contains('transfer-encoding'))); 50 | expect(response.read().toList(), completion(isEmpty)); 51 | }); 52 | 53 | test("doesn't add chunked encoding with status 204", () async { 54 | var response = await _chunkResponse( 55 | Response(204, body: const Stream>.empty())); 56 | expect(response.headers, isNot(contains('transfer-encoding'))); 57 | expect(response.read().toList(), completion(isEmpty)); 58 | }); 59 | 60 | test("doesn't add chunked encoding with status 204", () async { 61 | var response = await _chunkResponse( 62 | Response(204, body: const Stream>.empty())); 63 | expect(response.headers, isNot(contains('transfer-encoding'))); 64 | expect(response.read().toList(), completion(isEmpty)); 65 | }); 66 | } 67 | 68 | FutureOr _chunkResponse(Response response) => 69 | addChunkedEncoding((_) => response)( 70 | Request('GET', Uri.parse('http://example.com/')), 71 | ); 72 | -------------------------------------------------------------------------------- /pkgs/shelf/test/headers_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:shelf/src/headers.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test('Headers.from', () { 6 | var header = Headers.from({ 7 | 'FoO': ['x', 'y'], 8 | 'bAr': ['z'], 9 | }); 10 | 11 | expect(header['foo'], equals(['x', 'y'])); 12 | expect(header['BAR'], equals(['z'])); 13 | 14 | expect(() => header['X'] = ['x'], throwsA(isA())); 15 | }); 16 | 17 | test('Headers.fromEntries', () { 18 | var header = Headers.fromEntries({ 19 | 'FoO': ['x', 'y'], 20 | 'bAr': ['z'], 21 | }.entries); 22 | 23 | expect(header['foo'], equals(['x', 'y'])); 24 | expect(header['BAR'], equals(['z'])); 25 | 26 | expect(() => header['X'] = ['x'], throwsA(isA())); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /pkgs/shelf/test/hijack_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:shelf/shelf.dart'; 8 | import 'package:stream_channel/stream_channel.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | import 'test_util.dart'; 12 | 13 | void main() { 14 | test('hijacking a non-hijackable request throws a StateError', () { 15 | expect(() => Request('GET', localhostUri).hijack((_) {}), throwsStateError); 16 | }); 17 | 18 | test( 19 | 'hijacking a hijackable request throws a HijackException and calls ' 20 | 'onHijack', () { 21 | var request = 22 | Request('GET', localhostUri, onHijack: expectAsync1((callback) { 23 | var streamController = StreamController>(); 24 | streamController.add([1, 2, 3]); 25 | streamController.close(); 26 | 27 | var sinkController = StreamController>(); 28 | expect(sinkController.stream.first, completion(equals([4, 5, 6]))); 29 | 30 | callback(StreamChannel(streamController.stream, sinkController)); 31 | })); 32 | 33 | expect( 34 | () => request.hijack(expectAsync1((channel) { 35 | expect(channel.stream.first, completion(equals([1, 2, 3]))); 36 | channel.sink.add([4, 5, 6]); 37 | channel.sink.close(); 38 | })), 39 | throwsHijackException); 40 | }); 41 | 42 | test('hijacking a hijackable request twice throws a StateError', () { 43 | // Assert that the [onHijack] callback is only called once. 44 | var request = 45 | Request('GET', localhostUri, onHijack: expectAsync1((_) {}, count: 1)); 46 | 47 | expect(() => request.hijack((_) {}), throwsHijackException); 48 | 49 | expect(() => request.hijack((_) {}), throwsStateError); 50 | }); 51 | 52 | group('calling change', () { 53 | test('hijacking a non-hijackable request throws a StateError', () { 54 | var request = Request('GET', localhostUri); 55 | var newRequest = request.change(); 56 | expect(() => newRequest.hijack((_) {}), throwsStateError); 57 | }); 58 | 59 | test( 60 | 'hijacking a hijackable request throws a HijackException and calls ' 61 | 'onHijack', () { 62 | var request = 63 | Request('GET', localhostUri, onHijack: expectAsync1((callback) { 64 | var streamController = StreamController>(); 65 | streamController.add([1, 2, 3]); 66 | streamController.close(); 67 | 68 | var sinkController = StreamController>(); 69 | expect(sinkController.stream.first, completion(equals([4, 5, 6]))); 70 | 71 | callback(StreamChannel(streamController.stream, sinkController)); 72 | })); 73 | 74 | var newRequest = request.change(); 75 | 76 | expect( 77 | () => newRequest.hijack(expectAsync1((channel) { 78 | expect(channel.stream.first, completion(equals([1, 2, 3]))); 79 | channel.sink.add([4, 5, 6]); 80 | channel.sink.close(); 81 | })), 82 | throwsHijackException); 83 | }); 84 | 85 | test( 86 | 'hijacking the original request after calling change throws a ' 87 | 'StateError', () { 88 | // Assert that the [onHijack] callback is only called once. 89 | var request = Request('GET', localhostUri, 90 | onHijack: expectAsync1((_) {}, count: 1)); 91 | 92 | var newRequest = request.change(); 93 | 94 | expect(() => newRequest.hijack((_) {}), throwsHijackException); 95 | 96 | expect(() => request.hijack((_) {}), throwsStateError); 97 | }); 98 | }); 99 | } 100 | -------------------------------------------------------------------------------- /pkgs/shelf/test/io_server_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn('vm') 6 | library; 7 | 8 | import 'dart:async'; 9 | import 'dart:io'; 10 | 11 | import 'package:http/http.dart' as http; 12 | import 'package:shelf/shelf_io.dart'; 13 | import 'package:test/test.dart'; 14 | 15 | import 'test_util.dart'; 16 | 17 | void main() { 18 | late IOServer server; 19 | 20 | setUp(() async { 21 | try { 22 | server = await IOServer.bind(InternetAddress.loopbackIPv6, 0); 23 | } on SocketException catch (_) { 24 | server = await IOServer.bind(InternetAddress.loopbackIPv4, 0); 25 | } 26 | }); 27 | 28 | tearDown(() => server.close()); 29 | 30 | test('serves HTTP requests with the mounted handler', () async { 31 | server.mount(syncHandler); 32 | expect(await http.read(server.url), equals('Hello from /')); 33 | }); 34 | 35 | test('Handles malformed requests gracefully.', () async { 36 | server.mount(syncHandler); 37 | final rs = await http 38 | .get(Uri.parse('${server.url}/%D0%C2%BD%A8%CE%C4%BC%FE%BC%D0.zip')); 39 | expect(rs.statusCode, 400); 40 | expect(rs.body, 'Bad Request'); 41 | }); 42 | 43 | test('delays HTTP requests until a handler is mounted', () async { 44 | expect(http.read(server.url), completion(equals('Hello from /'))); 45 | await Future.delayed(Duration.zero); 46 | 47 | server.mount(asyncHandler); 48 | }); 49 | 50 | test('disallows more than one handler from being mounted', () async { 51 | server.mount((_) => throw UnimplementedError()); 52 | expect( 53 | () => server.mount((_) => throw UnimplementedError()), 54 | throwsStateError, 55 | ); 56 | expect( 57 | () => server.mount((_) => throw UnimplementedError()), 58 | throwsStateError, 59 | ); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /pkgs/shelf/test/log_middleware_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:shelf/shelf.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'test_util.dart'; 9 | 10 | void main() { 11 | late bool gotLog; 12 | 13 | setUp(() { 14 | gotLog = false; 15 | }); 16 | 17 | void logger(String msg, bool isError) { 18 | expect(gotLog, isFalse); 19 | gotLog = true; 20 | expect(isError, isFalse); 21 | expect(msg, contains('GET')); 22 | expect(msg, contains('[200]')); 23 | } 24 | 25 | test('logs a request with a synchronous response', () async { 26 | var handler = const Pipeline() 27 | .addMiddleware(logRequests(logger: logger)) 28 | .addHandler(syncHandler); 29 | 30 | await makeSimpleRequest(handler); 31 | expect(gotLog, isTrue); 32 | }); 33 | 34 | test('logs a request with an asynchronous response', () async { 35 | var handler = const Pipeline() 36 | .addMiddleware(logRequests(logger: logger)) 37 | .addHandler(asyncHandler); 38 | 39 | await makeSimpleRequest(handler); 40 | expect(gotLog, isTrue); 41 | }); 42 | 43 | test('logs a request with an asynchronous error response', () { 44 | var handler = 45 | const Pipeline().addMiddleware(logRequests(logger: (msg, isError) { 46 | expect(gotLog, isFalse); 47 | gotLog = true; 48 | expect(isError, isTrue); 49 | expect(msg, contains('\tGET\t/')); 50 | expect(msg, contains('oh no')); 51 | })).addHandler((request) { 52 | throw StateError('oh no'); 53 | }); 54 | 55 | expect(makeSimpleRequest(handler), throwsA(isOhNoStateError)); 56 | }); 57 | 58 | test("doesn't log a HijackException", () { 59 | var handler = const Pipeline() 60 | .addMiddleware(logRequests(logger: logger)) 61 | .addHandler((request) => throw const HijackException()); 62 | 63 | expect( 64 | makeSimpleRequest(handler).whenComplete(() { 65 | expect(gotLog, isFalse); 66 | }), 67 | throwsHijackException); 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /pkgs/shelf/test/message_change_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | 8 | import 'package:shelf/shelf.dart'; 9 | import 'package:shelf/src/message.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | import 'test_util.dart'; 13 | 14 | void main() { 15 | group('Request', () { 16 | _testChange(({ 17 | body, 18 | Map? headers, 19 | Map? context, 20 | }) { 21 | return Request( 22 | 'GET', 23 | localhostUri, 24 | body: body, 25 | headers: headers, 26 | context: context, 27 | ); 28 | }); 29 | }); 30 | 31 | group('Response', () { 32 | _testChange(({ 33 | body, 34 | Map? headers, 35 | Map? context, 36 | }) { 37 | return Response.ok( 38 | body, 39 | headers: headers, 40 | context: context, 41 | ); 42 | }); 43 | }); 44 | } 45 | 46 | /// Shared test method used by [Request] and [Response] tests to validate 47 | /// the behavior of `change` with different `headers` and `context` values. 48 | void _testChange( 49 | Message Function({ 50 | dynamic body, 51 | Map headers, 52 | Map context, 53 | }) factory) { 54 | group('body', () { 55 | test('with String', () async { 56 | var request = factory(body: 'Hello, world'); 57 | var copy = request.change(body: 'Goodbye, world'); 58 | 59 | var newBody = await copy.readAsString(); 60 | 61 | expect(newBody, equals('Goodbye, world')); 62 | }); 63 | 64 | test('with Stream', () async { 65 | var request = factory(body: 'Hello, world'); 66 | var copy = request.change( 67 | body: 68 | Stream.fromIterable(['Goodbye, world']).transform(utf8.encoder)); 69 | 70 | var newBody = await copy.readAsString(); 71 | 72 | expect(newBody, equals('Goodbye, world')); 73 | }); 74 | }); 75 | 76 | test('with empty headers returns identical instance', () { 77 | var request = factory(headers: {'header1': 'header value 1'}); 78 | var copy = request.change(headers: {}); 79 | 80 | expect(copy.headers, same(request.headers)); 81 | expect(copy.headersAll, same(request.headersAll)); 82 | }); 83 | 84 | test('with empty context returns identical instance', () { 85 | var request = factory(context: {'context1': 'context value 1'}); 86 | var copy = request.change(context: {}); 87 | 88 | expect(copy.context, same(request.context)); 89 | }); 90 | 91 | test('new header values are added', () { 92 | var request = factory(headers: {'test': 'test value'}); 93 | var copy = request.change(headers: {'test2': 'test2 value'}); 94 | 95 | expect(copy.headers, 96 | {'test': 'test value', 'test2': 'test2 value', 'content-length': '0'}); 97 | }); 98 | 99 | test('existing header values are overwritten', () { 100 | var request = factory(headers: {'test': 'test value'}); 101 | var copy = request.change(headers: {'test': 'new test value'}); 102 | 103 | expect(copy.headers, {'test': 'new test value', 'content-length': '0'}); 104 | }); 105 | 106 | test('new context values are added', () { 107 | var request = factory(context: {'test': 'test value'}); 108 | var copy = request.change(context: {'test2': 'test2 value'}); 109 | 110 | expect(copy.context, {'test': 'test value', 'test2': 'test2 value'}); 111 | }); 112 | 113 | test('existing context values are overwritten', () { 114 | var request = factory(context: {'test': 'test value'}); 115 | var copy = request.change(context: {'test': 'new test value'}); 116 | 117 | expect(copy.context, {'test': 'new test value'}); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /pkgs/shelf/test/pipeline_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:shelf/shelf.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'test_util.dart'; 9 | 10 | void main() { 11 | var accessLocation = 0; 12 | 13 | setUp(() { 14 | accessLocation = 0; 15 | }); 16 | 17 | Handler middlewareA(Handler innerHandler) => (request) { 18 | expect(accessLocation, 0); 19 | accessLocation = 1; 20 | final response = innerHandler(request); 21 | expect(accessLocation, 4); 22 | accessLocation = 5; 23 | return response; 24 | }; 25 | 26 | Handler middlewareB(Handler innerHandler) => (request) { 27 | expect(accessLocation, 1); 28 | accessLocation = 2; 29 | final response = innerHandler(request); 30 | expect(accessLocation, 3); 31 | accessLocation = 4; 32 | return response; 33 | }; 34 | 35 | Response innerHandler(Request request) { 36 | expect(accessLocation, 2); 37 | accessLocation = 3; 38 | return syncHandler(request); 39 | } 40 | 41 | test('compose middleware with Pipeline', () async { 42 | var handler = const Pipeline() 43 | .addMiddleware(middlewareA) 44 | .addMiddleware(middlewareB) 45 | .addHandler(innerHandler); 46 | 47 | final response = await makeSimpleRequest(handler); 48 | expect(response, isNotNull); 49 | expect(accessLocation, 5); 50 | }); 51 | 52 | test('extensions for composition', () async { 53 | var handler = 54 | middlewareA.addMiddleware(middlewareB).addHandler(innerHandler); 55 | 56 | final response = await makeSimpleRequest(handler); 57 | expect(response, isNotNull); 58 | expect(accessLocation, 5); 59 | }); 60 | 61 | test('Pipeline can be used as middleware', () async { 62 | var innerPipeline = 63 | const Pipeline().addMiddleware(middlewareA).addMiddleware(middlewareB); 64 | 65 | var handler = const Pipeline() 66 | .addMiddleware(innerPipeline.middleware) 67 | .addHandler(innerHandler); 68 | 69 | final response = await makeSimpleRequest(handler); 70 | expect(response, isNotNull); 71 | expect(accessLocation, 5); 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /pkgs/shelf/test/server_handler_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // ignore_for_file: deprecated_member_use_from_same_package 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:shelf/shelf.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | import 'test_util.dart'; 13 | 14 | void main() { 15 | test('passes the URL to the server', () { 16 | var serverHandler = ServerHandler(localhostUri); 17 | expect(serverHandler.server.url, equals(localhostUri)); 18 | }); 19 | 20 | test('pipes a request from ServerHandler.handler to a mounted handler', 21 | () async { 22 | var serverHandler = ServerHandler(localhostUri); 23 | serverHandler.server.mount(asyncHandler); 24 | 25 | var response = await makeSimpleRequest(serverHandler.handler); 26 | expect(response.statusCode, equals(200)); 27 | expect(response.readAsString(), completion(equals('Hello from /'))); 28 | }); 29 | 30 | test("waits until the server's handler is mounted to service a request", 31 | () async { 32 | var serverHandler = ServerHandler(localhostUri); 33 | var future = makeSimpleRequest(serverHandler.handler); 34 | await Future.delayed(Duration.zero); 35 | 36 | serverHandler.server.mount(syncHandler); 37 | var response = await future; 38 | expect(response.statusCode, equals(200)); 39 | expect(response.readAsString(), completion(equals('Hello from /'))); 40 | }); 41 | 42 | test('stops servicing requests after Server.close is called', () { 43 | var serverHandler = ServerHandler(localhostUri); 44 | serverHandler.server.mount( 45 | expectAsync1((_) => Response.internalServerError(), count: 0), 46 | ); 47 | serverHandler.server.close(); 48 | 49 | expect(makeSimpleRequest(serverHandler.handler), throwsStateError); 50 | }); 51 | 52 | test('calls onClose when Server.close is called', () async { 53 | var onCloseCalled = false; 54 | var completer = Completer(); 55 | var serverHandler = ServerHandler(localhostUri, onClose: () { 56 | onCloseCalled = true; 57 | return completer.future; 58 | }); 59 | 60 | var closeDone = false; 61 | unawaited(serverHandler.server.close().then((_) { 62 | closeDone = true; 63 | })); 64 | expect(onCloseCalled, isTrue); 65 | await Future.delayed(Duration.zero); 66 | 67 | expect(closeDone, isFalse); 68 | completer.complete(); 69 | await Future.delayed(Duration.zero); 70 | 71 | expect(closeDone, isTrue); 72 | }); 73 | 74 | test("doesn't allow Server.mount to be called multiple times", () { 75 | var serverHandler = ServerHandler(localhostUri); 76 | serverHandler.server.mount((_) => throw UnimplementedError()); 77 | expect( 78 | () => serverHandler.server.mount((_) => throw UnimplementedError()), 79 | throwsStateError, 80 | ); 81 | expect( 82 | () => serverHandler.server.mount((_) => throw UnimplementedError()), 83 | throwsStateError, 84 | ); 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /pkgs/shelf/test/test_util.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:shelf/shelf.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | // "hello," 11 | const helloBytes = [104, 101, 108, 108, 111, 44]; 12 | 13 | // " world" 14 | const worldBytes = [32, 119, 111, 114, 108, 100]; 15 | 16 | final Matcher throwsHijackException = throwsA(isA()); 17 | 18 | /// A simple, synchronous handler for [Request]. 19 | /// 20 | /// By default, replies with a status code 200, empty headers, and 21 | /// `Hello from ${request.url.path}`. 22 | Response syncHandler(Request request, 23 | {int? statusCode, Map? headers}) { 24 | return Response(statusCode ?? 200, 25 | headers: headers, body: 'Hello from ${request.requestedUri.path}'); 26 | } 27 | 28 | /// Calls [syncHandler] and wraps the response in a [Future]. 29 | Future asyncHandler(Request request) => 30 | Future(() => syncHandler(request)); 31 | 32 | /// Makes a simple GET request to [handler] and returns the result. 33 | Future makeSimpleRequest(Handler handler) => 34 | Future.sync(() => handler(_request)); 35 | 36 | final _request = Request('GET', localhostUri); 37 | 38 | final localhostUri = Uri.parse('http://localhost/'); 39 | 40 | final isOhNoStateError = 41 | isA().having((p0) => p0.message, 'message', 'oh no'); 42 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. 7 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.0.3-wip 2 | 3 | * Require Dart `^3.3.0`. 4 | 5 | ## 3.0.2 6 | 7 | * Added package topics to the pubspec file. 8 | * Require Dart `2.17`. 9 | 10 | ## 3.0.1 11 | 12 | * Require Dart `2.14`. 13 | * Update the pubspec `repository` field. 14 | 15 | ## 3.0.0 16 | 17 | * Migrate to null safety. 18 | 19 | ## 2.0.1 20 | 21 | * Allow the latest (null-safe) version of `pkg:shelf`. 22 | 23 | ## 2.0.0 24 | 25 | ### Breaking changes 26 | 27 | * Dropped the dependency on `package_resolver`. 28 | * All `PackageResolver` apis now take a `Map` of package name to the base uri for resolving `package:` 29 | uris for that package. 30 | * Named arguments have been renamed from `resolver` to `packageMap`. 31 | 32 | ## 1.0.4 33 | 34 | * Set max SDK version to `<3.0.0`, and adjust other dependencies. 35 | 36 | ## 1.0.3 37 | 38 | * Require Dart SDK 1.22.0 39 | * Support `package:async` v2 40 | 41 | ## 1.0.2 42 | 43 | * Fix Strong mode errors with `package:shelf` v0.7.x 44 | 45 | ## 1.0.1 46 | 47 | * Allow dependencies on `package:shelf` v0.7.x 48 | 49 | ## 0.0.1 50 | 51 | * Initial version 52 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/shelf_packages_handler.svg)](https://pub.dev/packages/shelf_packages_handler) 2 | [![package publisher](https://img.shields.io/pub/publisher/shelf_packages_handler.svg)](https://pub.dev/packages/shelf_packages_handler/publisher) 3 | 4 | A package that provides a [shelf][] handler for serving a `packages/` directory. 5 | It's intended to be usable as the first handler in a [`Cascade`][cascade], where 6 | any requests that include `/packages/` are served package assets, and all other 7 | requests cascade to additional handlers. 8 | 9 | [shelf]: http://github.com/dart-lang/shelf 10 | [cascade]: http://www.dartdocs.org/documentation/shelf/latest/index.html#shelf/shelf.Cascade 11 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/lib/shelf_packages_handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:shelf/shelf.dart'; 6 | 7 | import 'src/dir_handler.dart'; 8 | import 'src/package_config_handler.dart'; 9 | 10 | /// A handler that serves the contents of a virtual packages directory. 11 | /// 12 | /// This effectively serves `package:${request.url}`. It locates packages using 13 | /// the mapping defined by [packageMap]. If [packageMap] isn't passed, it 14 | /// defaults to the current isolate's package resolution logic. 15 | /// 16 | /// The [packageMap] maps package names to the base uri for resolving 17 | /// `package:` uris for that package. 18 | /// 19 | /// This can only serve assets from `file:` URIs. 20 | Handler packagesHandler({Map? packageMap}) => 21 | PackageConfigHandler(packageMap: packageMap).handleRequest; 22 | 23 | /// A handler that serves virtual `packages/` directories wherever they're 24 | /// requested. 25 | /// 26 | /// This serves the same assets as [packagesHandler] for every URL that contains 27 | /// `/packages/`. Otherwise, it returns 404s for all requests. 28 | /// 29 | /// This is useful for ensuring that `package:` imports work for all entrypoints 30 | /// in Dartium. 31 | Handler packagesDirHandler({Map? packageMap}) => 32 | DirHandler('packages', packagesHandler(packageMap: packageMap)).call; 33 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/lib/src/dir_handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:path/path.dart' as p; 8 | import 'package:shelf/shelf.dart'; 9 | 10 | import 'package_config_handler.dart'; 11 | 12 | /// A utility handler that mounts a sub-handler beneath a directory name, 13 | /// wherever that directory name appears in a URL. 14 | /// 15 | /// In practice, this is used to mount a [PackageConfigHandler] underneath 16 | /// `packages/` directories. 17 | class DirHandler { 18 | /// The directory name to look for. 19 | final String _name; 20 | 21 | /// The inner handler to mount. 22 | final Handler _inner; 23 | 24 | DirHandler(this._name, this._inner); 25 | 26 | /// The callback for handling a single request. 27 | FutureOr call(Request request) { 28 | final segments = request.url.pathSegments; 29 | for (var i = 0; i < segments.length; i++) { 30 | if (segments[i] != _name) continue; 31 | return _inner(request.change(path: p.url.joinAll(segments.take(i + 1)))); 32 | } 33 | 34 | return Response.notFound('Not found.'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/lib/src/package_config_handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:isolate'; 7 | 8 | import 'package:path/path.dart' as p; 9 | import 'package:shelf/shelf.dart'; 10 | import 'package:shelf_static/shelf_static.dart'; 11 | 12 | /// A shelf handler that serves a virtual packages directory based on a package 13 | /// config. 14 | class PackageConfigHandler { 15 | /// The static handlers for serving entries in the package config, indexed by 16 | /// name. 17 | final _packageHandlers = >{}; 18 | 19 | /// Optional, a map of package names to base uri for resolving `package:` 20 | /// uris for that package. 21 | final Map? _packageMap; 22 | 23 | PackageConfigHandler({Map? packageMap}) 24 | : _packageMap = packageMap; 25 | 26 | /// The callback for handling a single request. 27 | Future handleRequest(Request request) async { 28 | final segments = request.url.pathSegments; 29 | final handler = await _handlerFor(segments.first); 30 | return handler(request.change(path: segments.first)); 31 | } 32 | 33 | /// Creates a handler for [packageName] based on the package map in 34 | /// [_packageMap] or the current isolate resolver. 35 | Future _handlerFor(String packageName) => 36 | _packageHandlers.putIfAbsent(packageName, () async { 37 | Uri? packageUri; 38 | if (_packageMap != null) { 39 | packageUri = _packageMap[packageName]; 40 | } else { 41 | final fakeResolvedUri = await Isolate.resolvePackageUri( 42 | Uri(scheme: 'package', path: '$packageName/')); 43 | packageUri = fakeResolvedUri; 44 | } 45 | 46 | final handler = packageUri == null 47 | ? (_) => Response.notFound('Package $packageName not found.') 48 | : createStaticHandler(p.fromUri(packageUri), 49 | serveFilesOutsidePath: true); 50 | 51 | return handler; 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shelf_packages_handler 2 | version: 3.0.3-wip 3 | description: A shelf handler for serving a `packages/` directory. 4 | repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_packages_handler 5 | issue_tracker: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_packages_handler 6 | 7 | topics: 8 | - server 9 | - shelf 10 | 11 | environment: 12 | sdk: ^3.3.0 13 | 14 | dependencies: 15 | path: ^1.8.0 16 | shelf: ^1.0.0 17 | shelf_static: ^1.0.0 18 | 19 | dev_dependencies: 20 | dart_flutter_team_lints: ^3.0.0 21 | test: ^1.16.0 22 | -------------------------------------------------------------------------------- /pkgs/shelf_packages_handler/test/packages_handler_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:shelf/shelf.dart'; 8 | import 'package:shelf_packages_handler/shelf_packages_handler.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | void main() { 12 | late String dir; 13 | setUp(() { 14 | dir = 15 | Directory.systemTemp.createTempSync('shelf_packages_handler_test').path; 16 | Directory(dir).createSync(); 17 | Directory('$dir/foo').createSync(); 18 | File('$dir/foo/foo.dart') 19 | .writeAsStringSync("void main() => print('in foo');"); 20 | }); 21 | 22 | tearDown(() { 23 | Directory(dir).deleteSync(recursive: true); 24 | }); 25 | 26 | group('packagesHandler', () { 27 | test('defaults to the current method of package resolution', () async { 28 | final handler = packagesHandler(); 29 | final request = Request( 30 | 'GET', 31 | Uri.parse('http://example.com/shelf_packages_handler/' 32 | 'shelf_packages_handler.dart')); 33 | final response = await handler(request); 34 | expect(response.statusCode, equals(200)); 35 | expect( 36 | await response.readAsString(), contains('Handler packagesHandler')); 37 | }); 38 | 39 | group('with a package map', () { 40 | late Handler handler; 41 | 42 | setUp(() { 43 | handler = packagesHandler(packageMap: { 44 | 'foo': Uri.file('$dir/foo/'), 45 | }); 46 | }); 47 | 48 | test('looks up a real file', () async { 49 | final request = 50 | Request('GET', Uri.parse('http://example.com/foo/foo.dart')); 51 | final response = await handler(request); 52 | expect(response.statusCode, equals(200)); 53 | expect(await response.readAsString(), contains('in foo')); 54 | }); 55 | 56 | test('404s for a nonexistent package', () async { 57 | final request = 58 | Request('GET', Uri.parse('http://example.com/bar/foo.dart')); 59 | final response = await handler(request); 60 | expect(response.statusCode, equals(404)); 61 | expect( 62 | await response.readAsString(), contains('Package bar not found')); 63 | }); 64 | 65 | test('404s for a nonexistent file', () async { 66 | final request = 67 | Request('GET', Uri.parse('http://example.com/foo/bar.dart')); 68 | final response = await handler(request); 69 | expect(response.statusCode, equals(404)); 70 | }); 71 | }); 72 | }); 73 | 74 | group('packagesDirHandler', () { 75 | test('supports a directory at the root of the URL', () async { 76 | final handler = packagesDirHandler(); 77 | final request = Request( 78 | 'GET', 79 | Uri.parse('http://example.com/packages/shelf_packages_handler/' 80 | 'shelf_packages_handler.dart')); 81 | final response = await handler(request); 82 | expect(response.statusCode, equals(200)); 83 | expect( 84 | await response.readAsString(), contains('Handler packagesHandler')); 85 | }); 86 | 87 | test('supports a directory deep in the URL', () async { 88 | final handler = packagesDirHandler(); 89 | final request = Request( 90 | 'GET', 91 | Uri.parse('http://example.com/foo/bar/very/deep/packages/' 92 | 'shelf_packages_handler/shelf_packages_handler.dart')); 93 | final response = await handler(request); 94 | expect(response.statusCode, equals(200)); 95 | expect( 96 | await response.readAsString(), contains('Handler packagesHandler')); 97 | }); 98 | 99 | test('404s for a URL without a packages directory', () async { 100 | final handler = packagesDirHandler(); 101 | final request = Request( 102 | 'GET', 103 | Uri.parse('http://example.com/shelf_packages_handler/' 104 | 'shelf_packages_handler.dart')); 105 | final response = await handler(request); 106 | expect(response.statusCode, equals(404)); 107 | }); 108 | 109 | test('404s for a non-existent file within a packages directory', () async { 110 | final handler = packagesDirHandler(); 111 | final request = Request( 112 | 'GET', 113 | Uri.parse('http://example.com/packages/shelf_packages_handler/' 114 | 'non_existent.dart')); 115 | final response = await handler(request); 116 | expect(response.statusCode, equals(404)); 117 | }); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /pkgs/shelf_proxy/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.5-wip 2 | 3 | * Require Dart `^3.3.0`. 4 | 5 | ## 1.0.4 6 | 7 | * Require Dart `2.19`. 8 | * Allow `package:http` v1.0.0 9 | 10 | ## 1.0.3 11 | 12 | * Added package topics to the pubspec file. 13 | * Require Dart `2.17`. 14 | 15 | ## 1.0.2 16 | 17 | * Update the pubspec `repository` field. 18 | 19 | ## 1.0.1 20 | 21 | * Drop dependency on `package:pedantic`. 22 | * Require Dart `2.14`. 23 | 24 | ## 1.0.0 25 | 26 | * Require Dart `2.12`. 27 | * Enable null safety. 28 | * Removed deprecated `createProxyHandler`. 29 | 30 | ## 0.1.0+7 31 | 32 | * Added example. 33 | * Fixed links in README. 34 | 35 | ## 0.1.0+6 36 | 37 | * Support the latest version of `package:http`. 38 | 39 | ## 0.1.0+5 40 | 41 | * Support Dart 2. 42 | 43 | ## 0.1.0+4 44 | 45 | * Internal changes only. 46 | 47 | ## 0.1.0+3 48 | 49 | * Support version `0.7.0` of `shelf`. 50 | 51 | ## 0.1.0+2 52 | 53 | * Support version `0.6.0` of `shelf`. 54 | 55 | ## 0.1.0+1 56 | 57 | * Added `drone.io` badge to `README.md`. 58 | 59 | ## 0.1.0 60 | 61 | * `createProxyHandler` (new deprecated) is replaced with `proxyHandler`. 62 | 63 | * Updated to be compatible with RFC 2616 Proxy specification. 64 | 65 | ## 0.0.2 66 | 67 | * Updated `README.md` and doc comments on `createProxyHandler`. 68 | 69 | * Added an example. 70 | 71 | ## 0.0.1 72 | 73 | * First release. 74 | -------------------------------------------------------------------------------- /pkgs/shelf_proxy/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pkgs/shelf_proxy/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/shelf_proxy.svg)](https://pub.dev/packages/shelf_proxy) 2 | [![package publisher](https://img.shields.io/pub/publisher/shelf_proxy.svg)](https://pub.dev/packages/shelf_proxy/publisher) 3 | 4 | ## Proxy for Shelf 5 | 6 | `shelf_proxy` is a [Shelf][] handler that proxies requests to an external 7 | server. It can be served directly and used as a proxy server, or it can be 8 | mounted within a larger application to proxy only certain URLs. 9 | 10 | [Shelf]: https://pub.dev/packages/shelf 11 | 12 | ```dart 13 | import 'package:shelf/shelf_io.dart' as shelf_io; 14 | import 'package:shelf_proxy/shelf_proxy.dart'; 15 | 16 | void main() async { 17 | var server = await shelf_io.serve( 18 | proxyHandler("https://dart.dev"), 19 | 'localhost', 20 | 8080, 21 | ); 22 | 23 | print('Proxying at http://${server.address.host}:${server.port}'); 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /pkgs/shelf_proxy/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/shelf_proxy/example/example.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:shelf/shelf_io.dart' as shelf_io; 6 | import 'package:shelf_proxy/shelf_proxy.dart'; 7 | 8 | Future main() async { 9 | final server = await shelf_io.serve( 10 | proxyHandler('https://dart.dev'), 11 | 'localhost', 12 | 8080, 13 | ); 14 | 15 | print('Proxying at http://${server.address.host}:${server.port}'); 16 | } 17 | -------------------------------------------------------------------------------- /pkgs/shelf_proxy/lib/shelf_proxy.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http/http.dart' as http; 8 | import 'package:path/path.dart' as p; 9 | import 'package:shelf/shelf.dart'; 10 | 11 | /// A handler that proxies requests to [url]. 12 | /// 13 | /// To generate the proxy request, this concatenates [url] and [Request.url]. 14 | /// This means that if the handler mounted under `/documentation` and [url] is 15 | /// `http://example.com/docs`, a request to `/documentation/tutorials` 16 | /// will be proxied to `http://example.com/docs/tutorials`. 17 | /// 18 | /// [url] must be a [String] or [Uri]. 19 | /// 20 | /// [client] is used internally to make HTTP requests. It defaults to a 21 | /// `dart:io`-based client. 22 | /// 23 | /// [proxyName] is used in headers to identify this proxy. It should be a valid 24 | /// HTTP token or a hostname. It defaults to `shelf_proxy`. 25 | Handler proxyHandler(Object url, {http.Client? client, String? proxyName}) { 26 | Uri uri; 27 | if (url is String) { 28 | uri = Uri.parse(url); 29 | } else if (url is Uri) { 30 | uri = url; 31 | } else { 32 | throw ArgumentError.value(url, 'url', 'url must be a String or Uri.'); 33 | } 34 | final nonNullClient = client ?? http.Client(); 35 | proxyName ??= 'shelf_proxy'; 36 | 37 | return (serverRequest) async { 38 | // TODO(nweiz): Support WebSocket requests. 39 | 40 | // TODO(nweiz): Handle TRACE requests correctly. See 41 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8 42 | final requestUrl = uri.resolve(serverRequest.url.toString()); 43 | final clientRequest = http.StreamedRequest(serverRequest.method, requestUrl) 44 | ..followRedirects = false 45 | ..headers.addAll(serverRequest.headers) 46 | ..headers['Host'] = uri.authority; 47 | 48 | // Add a Via header. See 49 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.45 50 | _addHeader(clientRequest.headers, 'via', 51 | '${serverRequest.protocolVersion} $proxyName'); 52 | 53 | serverRequest 54 | .read() 55 | .forEach(clientRequest.sink.add) 56 | .catchError(clientRequest.sink.addError) 57 | .whenComplete(clientRequest.sink.close) 58 | .ignore(); 59 | final clientResponse = await nonNullClient.send(clientRequest); 60 | // Add a Via header. See 61 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.45 62 | _addHeader(clientResponse.headers, 'via', '1.1 $proxyName'); 63 | 64 | // Remove the transfer-encoding since the body has already been decoded by 65 | // [client]. 66 | clientResponse.headers.remove('transfer-encoding'); 67 | 68 | // If the original response was gzipped, it will be decoded by [client] 69 | // and we'll have no way of knowing its actual content-length. 70 | if (clientResponse.headers['content-encoding'] == 'gzip') { 71 | clientResponse.headers.remove('content-encoding'); 72 | clientResponse.headers.remove('content-length'); 73 | 74 | // Add a Warning header. See 75 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.2 76 | _addHeader( 77 | clientResponse.headers, 'warning', '214 $proxyName "GZIP decoded"'); 78 | } 79 | 80 | // Make sure the Location header is pointing to the proxy server rather 81 | // than the destination server, if possible. 82 | if (clientResponse.isRedirect && 83 | clientResponse.headers.containsKey('location')) { 84 | final location = 85 | requestUrl.resolve(clientResponse.headers['location']!).toString(); 86 | if (p.url.isWithin(uri.toString(), location)) { 87 | clientResponse.headers['location'] = 88 | '/${p.url.relative(location, from: uri.toString())}'; 89 | } else { 90 | clientResponse.headers['location'] = location; 91 | } 92 | } 93 | 94 | return Response(clientResponse.statusCode, 95 | body: clientResponse.stream, headers: clientResponse.headers); 96 | }; 97 | } 98 | 99 | // TODO(nweiz): use built-in methods for this when http and shelf support them. 100 | /// Add a header with [name] and [value] to [headers], handling existing headers 101 | /// gracefully. 102 | void _addHeader(Map headers, String name, String value) { 103 | final existing = headers[name]; 104 | headers[name] = existing == null ? value : '$existing, $value'; 105 | } 106 | -------------------------------------------------------------------------------- /pkgs/shelf_proxy/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | # TODO: Get tests passing on windows 16 | -------------------------------------------------------------------------------- /pkgs/shelf_proxy/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shelf_proxy 2 | version: 1.0.5-wip 3 | description: A shelf handler for proxying HTTP requests to another server. 4 | repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_proxy 5 | issue_tracker: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_proxy 6 | 7 | topics: 8 | - server 9 | - shelf 10 | 11 | environment: 12 | sdk: ^3.3.0 13 | 14 | dependencies: 15 | http: '>=0.13.0 <2.0.0' 16 | path: ^1.8.0 17 | shelf: ^1.0.0 18 | 19 | dev_dependencies: 20 | dart_flutter_team_lints: ^3.0.0 21 | test: ^1.6.0 22 | -------------------------------------------------------------------------------- /pkgs/shelf_router/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.1.5-wip 2 | 3 | * Require Dart `^3.3.0`. 4 | 5 | ## 1.1.4 6 | 7 | * Fixed a spelling issue in the pubspec file. 8 | * Added package topics to the pubspec file. 9 | * Require Dart >=2.17. 10 | 11 | ## 1.1.3 12 | 13 | * Update the pubspec `repository` field. 14 | 15 | ## v1.1.2 16 | 17 | * Remove trailing slash requirement when using `mount`. 18 | 19 | ## v1.1.1 20 | 21 | * Fix `Router.routeNotFound` to enable multiple `read()` calls on it. 22 | 23 | ## v1.1.0 24 | * `params` is deprecated in favor of `Request.params` adding using an extension 25 | on `Request`. 26 | * The default `notFoundHandler` now returns a sentinel `routeNotFound` response 27 | object which causes 404 with the message 'Route not found'. 28 | * __Minor breaking__: Handlers and sub-routers that return the sentinel 29 | `routeNotFound` response object will be ignored and pattern matching will 30 | continue on additional routes/handlers. 31 | 32 | Changing the router to continue pattern matching additional routes if a matched 33 | _handler_ or _nested router_ returns the sentinel `routeNotFound` response 34 | object is technically a _breaking change_. However, it only affects scenarios 35 | where the request matches a _mounted sub-router_, but does not match any route 36 | on this sub-router. In this case, `shelf_router` version `1.0.0` would 37 | immediately respond 404, without attempting to match further routes. With this 38 | release, the behavior changes to matching additional routes until one returns 39 | a custom 404 response object, or all routes have been matched. 40 | 41 | This behavior is more in line with how `shelf_router` version `0.7.x` worked, 42 | and since many affected users consider the behavior from `1.0.0` a defect, 43 | we decided to remedy the situation. 44 | 45 | ## v1.0.0 46 | 47 | * Migrate package to null-safety 48 | * Since handlers are not allowed to return `null` in `shelf` 1.0.0, a router 49 | will return a default 404 response instead. 50 | This behavior can be overridden with the `notFoundHandler` constructor 51 | parameter. 52 | * __Breaking__: Remove deprecated `Router.handler` getter. 53 | The router itself is a handler. 54 | 55 | ## v0.7.4 56 | 57 | * Update `Router.mount` parameter to accept a `Handler`. 58 | * Make `Router` to be considered a `Handler`. 59 | * Deprecate the `Router.handler` getter. 60 | 61 | ## v0.7.3 62 | 63 | * Added `@sealed` annotation to `Router` and `Route`. 64 | 65 | ## v0.7.2 66 | 67 | * Always register a `HEAD` handler whenever a `GET` handler is registered. 68 | Defaulting to calling the `GET` handler and throwing away the body. 69 | 70 | ## v0.7.1 71 | 72 | * Use `Function` instead of `dynamic` in `RouterEntry` to improve typing. 73 | 74 | ## v0.7.0+1 75 | 76 | * Fixed description to fit size recommendations. 77 | 78 | ## v0.7.0 79 | 80 | * Initial release 81 | -------------------------------------------------------------------------------- /pkgs/shelf_router/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/shelf_router.svg)](https://pub.dev/packages/shelf_router) 2 | [![package publisher](https://img.shields.io/pub/publisher/shelf_router.svg)](https://pub.dev/packages/shelf_router/publisher) 3 | 4 | ## Web Request Router for Shelf 5 | 6 | [Shelf][shelf] makes it easy to build web 7 | applications in Dart by composing request handlers. This package offers a 8 | request router for Shelf, matching request to handlers using route patterns. 9 | 10 | Also see the [`shelf_router_generator`][shelf_router_generator] package 11 | for how to automatically generate 12 | a `Route` using the `Route` annotation in this package. 13 | 14 | ## Example 15 | 16 | ```dart 17 | import 'package:shelf_router/shelf_router.dart'; 18 | import 'package:shelf/shelf.dart'; 19 | import 'package:shelf/shelf_io.dart' as io; 20 | 21 | // instantiate a router and configure your routes 22 | var router = Router(); 23 | 24 | router.get('/hello', (Request request) { 25 | return Response.ok('hello-world'); 26 | }); 27 | 28 | router.get('/user/', (Request request, String user) { 29 | return Response.ok('hello $user'); 30 | }); 31 | 32 | // use a Pipeline to configure your middleware, 33 | // then add the router as the handler 34 | final app = const Pipeline() 35 | .addMiddleware(logRequests()) 36 | .addHandler(router); 37 | 38 | var server = await io.serve(app, 'localhost', 8080); 39 | ``` 40 | 41 | See reference documentation of `Router` class for more information. 42 | 43 | ## See also 44 | * Package [`shelf`][shelf] for which this package can create routers. 45 | * Package [`shelf_router_generator`][shelf_router_generator] which can generate 46 | a router using source code annotations. 47 | * Third-party tutorial by [creativebracket.com]: 48 | * Video: [Build RESTful Web APIs with shelf_router][1] 49 | * Sample: [repository for tutorial][2] 50 | 51 | [shelf]: https://pub.dev/packages/shelf 52 | [shelf_router_generator]: https://pub.dev/packages/shelf_router_generator 53 | [creativebracket.com]: https://creativebracket.com/ 54 | [1]: https://www.youtube.com/watch?v=v7FhaV9e3yY 55 | [2]: https://github.com/graphicbeacon/shelf_router_api_tutorial 56 | -------------------------------------------------------------------------------- /pkgs/shelf_router/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/shelf_router/example/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async' show Future; 16 | 17 | import 'package:shelf/shelf.dart'; 18 | import 'package:shelf/shelf_io.dart' as shelf_io; 19 | import 'package:shelf_router/shelf_router.dart'; 20 | 21 | class Service { 22 | // The [Router] can be used to create a handler, which can be used with 23 | // [shelf_io.serve]. 24 | Handler get handler { 25 | final router = Router(); 26 | 27 | // Handlers can be added with `router.('', handler)`, the 28 | // '' may embed URL-parameters, and these may be taken as parameters 29 | // by the handler (but either all URL parameters or no URL parameters, must 30 | // be taken parameters by the handler). 31 | router.get('/say-hi/', (Request request, String name) { 32 | return Response.ok('hi $name'); 33 | }); 34 | 35 | // Embedded URL parameters may also be associated with a regular-expression 36 | // that the pattern must match. 37 | router.get('/user/', (Request request, String userId) { 38 | return Response.ok('User has the user-number: $userId'); 39 | }); 40 | 41 | // Handlers can be asynchronous (returning `FutureOr` is also allowed). 42 | router.get('/wave', (Request request) async { 43 | await Future.delayed(const Duration(milliseconds: 100)); 44 | return Response.ok('_o/'); 45 | }); 46 | 47 | // Other routers can be mounted... 48 | router.mount('/api/', Api().router.call); 49 | 50 | // You can catch all verbs and use a URL-parameter with a regular expression 51 | // that matches everything to catch app. 52 | router.all('/', (Request request) { 53 | return Response.notFound('Page not found'); 54 | }); 55 | 56 | // Set up your Pipeline with any middleware you want to use and set the 57 | // router as the handler. 58 | return const Pipeline() 59 | .addMiddleware(logRequests()) 60 | .addHandler(router.call); 61 | } 62 | } 63 | 64 | class Api { 65 | Future _messages(Request request) async { 66 | return Response.ok('[]'); 67 | } 68 | 69 | // By exposing a [Router] for an object, it can be mounted in other routers. 70 | Router get router { 71 | final router = Router(); 72 | 73 | // A handler can have more that one route. 74 | router.get('/messages', _messages); 75 | router.get('/messages/', _messages); 76 | 77 | // This nested catch-all, will only catch /api/.* when mounted above. 78 | // Notice that ordering if annotated handlers and mounts is significant. 79 | router.all('/', (Request request) => Response.notFound('null')); 80 | 81 | return router; 82 | } 83 | } 84 | 85 | // Run shelf server and host a [Service] instance on port 8080. 86 | void main() async { 87 | final service = Service(); 88 | final server = await shelf_io.serve(service.handler, 'localhost', 8080); 89 | print('Server running on localhost:${server.port}'); 90 | } 91 | -------------------------------------------------------------------------------- /pkgs/shelf_router/lib/shelf_router.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// A request routing library for shelf. 16 | /// 17 | /// When writing a shelf web server it is often desirable to route requests to 18 | /// different handlers based on HTTP method and path patterns. The following 19 | /// example demonstrates how to do this using [Router]. 20 | /// 21 | /// **Example** 22 | /// ```dart 23 | /// import 'package:shelf_router/shelf_router.dart'; 24 | /// import 'package:shelf/shelf.dart' show Request, Response; 25 | /// import 'package:shelf/shelf_io.dart' as io; 26 | /// 27 | /// void main() async { 28 | /// // Create a router 29 | /// final router = Router(); 30 | /// 31 | /// // Handle GET requests with a path matching ^/say-hello/[^\]*$ 32 | /// router.get('/say-hello/', (Request request, String name) async { 33 | /// return Response.ok('hello $name'); 34 | /// }); 35 | /// 36 | /// // Listen for requests on port localhost:8080 37 | /// await io.serve(router, 'localhost', 8080); 38 | /// } 39 | /// ``` 40 | /// 41 | /// As it is often useful to organize request handlers in classes, methods can 42 | /// be annotated with the [Route] annotation, allowing the 43 | /// `shelf_router_generator` package to generated a method for creating a 44 | /// [Router] wrapping the class. 45 | /// 46 | /// To automatically generate add the `shelf_router_generator` and 47 | /// `build_runner` packages to `dev_dependencies`. The follow the example 48 | /// below and generate code using `pub run build_runner build`. 49 | /// 50 | /// **Example**, assume file name is `hello.dart`. 51 | /// ```dart 52 | /// import 'package:shelf_router/shelf_router.dart'; 53 | /// import 'package:shelf/shelf.dart' show Request, Response; 54 | /// import 'package:shelf/shelf_io.dart' as io; 55 | /// 56 | /// // include the generated part, assumes current file is 'hello.dart'. 57 | /// part 'hello.g.dart'; 58 | /// 59 | /// class HelloService { 60 | /// // Annotate a handler with the `Route` annotation. 61 | /// @Route.get('/say-hello/') 62 | /// Future _sayHello(Request request, String name) async { 63 | /// return Response.ok('hello $name'); 64 | /// } 65 | /// 66 | /// // Use the generated function `_$Router( instance)` 67 | /// // to create a getter returning a `Router` for this instance of 68 | /// // `HelloService` 69 | /// Router get router => _$HelloServiceRouter(this); 70 | /// } 71 | /// 72 | /// void main() async { 73 | /// // Create a `HelloService` instance 74 | /// final service = HelloService(); 75 | /// 76 | /// await io.serve(service.router.handler, 'localhost', 8080); 77 | /// } 78 | /// ``` 79 | library; 80 | 81 | import 'src/route.dart'; 82 | import 'src/router.dart'; 83 | 84 | export 'package:shelf_router/src/route.dart'; 85 | export 'package:shelf_router/src/router.dart'; 86 | -------------------------------------------------------------------------------- /pkgs/shelf_router/lib/src/route.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:meta/meta.dart' show sealed; 16 | import 'router.dart'; 17 | 18 | /// Annotation for handler methods that requests should be routed when using 19 | /// package `shelf_router_generator`. 20 | /// 21 | /// The `shelf_router_generator` packages makes it easy to generate a function 22 | /// that wraps your class and returns a [Router] that forwards requests to 23 | /// annotated methods. Simply add the `shelf_router_generator` and 24 | /// `build_runner` packages to `dev_dependencies`, write as illustrated in the 25 | /// following example and run `pub run build_runner build` to generate code. 26 | /// 27 | /// **Example** 28 | /// ```dart 29 | /// // Always import 'shelf_router' without 'show' or 'as'. 30 | /// import 'package:shelf_router/shelf_router.dart'; 31 | /// import 'package:shelf/shelf.dart' show Request, Response; 32 | /// 33 | /// // Include generated code, this assumes current file is 'my_service.dart'. 34 | /// part 'my_service.g.dart'; 35 | /// 36 | /// class MyService { 37 | /// @Route.get('/say-hello/') 38 | /// Future _sayHello(Request request, String name) async { 39 | /// return Response.ok('hello $name'); 40 | /// } 41 | /// 42 | /// /// Get a router for this service. 43 | /// Router get router => _$MyServiceRouter(this); 44 | /// } 45 | /// ``` 46 | /// 47 | /// It is also permitted to annotate public members, the only requirement is 48 | /// that the member has a signature accepted by [Router] as `handler`. 49 | @sealed 50 | class Route { 51 | /// HTTP verb for requests routed to the annotated method. 52 | final String verb; 53 | 54 | /// HTTP route for request routed to the annotated method. 55 | final String route; 56 | 57 | /// Create an annotation that routes requests matching [verb] and [route] to 58 | /// the annotated method. 59 | const Route(this.verb, this.route); 60 | 61 | /// Route all requests matching [route] to annotated method. 62 | const Route.all(this.route) : verb = r'$all'; 63 | 64 | /// Route `GET` requests matching [route] to annotated method. 65 | const Route.get(this.route) : verb = 'GET'; 66 | 67 | /// Route `HEAD` requests matching [route] to annotated method. 68 | const Route.head(this.route) : verb = 'HEAD'; 69 | 70 | /// Route `POST` requests matching [route] to annotated method. 71 | const Route.post(this.route) : verb = 'POST'; 72 | 73 | /// Route `PUT` requests matching [route] to annotated method. 74 | const Route.put(this.route) : verb = 'PUT'; 75 | 76 | /// Route `DELETE` requests matching [route] to annotated method. 77 | const Route.delete(this.route) : verb = 'DELETE'; 78 | 79 | /// Route `CONNECT` requests matching [route] to annotated method. 80 | const Route.connect(this.route) : verb = 'CONNECT'; 81 | 82 | /// Route `OPTIONS` requests matching [route] to annotated method. 83 | const Route.options(this.route) : verb = 'OPTIONS'; 84 | 85 | /// Route `TRACE` requests matching [route] to annotated method. 86 | const Route.trace(this.route) : verb = 'TRACE'; 87 | 88 | /// Route `MOUNT` requests matching [route] to annotated method. 89 | const Route.mount(String prefix) 90 | : verb = r'$mount', 91 | route = prefix; 92 | } 93 | -------------------------------------------------------------------------------- /pkgs/shelf_router/lib/src/router_entry.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | 17 | import 'package:shelf/shelf.dart'; 18 | 19 | /// Check if the [regexp] is non-capturing. 20 | bool _isNoCapture(String regexp) { 21 | // Construct a new regular expression matching anything containing regexp, 22 | // then match with empty-string and count number of groups. 23 | return RegExp('^(?:$regexp)|.*\$').firstMatch('')!.groupCount == 0; 24 | } 25 | 26 | /// Entry in the router. 27 | /// 28 | /// This class implements the logic for matching the path pattern. 29 | class RouterEntry { 30 | /// Pattern for parsing the route pattern 31 | static final RegExp _parser = RegExp(r'([^<]*)(?:<([^>|]+)(?:\|([^>]*))?>)?'); 32 | 33 | final String verb, route; 34 | final Function _handler; 35 | final Middleware _middleware; 36 | 37 | /// Expression that the request path must match. 38 | /// 39 | /// This also captures any parameters in the route pattern. 40 | final RegExp _routePattern; 41 | 42 | /// Names for the parameters in the route pattern. 43 | final List _params; 44 | 45 | /// List of parameter names in the route pattern. 46 | List get params => _params.toList(); // exposed for using generator. 47 | 48 | RouterEntry._(this.verb, this.route, this._handler, this._middleware, 49 | this._routePattern, this._params); 50 | 51 | factory RouterEntry( 52 | String verb, 53 | String route, 54 | Function handler, { 55 | Middleware? middleware, 56 | }) { 57 | middleware = middleware ?? ((Handler fn) => fn); 58 | 59 | if (!route.startsWith('/')) { 60 | throw ArgumentError.value( 61 | route, 'route', 'expected route to start with a slash'); 62 | } 63 | 64 | final params = []; 65 | var pattern = ''; 66 | for (var m in _parser.allMatches(route)) { 67 | pattern += RegExp.escape(m[1]!); 68 | if (m[2] != null) { 69 | params.add(m[2]!); 70 | if (m[3] != null && !_isNoCapture(m[3]!)) { 71 | throw ArgumentError.value( 72 | route, 'route', 'expression for "${m[2]}" is capturing'); 73 | } 74 | pattern += '(${m[3] ?? r'[^/]+'})'; 75 | } 76 | } 77 | final routePattern = RegExp('^$pattern\$'); 78 | 79 | return RouterEntry._( 80 | verb, route, handler, middleware, routePattern, params); 81 | } 82 | 83 | /// Returns a map from parameter name to value, if the path matches the 84 | /// route pattern. Otherwise returns null. 85 | Map? match(String path) { 86 | // Check if path matches the route pattern 87 | var m = _routePattern.firstMatch(path); 88 | if (m == null) { 89 | return null; 90 | } 91 | // Construct map from parameter name to matched value 92 | var params = {}; 93 | for (var i = 0; i < _params.length; i++) { 94 | // first group is always the full match, we ignore this group. 95 | params[_params[i]] = m[i + 1]!; 96 | } 97 | return params; 98 | } 99 | 100 | // invoke handler with given request and params 101 | Future invoke(Request request, Map params) async { 102 | request = request.change(context: {'shelf_router/params': params}); 103 | 104 | return await _middleware((request) async { 105 | if (_handler is Handler || _params.isEmpty) { 106 | // ignore: avoid_dynamic_calls 107 | return await _handler(request) as Response; 108 | } 109 | return await Function.apply(_handler, [ 110 | request, 111 | ..._params.map((n) => params[n]), 112 | ]) as Response; 113 | })(request); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /pkgs/shelf_router/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | -------------------------------------------------------------------------------- /pkgs/shelf_router/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shelf_router 2 | version: 1.1.5-wip 3 | description: > 4 | A convenient request router for the shelf web-framework, with support for 5 | URL-parameters, nested routers and routers generated from source annotations. 6 | repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_router 7 | issue_tracker: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_router 8 | 9 | topics: 10 | - server 11 | - shelf 12 | 13 | environment: 14 | sdk: ^3.3.0 15 | 16 | dependencies: 17 | http_methods: ^1.1.0 18 | meta: ^1.3.0 19 | shelf: ^1.0.0 20 | 21 | dev_dependencies: 22 | dart_flutter_team_lints: ^3.0.0 23 | http: '>=0.13.0 <2.0.0' 24 | test: ^1.16.0 25 | -------------------------------------------------------------------------------- /pkgs/shelf_router/test/route_entry_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:shelf_router/src/router_entry.dart' show RouterEntry; 16 | import 'package:test/test.dart'; 17 | 18 | void main() { 19 | void testPattern( 20 | String pattern, { 21 | Map> match = const {}, 22 | List notMatch = const [], 23 | }) { 24 | group('RouterEntry: "$pattern"', () { 25 | final r = RouterEntry('GET', pattern, () => null); 26 | for (final e in match.entries) { 27 | test('Matches "${e.key}"', () { 28 | expect(r.match(e.key), equals(e.value)); 29 | }); 30 | } 31 | for (final v in notMatch) { 32 | test('NotMatch "$v"', () { 33 | expect(r.match(v), isNull); 34 | }); 35 | } 36 | }); 37 | } 38 | 39 | testPattern('/hello', match: { 40 | '/hello': {}, 41 | }, notMatch: [ 42 | '/not-hello', 43 | '/', 44 | ]); 45 | 46 | testPattern(r'/user//groups/', match: { 47 | '/user/jonasfj/groups/42': { 48 | 'user': 'jonasfj', 49 | 'group': '42', 50 | }, 51 | '/user/jonasfj/groups/0': { 52 | 'user': 'jonasfj', 53 | 'group': '0', 54 | }, 55 | '/user/123/groups/101': { 56 | 'user': '123', 57 | 'group': '101', 58 | }, 59 | }, notMatch: [ 60 | '/user/', 61 | '/user/jonasfj/groups/5-3', 62 | '/user/jonasfj/test/groups/5', 63 | '/user/jonasfjtest/groups/4/', 64 | '/user/jonasfj/groups/', 65 | '/not-hello', 66 | '/', 67 | ]); 68 | 69 | test('non-capture regex only', () { 70 | expect(() => RouterEntry('GET', '/users//info', () {}), 71 | throwsA(anything)); 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.1.1 2 | 3 | * Support the latest `package:analyzer` and `package:source_gen` 4 | * Require `sdk: ^3.3.0` 5 | 6 | ## 1.1.0 7 | 8 | * Require `sdk: ^3.0.0` 9 | * Require `analyzer: '>=4.6.0 <7.0.0'` 10 | * Remove trailing slash requirement when using `mount`. 11 | 12 | ## 1.0.6 13 | 14 | * Added package topics to the pubspec file. 15 | 16 | ## 1.0.5 17 | 18 | * Require `analyzer: '>=4.6.0 <6.0.0'` 19 | * Require `sdk: '>=2.17.0 <3.0.0'` 20 | * Require `code_builder: ^4.2.0` 21 | 22 | ## 1.0.4 23 | 24 | * Support update-to-date dependencies. 25 | 26 | ## 1.0.3 27 | 28 | * Update the pubspec `repository` field. 29 | 30 | ## 1.0.2 31 | 32 | * Support update-to-date dependencies. 33 | 34 | ## 1.0.1 35 | 36 | * Support update-to-date dependencies. 37 | 38 | ## 1.0.0 39 | 40 | * Support update-to-date dependencies. 41 | * Migrate implementation to null safety. 42 | * Generate null-safe code. 43 | 44 | ## 0.7.2+4 45 | 46 | * Relax dependency constraint on `analyzer`. 47 | 48 | ## 0.7.2+3 49 | 50 | * Relax dependency constraint on `analyzer`. 51 | 52 | ## 0.7.2+2 53 | 54 | * Relax dependency constraint on `analyzer`. 55 | 56 | ## 0.7.2+1 57 | 58 | * Relax dependency constraint on `analyzer`. 59 | 60 | ## 0.7.2 61 | 62 | * Use `literalString('...', raw: true)` from `package:code_builder` to ensure 63 | that generated strings are properly escaped (fixing [#10][issue-10]). 64 | 65 | [issue-10]: https://github.com/google/dart-neats/issues/10 66 | 67 | ## 0.7.1 68 | 69 | * Bumped dependencies. 70 | 71 | ## 0.7.0+1 72 | 73 | * Updated `README.md` with references to package `build_runner`. 74 | 75 | ## 0.7.0 76 | 77 | * Initial release 78 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/shelf_router_generator.svg)](https://pub.dev/packages/shelf_router_generator) 2 | [![package publisher](https://img.shields.io/pub/publisher/shelf_router_generator.svg)](https://pub.dev/packages/shelf_router_generator/publisher) 3 | 4 | ## Shelf Router Generator 5 | 6 | [Shelf](https://pub.dartlang.org/packages/shelf) makes it easy to build web 7 | applications in Dart by composing request handlers. The `shelf_router` package 8 | offers a request router for Shelf. this package enables generating a 9 | `shelf_route.Router` from annotations in code. 10 | 11 | This package should be a _development dependency_ along with 12 | [package `build_runner`](https://pub.dartlang.org/packages/build_runner), and 13 | used with [package `shelf`](https://pub.dartlang.org/packages/shelf) and 14 | [package `shelf_router`](https://pub.dartlang.org/packages/shelf_router) as 15 | dependencies. 16 | 17 | ```yaml 18 | dependencies: 19 | shelf: ^0.7.5 20 | shelf_router: ^0.7.0+1 21 | dev_dependencies: 22 | shelf_router_generator: ^0.7.0+1 23 | build_runner: ^1.3.1 24 | ``` 25 | 26 | Once your code have been annotated as illustrated in the example below the 27 | generated part can be created with `pub run build_runner build`. 28 | 29 | ## Example 30 | 31 | ```dart 32 | import 'package:shelf/shelf.dart'; 33 | import 'package:shelf_router/shelf_router.dart'; 34 | 35 | part 'userservice.g.dart'; // generated with 'pub run build_runner build' 36 | 37 | class UserService { 38 | final DatabaseConnection connection; 39 | UserService(this.connection); 40 | 41 | @Route.get('/users/') 42 | Future listUsers(Request request) async { 43 | return Response.ok('["user1"]'); 44 | } 45 | 46 | @Route.get('/users/') 47 | Future fetchUser(Request request, String userId) async { 48 | if (userId == 'user1') { 49 | return Response.ok('user1'); 50 | } 51 | return Response.notFound('no such user'); 52 | } 53 | 54 | // Create router using the generate function defined in 'userservice.g.dart'. 55 | Router get router => _$UserServiceRouter(this); 56 | } 57 | 58 | void main() async { 59 | // You can setup context, database connections, cache connections, email 60 | // services, before you create an instance of your service. 61 | var connection = await DatabaseConnection.connect('localhost:1234'); 62 | 63 | // Create an instance of your service, usine one of the constructors you've 64 | // defined. 65 | var service = UserService(connection); 66 | // Service request using the router, note the router can also be mounted. 67 | var router = service.router; 68 | var server = await io.serve(router.handler, 'localhost', 8080); 69 | } 70 | ``` 71 | 72 | 73 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | shelf_router_generator|shelf_router: 5 | enabled: true 6 | 7 | builders: 8 | shelf_router: 9 | import: "package:shelf_router_generator/builder.dart" 10 | builder_factories: ["shelfRouter"] 11 | build_extensions: {".dart": [".shelf_router.g.part"]} 12 | auto_apply: dependents 13 | build_to: cache 14 | applies_builders: ["source_gen|combining_builder"] 15 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/dart_test.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | presubmit-only: 3 | skip: "Should only be run during presubmit" 4 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/example/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async' show Future; 16 | 17 | import 'package:shelf/shelf.dart'; 18 | import 'package:shelf/shelf_io.dart' as shelf_io; 19 | import 'package:shelf_router/shelf_router.dart'; 20 | 21 | // Generated code will be written to 'main.g.dart' 22 | part 'main.g.dart'; 23 | 24 | class Service { 25 | // A handler is annotated with @Route.(''), the '' may 26 | // embed URL-parameters, and these may be taken as parameters by the handler. 27 | // But either all URL-parameters or none of the URL parameters must be taken 28 | // as parameters by the handler. 29 | @Route.get('/say-hi/') 30 | Response _hi(Request request, String name) => Response.ok('hi $name'); 31 | 32 | // Embedded URL parameters may also be associated with a regular-expression 33 | // that the pattern must match. 34 | @Route.get('/user/') 35 | Response _user(Request request, String userId) => 36 | Response.ok('User has the user-number: $userId'); 37 | 38 | // Handlers can be asynchronous (returning `FutureOr` is also allowed). 39 | @Route.get('/wave') 40 | Future _wave(Request request) async { 41 | await Future.delayed(const Duration(milliseconds: 100)); 42 | return Response.ok('_o/'); 43 | } 44 | 45 | // Other routers can be mounted... 46 | @Route.mount('/api') 47 | Router get _api => Api().router; 48 | 49 | // You can catch all verbs and use a URL-parameter with a regular expression 50 | // that matches everything to catch app. 51 | @Route.all('/') 52 | Response _notFound(Request request) => Response.notFound('Page not found'); 53 | 54 | // The generated function _$ServiceRouter can be used to get a [Handler] 55 | // for this object. This can be used with [shelf_io.serve]. 56 | Handler get handler => _$ServiceRouter(this).call; 57 | } 58 | 59 | class Api { 60 | // A handler can have more that one route :) 61 | @Route.get('/messages') 62 | @Route.get('/messages/') 63 | Future _messages(Request request) async => Response.ok('[]'); 64 | 65 | // This nested catch-all, will only catch /api/.* when mounted above. 66 | // Notice that ordering if annotated handlers and mounts is significant. 67 | @Route.all('/') 68 | Response _notFound(Request request) => Response.notFound('null'); 69 | 70 | // The generated function _$ApiRouter can be used to expose a [Router] for 71 | // this object. 72 | Router get router => _$ApiRouter(this); 73 | } 74 | 75 | // Run shelf server and host a [Service] instance on port 8080. 76 | void main() async { 77 | final service = Service(); 78 | final server = await shelf_io.serve(service.handler, 'localhost', 8080); 79 | print('Server running on localhost:${server.port}'); 80 | } 81 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/example/main.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'main.dart'; 4 | 5 | // ************************************************************************** 6 | // ShelfRouterGenerator 7 | // ************************************************************************** 8 | 9 | Router _$ServiceRouter(Service service) { 10 | final router = Router(); 11 | router.add( 12 | 'GET', 13 | r'/say-hi/', 14 | service._hi, 15 | ); 16 | router.add( 17 | 'GET', 18 | r'/user/', 19 | service._user, 20 | ); 21 | router.add( 22 | 'GET', 23 | r'/wave', 24 | service._wave, 25 | ); 26 | router.mount( 27 | r'/api', 28 | service._api.call, 29 | ); 30 | router.all( 31 | r'/', 32 | service._notFound, 33 | ); 34 | return router; 35 | } 36 | 37 | Router _$ApiRouter(Api service) { 38 | final router = Router(); 39 | router.add( 40 | 'GET', 41 | r'/messages', 42 | service._messages, 43 | ); 44 | router.add( 45 | 'GET', 46 | r'/messages/', 47 | service._messages, 48 | ); 49 | router.all( 50 | r'/', 51 | service._notFound, 52 | ); 53 | return router; 54 | } 55 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/lib/builder.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// This library provides a [Builder] for generating functions that can create 16 | /// a [shelf_router.Router] based on annotated members. 17 | /// 18 | /// This is **not intended** for consumption, this library should be used by 19 | /// running `pub run build_runner build`. Using this library through other means 20 | /// is not supported and may break arbitrarily. 21 | library; 22 | 23 | import 'package:build/build.dart'; 24 | import 'package:shelf_router/shelf_router.dart' as shelf_router; 25 | import 'package:source_gen/source_gen.dart'; 26 | 27 | import 'src/shelf_router_generator.dart'; 28 | 29 | /// A [Builder] that generates a `_$Router( service)` 30 | /// function for each class `` containing a member annotated with 31 | /// [shelf_router.Route]. 32 | Builder shelfRouter(BuilderOptions _) => SharedPartBuilder( 33 | [ShelfRouterGenerator()], 34 | 'shelf_router', 35 | ); 36 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | - test: --run-skipped -t presubmit-only 14 | sdk: dev 15 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shelf_router_generator 2 | version: 1.1.1 3 | description: > 4 | A package:build-compatible builder for generating request routers for the 5 | shelf web-framework based on source annotations. 6 | repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_router_generator 7 | issue_tracker: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_router_generator 8 | 9 | topics: 10 | - server 11 | - shelf 12 | 13 | environment: 14 | sdk: ^3.3.0 15 | 16 | dependencies: 17 | analyzer: '>=4.6.0 <8.0.0' 18 | build: ^2.2.2 19 | build_config: ^1.0.0 20 | code_builder: ^4.2.0 21 | http_methods: ^1.1.0 22 | shelf: ^1.1.0 23 | shelf_router: ^1.1.0 24 | source_gen: ">=1.2.2 <3.0.0" 25 | 26 | dev_dependencies: 27 | build_runner: ^2.1.9 28 | build_verify: ^3.0.0 29 | dart_flutter_team_lints: ^3.0.0 30 | http: ^1.0.0 31 | test: ^1.21.0 32 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/test/ensure_build_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @Tags(['presubmit-only']) 16 | @Timeout.factor(4) 17 | library; 18 | 19 | import 'package:build_verify/build_verify.dart'; 20 | import 'package:test/test.dart'; 21 | 22 | void main() { 23 | test('ensure_build', () async { 24 | await expectBuildClean( 25 | packageRelativeDirectory: 'pkgs/shelf_router_generator', 26 | ); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/test/server/api.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async' show Future; 16 | import 'package:shelf/shelf.dart'; 17 | import 'package:shelf_router/shelf_router.dart'; 18 | 19 | part 'api.g.dart'; 20 | 21 | class Api { 22 | @Route.get('/time') 23 | Response _time(Request request) => Response.ok('it is about now'); 24 | 25 | @Route.get('/to-uppercase/') 26 | Future _toUpperCase(Request request, String word) async => 27 | Response.ok(word.toUpperCase()); 28 | 29 | @Route.get(r'/$string-escape') 30 | Response _stringEscapingWorks(Request request) => 31 | Response.ok('Just testing string escaping'); 32 | 33 | Router get router => _$ApiRouter(this); 34 | } 35 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/test/server/api.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'api.dart'; 4 | 5 | // ************************************************************************** 6 | // ShelfRouterGenerator 7 | // ************************************************************************** 8 | 9 | Router _$ApiRouter(Api service) { 10 | final router = Router(); 11 | router.add( 12 | 'GET', 13 | r'/time', 14 | service._time, 15 | ); 16 | router.add( 17 | 'GET', 18 | r'/to-uppercase/', 19 | service._toUpperCase, 20 | ); 21 | router.add( 22 | 'GET', 23 | r'/$string-escape', 24 | service._stringEscapingWorks, 25 | ); 26 | return router; 27 | } 28 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/test/server/server.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async' show Future; 16 | import 'dart:io' show HttpServer; 17 | 18 | import 'package:shelf/shelf_io.dart' as shelf_io; 19 | 20 | import 'service.dart'; 21 | 22 | class Server { 23 | final _service = Service(); 24 | late HttpServer _server; 25 | 26 | Future start() async { 27 | _server = await shelf_io.serve(_service.router.call, 'localhost', 0); 28 | } 29 | 30 | Future stop() => _server.close(); 31 | 32 | Uri get uri => Uri( 33 | scheme: 'http', 34 | host: 'localhost', 35 | port: _server.port, 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/test/server/service.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async' show Future, FutureOr; 16 | 17 | import 'package:shelf/shelf.dart'; 18 | import 'package:shelf_router/shelf_router.dart'; 19 | 20 | import 'api.dart'; 21 | import 'unrelatedannotation.dart'; 22 | 23 | part 'service.g.dart'; 24 | 25 | class Service { 26 | @Route.get('/say-hello') 27 | @Route.get('/say-hello/') 28 | Response _sayHello(Request request) => Response.ok('hello world'); 29 | 30 | @Route.get('/wave') 31 | FutureOr _wave(Request request) async { 32 | await Future.delayed(const Duration(milliseconds: 50)); 33 | return Response.ok('_o/'); 34 | } 35 | 36 | @Route.get('/greet/') 37 | Future _greet(Request request, String user) async => 38 | Response.ok('Greetings, $user'); 39 | 40 | @Route.get('/hi/') 41 | Future _hi(Request request) async { 42 | final name = request.params['user']; 43 | return Response.ok('hi $name'); 44 | } 45 | 46 | @Route.mount('/api/') 47 | Router get _api => Api().router; 48 | 49 | @Route.all('/<_|.*>') 50 | Response _index(Request request) => Response.ok('nothing-here'); 51 | 52 | Router get router => _$ServiceRouter(this); 53 | } 54 | 55 | class UnrelatedThing { 56 | @EndPoint.put('/api/test') 57 | Future unrelatedMethod(Request request) async => 58 | Response.ok('hello world'); 59 | } 60 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/test/server/service.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'service.dart'; 4 | 5 | // ************************************************************************** 6 | // ShelfRouterGenerator 7 | // ************************************************************************** 8 | 9 | Router _$ServiceRouter(Service service) { 10 | final router = Router(); 11 | router.add( 12 | 'GET', 13 | r'/say-hello', 14 | service._sayHello, 15 | ); 16 | router.add( 17 | 'GET', 18 | r'/say-hello/', 19 | service._sayHello, 20 | ); 21 | router.add( 22 | 'GET', 23 | r'/wave', 24 | service._wave, 25 | ); 26 | router.add( 27 | 'GET', 28 | r'/greet/', 29 | service._greet, 30 | ); 31 | router.add( 32 | 'GET', 33 | r'/hi/', 34 | service._hi, 35 | ); 36 | router.mount( 37 | r'/api/', 38 | service._api.call, 39 | ); 40 | router.all( 41 | r'/<_|.*>', 42 | service._index, 43 | ); 44 | return router; 45 | } 46 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/test/server/unrelatedannotation.dart: -------------------------------------------------------------------------------- 1 | /// Annotation for an API end-point. 2 | class EndPoint { 3 | /// HTTP verb for requests routed to the annotated method. 4 | final String verb; 5 | 6 | /// HTTP route for request routed to the annotated method. 7 | final String route; 8 | 9 | /// Create an annotation that routes requests matching [verb] and [route] to 10 | /// the annotated method. 11 | const EndPoint(this.verb, this.route); 12 | 13 | /// Route `GET` requests matching [route] to annotated method. 14 | const EndPoint.get(this.route) : verb = 'GET'; 15 | 16 | /// Route `HEAD` requests matching [route] to annotated method. 17 | const EndPoint.head(this.route) : verb = 'HEAD'; 18 | 19 | /// Route `POST` requests matching [route] to annotated method. 20 | const EndPoint.post(this.route) : verb = 'POST'; 21 | 22 | /// Route `PUT` requests matching [route] to annotated method. 23 | const EndPoint.put(this.route) : verb = 'PUT'; 24 | 25 | /// Route `DELETE` requests matching [route] to annotated method. 26 | const EndPoint.delete(this.route) : verb = 'DELETE'; 27 | 28 | /// Route `CONNECT` requests matching [route] to annotated method. 29 | const EndPoint.connect(this.route) : verb = 'CONNECT'; 30 | 31 | /// Route `OPTIONS` requests matching [route] to annotated method. 32 | const EndPoint.options(this.route) : verb = 'OPTIONS'; 33 | 34 | /// Route `TRACE` requests matching [route] to annotated method. 35 | const EndPoint.trace(this.route) : verb = 'TRACE'; 36 | } 37 | -------------------------------------------------------------------------------- /pkgs/shelf_router_generator/test/server_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:http/http.dart' as http; 16 | import 'package:test/test.dart'; 17 | 18 | import 'server/server.dart'; 19 | 20 | void main() { 21 | final server = Server(); 22 | setUpAll(server.start); 23 | tearDownAll(server.stop); 24 | 25 | void testGet({ 26 | required String path, 27 | required String result, 28 | }) => 29 | test('GET $path', () async { 30 | final result = await http.get(server.uri.resolve(path)); 31 | expect(result, equals(result)); 32 | }); 33 | 34 | // Test simple handlers 35 | testGet(path: '/say-hello', result: 'hello world'); 36 | testGet(path: '/say-hello/', result: 'hello world'); 37 | testGet(path: '/wave', result: '_o/'); 38 | testGet(path: '/greet/jonasfj', result: 'Greetings, jonasfj'); 39 | testGet(path: '/greet/sigurdm', result: 'Greetings, sigurdm'); 40 | testGet(path: '/hi/jonasfj', result: 'hi jonasfj'); 41 | testGet(path: '/hi/sigurdm', result: 'hi sigurdm'); 42 | 43 | // Test /api/ 44 | testGet(path: '/api/time', result: 'it is about now'); 45 | testGet(path: '/api/to-uppercase/wEiRd%20Word', result: 'WEIRD WORD'); 46 | testGet(path: '/api/to-uppercase/wEiRd Word', result: 'WEIRD WORD'); 47 | 48 | // Test the catch all handler 49 | testGet(path: '/', result: 'nothing-here'); 50 | testGet(path: '/wrong-path', result: 'nothing-here'); 51 | testGet(path: '/hi/sigurdm/ups', result: 'nothing-here'); 52 | testGet(path: '/api/to-uppercase/too/many/slashs', result: 'nothing-here'); 53 | testGet(path: '/api/', result: 'nothing-here'); 54 | testGet(path: '/api/time/', result: 'nothing-here'); // notice the extra slash 55 | testGet(path: '/api/tim', result: 'nothing-here'); 56 | } 57 | -------------------------------------------------------------------------------- /pkgs/shelf_static/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.1.3 2 | 3 | * Require Dart `^3.3.0`. 4 | * Update `package:mime` constraint to `>=1.0.0 <3.0.0`. 5 | 6 | ## 1.1.2 7 | 8 | * Added package topics to the pubspec file. 9 | * Require Dart `2.17`. 10 | 11 | ## 1.1.1 12 | 13 | * Require Dart `2.14`. 14 | * Update the pubspec `repository` field. 15 | 16 | ## 1.1.0 17 | 18 | * Correctly handle `HEAD` requests. 19 | * Support HTTP range requests. 20 | 21 | ## 1.0.0 22 | 23 | * Migrate to null safety. 24 | 25 | ## 0.2.9+2 26 | 27 | * Change version constraint for the `shelf` dependency, so it accepts null-safe versions. 28 | 29 | ## 0.2.9+1 30 | 31 | * Change version constraint for the `mime` dependency, so it accepts null-safe versions. 32 | 33 | ## 0.2.9 34 | 35 | * Update SDK constraint to `>=2.3.0 <3.0.0`. 36 | * Allow `3.x` versions of `package:convert`. 37 | * Allow `4.x` versions of `package:http_parser`. 38 | * Use file `modified` dates instead of `changed` for `304 Not Modified` checks as `changed` returns creation dates on 39 | Windows. 40 | 41 | ## 0.2.8 42 | 43 | * Update SDK constraint to `>=2.0.0-dev.61 <3.0.0`. 44 | 45 | * Directory listings are now sorted. 46 | 47 | ## 0.2.7+1 48 | 49 | * Updated SDK version to 2.0.0-dev.17.0 50 | 51 | ## 0.2.7 52 | 53 | * Require at least Dart SDK 1.24.0. 54 | * Other internal changes e.g. removing dep on `scheduled_test`. 55 | 56 | ## 0.2.6 57 | 58 | * Add a `createFileHandler()` function that serves a single static file. 59 | 60 | ## 0.2.5 61 | 62 | * Add an optional `contentTypeResolver` argument to `createStaticHandler`. 63 | 64 | ## 0.2.4 65 | 66 | * Add support for "sniffing" the content of the file for the content-type via an optional 67 | `useHeaderBytesForContentType` argument on `createStaticHandler`. 68 | 69 | ## 0.2.3+4 70 | 71 | * Support `http_parser` 3.0.0. 72 | 73 | ## 0.2.3+3 74 | 75 | * Support `shelf` 0.7.0. 76 | 77 | ## 0.2.3+2 78 | 79 | * Support `http_parser` 2.0.0. 80 | 81 | ## 0.2.3+1 82 | 83 | * Support `http_parser` 1.0.0. 84 | 85 | ## 0.2.3 86 | 87 | * Added `listDirectories` argument to `createStaticHandler`. 88 | 89 | ## 0.2.2 90 | 91 | * Bumped up minimum SDK to 1.7.0. 92 | 93 | * Added support for `shelf` 0.6.0. 94 | 95 | ## 0.2.1 96 | 97 | * Removed `Uri` format checks now that the core libraries is more strict. 98 | 99 | ## 0.2.0 100 | 101 | * Removed deprecated `getHandler`. 102 | 103 | * Send correct mime type for default document. 104 | 105 | ## 0.1.4+6 106 | 107 | * Updated development dependencies. 108 | 109 | ## 0.1.4+5 110 | 111 | * Handle differences in resolution between `DateTime` and HTTP date format. 112 | 113 | ## 0.1.4+4 114 | 115 | * Using latest `shelf`. Cleaned up test code by using new features. 116 | 117 | ## 0.1.4 118 | 119 | * Added named (optional) `defaultDocument` argument to `createStaticHandler`. 120 | 121 | ## 0.1.3 122 | 123 | * `createStaticHandler` added `serveFilesOutsidePath` optional parameter. 124 | 125 | ## 0.1.2 126 | 127 | * The preferred top-level method is now `createStaticHandler`. `getHandler` is deprecated. 128 | * Set `content-type` header if the mime type of the requested file can be determined from the file extension. 129 | * Respond with `304-Not modified` against `IF-MODIFIED-SINCE` request header. 130 | * Better error when provided a non-existent `fileSystemPath`. 131 | * Added `example/example_server.dart`. 132 | 133 | ## 0.1.1+1 134 | 135 | * Removed work around for [issue](https://codereview.chromium.org/278783002/). 136 | 137 | ## 0.1.1 138 | 139 | * Correctly handle requests when not hosted at the root of a site. 140 | * Send `last-modified` header. 141 | * Work around [known issue](https://codereview.chromium.org/278783002/) with HTTP date formatting. 142 | -------------------------------------------------------------------------------- /pkgs/shelf_static/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pkgs/shelf_static/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/shelf_static.svg)](https://pub.dev/packages/shelf_static) 2 | [![package publisher](https://img.shields.io/pub/publisher/shelf_static.svg)](https://pub.dev/packages/shelf_static/publisher) 3 | 4 | `shelf_static` is a `Handler` for the Dart `shelf` package. 5 | 6 | ### Example 7 | ```dart 8 | import 'package:shelf/shelf_io.dart' as io; 9 | import 'package:shelf_static/shelf_static.dart'; 10 | 11 | void main() { 12 | var handler = createStaticHandler('example/files', 13 | defaultDocument: 'index.html'); 14 | 15 | io.serve(handler, 'localhost', 8080); 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /pkgs/shelf_static/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/shelf_static/example/example.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | import 'package:args/args.dart'; 7 | import 'package:shelf/shelf.dart' as shelf; 8 | import 'package:shelf/shelf_io.dart' as io; 9 | import 'package:shelf_static/shelf_static.dart'; 10 | 11 | void main(List args) { 12 | final parser = _getParser(); 13 | 14 | int port; 15 | bool logging; 16 | bool listDirectories; 17 | 18 | try { 19 | final result = parser.parse(args); 20 | port = int.parse(result['port'] as String); 21 | logging = result['logging'] as bool; 22 | listDirectories = result['list-directories'] as bool; 23 | } on FormatException catch (e) { 24 | stderr 25 | ..writeln(e.message) 26 | ..writeln(parser.usage); 27 | // http://linux.die.net/include/sysexits.h 28 | // #define EX_USAGE 64 /* command line usage error */ 29 | exit(64); 30 | } 31 | 32 | if (!FileSystemEntity.isFileSync('example/example.dart')) { 33 | throw StateError('Server expects to be started the ' 34 | 'root of the project.'); 35 | } 36 | var pipeline = const shelf.Pipeline(); 37 | 38 | if (logging) { 39 | pipeline = pipeline.addMiddleware(shelf.logRequests()); 40 | } 41 | 42 | String? defaultDoc = _defaultDoc; 43 | if (listDirectories) { 44 | defaultDoc = null; 45 | } 46 | 47 | final handler = pipeline.addHandler(createStaticHandler('example/files', 48 | defaultDocument: defaultDoc, listDirectories: listDirectories)); 49 | 50 | io.serve(handler, 'localhost', port).then((server) { 51 | print('Serving at http://${server.address.host}:${server.port}'); 52 | }); 53 | } 54 | 55 | ArgParser _getParser() => ArgParser() 56 | ..addFlag('logging', abbr: 'l', defaultsTo: true) 57 | ..addOption('port', abbr: 'p', defaultsTo: '8080') 58 | ..addFlag('list-directories', 59 | abbr: 'f', 60 | negatable: false, 61 | help: 'List the files in the source directory instead of serving the ' 62 | 'default document - "$_defaultDoc".'); 63 | 64 | const _defaultDoc = 'index.html'; 65 | -------------------------------------------------------------------------------- /pkgs/shelf_static/example/files/dart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-lang/shelf/082d3ac2d13a98700d8148e8fad8f3e12a6fd0e1/pkgs/shelf_static/example/files/dart.png -------------------------------------------------------------------------------- /pkgs/shelf_static/example/files/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-lang/shelf/082d3ac2d13a98700d8148e8fad8f3e12a6fd0e1/pkgs/shelf_static/example/files/favicon.ico -------------------------------------------------------------------------------- /pkgs/shelf_static/example/files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | shelf_static 7 | 8 | 9 |

Hello, shelf_static!

10 | Dart logo 11 | 12 | 13 | -------------------------------------------------------------------------------- /pkgs/shelf_static/lib/shelf_static.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'src/static_handler.dart'; 6 | -------------------------------------------------------------------------------- /pkgs/shelf_static/lib/src/directory_listing.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | import 'dart:io'; 8 | 9 | import 'package:path/path.dart' as path; 10 | import 'package:shelf/shelf.dart'; 11 | 12 | String _getHeader(String sanitizedHeading) => ''' 13 | 14 | 15 | Directory listing for $sanitizedHeading 16 | 42 | 43 | 44 |

$sanitizedHeading

45 |
    46 | '''; 47 | 48 | const String _trailer = '''
49 | 50 | 51 | '''; 52 | 53 | Response listDirectory(String fileSystemPath, String dirPath) { 54 | final controller = StreamController>(); 55 | const encoding = Utf8Codec(); 56 | const sanitizer = HtmlEscape(); 57 | 58 | void add(String string) { 59 | controller.add(encoding.encode(string)); 60 | } 61 | 62 | var heading = path.relative(dirPath, from: fileSystemPath); 63 | if (heading == '.') { 64 | heading = '/'; 65 | } else { 66 | heading = '/$heading/'; 67 | } 68 | 69 | add(_getHeader(sanitizer.convert(heading))); 70 | 71 | // Return a sorted listing of the directory contents asynchronously. 72 | Directory(dirPath).list().toList().then((entities) { 73 | entities.sort((e1, e2) { 74 | if (e1 is Directory && e2 is! Directory) { 75 | return -1; 76 | } 77 | if (e1 is! Directory && e2 is Directory) { 78 | return 1; 79 | } 80 | return e1.path.compareTo(e2.path); 81 | }); 82 | 83 | for (var entity in entities) { 84 | var name = path.relative(entity.path, from: dirPath); 85 | if (entity is Directory) name += '/'; 86 | final sanitizedName = sanitizer.convert(name); 87 | add('
  • $sanitizedName
  • \n'); 88 | } 89 | 90 | add(_trailer); 91 | controller.close(); 92 | }); 93 | 94 | return Response.ok( 95 | controller.stream, 96 | encoding: encoding, 97 | headers: {HttpHeaders.contentTypeHeader: 'text/html'}, 98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /pkgs/shelf_static/lib/src/util.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | DateTime toSecondResolution(DateTime dt) { 6 | if (dt.millisecond == 0) return dt; 7 | return dt.subtract(Duration(milliseconds: dt.millisecond)); 8 | } 9 | -------------------------------------------------------------------------------- /pkgs/shelf_static/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/shelf_static/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shelf_static 2 | version: 1.1.3 3 | description: Static file server support for the shelf package and ecosystem. 4 | repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_static 5 | issue_tracker: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_static 6 | 7 | topics: 8 | - server 9 | - shelf 10 | 11 | environment: 12 | sdk: ^3.3.0 13 | 14 | dependencies: 15 | convert: ^3.0.0 16 | http_parser: ^4.0.0 17 | mime: '>=1.0.0 <3.0.0' 18 | path: ^1.8.0 19 | # shelf version that allows correctly setting content-length w/ HEAD 20 | shelf: ^1.1.2 21 | 22 | dev_dependencies: 23 | args: ^2.0.0 24 | dart_flutter_team_lints: ^3.0.0 25 | test: ^1.16.0 26 | test_descriptor: ^2.0.0 27 | -------------------------------------------------------------------------------- /pkgs/shelf_static/test/alternative_root_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:shelf_static/shelf_static.dart'; 8 | import 'package:test/test.dart'; 9 | import 'package:test_descriptor/test_descriptor.dart' as d; 10 | 11 | import 'test_util.dart'; 12 | 13 | void main() { 14 | setUp(() async { 15 | await d.file('root.txt', 'root txt').create(); 16 | await d.dir('files', [ 17 | d.file('test.txt', 'test txt content'), 18 | d.file('with space.txt', 'with space content') 19 | ]).create(); 20 | }); 21 | 22 | test('access root file', () async { 23 | final handler = createStaticHandler(d.sandbox); 24 | 25 | final response = 26 | await makeRequest(handler, '/static/root.txt', handlerPath: 'static'); 27 | expect(response.statusCode, HttpStatus.ok); 28 | expect(response.contentLength, 8); 29 | expect(response.readAsString(), completion('root txt')); 30 | }); 31 | 32 | test('access root file with space', () async { 33 | final handler = createStaticHandler(d.sandbox); 34 | 35 | final response = await makeRequest( 36 | handler, '/static/files/with%20space.txt', 37 | handlerPath: 'static'); 38 | expect(response.statusCode, HttpStatus.ok); 39 | expect(response.contentLength, 18); 40 | expect(response.readAsString(), completion('with space content')); 41 | }); 42 | 43 | test('access root file with unencoded space', () async { 44 | final handler = createStaticHandler(d.sandbox); 45 | 46 | final response = await makeRequest( 47 | handler, '/static/files/with%20space.txt', 48 | handlerPath: 'static'); 49 | expect(response.statusCode, HttpStatus.ok); 50 | expect(response.contentLength, 18); 51 | expect(response.readAsString(), completion('with space content')); 52 | }); 53 | 54 | test('access file under directory', () async { 55 | final handler = createStaticHandler(d.sandbox); 56 | 57 | final response = await makeRequest(handler, '/static/files/test.txt', 58 | handlerPath: 'static'); 59 | expect(response.statusCode, HttpStatus.ok); 60 | expect(response.contentLength, 16); 61 | expect(response.readAsString(), completion('test txt content')); 62 | }); 63 | 64 | test('file not found', () async { 65 | final handler = createStaticHandler(d.sandbox); 66 | 67 | final response = await makeRequest(handler, '/static/not_here.txt', 68 | handlerPath: 'static'); 69 | expect(response.statusCode, HttpStatus.notFound); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /pkgs/shelf_static/test/default_document_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:shelf_static/shelf_static.dart'; 8 | import 'package:test/test.dart'; 9 | import 'package:test_descriptor/test_descriptor.dart' as d; 10 | 11 | import 'test_util.dart'; 12 | 13 | void main() { 14 | setUp(() async { 15 | await d.file('index.html', '').create(); 16 | await d.file('root.txt', 'root txt').create(); 17 | await d.dir('files', [ 18 | d.file('index.html', 'files'), 19 | d.file('with space.txt', 'with space content') 20 | ]).create(); 21 | }); 22 | 23 | group('default document value', () { 24 | test('cannot contain slashes', () { 25 | final invalidValues = [ 26 | 'file/foo.txt', 27 | '/bar.txt', 28 | '//bar.txt', 29 | '//news/bar.txt', 30 | 'foo/../bar.txt' 31 | ]; 32 | 33 | for (var val in invalidValues) { 34 | expect(() => createStaticHandler(d.sandbox, defaultDocument: val), 35 | throwsArgumentError); 36 | } 37 | }); 38 | }); 39 | 40 | group('no default document specified', () { 41 | test('access "/index.html"', () async { 42 | final handler = createStaticHandler(d.sandbox); 43 | 44 | final response = await makeRequest(handler, '/index.html'); 45 | expect(response.statusCode, HttpStatus.ok); 46 | expect(response.contentLength, 13); 47 | expect(response.readAsString(), completion('')); 48 | }); 49 | 50 | test('access "/"', () async { 51 | final handler = createStaticHandler(d.sandbox); 52 | 53 | final response = await makeRequest(handler, '/'); 54 | expect(response.statusCode, HttpStatus.notFound); 55 | }); 56 | 57 | test('access "/files"', () async { 58 | final handler = createStaticHandler(d.sandbox); 59 | 60 | final response = await makeRequest(handler, '/files'); 61 | expect(response.statusCode, HttpStatus.notFound); 62 | }); 63 | 64 | test('access "/files/" dir', () async { 65 | final handler = createStaticHandler(d.sandbox); 66 | 67 | final response = await makeRequest(handler, '/files/'); 68 | expect(response.statusCode, HttpStatus.notFound); 69 | }); 70 | }); 71 | 72 | group('default document specified', () { 73 | test('access "/index.html"', () async { 74 | final handler = 75 | createStaticHandler(d.sandbox, defaultDocument: 'index.html'); 76 | 77 | final response = await makeRequest(handler, '/index.html'); 78 | expect(response.statusCode, HttpStatus.ok); 79 | expect(response.contentLength, 13); 80 | expect(response.readAsString(), completion('')); 81 | expect(response.mimeType, 'text/html'); 82 | }); 83 | 84 | test('access "/"', () async { 85 | final handler = 86 | createStaticHandler(d.sandbox, defaultDocument: 'index.html'); 87 | 88 | final response = await makeRequest(handler, '/'); 89 | expect(response.statusCode, HttpStatus.ok); 90 | expect(response.contentLength, 13); 91 | expect(response.readAsString(), completion('')); 92 | expect(response.mimeType, 'text/html'); 93 | }); 94 | 95 | test('access "/files"', () async { 96 | final handler = 97 | createStaticHandler(d.sandbox, defaultDocument: 'index.html'); 98 | 99 | final response = await makeRequest(handler, '/files'); 100 | expect(response.statusCode, HttpStatus.movedPermanently); 101 | expect(response.headers, 102 | containsPair(HttpHeaders.locationHeader, 'http://localhost/files/')); 103 | }); 104 | 105 | test('access "/files/" dir', () async { 106 | final handler = 107 | createStaticHandler(d.sandbox, defaultDocument: 'index.html'); 108 | 109 | final response = await makeRequest(handler, '/files/'); 110 | expect(response.statusCode, HttpStatus.ok); 111 | expect(response.contentLength, 31); 112 | expect(response.readAsString(), 113 | completion('files')); 114 | expect(response.mimeType, 'text/html'); 115 | }); 116 | }); 117 | } 118 | -------------------------------------------------------------------------------- /pkgs/shelf_static/test/directory_listing_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:shelf_static/shelf_static.dart'; 8 | import 'package:test/test.dart'; 9 | import 'package:test_descriptor/test_descriptor.dart' as d; 10 | 11 | import 'test_util.dart'; 12 | 13 | void main() { 14 | setUp(() async { 15 | await d.file('index.html', '').create(); 16 | await d.file('root.txt', 'root txt').create(); 17 | await d.dir('files', [ 18 | d.file('index.html', 'files'), 19 | d.file('with space.txt', 'with space content'), 20 | d.dir('empty subfolder', []), 21 | ]).create(); 22 | }); 23 | 24 | test('access "/"', () async { 25 | final handler = createStaticHandler(d.sandbox, listDirectories: true); 26 | 27 | final response = await makeRequest(handler, '/'); 28 | expect(response.statusCode, HttpStatus.ok); 29 | expect(response.readAsString(), completes); 30 | }); 31 | 32 | test('access "/files"', () async { 33 | final handler = createStaticHandler(d.sandbox, listDirectories: true); 34 | 35 | final response = await makeRequest(handler, '/files'); 36 | expect(response.statusCode, HttpStatus.movedPermanently); 37 | expect(response.headers, 38 | containsPair(HttpHeaders.locationHeader, 'http://localhost/files/')); 39 | }); 40 | 41 | test('access "/files/"', () async { 42 | final handler = createStaticHandler(d.sandbox, listDirectories: true); 43 | 44 | final response = await makeRequest(handler, '/files/'); 45 | expect(response.statusCode, HttpStatus.ok); 46 | expect(response.readAsString(), completes); 47 | }); 48 | 49 | test('access "/files/empty subfolder"', () async { 50 | final handler = createStaticHandler(d.sandbox, listDirectories: true); 51 | 52 | final response = await makeRequest(handler, '/files/empty subfolder'); 53 | expect(response.statusCode, HttpStatus.movedPermanently); 54 | expect( 55 | response.headers, 56 | containsPair(HttpHeaders.locationHeader, 57 | 'http://localhost/files/empty%20subfolder/')); 58 | }); 59 | 60 | test('access "/files/empty subfolder/"', () async { 61 | final handler = createStaticHandler(d.sandbox, listDirectories: true); 62 | 63 | final response = await makeRequest(handler, '/files/empty subfolder/'); 64 | expect(response.statusCode, HttpStatus.ok); 65 | expect(response.readAsString(), completes); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /pkgs/shelf_static/test/get_handler_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:path/path.dart' as p; 6 | import 'package:shelf_static/shelf_static.dart'; 7 | import 'package:test/test.dart'; 8 | import 'package:test_descriptor/test_descriptor.dart' as d; 9 | 10 | void main() { 11 | setUp(() async { 12 | await d.file('root.txt', 'root txt').create(); 13 | await d.dir('files', [ 14 | d.file('test.txt', 'test txt content'), 15 | d.file('with space.txt', 'with space content') 16 | ]).create(); 17 | }); 18 | 19 | test('non-existent relative path', () async { 20 | expect(() => createStaticHandler('random/relative'), throwsArgumentError); 21 | }); 22 | 23 | test('existing relative path', () async { 24 | final existingRelative = p.relative(d.sandbox); 25 | expect(() => createStaticHandler(existingRelative), returnsNormally); 26 | }); 27 | 28 | test('non-existent absolute path', () { 29 | final nonExistingAbsolute = p.join(d.sandbox, 'not_here'); 30 | expect(() => createStaticHandler(nonExistingAbsolute), throwsArgumentError); 31 | }); 32 | 33 | test('existing absolute path', () { 34 | expect(() => createStaticHandler(d.sandbox), returnsNormally); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /pkgs/shelf_static/test/sample_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:path/path.dart' as p; 8 | import 'package:shelf/shelf.dart'; 9 | import 'package:shelf_static/shelf_static.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | import 'test_util.dart'; 13 | 14 | void main() { 15 | group('/index.html', () { 16 | test('body is correct', () async { 17 | await _testFileContents('index.html'); 18 | }); 19 | 20 | test('mimeType is text/html', () async { 21 | final response = await _requestFile('index.html'); 22 | expect(response.mimeType, 'text/html'); 23 | }); 24 | 25 | group('/favicon.ico', () { 26 | test('body is correct', () async { 27 | await _testFileContents('favicon.ico'); 28 | }); 29 | 30 | test('mimeType is text/html', () async { 31 | final response = await _requestFile('favicon.ico'); 32 | expect(response.mimeType, 'image/x-icon'); 33 | }); 34 | }); 35 | }); 36 | 37 | group('/dart.png', () { 38 | test('body is correct', () async { 39 | await _testFileContents('dart.png'); 40 | }); 41 | 42 | test('mimeType is image/png', () async { 43 | final response = await _requestFile('dart.png'); 44 | expect(response.mimeType, 'image/png'); 45 | }); 46 | }); 47 | } 48 | 49 | Future _requestFile(String filename) { 50 | final uri = Uri.parse('http://localhost/$filename'); 51 | 52 | return _request(Request('GET', uri)); 53 | } 54 | 55 | Future _testFileContents(String filename) async { 56 | final filePath = p.join(_samplePath, filename); 57 | final file = File(filePath); 58 | final fileContents = file.readAsBytesSync(); 59 | final fileStat = file.statSync(); 60 | 61 | final response = await _requestFile(filename); 62 | expect(response.contentLength, fileStat.size); 63 | expect(response.lastModified, atSameTimeToSecond(fileStat.modified.toUtc())); 64 | await _expectCompletesWithBytes(response, fileContents); 65 | } 66 | 67 | Future _expectCompletesWithBytes( 68 | Response response, List expectedBytes) async { 69 | final bytes = await response.read().toList(); 70 | final flatBytes = bytes.expand((e) => e); 71 | expect(flatBytes, orderedEquals(expectedBytes)); 72 | } 73 | 74 | Future _request(Request request) async { 75 | final handler = createStaticHandler(_samplePath); 76 | return await handler(request); 77 | } 78 | 79 | String get _samplePath { 80 | final sampleDir = p.join(p.current, 'example', 'files'); 81 | assert(FileSystemEntity.isDirectorySync(sampleDir)); 82 | return sampleDir; 83 | } 84 | -------------------------------------------------------------------------------- /pkgs/shelf_static/test/test_util.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:path/path.dart' as p; 6 | import 'package:shelf/shelf.dart'; 7 | import 'package:shelf_static/src/util.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | final p.Context _ctx = p.url; 11 | 12 | /// Makes a simple GET request to [handler] and returns the result. 13 | Future makeRequest( 14 | Handler handler, 15 | String path, { 16 | String? handlerPath, 17 | Map? headers, 18 | String method = 'GET', 19 | }) async { 20 | final rootedHandler = _rootHandler(handlerPath, handler); 21 | return rootedHandler(_fromPath(path, headers, method: method)); 22 | } 23 | 24 | Request _fromPath( 25 | String path, 26 | Map? headers, { 27 | required String method, 28 | }) => 29 | Request(method, Uri.parse('http://localhost$path'), headers: headers); 30 | 31 | Handler _rootHandler(String? path, Handler handler) { 32 | if (path == null || path.isEmpty) { 33 | return handler; 34 | } 35 | 36 | return (Request request) { 37 | if (!_ctx.isWithin('/$path', request.requestedUri.path)) { 38 | return Response.notFound('not found'); 39 | } 40 | assert(request.handlerPath == '/'); 41 | 42 | final relativeRequest = request.change(path: path); 43 | 44 | return handler(relativeRequest); 45 | }; 46 | } 47 | 48 | Matcher atSameTimeToSecond(DateTime value) => 49 | _SecondResolutionDateTimeMatcher(value); 50 | 51 | class _SecondResolutionDateTimeMatcher extends Matcher { 52 | final DateTime _target; 53 | 54 | _SecondResolutionDateTimeMatcher(DateTime target) 55 | : _target = toSecondResolution(target); 56 | 57 | @override 58 | bool matches(dynamic item, Map matchState) { 59 | if (item is! DateTime) return false; 60 | 61 | return _datesEqualToSecond(_target, item); 62 | } 63 | 64 | @override 65 | Description describe(Description description) => 66 | description.add('Must be at the same moment as $_target with resolution ' 67 | 'to the second.'); 68 | } 69 | 70 | bool _datesEqualToSecond(DateTime d1, DateTime d2) => 71 | toSecondResolution(d1).isAtSameMomentAs(toSecondResolution(d2)); 72 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. 7 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.3-wip 2 | 3 | * Require Dart `^3.3.0`. 4 | 5 | ## 2.0.2 6 | 7 | * Added package topics to the pubspec file. 8 | * Require Dart `2.17`. 9 | 10 | ## 2.0.1 11 | 12 | * Require Dart `2.14`. 13 | * Update the pubspec `repository` field. 14 | 15 | ## 2.0.0 16 | 17 | * Migration to null safety. 18 | 19 | ## 1.0.3 20 | 21 | * Support Dart 2 stable releases. 22 | 23 | ## 1.0.2 24 | 25 | * Support test `1.x.x`. 26 | 27 | ## 1.0.1 28 | 29 | * Support latest `pkg/shelf`. 30 | 31 | ## 1.0.0 32 | 33 | * Initial version. 34 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/shelf_test_handler.svg)](https://pub.dev/packages/shelf_test_handler) 2 | [![package publisher](https://img.shields.io/pub/publisher/shelf_test_handler.svg)](https://pub.dev/packages/shelf_test_handler/publisher) 3 | 4 | A [shelf][] handler that makes it easy to test HTTP interactions, especially 5 | when multiple different HTTP requests are expected in a particular sequence. 6 | 7 | [shelf]: [https://github.com/dart-lang/shelf#readme] 8 | 9 | You can construct a [ShelfTestHandler][] directly, but most users will probably 10 | want to use the [ShelfTestServer][] instead. This wraps the handler in a simple 11 | HTTP server, whose URL can be passed to client code. 12 | 13 | [ShelfTestHandler]: https://www.dartdocs.org/documentation/shelf_test_handler/latest/shelf_test_handler/ShelfTestHandler-class.html 14 | [ShelfTestServer]: https://www.dartdocs.org/documentation/shelf_test_handler/latest/shelf_test_handler/ShelfTestServer-class.html 15 | 16 | ```dart 17 | import 'package:shelf/shelf.dart' as shelf; 18 | import 'package:shelf_test_handler/shelf_test_handler.dart'; 19 | import 'package:test/test.dart'; 20 | 21 | import 'package:my_package/my_package.dart'; 22 | 23 | void main() { 24 | test("client performs protocol handshake", () async { 25 | // This is just a utility class that starts a server for a ShelfTestHandler. 26 | var server = new ShelfTestServer(); 27 | 28 | // Asserts that the client will make a GET request to /token. 29 | server.handler.expect("GET", "/token", (request) async { 30 | // This handles the GET /token request. 31 | var body = JSON.parse(await request.readAsString()); 32 | 33 | // Any failures in this handler will cause the test to fail, so it's safe 34 | // to make assertions. 35 | expect(body, containsPair("id", "my_package_id")); 36 | expect(body, containsPair("secret", "123abc")); 37 | 38 | return new shelf.Response.ok(JSON.encode({"token": "a1b2c3"}), 39 | headers: {"content-type": "application/json"}); 40 | }); 41 | 42 | // Requests made against `server.url` will be handled by the handlers we 43 | // declare. 44 | var myPackage = new MyPackage(server.url); 45 | 46 | // If the client makes any unexpected requests, the test will fail. 47 | await myPackage.performHandshake(); 48 | }); 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/lib/shelf_test_handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | export 'src/handler.dart'; 6 | export 'src/server.dart'; 7 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/lib/src/expectation.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:shelf/shelf.dart'; 6 | 7 | import 'handler.dart'; 8 | 9 | /// A single expectation for an HTTP request sent to a [ShelfTestHandler]. 10 | class Expectation { 11 | /// The expected request method, or `null` if this allows any requests. 12 | final String? method; 13 | 14 | /// The expected request path, or `null` if this allows any requests. 15 | final String? path; 16 | 17 | /// The handler to use for requests that match this expectation. 18 | final Handler handler; 19 | 20 | Expectation(this.method, this.path, this.handler); 21 | 22 | /// Creates an expectation that allows any method and path. 23 | Expectation.anything(this.handler) 24 | : method = null, 25 | path = null; 26 | } 27 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/lib/src/handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:collection'; 7 | 8 | import 'package:shelf/shelf.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | import 'expectation.dart'; 12 | 13 | /// A [Handler] that handles requests as specified by [expect] and 14 | /// [expectAnything]. 15 | class ShelfTestHandler { 16 | /// The description used in debugging output for this handler. 17 | final String description; 18 | 19 | /// Whether to log each request to [printOnFailure]. 20 | final bool _log; 21 | 22 | /// The zone in which this handler was created. 23 | final Zone _zone; 24 | 25 | /// The queue of expected requests to this handler. 26 | final _expectations = Queue(); 27 | 28 | /// Creates a new handler that handles requests using handlers provided by 29 | /// [expect] and [expectAnything]. 30 | /// 31 | /// If [log] is `true` (the default), this prints all requests using 32 | /// [printOnFailure]. 33 | /// 34 | /// The [description] is used in debugging output for this handler. It 35 | /// defaults to "ShelfTestHandler". 36 | ShelfTestHandler({bool log = true, String? description}) 37 | : _log = log, 38 | description = description ?? 'ShelfTestHandler', 39 | _zone = Zone.current; 40 | 41 | /// Expects that a single HTTP request with the given [method] and [path] will 42 | /// be made to `this`. 43 | /// 44 | /// The [path] should be root-relative; that is, it shuld start with "/". 45 | /// 46 | /// When a matching request is made, [handler] is used to handle that request. 47 | /// 48 | /// If this and/or [expectAnything] are called multiple times, the requests 49 | /// are expected to occur in the same order. 50 | void expect(String method, String path, Handler handler) { 51 | _expectations.add(Expectation(method, path, handler)); 52 | } 53 | 54 | /// Expects that a single HTTP request will be made to `this`. 55 | /// 56 | /// When a request is made, [handler] is used to handle that request. 57 | /// 58 | /// If this and/or [expect] are called multiple times, the requests are 59 | /// expected to occur in the same order. 60 | void expectAnything(Handler handler) { 61 | _expectations.add(Expectation.anything(handler)); 62 | } 63 | 64 | /// The implementation of [Handler]. 65 | FutureOr call(Request request) async { 66 | var requestInfo = '${request.method} /${request.url}'; 67 | if (_log) printOnFailure('[$description] $requestInfo'); 68 | 69 | try { 70 | if (_expectations.isEmpty) { 71 | throw TestFailure( 72 | '$description received unexpected request $requestInfo.'); 73 | } 74 | 75 | var expectation = _expectations.removeFirst(); 76 | if ((expectation.method != null && 77 | expectation.method != request.method) || 78 | (expectation.path != '/${request.url.path}' && 79 | expectation.path != null)) { 80 | var message = '$description received unexpected request $requestInfo.'; 81 | if (expectation.method != null) { 82 | message += '\nExpected ${expectation.method} ${expectation.path}.'; 83 | } 84 | throw TestFailure(message); 85 | } 86 | 87 | return await expectation.handler(request); 88 | } on HijackException catch (_) { 89 | rethrow; 90 | } catch (error, stackTrace) { 91 | _zone.handleUncaughtError(error, stackTrace); 92 | // We can't return null here, the handler type doesn't allow it. 93 | return Response.internalServerError(body: '$error'); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/lib/src/server.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:http_multi_server/http_multi_server.dart'; 9 | import 'package:shelf/shelf_io.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | import 'handler.dart'; 13 | 14 | /// A shorthand for creating an HTTP server serving a [ShelfTestHandler]. 15 | /// 16 | /// This is constructed using [create], and expectations may be registered 17 | /// through [handler]. 18 | class ShelfTestServer { 19 | /// The underlying HTTP server. 20 | final HttpServer _server; 21 | 22 | /// The handler on which expectations can be registered. 23 | final ShelfTestHandler handler; 24 | 25 | /// The URL of this server. 26 | Uri get url => Uri.parse('http://localhost:${_server.port}'); 27 | 28 | /// Creates a server serving a [ShelfTestHandler]. 29 | /// 30 | /// If [log] is `true` (the default), this prints all requests using 31 | /// [printOnFailure]. 32 | /// 33 | /// The [description] is used in debugging output for this handler. It 34 | /// defaults to "ShelfTestHandler". 35 | static Future create( 36 | {bool log = true, String? description}) async { 37 | var server = await HttpMultiServer.loopback(0); 38 | var handler = ShelfTestHandler(log: log, description: description); 39 | serveRequests(server, handler.call); 40 | return ShelfTestServer._(server, handler); 41 | } 42 | 43 | ShelfTestServer._(this._server, this.handler); 44 | 45 | /// Closes the server. 46 | /// 47 | /// If [force] is `true`, all active connections will be closed immediately. 48 | Future close({bool force = false}) => _server.close(force: force); 49 | } 50 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | - test: --test-randomize-ordering-seed=random -p chrome 17 | os: 18 | - linux 19 | - windows 20 | - test: --test-randomize-ordering-seed=random -p chrome -c dart2wasm 21 | sdk: dev 22 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shelf_test_handler 2 | version: 2.0.3-wip 3 | description: A Shelf handler that makes it easy to test HTTP interactions. 4 | repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_test_handler 5 | issue_tracker: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_test_handler 6 | 7 | topics: 8 | - server 9 | - shelf 10 | 11 | environment: 12 | sdk: ^3.3.0 13 | 14 | dependencies: 15 | http_multi_server: ^3.0.0 16 | shelf: ^1.0.0 17 | test: ^1.16.0 18 | 19 | dev_dependencies: 20 | dart_flutter_team_lints: ^3.0.0 21 | http: ^1.0.0 22 | shelf_web_socket: ^2.0.0 23 | web_socket_channel: ^2.0.0 24 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/test/handler_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:shelf/shelf.dart'; 8 | import 'package:shelf_test_handler/shelf_test_handler.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | void main() { 12 | group('invokes the handler(s)', () { 13 | late ShelfTestHandler handler; 14 | setUp(() { 15 | handler = ShelfTestHandler(); 16 | }); 17 | 18 | test('with the expected method and path', () async { 19 | handler.expect('GET', '/', expectAsync1((_) => Response.ok(''))); 20 | var response = await handler(_get('/')); 21 | expect(response.statusCode, equals(200)); 22 | }); 23 | 24 | test('in queue order', () async { 25 | handler.expect('GET', '/', expectAsync1((_) => Response.ok('1'))); 26 | handler.expect('GET', '/', expectAsync1((_) => Response.ok('2'))); 27 | handler.expect('GET', '/', expectAsync1((_) => Response.ok('3'))); 28 | 29 | expect(await (await handler(_get('/'))).readAsString(), equals('1')); 30 | expect(await (await handler(_get('/'))).readAsString(), equals('2')); 31 | expect(await (await handler(_get('/'))).readAsString(), equals('3')); 32 | }); 33 | 34 | test('interleaved with requests', () async { 35 | handler.expect('GET', '/', expectAsync1((_) => Response.ok('1'))); 36 | expect(await (await handler(_get('/'))).readAsString(), equals('1')); 37 | 38 | handler.expect('GET', '/', expectAsync1((_) => Response.ok('2'))); 39 | expect(await (await handler(_get('/'))).readAsString(), equals('2')); 40 | 41 | handler.expect('GET', '/', expectAsync1((_) => Response.ok('3'))); 42 | expect(await (await handler(_get('/'))).readAsString(), equals('3')); 43 | }); 44 | 45 | test('for expectAnything()', () async { 46 | handler.expectAnything(expectAsync1((request) { 47 | expect(request.method, equals('GET')); 48 | expect(request.url.path, equals('foo/bar')); 49 | return Response.ok(''); 50 | })); 51 | 52 | var response = await handler(_get('/foo/bar')); 53 | expect(response.statusCode, equals(200)); 54 | }); 55 | }); 56 | 57 | group('throws a TestFailure', () { 58 | test('without any expectations', () { 59 | _expectZoneFailure(() async { 60 | var handler = ShelfTestHandler(); 61 | await handler(_get('/')); 62 | }); 63 | }); 64 | 65 | test('when all expectations are exhausted', () { 66 | _expectZoneFailure(() async { 67 | var handler = ShelfTestHandler(); 68 | handler.expect('GET', '/', expectAsync1((_) => Response.ok(''))); 69 | await handler(_get('/')); 70 | await handler(_get('/')); 71 | }); 72 | }); 73 | 74 | test("when the method doesn't match the expectation", () { 75 | _expectZoneFailure(() async { 76 | var handler = ShelfTestHandler(); 77 | handler.expect( 78 | 'POST', 79 | '/', 80 | expectAsync1( 81 | (_) { 82 | fail('should never get here'); 83 | }, 84 | count: 0, 85 | )); 86 | await handler(_get('/')); 87 | }); 88 | }); 89 | 90 | test("when the path doesn't match the expectation", () { 91 | _expectZoneFailure(() async { 92 | var handler = ShelfTestHandler(); 93 | handler.expect( 94 | 'GET', 95 | '/foo', 96 | expectAsync1((_) { 97 | fail('should never get here'); 98 | }, count: 0)); 99 | await handler(_get('/')); 100 | }); 101 | }); 102 | }); 103 | 104 | test("doesn't swallow handler errors", () { 105 | runZonedGuarded(() async { 106 | var handler = ShelfTestHandler(); 107 | handler.expect('GET', '/', (_) => throw StateError('oh heck')); 108 | await handler(_get('/')); 109 | }, expectAsync2((error, stack) { 110 | expect(error, 111 | isA().having((p0) => p0.message, 'message', 'oh heck')); 112 | })); 113 | }); 114 | } 115 | 116 | void _expectZoneFailure(Future Function() callback) { 117 | runZonedGuarded(callback, expectAsync2((error, stack) { 118 | expect(error, isA()); 119 | })); 120 | } 121 | 122 | Request _get(String path) => 123 | Request('GET', Uri.parse('http://localhost:80$path')); 124 | -------------------------------------------------------------------------------- /pkgs/shelf_test_handler/test/server_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn('vm') 6 | library; 7 | 8 | import 'dart:io'; 9 | 10 | import 'package:http/http.dart' as http; 11 | import 'package:shelf/shelf.dart'; 12 | import 'package:shelf_test_handler/shelf_test_handler.dart'; 13 | import 'package:shelf_web_socket/shelf_web_socket.dart'; 14 | import 'package:test/test.dart'; 15 | import 'package:web_socket_channel/web_socket_channel.dart'; 16 | 17 | void main() { 18 | test('serves a ShelfTestHandler', () async { 19 | var server = await ShelfTestServer.create(); 20 | addTearDown(server.close); 21 | 22 | server.handler.expect('GET', '/', expectAsync1((_) => Response.ok(''))); 23 | var response = await http.get(server.url); 24 | expect(response.statusCode, equals(200)); 25 | }); 26 | 27 | test('supports request hijacking', () async { 28 | var server = await ShelfTestServer.create(); 29 | addTearDown(server.close); 30 | 31 | server.handler.expect('GET', '/', 32 | webSocketHandler((WebSocketChannel webSocket, _) { 33 | webSocket.sink.add('hello!'); 34 | webSocket.sink.close(); 35 | })); 36 | 37 | var webSocket = 38 | await WebSocket.connect('ws://localhost:${server.url.port}'); 39 | expect(webSocket, emits('hello!')); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /pkgs/shelf_web_socket/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.0.0 2 | 3 | * **BREAKING:**: Change the signature of the `webSocketHandler` method's 4 | `onConnection` callback. Previously this took an untyped function with either 5 | one or two parameters. This now requires a `ConnectionCallback`; a typedef 6 | taking two parameters. See also https://github.com/dart-lang/shelf/issues/457. 7 | * Add a API usage example. 8 | * Require Dart `^3.5.0`. 9 | 10 | Note that most clients seeing analysis issues from the above breaking change can 11 | fix it by adding a second parameter to their callback. So, they would change 12 | this: 13 | 14 | ```dart 15 | webSocketHandler((webSocket) { 16 | webSocket.stream.listen((message) { 17 | webSocket.sink.add('echo $message'); 18 | }); 19 | }); 20 | ``` 21 | 22 | to this: 23 | 24 | ``` 25 | webSocketHandler((webSocket, _) { 26 | webSocket.stream.listen((message) { 27 | webSocket.sink.add('echo $message'); 28 | }); 29 | }); 30 | ``` 31 | 32 | ## 2.0.1 33 | 34 | * Require Dart `^3.3.0`. 35 | 36 | ## 2.0.0 37 | 38 | * **BREAKING:**: Remove support for hijacking WebSocket requests that are not 39 | being transported using `dart:io` `Socket`s. 40 | * Require Dart `^3.0.0`. 41 | 42 | ## 1.0.4 43 | 44 | * Added package topics to the pubspec file. 45 | 46 | ## 1.0.3 47 | 48 | * Require Dart `2.17`. 49 | * Fix checking for binary callbacks with strong null safety. 50 | 51 | ## 1.0.2 52 | 53 | * Require Dart `2.14`. 54 | * Update the pubspec `repository` field. 55 | 56 | ## 1.0.1 57 | 58 | * Require the latest shelf, remove dead code. 59 | 60 | ## 1.0.0 61 | 62 | * Migrate to null safety. 63 | 64 | ## 0.2.4+1 65 | 66 | * Support the latest `package:web_socket_channel`. 67 | 68 | ## 0.2.4 69 | 70 | * Support the latest shelf release (`1.x.x`). 71 | * Require at least Dart 2.1 72 | * Allow omitting `protocols` argument even if the `onConnection` callback takes a second argument. 73 | 74 | ## 0.2.3 75 | 76 | * Add `pingInterval` argument to `webSocketHandler`, to be passed through to the created channel. 77 | 78 | ## 0.2.2+5 79 | 80 | * Allow `stream_channel` version 2.x 81 | 82 | ## 0.2.2+4 83 | 84 | * Fix the check for `onConnection` to check the number of arguments and not that the arguments are `dynamic`. 85 | 86 | ## 0.2.2+3 87 | 88 | * Set max SDK version to `<3.0.0`, and adjust other dependencies. 89 | 90 | ## 0.2.2+2 91 | 92 | * Stopped using deprected `HTML_ESCAPE` constant name. 93 | 94 | ## 0.2.2+1 95 | 96 | * Update SDK version to 2.0.0-dev.17.0. 97 | 98 | ## 0.2.2 99 | 100 | * Stop using comment-based generic syntax. 101 | 102 | ## 0.2.1 103 | 104 | * Fix all strong-mode warnings. 105 | 106 | ## 0.2.0 107 | 108 | * **Breaking change**: `webSocketHandler()` now uses the 109 | [`WebSocketChannel`][WebSocketChannel] class defined in the 110 | `web_socket_channel` package, rather than the deprecated class defined in 111 | `http_parser`. 112 | 113 | [WebSocketChannel]: https://pub.dev/documentation/web_socket_channel/latest/web_socket_channel/WebSocketChannel-class.html 114 | 115 | ## 0.1.0 116 | 117 | * **Breaking change**: `webSocketHandler()` now passes a `WebSocketChannel` to the `onConnection()` callback, rather 118 | than a deprecated `CompatibleWebSocket`. 119 | 120 | ## 0.0.1+5 121 | 122 | * Support `http_parser` 2.0.0. 123 | 124 | ## 0.0.1+4 125 | 126 | * Fix a link to `shelf` in the README. 127 | 128 | ## 0.0.1+3 129 | 130 | * Support `http_parser` 1.0.0. 131 | 132 | ## 0.0.1+2 133 | 134 | * Mark as compatible with version `0.6.0` of `shelf`. 135 | 136 | ## 0.0.1+1 137 | 138 | * Properly parse the `Connection` header. This fixes an issue where Firefox was unable to connect. 139 | -------------------------------------------------------------------------------- /pkgs/shelf_web_socket/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pkgs/shelf_web_socket/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/shelf_web_socket.svg)](https://pub.dev/packages/shelf_web_socket) 2 | [![package publisher](https://img.shields.io/pub/publisher/shelf_web_socket.svg)](https://pub.dev/packages/shelf_web_socket/publisher) 3 | 4 | ## Web Socket Handler for Shelf 5 | 6 | `shelf_web_socket` is a [Shelf][] handler for establishing [WebSocket][] 7 | connections. It exposes a single function, [webSocketHandler][], which calls an 8 | `onConnection` callback with a [WebSocketChannel][] object for every 9 | connection that's established. 10 | 11 | [Shelf]: https://pub.dev/packages/shelf 12 | 13 | [WebSocket]: https://tools.ietf.org/html/rfc6455 14 | 15 | [webSocketHandler]: https://pub.dev/documentation/shelf_web_socket/latest/shelf_web_socket/webSocketHandler.html 16 | 17 | [WebSocketChannel]: https://pub.dev/documentation/web_socket_channel/latest/web_socket_channel/WebSocketChannel-class.html 18 | 19 | ```dart 20 | import 'package:shelf/shelf_io.dart' as shelf_io; 21 | import 'package:shelf_web_socket/shelf_web_socket.dart'; 22 | 23 | void main() { 24 | var handler = webSocketHandler((webSocket, _) { 25 | webSocket.stream.listen((message) { 26 | webSocket.sink.add('echo $message'); 27 | }); 28 | }); 29 | 30 | shelf_io.serve(handler, 'localhost', 8080).then((server) { 31 | print('Serving at ws://${server.address.host}:${server.port}'); 32 | }); 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /pkgs/shelf_web_socket/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /pkgs/shelf_web_socket/example/example.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:shelf/shelf_io.dart' as shelf_io; 6 | import 'package:shelf_web_socket/shelf_web_socket.dart'; 7 | 8 | void main() { 9 | var handler = webSocketHandler((webSocket, _) { 10 | webSocket.stream.listen((message) { 11 | webSocket.sink.add('echo $message'); 12 | }); 13 | }); 14 | 15 | shelf_io.serve(handler, 'localhost', 8080).then((server) { 16 | print('Serving at ws://${server.address.host}:${server.port}'); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /pkgs/shelf_web_socket/lib/shelf_web_socket.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:shelf/shelf.dart'; 6 | import 'package:web_socket_channel/web_socket_channel.dart'; 7 | 8 | import 'src/web_socket_handler.dart'; 9 | 10 | /// Creates a Shelf handler that upgrades HTTP requests to WebSocket 11 | /// connections. 12 | /// 13 | /// Only valid WebSocket upgrade requests are upgraded. If a request doesn't 14 | /// look like a WebSocket upgrade request, a 404 Not Found is returned; if a 15 | /// request looks like an upgrade request but is invalid, a 400 Bad Request is 16 | /// returned; and if a request is a valid upgrade request but has an origin that 17 | /// doesn't match [allowedOrigins] (see below), a 403 Forbidden is returned. 18 | /// This means that this can be placed first in a [Cascade] and only upgrade 19 | /// requests will be handled. 20 | /// 21 | /// The [onConnection] takes a [WebSocketChannel] as its first argument and a 22 | /// nullable string, the [WebSocket subprotocol][], as its second argument. 23 | /// The subprotocol is determined by looking at the client's 24 | /// `Sec-WebSocket-Protocol` header and selecting the first entry that also 25 | /// appears in [protocols]. If no subprotocols are shared between the client and 26 | /// the server, `null` will be passed instead and no subprotocol header will be 27 | /// sent to the client which may cause it to disconnect. 28 | /// 29 | /// [WebSocket subprotocol]: https://tools.ietf.org/html/rfc6455#section-1.9 30 | /// 31 | /// If [allowedOrigins] is passed, browser connections will only be accepted if 32 | /// they're made by a script from one of the given origins. This ensures that 33 | /// malicious scripts running in the browser are unable to fake a WebSocket 34 | /// handshake. Note that non-browser programs can still make connections freely. 35 | /// See also the WebSocket spec's discussion of [origin considerations][]. 36 | /// 37 | /// [origin considerations]: https://tools.ietf.org/html/rfc6455#section-10.2 38 | /// 39 | /// If [pingInterval] is specified, it will get passed to the created 40 | /// channel instance, enabling round-trip disconnect detection. See 41 | /// [WebSocketChannel] for more details. 42 | Handler webSocketHandler( 43 | ConnectionCallback onConnection, { 44 | Iterable? protocols, 45 | Iterable? allowedOrigins, 46 | Duration? pingInterval, 47 | }) { 48 | return WebSocketHandler( 49 | onConnection, 50 | protocols?.toSet(), 51 | allowedOrigins?.map((origin) => origin.toLowerCase()).toSet(), 52 | pingInterval, 53 | ).handle; 54 | } 55 | -------------------------------------------------------------------------------- /pkgs/shelf_web_socket/mono_pkg.yaml: -------------------------------------------------------------------------------- 1 | sdk: 2 | - pubspec 3 | - dev 4 | 5 | stages: 6 | - analyze_and_format: 7 | - analyze: --fatal-infos . 8 | - format: 9 | sdk: 10 | - dev 11 | - unit_test: 12 | - test: --test-randomize-ordering-seed=random 13 | os: 14 | - linux 15 | - windows 16 | -------------------------------------------------------------------------------- /pkgs/shelf_web_socket/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shelf_web_socket 2 | version: 3.0.0 3 | description: A shelf handler that wires up a listener for every connection. 4 | repository: https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_web_socket 5 | issue_tracker: https://github.com/dart-lang/shelf/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ashelf_web_socket 6 | 7 | topics: 8 | - server 9 | - shelf 10 | 11 | environment: 12 | sdk: ^3.5.0 13 | 14 | dependencies: 15 | shelf: ^1.1.0 16 | stream_channel: ^2.1.0 17 | web_socket_channel: '>=2.0.0 <4.0.0' 18 | 19 | dev_dependencies: 20 | dart_flutter_team_lints: ^3.0.0 21 | http: '>=0.13.0 <2.0.0' 22 | test: ^1.25.2 23 | 24 | # TODO(devoncarew): Remove once package:test supports v2+v3 of shelf_web_socket. 25 | dependency_overrides: 26 | test: ^1.25.11 27 | -------------------------------------------------------------------------------- /tool/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Created with package:mono_repo v6.6.2 3 | 4 | # Support built in commands on windows out of the box. 5 | 6 | # When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") 7 | # then "flutter pub" is called instead of "dart pub". 8 | # This assumes that the Flutter SDK has been installed in a previous step. 9 | function pub() { 10 | if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then 11 | command flutter pub "$@" 12 | else 13 | command dart pub "$@" 14 | fi 15 | } 16 | 17 | function format() { 18 | command dart format "$@" 19 | } 20 | 21 | # When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") 22 | # then "flutter analyze" is called instead of "dart analyze". 23 | # This assumes that the Flutter SDK has been installed in a previous step. 24 | function analyze() { 25 | if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then 26 | command flutter analyze "$@" 27 | else 28 | command dart analyze "$@" 29 | fi 30 | } 31 | 32 | if [[ -z ${PKGS} ]]; then 33 | echo -e '\033[31mPKGS environment variable must be set! - TERMINATING JOB\033[0m' 34 | exit 64 35 | fi 36 | 37 | if [[ "$#" == "0" ]]; then 38 | echo -e '\033[31mAt least one task argument must be provided! - TERMINATING JOB\033[0m' 39 | exit 64 40 | fi 41 | 42 | SUCCESS_COUNT=0 43 | declare -a FAILURES 44 | 45 | for PKG in ${PKGS}; do 46 | echo -e "\033[1mPKG: ${PKG}\033[22m" 47 | EXIT_CODE=0 48 | pushd "${PKG}" >/dev/null || EXIT_CODE=$? 49 | 50 | if [[ ${EXIT_CODE} -ne 0 ]]; then 51 | echo -e "\033[31mPKG: '${PKG}' does not exist - TERMINATING JOB\033[0m" 52 | exit 64 53 | fi 54 | 55 | dart pub upgrade || EXIT_CODE=$? 56 | 57 | if [[ ${EXIT_CODE} -ne 0 ]]; then 58 | echo -e "\033[31mPKG: ${PKG}; 'dart pub upgrade' - FAILED (${EXIT_CODE})\033[0m" 59 | FAILURES+=("${PKG}; 'dart pub upgrade'") 60 | else 61 | for TASK in "$@"; do 62 | EXIT_CODE=0 63 | echo 64 | echo -e "\033[1mPKG: ${PKG}; TASK: ${TASK}\033[22m" 65 | case ${TASK} in 66 | analyze) 67 | echo 'dart analyze --fatal-infos .' 68 | dart analyze --fatal-infos . || EXIT_CODE=$? 69 | ;; 70 | format) 71 | echo 'dart format --output=none --set-exit-if-changed .' 72 | dart format --output=none --set-exit-if-changed . || EXIT_CODE=$? 73 | ;; 74 | test_0) 75 | echo 'dart test --test-randomize-ordering-seed=random' 76 | dart test --test-randomize-ordering-seed=random || EXIT_CODE=$? 77 | ;; 78 | test_1) 79 | echo 'dart test --test-randomize-ordering-seed=random -p chrome' 80 | dart test --test-randomize-ordering-seed=random -p chrome || EXIT_CODE=$? 81 | ;; 82 | test_2) 83 | echo 'dart test --test-randomize-ordering-seed=random -p chrome -c dart2wasm' 84 | dart test --test-randomize-ordering-seed=random -p chrome -c dart2wasm || EXIT_CODE=$? 85 | ;; 86 | test_3) 87 | echo 'dart test --run-skipped -t presubmit-only' 88 | dart test --run-skipped -t presubmit-only || EXIT_CODE=$? 89 | ;; 90 | *) 91 | echo -e "\033[31mUnknown TASK '${TASK}' - TERMINATING JOB\033[0m" 92 | exit 64 93 | ;; 94 | esac 95 | 96 | if [[ ${EXIT_CODE} -ne 0 ]]; then 97 | echo -e "\033[31mPKG: ${PKG}; TASK: ${TASK} - FAILED (${EXIT_CODE})\033[0m" 98 | FAILURES+=("${PKG}; TASK: ${TASK}") 99 | else 100 | echo -e "\033[32mPKG: ${PKG}; TASK: ${TASK} - SUCCEEDED\033[0m" 101 | SUCCESS_COUNT=$((SUCCESS_COUNT + 1)) 102 | fi 103 | 104 | done 105 | fi 106 | 107 | echo 108 | echo -e "\033[32mSUCCESS COUNT: ${SUCCESS_COUNT}\033[0m" 109 | 110 | if [ ${#FAILURES[@]} -ne 0 ]; then 111 | echo -e "\033[31mFAILURES: ${#FAILURES[@]}\033[0m" 112 | for i in "${FAILURES[@]}"; do 113 | echo -e "\033[31m $i\033[0m" 114 | done 115 | fi 116 | 117 | popd >/dev/null || exit 70 118 | echo 119 | done 120 | 121 | if [ ${#FAILURES[@]} -ne 0 ]; then 122 | exit 1 123 | fi 124 | --------------------------------------------------------------------------------