├── .gitignore ├── CHANGELOG.md ├── pubspec.yaml ├── README.md ├── LICENSE ├── lib └── http_throttle.dart └── test └── http_throttle_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Don’t commit the following directories created by pub. 2 | .dart_tool 3 | .pub/ 4 | build/ 5 | .packages 6 | pubspec.lock 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.4 2 | 3 | * Support the latest `pkg:http` release. 4 | 5 | ## 1.0.3 6 | 7 | * Set max SDK version to <3.0.0, and adjusted other dependencies. 8 | 9 | ## 1.0.2 10 | 11 | * Fix a Dart 2 type error. 12 | 13 | ## 1.0.1 14 | 15 | * Properly release resources for requests that error out. 16 | 17 | ## 1.0.0 18 | 19 | * Initial release. 20 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: http_throttle 2 | version: 1.0.4 3 | 4 | description: HTTP client middleware that throttles requests. 5 | author: Dart Team 6 | homepage: https://github.com/dart-lang/http_throttle 7 | 8 | environment: 9 | sdk: '>=2.0.0 <3.0.0' 10 | 11 | dependencies: 12 | http: '>=0.9.0 <0.13.0' 13 | pool: '>=1.0.0 <2.0.0' 14 | 15 | dev_dependencies: 16 | test: '>=0.12.0 <2.0.0' 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `http_throttle` is middleware for the [http package][] that throttles the number 2 | of concurrent requests that an HTTP client can make. 3 | 4 | ```dart 5 | // This client allows 32 concurrent requests. 6 | final client = new ThrottleClient(32); 7 | 8 | Future> readAllUrls(Iterable urls) { 9 | return Future.wait(urls.map((url) { 10 | // You can safely call as many client methods as you want concurrently, and 11 | // ThrottleClient will ensure that only 32 underlying HTTP requests will be 12 | // open at once. 13 | return client.read(url); 14 | })); 15 | } 16 | ``` 17 | 18 | [http package]: pub.dartlang.org/packages/http 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, the Dart project authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /lib/http_throttle.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'; 8 | import 'package:pool/pool.dart'; 9 | 10 | /// A middleware client that throttles the number of concurrent requests. 11 | /// 12 | /// As long as the number of requests is within the limit, this works just like 13 | /// a normal client. If a request is made beyond the limit, the underlying HTTP 14 | /// request won't be sent until other requests have completed. 15 | class ThrottleClient extends BaseClient { 16 | final Pool _pool; 17 | final Client _inner; 18 | 19 | /// Creates a new client that allows no more than [maxActiveRequests] 20 | /// concurrent requests. 21 | /// 22 | /// If [inner] is passed, it's used as the inner client for sending HTTP 23 | /// requests. It defaults to `new http.Client()`. 24 | ThrottleClient(int maxActiveRequests, [Client inner]) 25 | : _pool = new Pool(maxActiveRequests), 26 | _inner = inner == null ? new Client() : inner; 27 | 28 | Future send(BaseRequest request) async { 29 | var resource = await _pool.request(); 30 | 31 | StreamedResponse response; 32 | try { 33 | response = await _inner.send(request); 34 | } catch (_) { 35 | resource.release(); 36 | rethrow; 37 | } 38 | 39 | var stream = response.stream.transform( 40 | new StreamTransformer, List>.fromHandlers( 41 | handleDone: (sink) { 42 | resource.release(); 43 | sink.close(); 44 | })); 45 | return new StreamedResponse(stream, response.statusCode, 46 | contentLength: response.contentLength, 47 | request: response.request, 48 | headers: response.headers, 49 | isRedirect: response.isRedirect, 50 | persistentConnection: response.persistentConnection, 51 | reasonPhrase: response.reasonPhrase); 52 | } 53 | 54 | void close() => _inner.close(); 55 | } 56 | -------------------------------------------------------------------------------- /test/http_throttle_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:http/http.dart' as http; 8 | import 'package:http/testing.dart'; 9 | import 'package:http_throttle/http_throttle.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | void main() { 13 | test("makes requests until the limit is hit", () { 14 | var pendingResponses = []; 15 | var client = new ThrottleClient(10, new MockClient((request) { 16 | var completer = new Completer(); 17 | pendingResponses.add(completer); 18 | return completer.future.then((response) { 19 | pendingResponses.remove(completer); 20 | return response; 21 | }); 22 | })); 23 | 24 | // Make the first batch of requests. All of these should be sent 25 | // immediately. 26 | for (var i = 0; i < 10; i++) { 27 | client.get('/'); 28 | } 29 | 30 | return pumpEventQueue().then((_) { 31 | // All ten of the requests should have responses pending. 32 | expect(pendingResponses, hasLength(10)); 33 | 34 | // Make the second batch of requests. None of these should be sent 35 | // until the previous batch has finished. 36 | for (var i = 0; i < 5; i++) { 37 | client.get('/'); 38 | } 39 | 40 | return pumpEventQueue(); 41 | }).then((_) { 42 | // Only the original ten requests should have responses pending. 43 | expect(pendingResponses, hasLength(10)); 44 | 45 | // Send the first ten responses, allowing the next batch of requests to 46 | // fire. 47 | for (var completer in pendingResponses) { 48 | completer.complete(new http.Response("done", 200)); 49 | } 50 | 51 | return pumpEventQueue(); 52 | }).then((_) { 53 | // Now the second batch of responses should be pending. 54 | expect(pendingResponses, hasLength(5)); 55 | }); 56 | }); 57 | 58 | test("releases resources when HTTP requests error out", () { 59 | var client = new ThrottleClient( 60 | 10, new MockClient((request) => new Future.error("oh no!"))); 61 | 62 | // Every request should throw. If we aren't properly releasing resources, 63 | // all of these after the 10th will fail to complete. 64 | for (var i = 0; i < 20; i++) { 65 | expect(client.get('/'), throwsA("oh no!")); 66 | } 67 | }); 68 | } 69 | 70 | /// Returns a [Future] that completes after pumping the event queue [times] 71 | /// times. By default, this should pump the event queue enough times to allow 72 | /// any code to run, as long as it's not waiting on some external event. 73 | Future pumpEventQueue([int times = 20]) { 74 | if (times == 0) return new Future.value(); 75 | // We use a delayed future to allow microtask events to finish. The 76 | // Future.value or Future() constructors use scheduleMicrotask themselves and 77 | // would therefore not wait for microtask callbacks that are scheduled after 78 | // invoking this method. 79 | return new Future.delayed(Duration.zero, () => pumpEventQueue(times - 1)); 80 | } 81 | --------------------------------------------------------------------------------