Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 import 'dart:collection'; | |
| 9 | 8 |
| 10 // TODO(kevmoo): use UnmodifiableMapView from SDK once 1.4 ships | |
| 11 import 'package:collection/wrappers.dart' as pc; | |
| 12 import 'package:http_parser/http_parser.dart'; | 9 import 'package:http_parser/http_parser.dart'; |
| 13 import 'package:path/path.dart' as p; | 10 import 'package:path/path.dart' as p; |
| 14 | 11 |
| 15 import 'message.dart'; | 12 import 'message.dart'; |
| 16 | 13 |
| 17 /// Represents an HTTP request to be processed by a Shelf application. | 14 /// Represents an HTTP request to be processed by a Shelf application. |
| 18 class Request extends Message { | 15 class Request extends Message { |
| 19 /// The remainder of the [requestedUri] path designating the virtual | 16 /// The remainder of the [requestedUri] path and query designating the virtual |
| 20 /// "location" of the request's target within the handler. | 17 /// "location" of the request's target within the handler. |
| 21 /// | 18 /// |
| 22 /// [pathInfo] may be an empty string, if [requestedUri ]targets the handler | 19 /// [url] may be an empty, if [requestedUri]targets the handler |
| 23 /// root and does not have a trailing slash. | 20 /// root and does not have a trailing slash. |
| 24 /// | 21 /// |
| 25 /// [pathInfo] is never null. If it is not empty, it will start with `/`. | 22 /// [url] is never null. If it is not empty, it will start with `/`. |
| 26 /// | 23 /// |
| 27 /// [scriptName] and [pathInfo] combine to create a valid path that should | 24 /// [scriptName] and [url] combine to create a valid path that should |
| 28 /// correspond to the [requestedUri] path. | 25 /// correspond to the [requestedUri] path. |
| 29 final String pathInfo; | 26 final Uri url; |
| 30 | |
| 31 /// The portion of the request URL that follows the "?", if any. | |
| 32 final String queryString; | |
| 33 | 27 |
| 34 /// The HTTP request method, such as "GET" or "POST". | 28 /// The HTTP request method, such as "GET" or "POST". |
| 35 final String method; | 29 final String method; |
| 36 | 30 |
| 37 /// The initial portion of the [requestedUri] path that corresponds to the | 31 /// The initial portion of the [requestedUri] path that corresponds to the |
| 38 /// handler. | 32 /// handler. |
| 39 /// | 33 /// |
| 40 /// [scriptName] allows a handler to know its virtual "location". | 34 /// [scriptName] allows a handler to know its virtual "location". |
| 41 /// | 35 /// |
| 42 /// If the handler corresponds to the "root" of a server, it will be an | 36 /// If the handler corresponds to the "root" of a server, it will be an |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 59 /// This is parsed from the If-Modified-Since header in [headers]. If | 53 /// This is parsed from the If-Modified-Since header in [headers]. If |
| 60 /// [headers] doesn't have an If-Modified-Since header, this will be `null`. | 54 /// [headers] doesn't have an If-Modified-Since header, this will be `null`. |
| 61 DateTime get ifModifiedSince { | 55 DateTime get ifModifiedSince { |
| 62 if (_ifModifiedSinceCache != null) return _ifModifiedSinceCache; | 56 if (_ifModifiedSinceCache != null) return _ifModifiedSinceCache; |
| 63 if (!headers.containsKey('if-modified-since')) return null; | 57 if (!headers.containsKey('if-modified-since')) return null; |
| 64 _ifModifiedSinceCache = parseHttpDate(headers['if-modified-since']); | 58 _ifModifiedSinceCache = parseHttpDate(headers['if-modified-since']); |
| 65 return _ifModifiedSinceCache; | 59 return _ifModifiedSinceCache; |
| 66 } | 60 } |
| 67 DateTime _ifModifiedSinceCache; | 61 DateTime _ifModifiedSinceCache; |
| 68 | 62 |
| 69 Request(this.pathInfo, String queryString, this.method, | 63 /// Creates a new [Request]. |
| 70 this.scriptName, this.protocolVersion, this.requestedUri, | 64 /// |
| 71 Map<String, String> headers, {Stream<List<int>> body}) | 65 /// If [url] and [scriptName] are omitted, they are inferred from |
| 72 : this.queryString = queryString == null ? '' : queryString, | 66 /// [requestedUri]. |
| 73 super(new pc.UnmodifiableMapView(new HashMap.from(headers)), | 67 /// |
| 74 body == null ? new Stream.fromIterable([]) : body) { | 68 /// Setting one of [url] or [scriptName] and not the other will throw an |
| 69 /// [ArgumentError]. | |
| 70 /// | |
| 71 /// The default value for [protocolVersion] is '1.1'. | |
| 72 // TODO(kevmoo) finish documenting the rest of the arguments. | |
| 73 Request(this.method, Uri requestedUri, {String protocolVersion, | |
| 74 Map<String, String> headers, Uri url, String scriptName, | |
| 75 Stream<List<int>> body}) | |
| 76 : this.requestedUri = requestedUri, | |
| 77 this.protocolVersion = protocolVersion == null ? | |
| 78 '1.1' : protocolVersion, | |
| 79 this.url = _computeUrl(requestedUri, url, scriptName), | |
| 80 this.scriptName = _computeScriptName(requestedUri, url, scriptName), | |
| 81 super(body == null ? new Stream.fromIterable([]) : body, | |
| 82 headers: headers) { | |
| 75 if (method.isEmpty) throw new ArgumentError('method cannot be empty.'); | 83 if (method.isEmpty) throw new ArgumentError('method cannot be empty.'); |
| 76 | 84 |
| 77 if (scriptName.isNotEmpty && !scriptName.startsWith('/')) { | 85 // TODO(kevmoo) use isAbsolute property on Uri once Issue 18053 is fixed |
| 86 if (requestedUri.scheme.isEmpty) { | |
| 87 throw new ArgumentError('requstedUri must be an absolute URI.'); | |
| 88 } | |
| 89 | |
| 90 if (this.scriptName.isNotEmpty && !this.scriptName.startsWith('/')) { | |
| 78 throw new ArgumentError('scriptName must be empty or start with "/".'); | 91 throw new ArgumentError('scriptName must be empty or start with "/".'); |
| 79 } | 92 } |
| 80 | 93 |
| 81 if (scriptName == '/') { | 94 if (this.scriptName == '/') { |
| 82 throw new ArgumentError( | 95 throw new ArgumentError( |
| 83 'scriptName can never be "/". It should be empty instead.'); | 96 'scriptName can never be "/". It should be empty instead.'); |
| 84 } | 97 } |
| 85 | 98 |
| 86 if (scriptName.endsWith('/')) { | 99 if (this.scriptName.endsWith('/')) { |
| 87 throw new ArgumentError('scriptName must not end with "/".'); | 100 throw new ArgumentError('scriptName must not end with "/".'); |
| 88 } | 101 } |
| 89 | 102 |
| 90 if (pathInfo.isNotEmpty && !pathInfo.startsWith('/')) { | 103 if (this.url.path.isNotEmpty && !this.url.path.startsWith('/')) { |
| 91 throw new ArgumentError('pathInfo must be empty or start with "/".'); | 104 throw new ArgumentError('pathInfo must be empty or start with "/".'); |
|
nweiz
2014/04/08 22:52:44
"pathInfo" -> "url"
kevmoo
2014/04/08 23:23:03
Done.
| |
| 92 } | 105 } |
| 93 | 106 |
| 94 if (scriptName.isEmpty && pathInfo.isEmpty) { | 107 if (this.scriptName.isEmpty && this.url.path.isEmpty) { |
| 95 throw new ArgumentError('scriptName and pathInfo cannot both be empty.'); | 108 throw new ArgumentError('scriptName and pathInfo cannot both be empty.'); |
|
nweiz
2014/04/08 22:52:44
"pathInfo" -> "url"
kevmoo
2014/04/08 23:23:03
Done.
| |
| 96 } | 109 } |
| 97 } | 110 } |
| 111 } | |
| 98 | 112 |
| 99 /// Convenience property to access [pathInfo] data as a [List]. | 113 /// Computes `url` from the provided [Request] constructor arguments. |
| 100 List<String> get pathSegments { | 114 /// |
| 101 var segs = p.url.split(pathInfo); | 115 /// If [url] and [scriptName] are `null`, infer value from [requestedUrl], |
| 102 if (segs.length > 0) { | 116 /// otherwise return [url]. |
| 103 assert(segs.first == p.url.separator); | 117 /// |
| 104 segs.removeAt(0); | 118 /// If [url] is provided, but [scriptName] is omitted, throws an |
| 105 } | 119 /// [ArgumentError]. |
| 106 return segs; | 120 Uri _computeUrl(Uri requestedUri, Uri url, String scriptName) { |
| 121 if (url == null && scriptName == null) { | |
| 122 return new Uri(path: requestedUri.path, query: requestedUri.query, | |
| 123 fragment: requestedUri.fragment); | |
| 107 } | 124 } |
| 125 | |
| 126 if (url != null && scriptName != null) { | |
| 127 // TODO(kevmoo) use isAbsolute property on Uri once Issue 18053 is fixed | |
| 128 if (url.scheme.isNotEmpty) throw new ArgumentError('url must be relative.'); | |
| 129 return url; | |
| 130 } | |
| 131 | |
| 132 throw new ArgumentError( | |
| 133 'pathInfo and scriptName must both be null or both be set.'); | |
| 108 } | 134 } |
| 135 | |
| 136 /// Computes `scriptName` from the provided [Request] constructor arguments. | |
| 137 /// | |
| 138 /// If [url] and [scriptName] are `null` it returns an empty string, otherwise | |
| 139 /// [scriptName] is returned. | |
| 140 /// | |
| 141 /// If [script] is provided, but [url] is omitted, throws an | |
| 142 /// [ArgumentError]. | |
| 143 String _computeScriptName(Uri requstedUri, Uri pathInfo, String scriptName) { | |
| 144 if (pathInfo == null && scriptName == null) { | |
| 145 return ''; | |
| 146 } | |
| 147 | |
| 148 if (pathInfo != null && scriptName != null) { | |
| 149 return scriptName; | |
| 150 } | |
| 151 | |
| 152 throw new ArgumentError( | |
| 153 'pathInfo and scriptName must both be null or both be set.'); | |
| 154 } | |
| OLD | NEW |