blob: fbe765a291660f2fec122ddc30d7866cb77a7b60 [file] [log] [blame]
// Copyright (c) 2015, 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.
import 'dart:async';
import 'package:async/async.dart';
import 'request.dart';
import 'handler.dart';
import 'server.dart';
/// A connected pair of a [Server] and a [Handler].
///
/// Requests to the handler are sent to the server's mounted handler once it's
/// available. This is used to expose a virtual [Server] that's actually one
/// part of a larger URL-space.
class ServerHandler {
/// The server.
///
/// Once this has a handler mounted, it's passed all requests to [handler]
/// until this server is closed.
Server get server => _server;
final _HandlerServer _server;
/// The handler.
///
/// This passes requests to [server]'s handler. If that handler isn't mounted
/// yet, the requests are handled once it is.
Handler get handler => _onRequest;
/// Creates a new connected pair of a [Server] with the given [url] and a
/// [Handler].
///
/// The caller is responsible for ensuring that requests to [url] or any URL
/// beneath it are handled by [handler].
///
/// If [onClose] is passed, it's called when [server] is closed. It may return
/// a [Future] or `null`; its return value is returned by [Server.close].
ServerHandler(Uri url, {onClose()})
: _server = new _HandlerServer(url, onClose);
/// Pipes requests to [server]'s handler.
_onRequest(Request request) {
if (_server._closeMemo.hasRun) {
throw new StateError("Request received after the server was closed.");
}
if (_server._handler != null) return _server._handler(request);
// Avoid async/await so that the common case of a handler already being
// mounted doesn't involve any extra asynchronous delays.
return _server._onMounted.then((_) => _server._handler(request));
}
}
/// The [Server] returned by [ServerHandler].
class _HandlerServer implements Server {
final Uri url;
/// The callback to call when [close] is called, or `null`.
final ZoneCallback _onClose;
/// The mounted handler.
///
/// This is `null` until [mount] is called.
Handler _handler;
/// A future that fires once [mount] has been called.
Future get _onMounted => _onMountedCompleter.future;
final _onMountedCompleter = new Completer();
_HandlerServer(this.url, this._onClose);
void mount(Handler handler) {
if (_handler != null) {
throw new StateError("Can't mount two handlers for the same server.");
}
_handler = handler;
_onMountedCompleter.complete();
}
Future close() => _closeMemo.runOnce(() {
return _onClose == null ? null : _onClose();
});
final _closeMemo = new AsyncMemoizer();
}