Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(283)

Unified Diff: pkg/shelf/lib/src/request.dart

Issue 260933004: Support Request hijacking in Shelf, using a similar API to Rack. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: code review Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/shelf/lib/src/middleware.dart ('k') | pkg/shelf/lib/src/util.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/shelf/lib/src/request.dart
diff --git a/pkg/shelf/lib/src/request.dart b/pkg/shelf/lib/src/request.dart
index 192a78227a9664ea5f1811ea27ab544d8738855b..4a08468750a1d6a37f8a01fe2fdaa44984fefcbe 100644
--- a/pkg/shelf/lib/src/request.dart
+++ b/pkg/shelf/lib/src/request.dart
@@ -8,9 +8,18 @@ import 'dart:async';
import 'package:http_parser/http_parser.dart';
+import 'hijack_exception.dart';
import 'message.dart';
import 'util.dart';
+/// A callback provided by a Shelf handler that's passed to [Request.hijack].
+typedef void HijackCallback(
+ Stream<List<int>> stream, StreamSink<List<int>> sink);
+
+/// A callback provided by a Shelf adapter that's used by [Request.hijack] to
+/// provide a [HijackCallback] with a socket.
+typedef void OnHijackCallback(HijackCallback callback);
+
/// Represents an HTTP request to be processed by a Shelf application.
class Request extends Message {
/// The remainder of the [requestedUri] path and query designating the virtual
@@ -46,6 +55,17 @@ class Request extends Message {
/// The original [Uri] for the request.
final Uri requestedUri;
+ /// The callback wrapper for hijacking this request.
+ ///
+ /// This will be `null` if this request can't be hijacked.
+ final _OnHijack _onHijack;
+
+ /// Whether this request can be hijacked.
+ ///
+ /// This will be `false` either if the adapter doesn't support hijacking, or
+ /// if the request has already been hijacked.
+ bool get canHijack => _onHijack != null && !_onHijack.called;
+
/// If this is non-`null` and the requested resource hasn't been modified
/// since this date and time, the server should return a 304 Not Modified
/// response.
@@ -69,15 +89,43 @@ class Request extends Message {
/// [ArgumentError].
///
/// The default value for [protocolVersion] is '1.1'.
+ ///
+ /// ## `onHijack`
+ ///
+ /// [onHijack] allows handlers to take control of the underlying socket for
+ /// the request. It should be passed by adapters that can provide access to
+ /// the bidirectional socket underlying the HTTP connection stream.
+ ///
+ /// The [onHijack] callback will only be called once per request. It will be
+ /// passed another callback which takes a byte stream and a byte sink.
+ /// [onHijack] must pass the stream and sink for the connection stream to this
+ /// callback, although it may do so asynchronously. Both parameters may be the
+ /// same object. If the user closes the sink, the adapter should ensure that
+ /// the stream is closed as well.
+ ///
+ /// If a request is hijacked, the adapter should expect to receive a
+ /// [HijackException] from the handler. This is a special exception used to
+ /// indicate that hijacking has occurred. The adapter should avoid either
+ /// sending a response or notifying the user of an error if a
+ /// [HijackException] is caught.
+ ///
+ /// An adapter can check whether a request was hijacked using [canHijack],
+ /// which will be `false` for a hijacked request. The adapter may throw an
+ /// error if a [HijackException] is received for a non-hijacked request, or if
+ /// no [HijackException] is received for a hijacked request.
+ ///
+ /// See also [hijack].
// TODO(kevmoo) finish documenting the rest of the arguments.
Request(this.method, Uri requestedUri, {String protocolVersion,
Map<String, String> headers, Uri url, String scriptName,
- Stream<List<int>> body, Map<String, Object> context})
+ Stream<List<int>> body, Map<String, Object> context,
+ OnHijackCallback onHijack})
: this.requestedUri = requestedUri,
this.protocolVersion = protocolVersion == null ?
'1.1' : protocolVersion,
this.url = _computeUrl(requestedUri, url, scriptName),
this.scriptName = _computeScriptName(requestedUri, url, scriptName),
+ this._onHijack = onHijack == null ? null : new _OnHijack(onHijack),
super(body == null ? new Stream.fromIterable([]) : body,
headers: headers, context: context) {
if (method.isEmpty) throw new ArgumentError('method cannot be empty.');
@@ -128,6 +176,52 @@ class Request extends Message {
protocolVersion: this.protocolVersion, headers: headers, url: this.url,
scriptName: this.scriptName, body: this.read(), context: context);
}
+
+ /// Takes control of the underlying request socket.
+ ///
+ /// Synchronously, this throws a [HijackException] that indicates to the
+ /// adapter that it shouldn't emit a response itself. Asynchronously,
+ /// [callback] is called with a [Stream<List<int>>] and
+ /// [StreamSink<List<int>>], respectively, that provide access to the
+ /// underlying request socket.
+ ///
+ /// If the sink is closed, the stream will be closed as well. The stream and
+ /// sink may be the same object, as in the case of a `dart:io` `Socket`
+ /// object.
+ ///
+ /// This may only be called when using a Shelf adapter that supports
+ /// hijacking, such as the `dart:io` adapter. In addition, a given request may
+ /// only be hijacked once. [canHijack] can be used to detect whether this
+ /// request can be hijacked.
+ void hijack(HijackCallback callback) {
+ if (_onHijack == null) {
+ throw new StateError("This request can't be hijacked.");
+ }
+
+ _onHijack.run(callback);
+ throw const HijackException();
+ }
+}
+
+/// A class containing a callback for [Request.hijack] that also tracks whether
+/// the callback has been called.
+class _OnHijack {
+ /// The callback.
+ final OnHijackCallback _callback;
+
+ /// Whether [this] has been called.
+ bool called = false;
+
+ _OnHijack(this._callback);
+
+ /// Calls [this].
+ ///
+ /// Throws a [StateError] if [this] has already been called.
+ void run(HijackCallback callback) {
+ if (called) throw new StateError("This request has already been hijacked.");
+ called = true;
+ newFuture(() => _callback(callback));
+ }
}
/// Computes `url` from the provided [Request] constructor arguments.
« no previous file with comments | « pkg/shelf/lib/src/middleware.dart ('k') | pkg/shelf/lib/src/util.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698