OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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 /// A Shelf adapter for handling [HttpRequest] objects from `dart:io`. | 5 /// A Shelf adapter for handling [HttpRequest] objects from `dart:io`. |
6 /// | 6 /// |
7 /// One can provide an instance of [HttpServer] as the `requests` parameter in | 7 /// One can provide an instance of [HttpServer] as the `requests` parameter in |
8 /// [serveRequests]. | 8 /// [serveRequests]. |
9 /// | 9 /// |
10 /// This adapter supports request hijacking; see [Request.hijack]. It also | 10 /// This adapter supports request hijacking; see [Request.hijack]. It also |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
46 /// to [serveRequests]. | 46 /// to [serveRequests]. |
47 /// | 47 /// |
48 /// Errors thrown by [handler] while serving a request will be printed to the | 48 /// Errors thrown by [handler] while serving a request will be printed to the |
49 /// console and cause a 500 response with no body. Errors thrown asynchronously | 49 /// console and cause a 500 response with no body. Errors thrown asynchronously |
50 /// by [handler] will be printed to the console or, if there's an active error | 50 /// by [handler] will be printed to the console or, if there's an active error |
51 /// zone, passed to that zone. | 51 /// zone, passed to that zone. |
52 void serveRequests(Stream<HttpRequest> requests, Handler handler) { | 52 void serveRequests(Stream<HttpRequest> requests, Handler handler) { |
53 catchTopLevelErrors(() { | 53 catchTopLevelErrors(() { |
54 requests.listen((request) => handleRequest(request, handler)); | 54 requests.listen((request) => handleRequest(request, handler)); |
55 }, (error, stackTrace) { | 55 }, (error, stackTrace) { |
56 _logError('Asynchronous error\n$error', stackTrace); | 56 _logTopLevelError('Asynchronous error\n$error', stackTrace); |
57 }); | 57 }); |
58 } | 58 } |
59 | 59 |
60 /// Uses [handler] to handle [request]. | 60 /// Uses [handler] to handle [request]. |
61 /// | 61 /// |
62 /// Returns a [Future] which completes when the request has been handled. | 62 /// Returns a [Future] which completes when the request has been handled. |
63 Future handleRequest(HttpRequest request, Handler handler) { | 63 Future handleRequest(HttpRequest request, Handler handler) async { |
64 var shelfRequest; | 64 var shelfRequest; |
65 try { | 65 try { |
66 shelfRequest = _fromHttpRequest(request); | 66 shelfRequest = _fromHttpRequest(request); |
67 } catch (error, stackTrace) { | 67 } catch (error, stackTrace) { |
68 var response = _logError('Error parsing request.\n$error', stackTrace); | 68 var response = _logTopLevelError( |
69 return _writeResponse(response, request.response); | 69 'Error parsing request.\n$error', stackTrace); |
| 70 await _writeResponse(response, request.response); |
| 71 return; |
70 } | 72 } |
71 | 73 |
72 // TODO(nweiz): abstract out hijack handling to make it easier to implement an | 74 // TODO(nweiz): abstract out hijack handling to make it easier to implement an |
73 // adapter. | 75 // adapter. |
74 return new Future.sync(() => handler(shelfRequest)) | 76 var response; |
75 .catchError((error, stackTrace) { | 77 try { |
76 if (error is HijackException) { | 78 response = await handler(shelfRequest); |
77 // A HijackException should bypass the response-writing logic entirely. | 79 } on HijackException catch (error, stackTrace) { |
78 if (!shelfRequest.canHijack) throw error; | 80 // A HijackException should bypass the response-writing logic entirely. |
| 81 if (!shelfRequest.canHijack) return; |
79 | 82 |
80 // If the request wasn't hijacked, we shouldn't be seeing this exception. | 83 // If the request wasn't hijacked, we shouldn't be seeing this exception. |
81 return _logError( | 84 response = _logError( |
82 "Caught HijackException, but the request wasn't hijacked.", | 85 shelfRequest, |
83 stackTrace); | 86 "Caught HijackException, but the request wasn't hijacked.", |
84 } | 87 stackTrace); |
| 88 } catch (error, stackTrace) { |
| 89 response = _logError( |
| 90 shelfRequest, 'Error thrown by handler.\n$error', stackTrace); |
| 91 } |
85 | 92 |
86 return _logError('Error thrown by handler.\n$error', stackTrace); | 93 if (response == null) { |
87 }).then((response) { | 94 await _writeResponse( |
88 if (response == null) { | 95 _logError(shelfRequest, 'null response from handler.'), |
89 return _writeResponse( | 96 request.response); |
90 _logError('null response from handler.'), request.response); | 97 return; |
91 } else if (shelfRequest.canHijack) { | 98 } else if (shelfRequest.canHijack) { |
92 return _writeResponse(response, request.response); | 99 await _writeResponse(response, request.response); |
93 } | 100 return; |
| 101 } |
94 | 102 |
95 var message = new StringBuffer() | 103 var message = new StringBuffer() |
96 ..writeln("Got a response for hijacked request " | 104 ..writeln("Got a response for hijacked request " |
97 "${shelfRequest.method} ${shelfRequest.requestedUri}:") | 105 "${shelfRequest.method} ${shelfRequest.requestedUri}:") |
98 ..writeln(response.statusCode); | 106 ..writeln(response.statusCode); |
99 response.headers | 107 response.headers |
100 .forEach((key, value) => message.writeln("${key}: ${value}")); | 108 .forEach((key, value) => message.writeln("${key}: ${value}")); |
101 throw new Exception(message.toString().trim()); | 109 throw new Exception(message.toString().trim()); |
102 }).catchError((error, stackTrace) { | |
103 // Ignore HijackExceptions. | |
104 if (error is! HijackException) throw error; | |
105 }); | |
106 } | 110 } |
107 | 111 |
108 /// Creates a new [Request] from the provided [HttpRequest]. | 112 /// Creates a new [Request] from the provided [HttpRequest]. |
109 Request _fromHttpRequest(HttpRequest request) { | 113 Request _fromHttpRequest(HttpRequest request) { |
110 var headers = {}; | 114 var headers = {}; |
111 request.headers.forEach((k, v) { | 115 request.headers.forEach((k, v) { |
112 // Multiple header values are joined with commas. | 116 // Multiple header values are joined with commas. |
113 // See http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21#page-22 | 117 // See http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21#page-22 |
114 headers[k] = v.join(','); | 118 headers[k] = v.join(','); |
115 }); | 119 }); |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
147 httpResponse.headers.date = new DateTime.now().toUtc(); | 151 httpResponse.headers.date = new DateTime.now().toUtc(); |
148 } | 152 } |
149 | 153 |
150 return httpResponse | 154 return httpResponse |
151 .addStream(response.read()) | 155 .addStream(response.read()) |
152 .then((_) => httpResponse.close()); | 156 .then((_) => httpResponse.close()); |
153 } | 157 } |
154 | 158 |
155 // TODO(kevmoo) A developer mode is needed to include error info in response | 159 // TODO(kevmoo) A developer mode is needed to include error info in response |
156 // TODO(kevmoo) Make error output plugable. stderr, logging, etc | 160 // TODO(kevmoo) Make error output plugable. stderr, logging, etc |
157 Response _logError(String message, [StackTrace stackTrace]) { | 161 Response _logError(Request request, String message, [StackTrace stackTrace]) { |
| 162 // Add information about the request itself. |
| 163 var buffer = new StringBuffer(); |
| 164 buffer.write("${request.method} ${request.requestedUri.path}"); |
| 165 if (request.requestedUri.query.isNotEmpty) { |
| 166 buffer.write("?${request.requestedUri.query}"); |
| 167 } |
| 168 buffer.writeln(); |
| 169 buffer.write(message); |
| 170 |
| 171 return _logTopLevelError(buffer.toString(), stackTrace); |
| 172 } |
| 173 |
| 174 Response _logTopLevelError(String message, [StackTrace stackTrace]) { |
158 var chain = new Chain.current(); | 175 var chain = new Chain.current(); |
159 if (stackTrace != null) { | 176 if (stackTrace != null) { |
160 chain = new Chain.forTrace(stackTrace); | 177 chain = new Chain.forTrace(stackTrace); |
161 } | 178 } |
162 chain = chain | 179 chain = chain |
163 .foldFrames((frame) => frame.isCore || frame.package == 'shelf').terse; | 180 .foldFrames((frame) => frame.isCore || frame.package == 'shelf').terse; |
164 | 181 |
165 stderr.writeln('ERROR - ${new DateTime.now()}'); | 182 stderr.writeln('ERROR - ${new DateTime.now()}'); |
166 stderr.writeln(message); | 183 stderr.writeln(message); |
167 stderr.writeln(chain); | 184 stderr.writeln(chain); |
168 return new Response.internalServerError(); | 185 return new Response.internalServerError(); |
169 } | 186 } |
OLD | NEW |