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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/shelf/lib/src/middleware.dart ('k') | pkg/shelf/lib/src/util.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 library shelf.request; 5 library shelf.request;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 8
9 import 'package:http_parser/http_parser.dart'; 9 import 'package:http_parser/http_parser.dart';
10 10
11 import 'hijack_exception.dart';
11 import 'message.dart'; 12 import 'message.dart';
12 import 'util.dart'; 13 import 'util.dart';
13 14
15 /// A callback provided by a Shelf handler that's passed to [Request.hijack].
16 typedef void HijackCallback(
17 Stream<List<int>> stream, StreamSink<List<int>> sink);
18
19 /// A callback provided by a Shelf adapter that's used by [Request.hijack] to
20 /// provide a [HijackCallback] with a socket.
21 typedef void OnHijackCallback(HijackCallback callback);
22
14 /// Represents an HTTP request to be processed by a Shelf application. 23 /// Represents an HTTP request to be processed by a Shelf application.
15 class Request extends Message { 24 class Request extends Message {
16 /// The remainder of the [requestedUri] path and query designating the virtual 25 /// The remainder of the [requestedUri] path and query designating the virtual
17 /// "location" of the request's target within the handler. 26 /// "location" of the request's target within the handler.
18 /// 27 ///
19 /// [url] may be an empty, if [requestedUri]targets the handler 28 /// [url] may be an empty, if [requestedUri]targets the handler
20 /// root and does not have a trailing slash. 29 /// root and does not have a trailing slash.
21 /// 30 ///
22 /// [url] is never null. If it is not empty, it will start with `/`. 31 /// [url] is never null. If it is not empty, it will start with `/`.
23 /// 32 ///
(...skipping 15 matching lines...) Expand all
39 /// [scriptName] and [url] combine to create a valid path that should 48 /// [scriptName] and [url] combine to create a valid path that should
40 /// correspond to the [requestedUri] path. 49 /// correspond to the [requestedUri] path.
41 final String scriptName; 50 final String scriptName;
42 51
43 /// The HTTP protocol version used in the request, either "1.0" or "1.1". 52 /// The HTTP protocol version used in the request, either "1.0" or "1.1".
44 final String protocolVersion; 53 final String protocolVersion;
45 54
46 /// The original [Uri] for the request. 55 /// The original [Uri] for the request.
47 final Uri requestedUri; 56 final Uri requestedUri;
48 57
58 /// The callback wrapper for hijacking this request.
59 ///
60 /// This will be `null` if this request can't be hijacked.
61 final _OnHijack _onHijack;
62
63 /// Whether this request can be hijacked.
64 ///
65 /// This will be `false` either if the adapter doesn't support hijacking, or
66 /// if the request has already been hijacked.
67 bool get canHijack => _onHijack != null && !_onHijack.called;
68
49 /// If this is non-`null` and the requested resource hasn't been modified 69 /// If this is non-`null` and the requested resource hasn't been modified
50 /// since this date and time, the server should return a 304 Not Modified 70 /// since this date and time, the server should return a 304 Not Modified
51 /// response. 71 /// response.
52 /// 72 ///
53 /// This is parsed from the If-Modified-Since header in [headers]. If 73 /// This is parsed from the If-Modified-Since header in [headers]. If
54 /// [headers] doesn't have an If-Modified-Since header, this will be `null`. 74 /// [headers] doesn't have an If-Modified-Since header, this will be `null`.
55 DateTime get ifModifiedSince { 75 DateTime get ifModifiedSince {
56 if (_ifModifiedSinceCache != null) return _ifModifiedSinceCache; 76 if (_ifModifiedSinceCache != null) return _ifModifiedSinceCache;
57 if (!headers.containsKey('if-modified-since')) return null; 77 if (!headers.containsKey('if-modified-since')) return null;
58 _ifModifiedSinceCache = parseHttpDate(headers['if-modified-since']); 78 _ifModifiedSinceCache = parseHttpDate(headers['if-modified-since']);
59 return _ifModifiedSinceCache; 79 return _ifModifiedSinceCache;
60 } 80 }
61 DateTime _ifModifiedSinceCache; 81 DateTime _ifModifiedSinceCache;
62 82
63 /// Creates a new [Request]. 83 /// Creates a new [Request].
64 /// 84 ///
65 /// If [url] and [scriptName] are omitted, they are inferred from 85 /// If [url] and [scriptName] are omitted, they are inferred from
66 /// [requestedUri]. 86 /// [requestedUri].
67 /// 87 ///
68 /// Setting one of [url] or [scriptName] and not the other will throw an 88 /// Setting one of [url] or [scriptName] and not the other will throw an
69 /// [ArgumentError]. 89 /// [ArgumentError].
70 /// 90 ///
71 /// The default value for [protocolVersion] is '1.1'. 91 /// The default value for [protocolVersion] is '1.1'.
92 ///
93 /// ## `onHijack`
94 ///
95 /// [onHijack] allows handlers to take control of the underlying socket for
96 /// the request. It should be passed by adapters that can provide access to
97 /// the bidirectional socket underlying the HTTP connection stream.
98 ///
99 /// The [onHijack] callback will only be called once per request. It will be
100 /// passed another callback which takes a byte stream and a byte sink.
101 /// [onHijack] must pass the stream and sink for the connection stream to this
102 /// callback, although it may do so asynchronously. Both parameters may be the
103 /// same object. If the user closes the sink, the adapter should ensure that
104 /// the stream is closed as well.
105 ///
106 /// If a request is hijacked, the adapter should expect to receive a
107 /// [HijackException] from the handler. This is a special exception used to
108 /// indicate that hijacking has occurred. The adapter should avoid either
109 /// sending a response or notifying the user of an error if a
110 /// [HijackException] is caught.
111 ///
112 /// An adapter can check whether a request was hijacked using [canHijack],
113 /// which will be `false` for a hijacked request. The adapter may throw an
114 /// error if a [HijackException] is received for a non-hijacked request, or if
115 /// no [HijackException] is received for a hijacked request.
116 ///
117 /// See also [hijack].
72 // TODO(kevmoo) finish documenting the rest of the arguments. 118 // TODO(kevmoo) finish documenting the rest of the arguments.
73 Request(this.method, Uri requestedUri, {String protocolVersion, 119 Request(this.method, Uri requestedUri, {String protocolVersion,
74 Map<String, String> headers, Uri url, String scriptName, 120 Map<String, String> headers, Uri url, String scriptName,
75 Stream<List<int>> body, Map<String, Object> context}) 121 Stream<List<int>> body, Map<String, Object> context,
122 OnHijackCallback onHijack})
76 : this.requestedUri = requestedUri, 123 : this.requestedUri = requestedUri,
77 this.protocolVersion = protocolVersion == null ? 124 this.protocolVersion = protocolVersion == null ?
78 '1.1' : protocolVersion, 125 '1.1' : protocolVersion,
79 this.url = _computeUrl(requestedUri, url, scriptName), 126 this.url = _computeUrl(requestedUri, url, scriptName),
80 this.scriptName = _computeScriptName(requestedUri, url, scriptName), 127 this.scriptName = _computeScriptName(requestedUri, url, scriptName),
128 this._onHijack = onHijack == null ? null : new _OnHijack(onHijack),
81 super(body == null ? new Stream.fromIterable([]) : body, 129 super(body == null ? new Stream.fromIterable([]) : body,
82 headers: headers, context: context) { 130 headers: headers, context: context) {
83 if (method.isEmpty) throw new ArgumentError('method cannot be empty.'); 131 if (method.isEmpty) throw new ArgumentError('method cannot be empty.');
84 132
85 if (!requestedUri.isAbsolute) { 133 if (!requestedUri.isAbsolute) {
86 throw new ArgumentError('requstedUri must be an absolute URI.'); 134 throw new ArgumentError('requstedUri must be an absolute URI.');
87 } 135 }
88 136
89 if (this.scriptName.isNotEmpty && !this.scriptName.startsWith('/')) { 137 if (this.scriptName.isNotEmpty && !this.scriptName.startsWith('/')) {
90 throw new ArgumentError('scriptName must be empty or start with "/".'); 138 throw new ArgumentError('scriptName must be empty or start with "/".');
(...skipping 30 matching lines...) Expand all
121 /// All other context and header values from the [Request] will be included 169 /// All other context and header values from the [Request] will be included
122 /// in the copied [Request] unchanged. 170 /// in the copied [Request] unchanged.
123 Request change({Map<String, String> headers, Map<String, Object> context}) { 171 Request change({Map<String, String> headers, Map<String, Object> context}) {
124 headers = updateMap(this.headers, headers); 172 headers = updateMap(this.headers, headers);
125 context = updateMap(this.context, context); 173 context = updateMap(this.context, context);
126 174
127 return new Request(this.method, this.requestedUri, 175 return new Request(this.method, this.requestedUri,
128 protocolVersion: this.protocolVersion, headers: headers, url: this.url, 176 protocolVersion: this.protocolVersion, headers: headers, url: this.url,
129 scriptName: this.scriptName, body: this.read(), context: context); 177 scriptName: this.scriptName, body: this.read(), context: context);
130 } 178 }
179
180 /// Takes control of the underlying request socket.
181 ///
182 /// Synchronously, this throws a [HijackException] that indicates to the
183 /// adapter that it shouldn't emit a response itself. Asynchronously,
184 /// [callback] is called with a [Stream<List<int>>] and
185 /// [StreamSink<List<int>>], respectively, that provide access to the
186 /// underlying request socket.
187 ///
188 /// If the sink is closed, the stream will be closed as well. The stream and
189 /// sink may be the same object, as in the case of a `dart:io` `Socket`
190 /// object.
191 ///
192 /// This may only be called when using a Shelf adapter that supports
193 /// hijacking, such as the `dart:io` adapter. In addition, a given request may
194 /// only be hijacked once. [canHijack] can be used to detect whether this
195 /// request can be hijacked.
196 void hijack(HijackCallback callback) {
197 if (_onHijack == null) {
198 throw new StateError("This request can't be hijacked.");
199 }
200
201 _onHijack.run(callback);
202 throw const HijackException();
203 }
204 }
205
206 /// A class containing a callback for [Request.hijack] that also tracks whether
207 /// the callback has been called.
208 class _OnHijack {
209 /// The callback.
210 final OnHijackCallback _callback;
211
212 /// Whether [this] has been called.
213 bool called = false;
214
215 _OnHijack(this._callback);
216
217 /// Calls [this].
218 ///
219 /// Throws a [StateError] if [this] has already been called.
220 void run(HijackCallback callback) {
221 if (called) throw new StateError("This request has already been hijacked.");
222 called = true;
223 newFuture(() => _callback(callback));
224 }
131 } 225 }
132 226
133 /// Computes `url` from the provided [Request] constructor arguments. 227 /// Computes `url` from the provided [Request] constructor arguments.
134 /// 228 ///
135 /// If [url] and [scriptName] are `null`, infer value from [requestedUrl], 229 /// If [url] and [scriptName] are `null`, infer value from [requestedUrl],
136 /// otherwise return [url]. 230 /// otherwise return [url].
137 /// 231 ///
138 /// If [url] is provided, but [scriptName] is omitted, throws an 232 /// If [url] is provided, but [scriptName] is omitted, throws an
139 /// [ArgumentError]. 233 /// [ArgumentError].
140 Uri _computeUrl(Uri requestedUri, Uri url, String scriptName) { 234 Uri _computeUrl(Uri requestedUri, Uri url, String scriptName) {
(...skipping 23 matching lines...) Expand all
164 return ''; 258 return '';
165 } 259 }
166 260
167 if (url != null && scriptName != null) { 261 if (url != null && scriptName != null) {
168 return scriptName; 262 return scriptName;
169 } 263 }
170 264
171 throw new ArgumentError( 265 throw new ArgumentError(
172 'url and scriptName must both be null or both be set.'); 266 'url and scriptName must both be null or both be set.');
173 } 267 }
OLDNEW
« 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