├── .github ├── dependabot.yml └── workflows │ ├── no-response.yml │ ├── publish.yaml │ └── test-package.yml ├── .gitignore ├── .test_config ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example └── main.dart ├── lib ├── http_multi_server.dart └── src │ ├── multi_headers.dart │ └── utils.dart ├── pubspec.yaml └── test └── http_multi_server_test.dart /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file. 2 | # See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates 3 | version: 2 4 | 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/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@28ca1036281a5e5922ead5184a1bbf96e5fc984e 23 | with: 24 | # Don't automatically mark inactive issues+PRs as stale. 25 | days-before-stale: -1 26 | # Close needs-info issues and PRs after 14 days of inactivity. 27 | days-before-close: 14 28 | stale-issue-label: "needs-info" 29 | close-issue-message: > 30 | Without additional information we're not able to resolve this issue. 31 | Feel free to add more info or respond to any questions above and we 32 | can reopen the case. Thanks for your contribution! 33 | stale-pr-label: "needs-info" 34 | close-pr-message: > 35 | Without additional information we're not able to resolve this PR. 36 | Feel free to add more info or respond to any questions above. 37 | Thanks for your contribution! 38 | -------------------------------------------------------------------------------- /.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 | push: 9 | tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ] 10 | 11 | jobs: 12 | publish: 13 | if: ${{ github.repository_owner == 'dart-lang' }} 14 | uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main 15 | permissions: 16 | id-token: write # Required for authentication using OIDC 17 | pull-requests: write # Required for writing the pull request note 18 | -------------------------------------------------------------------------------- /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | # Run on PRs and pushes to the default branch. 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | schedule: 10 | - cron: "0 0 * * 0" 11 | 12 | env: 13 | PUB_ENVIRONMENT: bot.github 14 | 15 | jobs: 16 | # Check code formatting and static analysis on a single OS (linux) 17 | # against Dart dev. 18 | analyze: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | sdk: [dev] 24 | steps: 25 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 26 | - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 27 | with: 28 | sdk: ${{ matrix.sdk }} 29 | - id: install 30 | name: Install dependencies 31 | run: dart pub get 32 | - name: Check formatting 33 | run: dart format --output=none --set-exit-if-changed . 34 | if: always() && steps.install.outcome == 'success' 35 | - name: Analyze code 36 | run: dart analyze --fatal-infos 37 | if: always() && steps.install.outcome == 'success' 38 | 39 | # Run tests on a matrix consisting of two dimensions: 40 | # 1. OS: ubuntu-latest, (macos-latest, windows-latest) 41 | # 2. release channel: dev 42 | test: 43 | needs: analyze 44 | runs-on: ${{ matrix.os }} 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | # Add macos-latest and/or windows-latest if relevant for this package. 49 | os: [ubuntu-latest] 50 | sdk: [3.2, dev] 51 | steps: 52 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 53 | - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 54 | with: 55 | sdk: ${{ matrix.sdk }} 56 | - id: install 57 | name: Install dependencies 58 | run: dart pub get 59 | - name: Run VM tests 60 | run: dart test --platform vm 61 | if: always() && steps.install.outcome == 'success' 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don’t commit the following directories created by pub. 2 | .buildlog 3 | .dart_tool/ 4 | .packages 5 | build/ 6 | 7 | # Or the files created by dart2js. 8 | *.dart.js 9 | *.js_ 10 | *.js.deps 11 | *.js.map 12 | 13 | # Include when developing application packages. 14 | pubspec.lock 15 | -------------------------------------------------------------------------------- /.test_config: -------------------------------------------------------------------------------- 1 | { 2 | "test_package": { 3 | "platforms": ["vm"] 4 | } 5 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.2.2-wip 2 | 3 | * Require Dart 3.2 4 | 5 | ## 3.2.1 6 | 7 | * Populate the pubspec `repository` field. 8 | 9 | ## 3.2.0 10 | 11 | * Honor the `preserveHeaderCase` argument to `MultiHeaders.set` and `.add`. 12 | 13 | ## 3.1.0 14 | 15 | * Add `HttpMultiServer.bindSecure` to match `HttpMultiServer.bind`. 16 | 17 | ## 3.0.1 18 | 19 | * Fix an issue where `bind` would bind to the `anyIPv6` address in unsupported 20 | environments. 21 | 22 | ## 3.0.0 23 | 24 | * Migrate to null safety. 25 | 26 | ## 2.2.0 27 | 28 | * Preparation for [HttpHeaders change]. Update signature of `MultiHeaders.add()` 29 | and `MultiHeaders.set()` to match new signature of `HttpHeaders`. The 30 | parameter is not yet forwarded and will not behave as expected. 31 | 32 | [HttpHeaders change]: https://github.com/dart-lang/sdk/issues/39657 33 | 34 | ## 2.1.0 35 | 36 | * Add `HttpMultiServer.bind` static which centralizes logic around common local 37 | serving scenarios - handling a more flexible 'localhost' and listening on 38 | 'any' hostname. 39 | * Update SDK constraints to `>=2.1.0 <3.0.0`. 40 | 41 | ## 2.0.6 42 | 43 | * If there is a problem starting a loopback Ipv6 server, don't keep the Ipv4 44 | server open when throwing the exception. 45 | 46 | ## 2.0.5 47 | 48 | * Update SDK constraints to `>=2.0.0-dev <3.0.0`. 49 | 50 | ## 2.0.4 51 | 52 | * Declare support for `async` 2.0.0. 53 | 54 | ## 2.0.3 55 | 56 | * Fix `HttpMultiServer.loopback()` and `.loopbackSecure()` for environments that 57 | don't support IPv4. 58 | 59 | ## 2.0.2 60 | 61 | * Fix a dependency that was incorrectly marked as dev-only. 62 | 63 | ## 2.0.1 64 | 65 | * Fix most strong mode errors and warnings. 66 | 67 | ## 2.0.0 68 | 69 | * **Breaking:** Change the signature of `HttpMultiServer.loopbackSecure()` to 70 | match the new Dart 1.13 `HttpServer.bindSecure()` signature. This removes the 71 | `certificateName` named parameter and adds the required `context` parameter 72 | and the named `v6Only` and `shared` parameters. 73 | 74 | * Added `v6Only` and `shared` parameters to `HttpMultiServer.loopback()` to 75 | match `HttpServer.bind()`. 76 | 77 | ## 1.3.2 78 | 79 | * Eventually stop retrying port allocation if it fails repeatedly. 80 | 81 | * Properly detect socket errors caused by already-in-use addresses. 82 | 83 | ## 1.3.1 84 | 85 | * `loopback()` and `loopbackSecure()` recover gracefully if an ephemeral port is 86 | requested and the located port isn't available on both IPv4 and IPv6. 87 | 88 | ## 1.3.0 89 | 90 | * Add support for `HttpServer.autoCompress`. 91 | 92 | ## 1.2.0 93 | 94 | * Add support for `HttpServer.defaultResponseHeaders.clear`. 95 | 96 | * Fix `HttpServer.defaultResponseHeaders.remove` and `.removeAll`. 97 | 98 | ## 1.1.0 99 | 100 | * Add support for `HttpServer.defaultResponseHeaders`. 101 | 102 | ## 1.0.2 103 | 104 | * Remove the workaround for [issue 19815][]. 105 | 106 | ## 1.0.1 107 | 108 | * Ignore errors from one of the servers if others are still bound. In 109 | particular, this works around [issue 19815][] on some Windows machines where 110 | IPv6 failure isn't discovered until we try to connect to the socket. 111 | 112 | [issue 19815]: https://code.google.com/p/dart/issues/detail?id=19815 113 | -------------------------------------------------------------------------------- /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 | > [!IMPORTANT] 2 | > This repo has moved to https://github.com/dart-lang/http/tree/master/pkgs/http_multi_server 3 | 4 | [![Dart CI](https://github.com/dart-lang/http_multi_server/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/http_multi_server/actions/workflows/test-package.yml) 5 | [![pub package](https://img.shields.io/pub/v/http_multi_server.svg)](https://pub.dev/packages/http_multi_server) 6 | [![package publisher](https://img.shields.io/pub/publisher/http_multi_server.svg)](https://pub.dev/packages/http_multi_server/publisher) 7 | 8 | An implementation of `dart:io`'s [HttpServer][] that wraps multiple servers and 9 | forwards methods to all of them. It's useful for serving the same application on 10 | multiple network interfaces while still having a unified way of controlling the 11 | servers. In particular, it supports serving on both the IPv4 and IPv6 loopback 12 | addresses using [HttpMultiServer.loopback][]. 13 | 14 | ```dart 15 | import 'package:http_multi_server/http_multi_server.dart'; 16 | import 'package:shelf/shelf.dart' as shelf; 17 | import 'package:shelf/shelf_io.dart' as shelf_io; 18 | 19 | void main() async { 20 | // Both http://127.0.0.1:8080 and http://[::1]:8080 will be bound to the same 21 | // server. 22 | var server = await HttpMultiServer.loopback(8080); 23 | shelf_io.serveRequests(server, (request) { 24 | return shelf.Response.ok("Hello, world!"); 25 | }); 26 | } 27 | ``` 28 | 29 | [HttpServer]: https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart-io.HttpServer 30 | 31 | [HttpMultiServer.loopback]: https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/http_multi_server/http_multi_server.HttpMultiServer#id_loopback 32 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # https://dart.dev/tools/analysis#the-analysis-options-file 2 | include: package:dart_flutter_team_lints/analysis_options.yaml 3 | 4 | analyzer: 5 | language: 6 | strict-casts: true 7 | 8 | linter: 9 | rules: 10 | - avoid_bool_literals_in_conditional_expressions 11 | - avoid_classes_with_only_static_members 12 | - avoid_private_typedef_functions 13 | - avoid_redundant_argument_values 14 | - avoid_returning_this 15 | - avoid_unused_constructor_parameters 16 | - cancel_subscriptions 17 | - cascade_invocations 18 | - join_return_with_assignment 19 | - literal_only_boolean_expressions 20 | - no_adjacent_strings_in_list 21 | - no_runtimeType_toString 22 | - prefer_const_declarations 23 | - prefer_expression_function_bodies 24 | - prefer_final_locals 25 | - use_string_buffers 26 | -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:http_multi_server/http_multi_server.dart'; 2 | import 'package:shelf/shelf.dart' as shelf; 3 | import 'package:shelf/shelf_io.dart' as shelf_io; 4 | 5 | void main() async { 6 | // Both http://127.0.0.1:8080 and http://[::1]:8080 will be bound to the same 7 | // server. 8 | final server = await HttpMultiServer.loopback(8080); 9 | shelf_io.serveRequests( 10 | server, 11 | (request) => shelf.Response.ok('Hello, world!'), 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /lib/http_multi_server.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:io'; 7 | 8 | import 'package:async/async.dart'; 9 | 10 | import 'src/multi_headers.dart'; 11 | import 'src/utils.dart'; 12 | 13 | /// The error code for an error caused by a port already being in use. 14 | final _addressInUseErrno = _computeAddressInUseErrno(); 15 | int _computeAddressInUseErrno() { 16 | if (Platform.isWindows) return 10048; 17 | if (Platform.isMacOS) return 48; 18 | assert(Platform.isLinux); 19 | return 98; 20 | } 21 | 22 | /// An implementation of `dart:io`'s [HttpServer] that wraps multiple servers 23 | /// and forwards methods to all of them. 24 | /// 25 | /// This is useful for serving the same application on multiple network 26 | /// interfaces while still having a unified way of controlling the servers. In 27 | /// particular, it supports serving on both the IPv4 and IPv6 loopback addresses 28 | /// using [HttpMultiServer.loopback]. 29 | class HttpMultiServer extends StreamView implements HttpServer { 30 | /// The wrapped servers. 31 | final Set _servers; 32 | 33 | /// Returns the default value of the `Server` header for all responses 34 | /// generated by each server. 35 | /// 36 | /// If the wrapped servers have different default values, it's not defined 37 | /// which value is returned. 38 | @override 39 | String? get serverHeader => _servers.first.serverHeader; 40 | 41 | @override 42 | set serverHeader(String? value) { 43 | for (var server in _servers) { 44 | server.serverHeader = value; 45 | } 46 | } 47 | 48 | /// Returns the default set of headers added to all response objects. 49 | /// 50 | /// If the wrapped servers have different default headers, it's not defined 51 | /// which header is returned for accessor methods. 52 | @override 53 | final HttpHeaders defaultResponseHeaders; 54 | 55 | @override 56 | Duration? get idleTimeout => _servers.first.idleTimeout; 57 | @override 58 | set idleTimeout(Duration? value) { 59 | for (var server in _servers) { 60 | server.idleTimeout = value; 61 | } 62 | } 63 | 64 | @override 65 | bool get autoCompress => _servers.first.autoCompress; 66 | @override 67 | set autoCompress(bool value) { 68 | for (var server in _servers) { 69 | server.autoCompress = value; 70 | } 71 | } 72 | 73 | /// Returns the port that one of the wrapped servers is listening on. 74 | /// 75 | /// If the wrapped servers are listening on different ports, it's not defined 76 | /// which port is returned. 77 | @override 78 | int get port => _servers.first.port; 79 | 80 | /// Returns the address that one of the wrapped servers is listening on. 81 | /// 82 | /// If the wrapped servers are listening on different addresses, it's not 83 | /// defined which address is returned. 84 | @override 85 | InternetAddress get address => _servers.first.address; 86 | 87 | @override 88 | set sessionTimeout(int value) { 89 | for (var server in _servers) { 90 | server.sessionTimeout = value; 91 | } 92 | } 93 | 94 | /// Creates an [HttpMultiServer] wrapping [servers]. 95 | /// 96 | /// All [servers] should have the same configuration and none should be 97 | /// listened to when this is called. 98 | HttpMultiServer(Iterable servers) 99 | : _servers = servers.toSet(), 100 | defaultResponseHeaders = MultiHeaders( 101 | servers.map((server) => server.defaultResponseHeaders)), 102 | super(StreamGroup.merge(servers)); 103 | 104 | /// Creates an [HttpServer] listening on all available loopback addresses for 105 | /// this computer. 106 | /// 107 | /// See [HttpServer.bind]. 108 | static Future loopback(int port, 109 | {int backlog = 0, bool v6Only = false, bool shared = false}) => 110 | _loopback( 111 | port, 112 | (address, port) => HttpServer.bind(address, port, 113 | backlog: backlog, v6Only: v6Only, shared: shared)); 114 | 115 | /// Like [loopback], but supports HTTPS requests. 116 | /// 117 | /// See [HttpServer.bindSecure]. 118 | static Future loopbackSecure(int port, SecurityContext context, 119 | {int backlog = 0, 120 | bool v6Only = false, 121 | bool requestClientCertificate = false, 122 | bool shared = false}) => 123 | _loopback( 124 | port, 125 | (address, port) => HttpServer.bindSecure(address, port, context, 126 | backlog: backlog, 127 | v6Only: v6Only, 128 | shared: shared, 129 | requestClientCertificate: requestClientCertificate)); 130 | 131 | /// Bind an [HttpServer] with handling for special addresses 'localhost' and 132 | /// 'any'. 133 | /// 134 | /// For address 'localhost' behaves like [loopback]. 135 | /// 136 | /// For 'any' listens on [InternetAddress.anyIPv6] if the system supports IPv6 137 | /// otherwise [InternetAddress.anyIPv4]. Note [InternetAddress.anyIPv6] 138 | /// listens on all hostnames for both IPv4 and IPv6. 139 | /// 140 | /// For any other address forwards directly to `HttpServer.bind` where 141 | /// the IPvX support may vary. 142 | /// 143 | /// See [HttpServer.bind]. 144 | static Future bind(dynamic address, int port, 145 | {int backlog = 0, bool v6Only = false, bool shared = false}) async { 146 | if (address == 'localhost') { 147 | return HttpMultiServer.loopback(port, 148 | backlog: backlog, v6Only: v6Only, shared: shared); 149 | } 150 | if (address == 'any') { 151 | return HttpServer.bind( 152 | await supportsIPv6 153 | ? InternetAddress.anyIPv6 154 | : InternetAddress.anyIPv4, 155 | port, 156 | backlog: backlog, 157 | v6Only: v6Only, 158 | shared: shared); 159 | } 160 | return HttpServer.bind(address, port, 161 | backlog: backlog, v6Only: v6Only, shared: shared); 162 | } 163 | 164 | /// Bind a secure [HttpServer] with handling for special addresses 'localhost' 165 | /// and 'any'. 166 | /// 167 | /// For address 'localhost' behaves like [loopback]. 168 | /// 169 | /// For 'any' listens on [InternetAddress.anyIPv6] if the system supports IPv6 170 | /// otherwise [InternetAddress.anyIPv4]. Note [InternetAddress.anyIPv6] 171 | /// listens on all hostnames for both IPv4 and IPv6. 172 | /// 173 | /// See [HttpServer.bindSecure]. 174 | static Future bindSecure( 175 | dynamic address, int port, SecurityContext context, 176 | {int backlog = 0, bool v6Only = false, bool shared = false}) async { 177 | if (address == 'localhost') { 178 | return await HttpMultiServer.loopbackSecure(port, context, 179 | backlog: backlog, v6Only: v6Only, shared: shared); 180 | } 181 | if (address == 'any') { 182 | return await HttpServer.bindSecure( 183 | await supportsIPv6 184 | ? InternetAddress.anyIPv6 185 | : InternetAddress.anyIPv4, 186 | port, 187 | context, 188 | backlog: backlog, 189 | v6Only: v6Only, 190 | shared: shared); 191 | } 192 | return await HttpServer.bindSecure(address, port, context, 193 | backlog: backlog, v6Only: v6Only, shared: shared); 194 | } 195 | 196 | /// A helper method for initializing loopback servers. 197 | /// 198 | /// [bind] should forward to either [HttpServer.bind] or 199 | /// [HttpServer.bindSecure]. 200 | static Future _loopback( 201 | int port, Future Function(InternetAddress, int port) bind, 202 | [int remainingRetries = 5]) async { 203 | if (!await supportsIPv4) { 204 | return await bind(InternetAddress.loopbackIPv6, port); 205 | } 206 | 207 | final v4Server = await bind(InternetAddress.loopbackIPv4, port); 208 | if (!await supportsIPv6) return v4Server; 209 | 210 | try { 211 | // Reuse the IPv4 server's port so that if [port] is 0, both servers use 212 | // the same ephemeral port. 213 | final v6Server = await bind(InternetAddress.loopbackIPv6, v4Server.port); 214 | return HttpMultiServer([v4Server, v6Server]); 215 | } on SocketException catch (error) { 216 | // If there is already a server listening we'll lose the reference on a 217 | // rethrow. 218 | await v4Server.close(); 219 | 220 | if (error.osError?.errorCode != _addressInUseErrno) rethrow; 221 | if (port != 0) rethrow; 222 | if (remainingRetries == 0) rethrow; 223 | 224 | // A port being available on IPv4 doesn't necessarily mean that the same 225 | // port is available on IPv6. If it's not (which is rare in practice), 226 | // we try again until we find one that's available on both. 227 | return await _loopback(port, bind, remainingRetries - 1); 228 | } 229 | } 230 | 231 | @override 232 | Future close({bool force = false}) => 233 | Future.wait(_servers.map((server) => server.close(force: force))); 234 | 235 | /// Returns an HttpConnectionsInfo object summarizing the total number of 236 | /// current connections handled by all the servers. 237 | @override 238 | HttpConnectionsInfo connectionsInfo() { 239 | final info = HttpConnectionsInfo(); 240 | for (var server in _servers) { 241 | final subInfo = server.connectionsInfo(); 242 | info 243 | ..total += subInfo.total 244 | ..active += subInfo.active 245 | ..idle += subInfo.idle 246 | ..closing += subInfo.closing; 247 | } 248 | return info; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /lib/src/multi_headers.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:io'; 6 | 7 | /// A class that delegates header access and setting to many [HttpHeaders] 8 | /// instances. 9 | class MultiHeaders implements HttpHeaders { 10 | /// The wrapped headers. 11 | final Set _headers; 12 | 13 | @override 14 | bool get chunkedTransferEncoding => _headers.first.chunkedTransferEncoding; 15 | @override 16 | set chunkedTransferEncoding(bool value) { 17 | for (var headers in _headers) { 18 | headers.chunkedTransferEncoding = value; 19 | } 20 | } 21 | 22 | @override 23 | int get contentLength => _headers.first.contentLength; 24 | @override 25 | set contentLength(int value) { 26 | for (var headers in _headers) { 27 | headers.contentLength = value; 28 | } 29 | } 30 | 31 | @override 32 | ContentType? get contentType => _headers.first.contentType; 33 | @override 34 | set contentType(ContentType? value) { 35 | for (var headers in _headers) { 36 | headers.contentType = value; 37 | } 38 | } 39 | 40 | @override 41 | DateTime? get date => _headers.first.date; 42 | @override 43 | set date(DateTime? value) { 44 | for (var headers in _headers) { 45 | headers.date = value; 46 | } 47 | } 48 | 49 | @override 50 | DateTime? get expires => _headers.first.expires; 51 | @override 52 | set expires(DateTime? value) { 53 | for (var headers in _headers) { 54 | headers.expires = value; 55 | } 56 | } 57 | 58 | @override 59 | String? get host => _headers.first.host; 60 | @override 61 | set host(String? value) { 62 | for (var headers in _headers) { 63 | headers.host = value; 64 | } 65 | } 66 | 67 | @override 68 | DateTime? get ifModifiedSince => _headers.first.ifModifiedSince; 69 | @override 70 | set ifModifiedSince(DateTime? value) { 71 | for (var headers in _headers) { 72 | headers.ifModifiedSince = value; 73 | } 74 | } 75 | 76 | @override 77 | bool get persistentConnection => _headers.first.persistentConnection; 78 | @override 79 | set persistentConnection(bool value) { 80 | for (var headers in _headers) { 81 | headers.persistentConnection = value; 82 | } 83 | } 84 | 85 | @override 86 | int? get port => _headers.first.port; 87 | @override 88 | set port(int? value) { 89 | for (var headers in _headers) { 90 | headers.port = value; 91 | } 92 | } 93 | 94 | MultiHeaders(Iterable headers) : _headers = headers.toSet(); 95 | 96 | @override 97 | void add(String name, Object value, {bool preserveHeaderCase = false}) { 98 | for (var headers in _headers) { 99 | headers.add(name, value, preserveHeaderCase: preserveHeaderCase); 100 | } 101 | } 102 | 103 | @override 104 | void forEach(void Function(String name, List values) f) => 105 | _headers.first.forEach(f); 106 | 107 | @override 108 | void noFolding(String name) { 109 | for (var headers in _headers) { 110 | headers.noFolding(name); 111 | } 112 | } 113 | 114 | @override 115 | void remove(String name, Object value) { 116 | for (var headers in _headers) { 117 | headers.remove(name, value); 118 | } 119 | } 120 | 121 | @override 122 | void removeAll(String name) { 123 | for (var headers in _headers) { 124 | headers.removeAll(name); 125 | } 126 | } 127 | 128 | @override 129 | void set(String name, Object value, {bool preserveHeaderCase = false}) { 130 | for (var headers in _headers) { 131 | headers.set(name, value, preserveHeaderCase: preserveHeaderCase); 132 | } 133 | } 134 | 135 | @override 136 | String? value(String name) => _headers.first.value(name); 137 | 138 | @override 139 | List? operator [](String name) => _headers.first[name]; 140 | 141 | @override 142 | void clear() { 143 | for (var headers in _headers) { 144 | headers.clear(); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /lib/src/utils.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:io'; 6 | 7 | /// Returns whether this computer supports binding to IPv6 addresses. 8 | final Future supportsIPv6 = () async { 9 | try { 10 | final socket = await ServerSocket.bind(InternetAddress.loopbackIPv6, 0); 11 | await socket.close(); 12 | return true; 13 | } on SocketException catch (_) { 14 | return false; 15 | } 16 | }(); 17 | 18 | /// Returns whether this computer supports binding to IPv4 addresses. 19 | final Future supportsIPv4 = () async { 20 | try { 21 | final socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); 22 | await socket.close(); 23 | return true; 24 | } on SocketException catch (_) { 25 | return false; 26 | } 27 | }(); 28 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: http_multi_server 2 | version: 3.2.2-wip 3 | description: >- 4 | A dart:io HttpServer wrapper that handles requests from multiple servers. 5 | repository: https://github.com/dart-lang/http_multi_server 6 | 7 | environment: 8 | sdk: ^3.2.0 9 | 10 | dependencies: 11 | async: ^2.5.0 12 | 13 | dev_dependencies: 14 | dart_flutter_team_lints: ^2.0.0 15 | http: ^1.0.0 16 | shelf: ^1.4.0 17 | test: ^1.16.0 18 | -------------------------------------------------------------------------------- /test/http_multi_server_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 | import 'dart:io'; 8 | 9 | import 'package:http/http.dart' as http; 10 | import 'package:http/io_client.dart' as http; 11 | import 'package:http_multi_server/http_multi_server.dart'; 12 | import 'package:http_multi_server/src/utils.dart'; 13 | import 'package:test/test.dart'; 14 | 15 | void main() { 16 | group('with multiple HttpServers', () { 17 | late HttpMultiServer multiServer; 18 | late HttpServer subServer1; 19 | late HttpServer subServer2; 20 | late HttpServer subServer3; 21 | 22 | setUp(() => Future.wait([ 23 | HttpServer.bind('localhost', 0).then((server) => subServer1 = server), 24 | HttpServer.bind('localhost', 0).then((server) => subServer2 = server), 25 | HttpServer.bind('localhost', 0).then((server) => subServer3 = server) 26 | ]).then((servers) => multiServer = HttpMultiServer(servers))); 27 | 28 | tearDown(() => multiServer.close()); 29 | 30 | test('listen listens to all servers', () { 31 | multiServer.listen((request) { 32 | request.response.write('got request'); 33 | request.response.close(); 34 | }); 35 | 36 | expect(_read(subServer1), completion(equals('got request'))); 37 | expect(_read(subServer2), completion(equals('got request'))); 38 | expect(_read(subServer3), completion(equals('got request'))); 39 | }); 40 | 41 | test('serverHeader= sets the value for all servers', () { 42 | multiServer 43 | ..serverHeader = 'http_multi_server test' 44 | ..listen((request) { 45 | request.response.write('got request'); 46 | request.response.close(); 47 | }); 48 | 49 | expect( 50 | _get(subServer1).then((response) { 51 | expect( 52 | response.headers['server'], equals('http_multi_server test')); 53 | }), 54 | completes); 55 | 56 | expect( 57 | _get(subServer2).then((response) { 58 | expect( 59 | response.headers['server'], equals('http_multi_server test')); 60 | }), 61 | completes); 62 | 63 | expect( 64 | _get(subServer3).then((response) { 65 | expect( 66 | response.headers['server'], equals('http_multi_server test')); 67 | }), 68 | completes); 69 | }); 70 | 71 | test('autoCompress= sets the value for all servers', () { 72 | multiServer 73 | ..autoCompress = true 74 | ..listen((request) { 75 | request.response.write('got request'); 76 | request.response.close(); 77 | }); 78 | 79 | expect( 80 | _get(subServer1).then((response) { 81 | expect(response.headers['content-encoding'], equals('gzip')); 82 | }), 83 | completes); 84 | 85 | expect( 86 | _get(subServer2).then((response) { 87 | expect(response.headers['content-encoding'], equals('gzip')); 88 | }), 89 | completes); 90 | 91 | expect( 92 | _get(subServer3).then((response) { 93 | expect(response.headers['content-encoding'], equals('gzip')); 94 | }), 95 | completes); 96 | }); 97 | 98 | test('headers.set sets the value for all servers', () { 99 | multiServer.defaultResponseHeaders 100 | .set('server', 'http_multi_server test'); 101 | 102 | multiServer.listen((request) { 103 | request.response.write('got request'); 104 | request.response.close(); 105 | }); 106 | 107 | expect( 108 | _get(subServer1).then((response) { 109 | expect( 110 | response.headers['server'], equals('http_multi_server test')); 111 | }), 112 | completes); 113 | 114 | expect( 115 | _get(subServer2).then((response) { 116 | expect( 117 | response.headers['server'], equals('http_multi_server test')); 118 | }), 119 | completes); 120 | 121 | expect( 122 | _get(subServer3).then((response) { 123 | expect( 124 | response.headers['server'], equals('http_multi_server test')); 125 | }), 126 | completes); 127 | }); 128 | 129 | test('connectionsInfo sums the values for all servers', () { 130 | var pendingRequests = 0; 131 | final awaitingResponseCompleter = Completer(); 132 | final sendResponseCompleter = Completer(); 133 | multiServer.listen((request) { 134 | sendResponseCompleter.future.then((_) { 135 | request.response.write('got request'); 136 | request.response.close(); 137 | }); 138 | 139 | pendingRequests++; 140 | if (pendingRequests == 2) awaitingResponseCompleter.complete(); 141 | }); 142 | 143 | // Queue up some requests, then wait on [awaitingResponseCompleter] to 144 | // make sure they're in-flight before we check [connectionsInfo]. 145 | expect(_get(subServer1), completes); 146 | expect(_get(subServer2), completes); 147 | 148 | return awaitingResponseCompleter.future.then((_) { 149 | final info = multiServer.connectionsInfo(); 150 | expect(info.total, equals(2)); 151 | expect(info.active, equals(2)); 152 | expect(info.idle, equals(0)); 153 | expect(info.closing, equals(0)); 154 | 155 | sendResponseCompleter.complete(); 156 | }); 157 | }); 158 | }); 159 | 160 | group('HttpMultiServer.loopback', () { 161 | late HttpServer server; 162 | 163 | setUp(() => HttpMultiServer.loopback(0).then((s) => server = s)); 164 | 165 | tearDown(() => server.close()); 166 | 167 | test('listens on all localhost interfaces', () async { 168 | server.listen((request) { 169 | request.response.write('got request'); 170 | request.response.close(); 171 | }); 172 | 173 | if (await supportsIPv4) { 174 | expect(http.read(Uri.http('127.0.0.1:${server.port}', '/')), 175 | completion(equals('got request'))); 176 | } 177 | 178 | if (await supportsIPv6) { 179 | expect(http.read(Uri.http('[::1]:${server.port}', '/')), 180 | completion(equals('got request'))); 181 | } 182 | }); 183 | }); 184 | 185 | group('HttpMultiServer.bind', () { 186 | test("listens on all localhost interfaces for 'localhost'", () async { 187 | final server = await HttpMultiServer.bind('localhost', 0); 188 | server.listen((request) { 189 | request.response.write('got request'); 190 | request.response.close(); 191 | }); 192 | 193 | if (await supportsIPv4) { 194 | expect(http.read(Uri.http('127.0.0.1:${server.port}', '/')), 195 | completion(equals('got request'))); 196 | } 197 | 198 | if (await supportsIPv6) { 199 | expect(http.read(Uri.http('[::1]:${server.port}', '/')), 200 | completion(equals('got request'))); 201 | } 202 | }); 203 | 204 | test("listens on all localhost interfaces for 'any'", () async { 205 | final server = await HttpMultiServer.bind('any', 0); 206 | server.listen((request) { 207 | request.response.write('got request'); 208 | request.response.close(); 209 | }); 210 | 211 | if (await supportsIPv4) { 212 | expect(http.read(Uri.http('127.0.0.1:${server.port}', '/')), 213 | completion(equals('got request'))); 214 | } 215 | 216 | if (await supportsIPv6) { 217 | expect(http.read(Uri.http('[::1]:${server.port}', '/')), 218 | completion(equals('got request'))); 219 | } 220 | }); 221 | 222 | test("uses the correct server address for 'any'", () async { 223 | final server = await HttpMultiServer.bind('any', 0); 224 | 225 | if (!await supportsIPv6) { 226 | expect(server.address, InternetAddress.anyIPv4); 227 | } else { 228 | expect(server.address, InternetAddress.anyIPv6); 229 | } 230 | }); 231 | 232 | test('listens on specified hostname', () async { 233 | if (!await supportsIPv4) return; 234 | final server = await HttpMultiServer.bind(InternetAddress.anyIPv4, 0); 235 | server.listen((request) { 236 | request.response.write('got request'); 237 | request.response.close(); 238 | }); 239 | 240 | expect(http.read(Uri.http('127.0.0.1:${server.port}', '/')), 241 | completion(equals('got request'))); 242 | 243 | if (await supportsIPv6) { 244 | expect(http.read(Uri.http('[::1]:${server.port}', '/')), 245 | throwsA(isA())); 246 | } 247 | }); 248 | }); 249 | 250 | group('HttpMultiServer.bindSecure', () { 251 | late http.Client client; 252 | late SecurityContext context; 253 | setUp(() async { 254 | context = SecurityContext() 255 | ..setTrustedCertificatesBytes(_sslCert) 256 | ..useCertificateChainBytes(_sslCert) 257 | ..usePrivateKeyBytes(_sslKey, password: 'dartdart'); 258 | client = http.IOClient(HttpClient(context: context)); 259 | }); 260 | test('listens on all localhost interfaces for "localhost"', () async { 261 | final server = await HttpMultiServer.bindSecure('localhost', 0, context); 262 | server.listen((request) { 263 | request.response.write('got request'); 264 | request.response.close(); 265 | }); 266 | 267 | if (await supportsIPv4) { 268 | expect(client.read(Uri.https('127.0.0.1:${server.port}')), 269 | completion(equals('got request'))); 270 | } 271 | 272 | if (await supportsIPv6) { 273 | expect(client.read(Uri.https('[::1]:${server.port}')), 274 | completion(equals('got request'))); 275 | } 276 | }); 277 | 278 | test('listens on all localhost interfaces for "any"', () async { 279 | final server = await HttpMultiServer.bindSecure('any', 0, context); 280 | server.listen((request) { 281 | request.response.write('got request'); 282 | request.response.close(); 283 | }); 284 | 285 | if (await supportsIPv4) { 286 | expect(client.read(Uri.https('127.0.0.1:${server.port}')), 287 | completion(equals('got request'))); 288 | } 289 | 290 | if (await supportsIPv6) { 291 | expect(client.read(Uri.https('[::1]:${server.port}')), 292 | completion(equals('got request'))); 293 | } 294 | }); 295 | 296 | test('listens on specified hostname', () async { 297 | if (!await supportsIPv4) return; 298 | final server = 299 | await HttpMultiServer.bindSecure(InternetAddress.anyIPv4, 0, context); 300 | server.listen((request) { 301 | request.response.write('got request'); 302 | request.response.close(); 303 | }); 304 | 305 | expect(client.read(Uri.https('127.0.0.1:${server.port}')), 306 | completion(equals('got request'))); 307 | 308 | if (await supportsIPv6) { 309 | expect(client.read(Uri.https('[::1]:${server.port}')), 310 | throwsA(isA())); 311 | } 312 | }); 313 | }); 314 | } 315 | 316 | /// Makes a GET request to the root of [server] and returns the response. 317 | Future _get(HttpServer server) => http.get(_urlFor(server)); 318 | 319 | /// Makes a GET request to the root of [server] and returns the response body. 320 | Future _read(HttpServer server) => http.read(_urlFor(server)); 321 | 322 | /// Returns the URL for the root of [server]. 323 | Uri _urlFor(HttpServer server) => 324 | Uri.http('${server.address.host}:${server.port}', '/'); 325 | 326 | final _sslCert = utf8.encode(''' 327 | -----BEGIN CERTIFICATE----- 328 | MIIDZDCCAkygAwIBAgIBATANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVpbnRl 329 | cm1lZGlhdGVhdXRob3JpdHkwHhcNMTUxMDI3MTAyNjM1WhcNMjUxMDI0MTAyNjM1 330 | WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 331 | ggEKAoIBAQCkg/Qr8RQeLTOSgCkyiEX2ztgkgscX8hKGHEHdvlkmVK3JVEIIwkvu 332 | /Y9LtHZUia3nPAgqEEbexzTENZjSCcC0V6I2XW/e5tIE3rO0KLZyhtZhN/2SfJ6p 333 | KbOh0HLr1VtkKJGp1tzUmHW/aZI32pK60ZJ/N917NLPCJpCaL8+wHo3+w3oNqln6 334 | oJsfgxy9SUM8Bsc9WMYKMUdqLO1QKs1A5YwqZuO7Mwj+4LY2QDixC7Ua7V9YAPo2 335 | 1SBeLvMCHbYxSPCuxcZ/kDkgax/DF9u7aZnGhMImkwBka0OQFvpfjKtTIuoobTpe 336 | PAG7MQYXk4RjnjdyEX/9XAQzvNo1CDObAgMBAAGjgbQwgbEwPAYDVR0RBDUwM4IJ 337 | bG9jYWxob3N0ggkxMjcuMC4wLjGCAzo6MYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAA 338 | ATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSvhJo6taTggJQBukEvMo/PDk8tKTAf 339 | BgNVHSMEGDAWgBS98L4T5RaIToE3DkBRsoeWPil0eDAOBgNVHQ8BAf8EBAMCA6gw 340 | EwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAHLOt0mL2S4A 341 | B7vN7KsfQeGlVgZUVlEjem6kqBh4fIzl4CsQuOO8oJ0FlO1z5JAIo98hZinymJx1 342 | phBVpyGIKakT/etMH0op5evLe9dD36VA3IM/FEv5ibk35iGnPokiJXIAcdHd1zam 343 | YaTHRAnZET5S03+7BgRTKoRuszhbvuFz/vKXaIAnVNOF4Gf2NUJ/Ax7ssJtRkN+5 344 | UVxe8TZVxzgiRv1uF6NTr+J8PDepkHCbJ6zEQNudcFKAuC56DN1vUe06gRDrNbVq 345 | 2JHEh4pRfMpdsPCrS5YHBjVq/XHtFHgwDR6g0WTwSUJvDeM4OPQY5f61FB0JbFza 346 | PkLkXmoIod8= 347 | -----END CERTIFICATE----- 348 | -----BEGIN CERTIFICATE----- 349 | MIIDLjCCAhagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1yb290 350 | YXV0aG9yaXR5MB4XDTE1MTAyNzEwMjYzNVoXDTI1MTAyNDEwMjYzNVowIDEeMBwG 351 | A1UEAwwVaW50ZXJtZWRpYXRlYXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC 352 | AQ8AMIIBCgKCAQEA6GndRFiXk+2q+Ig7ZOWKKGta+is8137qyXz+eVFs5sA0ajMN 353 | ZBAMWS0TIXw/Yks+y6fEcV/tfv91k1eUN4YXPcoxTdDF97d2hO9wxumeYOMnQeDy 354 | VZVDKQBZ+jFMeI+VkNpMEdmsLErpZDGob/1dC8tLEuR6RuRR8X6IDGMPOCMw1jLK 355 | V1bQjPtzqKadTscfjLuKxuLgspJdTrzsu6hdcl1mm8K6CjTY2HNXWxs1yYmwfuQ2 356 | Z4/8sOMNqFqLjN+ChD7pksTMq7IosqGiJzi2bpd5f44ek/k822Y0ATncJHk4h1Z+ 357 | kZBnW6kgcLna1gDri9heRwSZ+M8T8nlHgIMZIQIDAQABo3sweTASBgNVHRMBAf8E 358 | CDAGAQH/AgEAMB0GA1UdDgQWBBS98L4T5RaIToE3DkBRsoeWPil0eDAfBgNVHSME 359 | GDAWgBRxD5DQHTmtpDFKDOiMf5FAi6vfbzAOBgNVHQ8BAf8EBAMCAgQwEwYDVR0l 360 | BAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAD+4KpUeV5mUPw5IG/7w 361 | eOXnUpeS96XFGuS1JuFo/TbgntPWSPyo+rD4GrPIkUXyoHaMCDd2UBEjyGbBIKlB 362 | NZA3RJOAEp7DTkLNK4RFn/OEcLwG0J5brL7kaLRO4vwvItVIdZ2XIqzypRQTc0MG 363 | MmF08zycnSlaN01ryM67AsMhwdHqVa+uXQPo8R8sdFGnZ33yywTYD73FeImXilQ2 364 | rDnFUVqmrW1fjl0Fi4rV5XI0EQiPrzKvRtmF8ZqjGATPOsRd64cwQX6V+P5hNeIR 365 | 9pba6td7AbNGausHfacRYMyoGJWWWkFPd+7jWOCPqW7Fk1tmBgdB8GzXa3inWIRM 366 | RUE= 367 | -----END CERTIFICATE----- 368 | -----BEGIN CERTIFICATE----- 369 | MIIC+zCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1yb290 370 | YXV0aG9yaXR5MB4XDTE1MTAyNzEwMjYzNFoXDTI1MTAyNDEwMjYzNFowGDEWMBQG 371 | A1UEAwwNcm9vdGF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 372 | ggEBAMl+dcraUM/E7E6zl7+7hK9oUJYXJLnfiMtP/TRFVbH4+2aEN8vXzPbzKdR3 373 | FfaHczXQTwnTCaYA4u4uSDvSOsFFEfxEwYORsdKmQEM8nGpVX2NVvKsMcGIhh8kh 374 | ZwJfkMIOcAxmGIHGdMhF8VghonJ8uGiuqktxdfpARq0g3fqIjDHsF9/LpfshUfk9 375 | wsRyTF0yr90U/dsfnE+u8l7GvVl8j2Zegp0sagAGtLaNv7tP17AibqEGg2yDBrBN 376 | 9r9ihe4CqMjx+Q2kQ2S9Gz2V2ReO/n6vm2VQxsPRB/lV/9jh7cUcS0/9mggLYrDy 377 | cq1v7rLLQrWuxMz1E3gOhyCYJ38CAwEAAaNQME4wHQYDVR0OBBYEFHEPkNAdOa2k 378 | MUoM6Ix/kUCLq99vMB8GA1UdIwQYMBaAFHEPkNAdOa2kMUoM6Ix/kUCLq99vMAwG 379 | A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABrhjnWC6b+z9Kw73C/niOwo 380 | 9sPdufjS6tb0sCwDjt3mjvE4NdNWt+/+ZOugW6dqtvqhtqZM1q0u9pJkNwIrqgFD 381 | ZHcfNaf31G6Z2YE+Io7woTVw6fFobg/EFo+a/qwbvWL26McmiRL5yiSBjVjpX4a5 382 | kdZ+aPQUCBaLrTWwlCDqzSVIULWUQvveRWbToMFKPNID58NtEpymAx3Pgir7YjV9 383 | UnlU2l5vZrh1PTCqZxvC/IdRESUfW80LdHaeyizRUP+6vKxGgSz2MRuYINjbd6GO 384 | hGiCpWlwziW2xLV1l2qSRLko2kIafLZP18N0ThM9zKbU5ps9NgFOf//wqSGtLaE= 385 | -----END CERTIFICATE----- 386 | '''); 387 | 388 | List _sslKey = utf8.encode(''' 389 | -----BEGIN ENCRYPTED PRIVATE KEY----- 390 | MIIE4zAcBgoqhkiG9w0BDAEBMA4ECBMCjlg8JYZ4AgIIAASCBMFd9cBoZ5xcTock 391 | AVQcg/HzYJtMceKn1gtMDdC7mmXuyN0shoxhG4BpQInHkFARL+nenesXFxEm4X5e 392 | L603Pcgw72/ratxVpTW7hPMjiLTEBqza0GjQm7Sarbdy+Vzdp/6XFrAcPfFl1juY 393 | oyYzbozPsvFHz3Re44y1KmI4HAzU/qkjJUbNTTiPPVI2cDP6iYN2XXxBb1wwp8jR 394 | iqdZqFG7lU/wvPEbD7BVPpmJBHWNG681zb4ea5Zn4hW8UaxpiIBiaH0/IWc2SVZd 395 | RliAFo3NEsGxCcsnBo/n00oudGbOJxdOp7FbH5hJpeqX2WhCyJRxIeHOWmeuMAet 396 | 03HFriiEmJ99m2nEJN1x0A3QUUM7ji6vZAb4qb1dyq7LlX4M2aaqixRnaTcQkapf 397 | DOxX35DEBXSKrDpyWp6Rx4wNpUyi1TKyhaVnYgD3Gn0VfC/2w86gSFlrf9PMYGM0 398 | PvFxTDzTyjOuPBRa728gZOGXgDOL7qvdInU/opVew7kFeRQHXxHzFCLK5dD+Vrig 399 | 5fS3m0++f55ODkxqHXB8gbXbd3GMmsW6MrGpU7VsCNtbVPdSMW0FalovEB0M+2lj 400 | 1VfuvL+0F5huTe+BgZAt6xgET/CIcZXdNMRPVhraqUjqWtI9Rdk4STPCpU1rDkjG 401 | YDl/fo4W2T6qQWFUpiC9IvVVGkVxaqfZZ4Qu+V5xPUi6vk95QiTNkN1t+m+sCCgS 402 | Llkea8Um0aHMy33Lj3NsfL0LMrnpniqcAks8BvcgIZwk1VRqcj7BQVCygJSYrmAR 403 | DBhMpjWlXuSggnyVPuduZDtnTN+8lCHLOKL3a3bDb6ySaKX49Km6GutDLfpDtEA0 404 | 3mQvmEG4XVm7zy+AlN72qFbtSLDRi/D/uQh2q/ZrFQLOBQBQB56TvEbKouLimUDM 405 | ascQA3aUyhOE7e+d02NOFIFTozwc/C//CIFeA+ZEwxyfha/3Bor6Jez7PC/eHNxZ 406 | w7YMXzPW9NhcCcerhYGebuCJxLwzqJ+IGdukjKsGV2ytWDoB2xZiJNu096j4RKcq 407 | YSJoen0R7IH8N4eDujXR8m9kAl724Uqs1OoAs4VNICvzTutbsgVZ6Z+NMOcfnPw9 408 | jZkFhot16w8znD+OmhBR7/bzOLpaeUhk7EhNq5M6U0NNWx3WwkDlvU/jx+6/EQe3 409 | iLEHptH2HYBF1xscaKGbtKNtuQsfdzgWpOX0qK2YbK3yCKvL/xIm1DQmDZDKkWdW 410 | VNh8oGV1H96CivWlvxhAgXKz9F/83CjMw8YXRk7RJvWR4vtNvXFAvGkFIYCN9Jv9 411 | p+1ukaYoxSLGBik907I6gWSHqumJiCprUyAX/bVfZfNiYh4hzeA3lhwxZSax3JG4 412 | 7QFPvyepOmF/3AAzS/Pusx6jOZnuCMCkfQi6Wpem1o3s4x+fP7kz00Xuj01ErucM 413 | S10ixfIh84kXBN3dTRDtDdeCyoMsBKO0W5jDBBlWL02YfdF6Opo1Q4cPh2DYgXMh 414 | XEszNZSK5LB0y+f3A6Kdx/hkZzHVvMONA70OyrkoZzGyWENhcB0c7ntTJyPPD2qM 415 | s0HRA2VwF/0ypU3OKERM1Ua5NSkTgvnnVTlV9GO90Tkn5v4fxdl8NzIuJLyGguTP 416 | Xc0tRM34Lg== 417 | -----END ENCRYPTED PRIVATE KEY----- 418 | '''); 419 | --------------------------------------------------------------------------------