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

Side by Side Diff: mojo/public/dart/third_party/shelf/lib/src/request.dart

Issue 1346773002: Stop running pub get at gclient sync time and fix build bugs (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 3 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
OLDNEW
(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 library shelf.request;
6
7 import 'dart:async';
8 import 'dart:convert';
9
10 import 'package:http_parser/http_parser.dart';
11
12 import 'hijack_exception.dart';
13 import 'message.dart';
14 import 'util.dart';
15
16 /// A callback provided by a Shelf handler that's passed to [Request.hijack].
17 typedef void HijackCallback(
18 Stream<List<int>> stream, StreamSink<List<int>> sink);
19
20 /// A callback provided by a Shelf adapter that's used by [Request.hijack] to
21 /// provide a [HijackCallback] with a socket.
22 typedef void OnHijackCallback(HijackCallback callback);
23
24 /// Represents an HTTP request to be processed by a Shelf application.
25 class Request extends Message {
26 /// The URL path from the current handler to the requested resource, relative
27 /// to [handlerPath], plus any query parameters.
28 ///
29 /// This should be used by handlers for determining which resource to serve,
30 /// in preference to [requestedUri]. This allows handlers to do the right
31 /// thing when they're mounted anywhere in the application. Routers should be
32 /// sure to update this when dispatching to a nested handler, using the
33 /// `path` parameter to [change].
34 ///
35 /// [url]'s path is always relative. It may be empty, if [requestedUri] ends
36 /// at this handler. [url] will always have the same query parameters as
37 /// [requestedUri].
38 ///
39 /// [handlerPath] and [url]'s path combine to create [requestedUri]'s path.
40 final Uri url;
41
42 /// The HTTP request method, such as "GET" or "POST".
43 final String method;
44
45 /// The URL path to the current handler.
46 ///
47 /// This allows a handler to know its location within the URL-space of an
48 /// application. Routers should be sure to update this when dispatching to a
49 /// nested handler, using the `path` parameter to [change].
50 ///
51 /// [handlerPath] is always a root-relative URL path; that is, it always
52 /// starts with `/`. It will also end with `/` whenever [url]'s path is
53 /// non-empty, or if [requestUri]'s path ends with `/`.
54 ///
55 /// [handlerPath] and [url]'s path combine to create [requestedUri]'s path.
56 final String handlerPath;
57
58 /// The HTTP protocol version used in the request, either "1.0" or "1.1".
59 final String protocolVersion;
60
61 /// The original [Uri] for the request.
62 final Uri requestedUri;
63
64 /// The callback wrapper for hijacking this request.
65 ///
66 /// This will be `null` if this request can't be hijacked.
67 final _OnHijack _onHijack;
68
69 /// Whether this request can be hijacked.
70 ///
71 /// This will be `false` either if the adapter doesn't support hijacking, or
72 /// if the request has already been hijacked.
73 bool get canHijack => _onHijack != null && !_onHijack.called;
74
75 /// If this is non-`null` and the requested resource hasn't been modified
76 /// since this date and time, the server should return a 304 Not Modified
77 /// response.
78 ///
79 /// This is parsed from the If-Modified-Since header in [headers]. If
80 /// [headers] doesn't have an If-Modified-Since header, this will be `null`.
81 DateTime get ifModifiedSince {
82 if (_ifModifiedSinceCache != null) return _ifModifiedSinceCache;
83 if (!headers.containsKey('if-modified-since')) return null;
84 _ifModifiedSinceCache = parseHttpDate(headers['if-modified-since']);
85 return _ifModifiedSinceCache;
86 }
87 DateTime _ifModifiedSinceCache;
88
89 /// Creates a new [Request].
90 ///
91 /// [handlerPath] must be root-relative. [url]'s path must be fully relative,
92 /// and it must have the same query parameters as [requestedUri].
93 /// [handlerPath] and [url]'s path must combine to be the path component of
94 /// [requestedUri]. If they're not passed, [handlerPath] will default to `/`
95 /// and [url] to `requestedUri.path` without the initial `/`. If only one is
96 /// passed, the other will be inferred.
97 ///
98 /// [body] is the request body. It may be either a [String], a
99 /// [Stream<List<int>>], or `null` to indicate no body.
100 /// If it's a [String], [encoding] is used to encode it to a
101 /// [Stream<List<int>>]. The default encoding is UTF-8.
102 ///
103 /// If [encoding] is passed, the "encoding" field of the Content-Type header
104 /// in [headers] will be set appropriately. If there is no existing
105 /// Content-Type header, it will be set to "application/octet-stream".
106 ///
107 /// The default value for [protocolVersion] is '1.1'.
108 ///
109 /// ## `onHijack`
110 ///
111 /// [onHijack] allows handlers to take control of the underlying socket for
112 /// the request. It should be passed by adapters that can provide access to
113 /// the bidirectional socket underlying the HTTP connection stream.
114 ///
115 /// The [onHijack] callback will only be called once per request. It will be
116 /// passed another callback which takes a byte stream and a byte sink.
117 /// [onHijack] must pass the stream and sink for the connection stream to this
118 /// callback, although it may do so asynchronously. Both parameters may be the
119 /// same object. If the user closes the sink, the adapter should ensure that
120 /// the stream is closed as well.
121 ///
122 /// If a request is hijacked, the adapter should expect to receive a
123 /// [HijackException] from the handler. This is a special exception used to
124 /// indicate that hijacking has occurred. The adapter should avoid either
125 /// sending a response or notifying the user of an error if a
126 /// [HijackException] is caught.
127 ///
128 /// An adapter can check whether a request was hijacked using [canHijack],
129 /// which will be `false` for a hijacked request. The adapter may throw an
130 /// error if a [HijackException] is received for a non-hijacked request, or if
131 /// no [HijackException] is received for a hijacked request.
132 ///
133 /// See also [hijack].
134 // TODO(kevmoo) finish documenting the rest of the arguments.
135 Request(String method, Uri requestedUri, {String protocolVersion,
136 Map<String, String> headers, String handlerPath, Uri url, body,
137 Encoding encoding, Map<String, Object> context,
138 OnHijackCallback onHijack})
139 : this._(method, requestedUri,
140 protocolVersion: protocolVersion,
141 headers: headers,
142 url: url,
143 handlerPath: handlerPath,
144 body: body,
145 encoding: encoding,
146 context: context,
147 onHijack: onHijack == null ? null : new _OnHijack(onHijack));
148
149 /// This constructor has the same signature as [new Request] except that
150 /// accepts [onHijack] as [_OnHijack].
151 ///
152 /// Any [Request] created by calling [change] will pass [_onHijack] from the
153 /// source [Request] to ensure that [hijack] can only be called once, even
154 /// from a changed [Request].
155 Request._(this.method, Uri requestedUri, {String protocolVersion,
156 Map<String, String> headers, String handlerPath, Uri url, body,
157 Encoding encoding, Map<String, Object> context, _OnHijack onHijack})
158 : this.requestedUri = requestedUri,
159 this.protocolVersion = protocolVersion == null
160 ? '1.1'
161 : protocolVersion,
162 this.url = _computeUrl(requestedUri, handlerPath, url),
163 this.handlerPath = _computeHandlerPath(requestedUri, handlerPath, url),
164 this._onHijack = onHijack,
165 super(body, encoding: encoding, headers: headers, context: context) {
166 if (method.isEmpty) throw new ArgumentError('method cannot be empty.');
167
168 if (!requestedUri.isAbsolute) {
169 throw new ArgumentError(
170 'requestedUri "$requestedUri" must be an absolute URL.');
171 }
172
173 if (requestedUri.fragment.isNotEmpty) {
174 throw new ArgumentError(
175 'requestedUri "$requestedUri" may not have a fragment.');
176 }
177
178 if (this.handlerPath + this.url.path != this.requestedUri.path) {
179 throw new ArgumentError('handlerPath "$handlerPath" and url "$url" must '
180 'combine to equal requestedUri path "${requestedUri.path}".');
181 }
182 }
183
184 /// Creates a new [Request] by copying existing values and applying specified
185 /// changes.
186 ///
187 /// New key-value pairs in [context] and [headers] will be added to the copied
188 /// [Request]. If [context] or [headers] includes a key that already exists,
189 /// the key-value pair will replace the corresponding entry in the copied
190 /// [Request]. All other context and header values from the [Request] will be
191 /// included in the copied [Request] unchanged.
192 ///
193 /// [body] is the request body. It may be either a [String] or a
194 /// [Stream<List<int>>].
195 ///
196 /// [path] is used to update both [handlerPath] and [url]. It's designed for
197 /// routing middleware, and represents the path from the current handler to
198 /// the next handler. It must be a prefix of [url]; [handlerPath] becomes
199 /// `handlerPath + "/" + path`, and [url] becomes relative to that. For
200 /// example:
201 ///
202 /// print(request.handlerPath); // => /static/
203 /// print(request.url); // => dir/file.html
204 ///
205 /// request = request.change(path: "dir");
206 /// print(request.handlerPath); // => /static/dir/
207 /// print(request.url); // => file.html
208 Request change({Map<String, String> headers, Map<String, Object> context,
209 String path, body}) {
210 headers = updateMap(this.headers, headers);
211 context = updateMap(this.context, context);
212
213 if (body == null) body = getBody(this);
214
215 var handlerPath = this.handlerPath;
216 if (path != null) handlerPath += path;
217
218 return new Request._(this.method, this.requestedUri,
219 protocolVersion: this.protocolVersion,
220 headers: headers,
221 handlerPath: handlerPath,
222 body: body,
223 context: context,
224 onHijack: _onHijack);
225 }
226
227 /// Takes control of the underlying request socket.
228 ///
229 /// Synchronously, this throws a [HijackException] that indicates to the
230 /// adapter that it shouldn't emit a response itself. Asynchronously,
231 /// [callback] is called with a [Stream<List<int>>] and
232 /// [StreamSink<List<int>>], respectively, that provide access to the
233 /// underlying request socket.
234 ///
235 /// If the sink is closed, the stream will be closed as well. The stream and
236 /// sink may be the same object, as in the case of a `dart:io` `Socket`
237 /// object.
238 ///
239 /// This may only be called when using a Shelf adapter that supports
240 /// hijacking, such as the `dart:io` adapter. In addition, a given request may
241 /// only be hijacked once. [canHijack] can be used to detect whether this
242 /// request can be hijacked.
243 void hijack(HijackCallback callback) {
244 if (_onHijack == null) {
245 throw new StateError("This request can't be hijacked.");
246 }
247
248 _onHijack.run(callback);
249 throw const HijackException();
250 }
251 }
252
253 /// A class containing a callback for [Request.hijack] that also tracks whether
254 /// the callback has been called.
255 class _OnHijack {
256 /// The callback.
257 final OnHijackCallback _callback;
258
259 /// Whether [this] has been called.
260 bool called = false;
261
262 _OnHijack(this._callback);
263
264 /// Calls [this].
265 ///
266 /// Throws a [StateError] if [this] has already been called.
267 void run(HijackCallback callback) {
268 if (called) throw new StateError("This request has already been hijacked.");
269 called = true;
270 newFuture(() => _callback(callback));
271 }
272 }
273
274 /// Computes `url` from the provided [Request] constructor arguments.
275 ///
276 /// If [url] is `null`, the value is inferred from [requestedUrl] and
277 /// [handlerPath] if available. Otherwise [url] is returned.
278 Uri _computeUrl(Uri requestedUri, String handlerPath, Uri url) {
279 if (handlerPath != null &&
280 handlerPath != requestedUri.path &&
281 !handlerPath.endsWith("/")) {
282 handlerPath += "/";
283 }
284
285 if (url != null) {
286 if (url.scheme.isNotEmpty || url.hasAuthority || url.fragment.isNotEmpty) {
287 throw new ArgumentError('url "$url" may contain only a path and query '
288 'parameters.');
289 }
290
291 if (!requestedUri.path.endsWith(url.path)) {
292 throw new ArgumentError('url "$url" must be a suffix of requestedUri '
293 '"$requestedUri".');
294 }
295
296 if (requestedUri.query != url.query) {
297 throw new ArgumentError('url "$url" must have the same query parameters '
298 'as requestedUri "$requestedUri".');
299 }
300
301 if (url.path.startsWith('/')) {
302 throw new ArgumentError('url "$url" must be relative.');
303 }
304
305 var startOfUrl = requestedUri.path.length - url.path.length;
306 if (url.path.isNotEmpty &&
307 requestedUri.path.substring(startOfUrl - 1, startOfUrl) != '/') {
308 throw new ArgumentError('url "$url" must be on a path boundary in '
309 'requestedUri "$requestedUri".');
310 }
311
312 return url;
313 } else if (handlerPath != null) {
314 return new Uri(
315 path: requestedUri.path.substring(handlerPath.length),
316 query: requestedUri.query);
317 } else {
318 // Skip the initial "/".
319 var path = requestedUri.path.substring(1);
320 return new Uri(path: path, query: requestedUri.query);
321 }
322 }
323
324 /// Computes `handlerPath` from the provided [Request] constructor arguments.
325 ///
326 /// If [handlerPath] is `null`, the value is inferred from [requestedUrl] and
327 /// [url] if available. Otherwise [handlerPath] is returned.
328 String _computeHandlerPath(Uri requestedUri, String handlerPath, Uri url) {
329 if (handlerPath != null &&
330 handlerPath != requestedUri.path &&
331 !handlerPath.endsWith("/")) {
332 handlerPath += "/";
333 }
334
335 if (handlerPath != null) {
336 if (!requestedUri.path.startsWith(handlerPath)) {
337 throw new ArgumentError('handlerPath "$handlerPath" must be a prefix of '
338 'requestedUri path "${requestedUri.path}"');
339 }
340
341 if (!handlerPath.startsWith('/')) {
342 throw new ArgumentError(
343 'handlerPath "$handlerPath" must be root-relative.');
344 }
345
346 return handlerPath;
347 } else if (url != null) {
348 if (url.path.isEmpty) return requestedUri.path;
349
350 var index = requestedUri.path.indexOf(url.path);
351 return requestedUri.path.substring(0, index);
352 } else {
353 return '/';
354 }
355 }
OLDNEW
« no previous file with comments | « mojo/public/dart/third_party/shelf/lib/src/pipeline.dart ('k') | mojo/public/dart/third_party/shelf/lib/src/response.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698