OLD | NEW |
(Empty) | |
| 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 /// A Shelf adapter for handling [HttpRequest] objects from `dart:io`. |
| 6 /// |
| 7 /// One can provide an instance of [HttpServer] as the `requests` parameter in |
| 8 /// [serveRequests]. |
| 9 /// |
| 10 /// This adapter supports request hijacking; see [Request.hijack]. It also |
| 11 /// supports the `"shelf.io.buffer_output"` `Response.context` property. If this |
| 12 /// property is `true` (the default), streamed responses will be buffered to |
| 13 /// improve performance; if it's `false`, all chunks will be pushed over the |
| 14 /// wire as they're received. See [`HttpResponse.bufferOutput`][bufferOutput] |
| 15 /// for more information. |
| 16 /// |
| 17 /// [bufferOutput]: https://api.dartlang.org/apidocs/channels/stable/dartdoc-vie
wer/dart:io.HttpResponse#id_bufferOutput |
| 18 library shelf.io; |
| 19 |
| 20 import 'dart:async'; |
| 21 import 'dart:io'; |
| 22 |
| 23 import 'package:stack_trace/stack_trace.dart'; |
| 24 |
| 25 import 'shelf.dart'; |
| 26 import 'src/util.dart'; |
| 27 |
| 28 /// Starts an [HttpServer] that listens on the specified [address] and |
| 29 /// [port] and sends requests to [handler]. |
| 30 /// |
| 31 /// See the documentation for [HttpServer.bind] for more details on [address], |
| 32 /// [port], and [backlog]. |
| 33 Future<HttpServer> serve(Handler handler, address, int port, {int backlog}) { |
| 34 if (backlog == null) backlog = 0; |
| 35 return HttpServer.bind(address, port, backlog: backlog).then((server) { |
| 36 serveRequests(server, handler); |
| 37 return server; |
| 38 }); |
| 39 } |
| 40 |
| 41 /// Serve a [Stream] of [HttpRequest]s. |
| 42 /// |
| 43 /// [HttpServer] implements [Stream<HttpRequest>] so it can be passed directly |
| 44 /// to [serveRequests]. |
| 45 /// |
| 46 /// Errors thrown by [handler] while serving a request will be printed to the |
| 47 /// console and cause a 500 response with no body. Errors thrown asynchronously |
| 48 /// by [handler] will be printed to the console or, if there's an active error |
| 49 /// zone, passed to that zone. |
| 50 void serveRequests(Stream<HttpRequest> requests, Handler handler) { |
| 51 catchTopLevelErrors(() { |
| 52 requests.listen((request) => handleRequest(request, handler)); |
| 53 }, (error, stackTrace) { |
| 54 _logError('Asynchronous error\n$error', stackTrace); |
| 55 }); |
| 56 } |
| 57 |
| 58 /// Uses [handler] to handle [request]. |
| 59 /// |
| 60 /// Returns a [Future] which completes when the request has been handled. |
| 61 Future handleRequest(HttpRequest request, Handler handler) { |
| 62 var shelfRequest; |
| 63 try { |
| 64 shelfRequest = _fromHttpRequest(request); |
| 65 } catch (error, stackTrace) { |
| 66 var response = _logError('Error parsing request.\n$error', stackTrace); |
| 67 return _writeResponse(response, request.response); |
| 68 } |
| 69 |
| 70 // TODO(nweiz): abstract out hijack handling to make it easier to implement an |
| 71 // adapter. |
| 72 return new Future.sync(() => handler(shelfRequest)) |
| 73 .catchError((error, stackTrace) { |
| 74 if (error is HijackException) { |
| 75 // A HijackException should bypass the response-writing logic entirely. |
| 76 if (!shelfRequest.canHijack) throw error; |
| 77 |
| 78 // If the request wasn't hijacked, we shouldn't be seeing this exception. |
| 79 return _logError( |
| 80 "Caught HijackException, but the request wasn't hijacked.", |
| 81 stackTrace); |
| 82 } |
| 83 |
| 84 return _logError('Error thrown by handler.\n$error', stackTrace); |
| 85 }).then((response) { |
| 86 if (response == null) { |
| 87 return _writeResponse( |
| 88 _logError('null response from handler.'), request.response); |
| 89 } else if (shelfRequest.canHijack) { |
| 90 return _writeResponse(response, request.response); |
| 91 } |
| 92 |
| 93 var message = new StringBuffer() |
| 94 ..writeln("Got a response for hijacked request " |
| 95 "${shelfRequest.method} ${shelfRequest.requestedUri}:") |
| 96 ..writeln(response.statusCode); |
| 97 response.headers |
| 98 .forEach((key, value) => message.writeln("${key}: ${value}")); |
| 99 throw new Exception(message.toString().trim()); |
| 100 }).catchError((error, stackTrace) { |
| 101 // Ignore HijackExceptions. |
| 102 if (error is! HijackException) throw error; |
| 103 }); |
| 104 } |
| 105 |
| 106 /// Creates a new [Request] from the provided [HttpRequest]. |
| 107 Request _fromHttpRequest(HttpRequest request) { |
| 108 var headers = {}; |
| 109 request.headers.forEach((k, v) { |
| 110 // Multiple header values are joined with commas. |
| 111 // See http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21#page-22 |
| 112 headers[k] = v.join(','); |
| 113 }); |
| 114 |
| 115 onHijack(callback) { |
| 116 return request.response |
| 117 .detachSocket(writeHeaders: false) |
| 118 .then((socket) => callback(socket, socket)); |
| 119 } |
| 120 |
| 121 return new Request(request.method, request.requestedUri, |
| 122 protocolVersion: request.protocolVersion, |
| 123 headers: headers, |
| 124 body: request, |
| 125 onHijack: onHijack); |
| 126 } |
| 127 |
| 128 Future _writeResponse(Response response, HttpResponse httpResponse) { |
| 129 if (response.context.containsKey("shelf.io.buffer_output")) { |
| 130 httpResponse.bufferOutput = response.context["shelf.io.buffer_output"]; |
| 131 } |
| 132 |
| 133 httpResponse.statusCode = response.statusCode; |
| 134 |
| 135 response.headers.forEach((header, value) { |
| 136 if (value == null) return; |
| 137 httpResponse.headers.set(header, value); |
| 138 }); |
| 139 |
| 140 if (!response.headers.containsKey(HttpHeaders.SERVER)) { |
| 141 httpResponse.headers.set(HttpHeaders.SERVER, 'dart:io with Shelf'); |
| 142 } |
| 143 |
| 144 if (!response.headers.containsKey(HttpHeaders.DATE)) { |
| 145 httpResponse.headers.date = new DateTime.now().toUtc(); |
| 146 } |
| 147 |
| 148 return httpResponse |
| 149 .addStream(response.read()) |
| 150 .then((_) => httpResponse.close()); |
| 151 } |
| 152 |
| 153 // TODO(kevmoo) A developer mode is needed to include error info in response |
| 154 // TODO(kevmoo) Make error output plugable. stderr, logging, etc |
| 155 Response _logError(String message, [StackTrace stackTrace]) { |
| 156 var chain = new Chain.current(); |
| 157 if (stackTrace != null) { |
| 158 chain = new Chain.forTrace(stackTrace); |
| 159 } |
| 160 chain = chain |
| 161 .foldFrames((frame) => frame.isCore || frame.package == 'shelf').terse; |
| 162 |
| 163 stderr.writeln('ERROR - ${new DateTime.now()}'); |
| 164 stderr.writeln(message); |
| 165 stderr.writeln(chain); |
| 166 return new Response.internalServerError(); |
| 167 } |
OLD | NEW |