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 | 8 |
9 import 'package:http_parser/http_parser.dart'; | 9 import 'package:http_parser/http_parser.dart'; |
10 | 10 |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
127 this.scriptName = _computeScriptName(requestedUri, url, scriptName), | 127 this.scriptName = _computeScriptName(requestedUri, url, scriptName), |
128 this._onHijack = onHijack == null ? null : new _OnHijack(onHijack), | 128 this._onHijack = onHijack == null ? null : new _OnHijack(onHijack), |
129 super(body == null ? new Stream.fromIterable([]) : body, | 129 super(body == null ? new Stream.fromIterable([]) : body, |
130 headers: headers, context: context) { | 130 headers: headers, context: context) { |
131 if (method.isEmpty) throw new ArgumentError('method cannot be empty.'); | 131 if (method.isEmpty) throw new ArgumentError('method cannot be empty.'); |
132 | 132 |
133 if (!requestedUri.isAbsolute) { | 133 if (!requestedUri.isAbsolute) { |
134 throw new ArgumentError('requstedUri must be an absolute URI.'); | 134 throw new ArgumentError('requstedUri must be an absolute URI.'); |
135 } | 135 } |
136 | 136 |
| 137 // TODO(kevmoo) if defined, check that scriptName is a fully-encoded, valid |
| 138 // path component |
137 if (this.scriptName.isNotEmpty && !this.scriptName.startsWith('/')) { | 139 if (this.scriptName.isNotEmpty && !this.scriptName.startsWith('/')) { |
138 throw new ArgumentError('scriptName must be empty or start with "/".'); | 140 throw new ArgumentError('scriptName must be empty or start with "/".'); |
139 } | 141 } |
140 | 142 |
141 if (this.scriptName == '/') { | 143 if (this.scriptName == '/') { |
142 throw new ArgumentError( | 144 throw new ArgumentError( |
143 'scriptName can never be "/". It should be empty instead.'); | 145 'scriptName can never be "/". It should be empty instead.'); |
144 } | 146 } |
145 | 147 |
146 if (this.scriptName.endsWith('/')) { | 148 if (this.scriptName.endsWith('/')) { |
(...skipping 14 matching lines...) Expand all Loading... |
161 /// | 163 /// |
162 /// New key-value pairs in [context] and [headers] will be added to the copied | 164 /// New key-value pairs in [context] and [headers] will be added to the copied |
163 /// [Request]. | 165 /// [Request]. |
164 /// | 166 /// |
165 /// If [context] or [headers] includes a key that already exists, the | 167 /// If [context] or [headers] includes a key that already exists, the |
166 /// key-value pair will replace the corresponding entry in the copied | 168 /// key-value pair will replace the corresponding entry in the copied |
167 /// [Request]. | 169 /// [Request]. |
168 /// | 170 /// |
169 /// All other context and header values from the [Request] will be included | 171 /// All other context and header values from the [Request] will be included |
170 /// in the copied [Request] unchanged. | 172 /// in the copied [Request] unchanged. |
171 Request change({Map<String, String> headers, Map<String, Object> context}) { | 173 /// |
| 174 /// If [scriptName] is provided and [url] is not, [scriptName] must be a |
| 175 /// prefix of [this.url]. [url] will default to [this.url] with this prefix |
| 176 /// removed. Useful for routing middleware that sends requests to an inner |
| 177 /// [Handler]. |
| 178 Request change({Map<String, String> headers, Map<String, Object> context, |
| 179 String scriptName, Uri url}) { |
172 headers = updateMap(this.headers, headers); | 180 headers = updateMap(this.headers, headers); |
173 context = updateMap(this.context, context); | 181 context = updateMap(this.context, context); |
174 | 182 |
| 183 if (scriptName != null && url == null) { |
| 184 var path = this.url.path; |
| 185 if (path.startsWith(scriptName)) { |
| 186 path = path.substring(scriptName.length); |
| 187 url = new Uri(path: path, query: this.url.query); |
| 188 } else { |
| 189 throw new ArgumentError('If scriptName is provided without url, it must' |
| 190 ' be a prefix of the existing url path.'); |
| 191 } |
| 192 } |
| 193 |
| 194 if (url == null) url = this.url; |
| 195 if (scriptName == null) scriptName = this.scriptName; |
| 196 |
175 return new Request(this.method, this.requestedUri, | 197 return new Request(this.method, this.requestedUri, |
176 protocolVersion: this.protocolVersion, headers: headers, url: this.url, | 198 protocolVersion: this.protocolVersion, headers: headers, url: url, |
177 scriptName: this.scriptName, body: this.read(), context: context); | 199 scriptName: scriptName, body: this.read(), context: context); |
178 } | 200 } |
179 | 201 |
180 /// Takes control of the underlying request socket. | 202 /// Takes control of the underlying request socket. |
181 /// | 203 /// |
182 /// Synchronously, this throws a [HijackException] that indicates to the | 204 /// Synchronously, this throws a [HijackException] that indicates to the |
183 /// adapter that it shouldn't emit a response itself. Asynchronously, | 205 /// adapter that it shouldn't emit a response itself. Asynchronously, |
184 /// [callback] is called with a [Stream<List<int>>] and | 206 /// [callback] is called with a [Stream<List<int>>] and |
185 /// [StreamSink<List<int>>], respectively, that provide access to the | 207 /// [StreamSink<List<int>>], respectively, that provide access to the |
186 /// underlying request socket. | 208 /// underlying request socket. |
187 /// | 209 /// |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
226 | 248 |
227 /// Computes `url` from the provided [Request] constructor arguments. | 249 /// Computes `url` from the provided [Request] constructor arguments. |
228 /// | 250 /// |
229 /// If [url] and [scriptName] are `null`, infer value from [requestedUrl], | 251 /// If [url] and [scriptName] are `null`, infer value from [requestedUrl], |
230 /// otherwise return [url]. | 252 /// otherwise return [url]. |
231 /// | 253 /// |
232 /// If [url] is provided, but [scriptName] is omitted, throws an | 254 /// If [url] is provided, but [scriptName] is omitted, throws an |
233 /// [ArgumentError]. | 255 /// [ArgumentError]. |
234 Uri _computeUrl(Uri requestedUri, Uri url, String scriptName) { | 256 Uri _computeUrl(Uri requestedUri, Uri url, String scriptName) { |
235 if (url == null && scriptName == null) { | 257 if (url == null && scriptName == null) { |
236 return new Uri(path: requestedUri.path, query: requestedUri.query, | 258 return new Uri(path: requestedUri.path, query: requestedUri.query); |
237 fragment: requestedUri.fragment); | |
238 } | 259 } |
239 | 260 |
240 if (url != null && scriptName != null) { | 261 if (url != null && scriptName != null) { |
241 if (url.scheme.isNotEmpty) throw new ArgumentError('url must be relative.'); | 262 if (url.scheme.isNotEmpty) throw new ArgumentError('url must be relative.'); |
242 return url; | 263 return url; |
243 } | 264 } |
244 | 265 |
245 throw new ArgumentError( | 266 throw new ArgumentError( |
246 'url and scriptName must both be null or both be set.'); | 267 'url and scriptName must both be null or both be set.'); |
247 } | 268 } |
(...skipping 10 matching lines...) Expand all Loading... |
258 return ''; | 279 return ''; |
259 } | 280 } |
260 | 281 |
261 if (url != null && scriptName != null) { | 282 if (url != null && scriptName != null) { |
262 return scriptName; | 283 return scriptName; |
263 } | 284 } |
264 | 285 |
265 throw new ArgumentError( | 286 throw new ArgumentError( |
266 'url and scriptName must both be null or both be set.'); | 287 'url and scriptName must both be null or both be set.'); |
267 } | 288 } |
OLD | NEW |