blob: 64adf5b8ac691f4daee8c7c56489300a8478db99 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library safe_http_server;
import 'dart:async';
import 'dart:io';
import 'dart:uri';
// TODO(nweiz): remove this when issue 9140 is fixed.
/// A wrapper around [HttpServer] that swallows errors caused by requests
/// behaving badly. This provides the following guarantees:
///
/// * The [SafeHttpServer.listen] onError callback will only emit server-wide
/// errors. It will not emit errors for requests that were unparseable or
/// where the connection was closed too soon.
/// * [HttpResponse.done] will emit no errors.
///
/// The [HttpRequest] data stream can still emit errors.
class SafeHttpServer extends StreamView<HttpRequest> implements HttpServer {
final HttpServer _inner;
static Future<SafeHttpServer> bind([String host = "127.0.0.1",
int port = 0, int backlog = 0]) {
return HttpServer.bind(host, port, backlog)
.then((server) => new SafeHttpServer(server));
}
SafeHttpServer(HttpServer server)
: super(server),
_inner = server;
void close() => _inner.close();
int get port => _inner.port;
set sessionTimeout(int timeout) {
_inner.sessionTimeout = timeout;
}
HttpConnectionsInfo connectionsInfo() => _inner.connectionsInfo();
StreamSubscription<HttpRequest> listen(void onData(HttpRequest value),
{void onError(AsyncError error), void onDone(),
bool unsubscribeOnError: false}) {
var subscription;
subscription = super.listen((request) {
onData(new _HttpRequestWrapper(request));
}, onError: (e) {
var error = e.error;
// Ignore socket error 104, which is caused by a request being cancelled
// before it writes any headers. There's no reason to care about such
// requests.
if (error is SocketIOException && error.osError.errorCode == 104) return;
// Ignore any parsing errors, which come from malformed requests.
if (error is HttpParserException) return;
// Manually handle unsubscribeOnError so the above (ignored) errors don't
// cause unsubscription.
if (unsubscribeOnError) subscription.cancel();
if (onError != null) onError(e);
}, onDone: onDone);
return subscription;
}
}
/// A wrapper around [HttpRequest] for the sole purpose of swallowing errors on
/// [HttpResponse.done].
class _HttpRequestWrapper extends StreamView<List<int>> implements HttpRequest {
final HttpRequest _inner;
final HttpResponse response;
_HttpRequestWrapper(HttpRequest inner)
: super(inner),
_inner = inner,
response = new _HttpResponseWrapper(inner.response);
int get contentLength => _inner.contentLength;
String get method => _inner.method;
Uri get uri => _inner.uri;
Map<String, String> get queryParameters => _inner.queryParameters;
HttpHeaders get headers => _inner.headers;
List<Cookie> get cookies => _inner.cookies;
bool get persistentConnection => _inner.persistentConnection;
X509Certificate get certificate => _inner.certificate;
HttpSession get session => _inner.session;
String get protocolVersion => _inner.protocolVersion;
HttpConnectionInfo get connectionInfo => _inner.connectionInfo;
}
/// A wrapper around [HttpResponse] for the sole purpose of swallowing errors on
/// [done].
class _HttpResponseWrapper implements HttpResponse {
final HttpResponse _inner;
Future<HttpResponse> _done;
_HttpResponseWrapper(this._inner);
/// Swallows all errors from writing to the response.
Future<HttpResponse> get done {
if (_done == null) _done = _inner.done.catchError((_) {});
return _done;
}
int get contentLength => _inner.contentLength;
set contentLength(int value) {
_inner.contentLength = value;
}
int get statusCode => _inner.statusCode;
set statusCode(int value) {
_inner.statusCode = value;
}
String get reasonPhrase => _inner.reasonPhrase;
set reasonPhrase(String value) {
_inner.reasonPhrase = value;
}
bool get persistentConnection => _inner.persistentConnection;
set persistentConnection(bool value) {
_inner.persistentConnection = value;
}
Encoding get encoding => _inner.encoding;
set encoding(Encoding value) {
_inner.encoding = value;
}
HttpHeaders get headers => _inner.headers;
List<Cookie> get cookies => _inner.cookies;
Future<Socket> detachSocket() => _inner.detachSocket();
HttpConnectionInfo get connectionInfo => _inner.connectionInfo;
void writeBytes(List<int> data) => _inner.writeBytes(data);
Future<HttpResponse> consume(Stream<List<int>> stream) =>
_inner.consume(stream);
Future<HttpResponse> writeStream(Stream<List<int>> stream) =>
_inner.writeStream(stream);
void close() => _inner.close();
void write(Object obj) => _inner.write(obj);
void writeAll(Iterable objects) => _inner.writeAll(objects);
void writeCharCode(int charCode) => _inner.writeCharCode(charCode);
void writeln([Object obj = ""]) => _inner.writeln(obj);
}