OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library scheduled_test.scheduled_server; | 5 library scheduled_test.scheduled_server; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection'; | 8 import 'dart:collection'; |
9 import 'dart:io'; | 9 import 'dart:io'; |
10 | 10 |
11 import 'package:stack_trace/stack_trace.dart'; | 11 import 'package:shelf/shelf.dart' as shelf; |
| 12 import 'package:shelf/shelf_io.dart' as shelf_io; |
12 | 13 |
13 import 'scheduled_test.dart'; | 14 import 'scheduled_test.dart'; |
14 import 'src/scheduled_server/handler.dart'; | 15 import 'src/scheduled_server/handler.dart'; |
15 import 'src/utils.dart'; | 16 import 'src/utils.dart'; |
16 | 17 |
17 typedef Future ScheduledHandler(HttpRequest request); | 18 /// A class representing an HTTP server that's scheduled to run in the course of |
18 | 19 /// the test. This class allows the server's request handling to be scheduled |
19 /// A class representing an [HttpServer] that's scheduled to run in the course | |
20 /// of the test. This class allows the server's request handling to be scheduled | |
21 /// synchronously. | 20 /// synchronously. |
22 /// | 21 /// |
23 /// The server expects requests to be received in the order [handle] is called, | 22 /// The server expects requests to be received in the order [handle] is called, |
24 /// and expects that no additional requests will be received. | 23 /// and expects that no additional requests will be received. |
25 class ScheduledServer { | 24 class ScheduledServer { |
26 /// The description of the server. | 25 /// The description of the server. |
27 final String description; | 26 final String description; |
28 | 27 |
29 /// The wrapped server. | 28 /// The wrapped server. |
30 final Future<HttpServer> _server; | 29 final Future<HttpServer> _server; |
(...skipping 10 matching lines...) Expand all Loading... |
41 | 40 |
42 /// Creates a new server listening on an automatically-allocated port on | 41 /// Creates a new server listening on an automatically-allocated port on |
43 /// 127.0.0.1. [description] is used to refer to the server in debugging | 42 /// 127.0.0.1. [description] is used to refer to the server in debugging |
44 /// messages. | 43 /// messages. |
45 factory ScheduledServer([String description]) { | 44 factory ScheduledServer([String description]) { |
46 var id = _count++; | 45 var id = _count++; |
47 if (description == null) description = 'scheduled server $id'; | 46 if (description == null) description = 'scheduled server $id'; |
48 | 47 |
49 var scheduledServer; | 48 var scheduledServer; |
50 scheduledServer = new ScheduledServer._(schedule(() { | 49 scheduledServer = new ScheduledServer._(schedule(() { |
51 return Chain.track(HttpServer.bind("127.0.0.1", 0)).then((server) { | 50 return shelf_io.serve(scheduledServer._handleRequest, "127.0.0.1", 0) |
52 Chain.track(server).listen(scheduledServer._handleRequest, | 51 .then((server) { |
53 onError: currentSchedule.signalError); | 52 currentSchedule.onComplete.schedule(() => server.close(force: true)); |
54 currentSchedule.onComplete.schedule(server.close); | |
55 return server; | 53 return server; |
56 }); | 54 }); |
57 }, "starting '$description'"), description); | 55 }, "starting '$description'"), description); |
58 return scheduledServer; | 56 return scheduledServer; |
59 } | 57 } |
60 | 58 |
61 /// The port on which the server is listening. | 59 /// The port on which the server is listening. |
62 Future<int> get port => _server.then((s) => s.port); | 60 Future<int> get port => _server.then((s) => s.port); |
63 | 61 |
64 /// The base URL of the server, including its port. | 62 /// The base URL of the server, including its port. |
65 Future<Uri> get url => port.then((p) => Uri.parse("http://127.0.0.1:$p")); | 63 Future<Uri> get url => port.then((p) => Uri.parse("http://127.0.0.1:$p")); |
66 | 64 |
67 /// Schedules [handler] to handle a request to the server with [method] and | 65 /// Schedules [handler] to handle a request to the server with [method] and |
68 /// [path]. The schedule will wait until an HTTP request is received. If that | 66 /// [path]. The schedule will wait until an HTTP request is received. If that |
69 /// request doesn't have the expected [method] and [path], it will fail. | 67 /// request doesn't have the expected [method] and [path], it will fail. |
70 /// Otherwise, it will run [fn]. If [fn] returns a [Future], the schedule will | 68 /// Otherwise, it will run [fn]. If [fn] returns a [Future], the schedule will |
71 /// wait until that [Future] completes. | 69 /// wait until that [Future] completes. |
72 /// | 70 /// |
73 /// The request must be received at the point in the schedule at which | 71 /// The request must be received at the point in the schedule at which |
74 /// [handle] was called, or in the task immediately prior (to allow for | 72 /// [handle] was called, or in the task immediately prior (to allow for |
75 /// non-deterministic asynchronicity). Otherwise, an error will be thrown. | 73 /// non-deterministic asynchronicity). Otherwise, an error will be thrown. |
76 void handle(String method, String path, ScheduledHandler fn) { | 74 void handle(String method, String path, shelf.Handler fn) { |
77 var handler = new Handler(this, method, path, fn); | 75 var handler = new Handler(this, method, path, fn); |
78 _handlers.add(handler); | 76 _handlers.add(handler); |
79 schedule(() { | 77 schedule(() { |
80 handler.ready = true; | 78 handler.ready = true; |
81 return handler.result.catchError((e) { | 79 return handler.result; |
82 // Close the server so that we don't leave a dangling request. | |
83 _server.then((s) => s.close(force: true)); | |
84 throw e; | |
85 }); | |
86 }, "'$description' waiting for $method $path"); | 80 }, "'$description' waiting for $method $path"); |
87 } | 81 } |
88 | 82 |
89 /// The handler for incoming [HttpRequest]s to this server. This dispatches | 83 /// The handler for incoming [shelf.Request]s to this server. |
90 /// the request to the first handler in the queue. It's that handler's | 84 /// |
91 /// responsibility to check that the method/path are correct and that it's | 85 /// This dispatches the request to the first handler in the queue. It's that |
92 /// being run at the correct time. | 86 /// handler's responsibility to check that the method/path are correct and |
93 void _handleRequest(HttpRequest request) { | 87 /// that it's being run at the correct time. |
94 wrapFuture(syncFuture(() { | 88 Future<shelf.Response> _handleRequest(shelf.Request request) { |
| 89 return wrapFuture(syncFuture(() { |
95 if (_handlers.isEmpty) { | 90 if (_handlers.isEmpty) { |
96 fail("'$description' received ${request.method} ${request.uri.path} " | 91 fail("'$description' received ${request.method} ${request.pathInfo} " |
97 "when no more requests were expected."); | 92 "when no more requests were expected."); |
98 } | 93 } |
99 return _handlers.removeFirst().fn(request); | 94 return _handlers.removeFirst().fn(request); |
100 }).catchError((e) { | 95 }), 'receiving ${request.method} ${request.pathInfo}').catchError((error) { |
101 // Close the server so that we don't leave a dangling request. | 96 // Don't let errors bubble up to the shelf handler. It will print them to |
102 _server.then((s) => s.close(force: true)); | 97 // stderr, but the user will already be notified via the scheduled_test |
103 throw e; | 98 // infrastructure. |
104 }), 'receiving ${request.method} ${request.uri}'); | 99 return new shelf.Response.internalServerError( |
| 100 body: error.toString(), |
| 101 headers: {'content-type': 'text/plain'}); |
| 102 }); |
105 } | 103 } |
106 } | 104 } |
OLD | NEW |