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

Side by Side Diff: pkg/http_server/lib/src/virtual_directory.dart

Issue 124833003: pkg/http_server: return future for VirtualDirectory serveRequest (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: docs and cleanup for mock logic in tests Created 6 years, 11 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 | « no previous file | pkg/http_server/test/http_mock.dart » ('j') | pkg/http_server/test/http_mock.dart » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, 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 part of http_server; 5 part of http_server;
6 6
7 7
8 // Used for signal a directory redirecting, where a tailing slash is missing. 8 // Used for signal a directory redirecting, where a tailing slash is missing.
9 class _DirectoryRedirect { 9 class _DirectoryRedirect {
10 const _DirectoryRedirect(); 10 const _DirectoryRedirect();
11 } 11 }
12 12
13 typedef dynamic _DirCallback(Directory dir, HttpRequest request);
14 typedef dynamic _ErrorCallback(HttpRequest request);
15
13 /** 16 /**
14 * A [VirtualDirectory] can serve files and directory-listing from a root path, 17 * A [VirtualDirectory] can serve files and directory-listing from a root path,
15 * to [HttpRequest]s. 18 * to [HttpRequest]s.
16 * 19 *
17 * The [VirtualDirectory] providing secure handling of request uris and 20 * The [VirtualDirectory] providing secure handling of request uris and
18 * file-system links, correct mime-types and custom error pages. 21 * file-system links, correct mime-types and custom error pages.
19 */ 22 */
20 class VirtualDirectory { 23 class VirtualDirectory {
21 final String root; 24 final String root;
22 25
(...skipping 10 matching lines...) Expand all
33 bool followLinks = true; 36 bool followLinks = true;
34 37
35 /** 38 /**
36 * Set or get if the [VirtualDirectory] should jail the root. When the root is 39 * Set or get if the [VirtualDirectory] should jail the root. When the root is
37 * not jailed, links can be followed to outside the [root] directory. 40 * not jailed, links can be followed to outside the [root] directory.
38 */ 41 */
39 bool jailRoot = true; 42 bool jailRoot = true;
40 43
41 final RegExp _invalidPathRegExp = new RegExp("[\\\/\x00]"); 44 final RegExp _invalidPathRegExp = new RegExp("[\\\/\x00]");
42 45
43 Function _errorCallback; 46 _ErrorCallback _errorCallback;
44 Function _dirCallback; 47 _DirCallback _dirCallback;
45 48
46 /* 49 /*
47 * Create a new [VirtualDirectory] for serving static file content of 50 * Create a new [VirtualDirectory] for serving static file content of
48 * the path [root]. 51 * the path [root].
49 * 52 *
50 * The [root] is not required to exist. If the [root] doesn't exist at time of 53 * The [root] is not required to exist. If the [root] doesn't exist at time of
51 * a request, a 404 is generated. 54 * a request, a 404 is generated.
52 */ 55 */
53 VirtualDirectory(this.root); 56 VirtualDirectory(this.root);
54 57
55 /** 58 /**
56 * Serve a [Stream] of [HttpRequest]s, in this [VirtualDirectory]. 59 * Serve a [Stream] of [HttpRequest]s, in this [VirtualDirectory].
57 */ 60 */
58 void serve(Stream<HttpRequest> requests) { 61 StreamSubscription<HttpRequest> serve(Stream<HttpRequest> requests) =>
59 requests.listen(serveRequest); 62 requests.listen(serveRequest);
60 }
61 63
62 /** 64 /**
63 * Serve a single [HttpRequest], in this [VirtualDirectory]. 65 * Serve a single [HttpRequest], in this [VirtualDirectory].
64 */ 66 */
65 void serveRequest(HttpRequest request) { 67 Future serveRequest(HttpRequest request) {
66 _locateResource('.', request.uri.pathSegments.iterator..moveNext()) 68 return _locateResource('.', request.uri.pathSegments.iterator..moveNext())
67 .then((entity) { 69 .then((entity) {
68 if (entity == null) {
69 _serveErrorPage(HttpStatus.NOT_FOUND, request);
70 return;
71 }
72 if (entity is File) { 70 if (entity is File) {
73 serveFile(entity, request); 71 serveFile(entity, request);
74 } else if (entity is Directory) { 72 } else if (entity is Directory) {
75 if (allowDirectoryListing) { 73 if (allowDirectoryListing) {
76 _serveDirectory(entity, request); 74 _serveDirectory(entity, request);
77 } else { 75 } else {
78 _serveErrorPage(HttpStatus.NOT_FOUND, request); 76 _serveErrorPage(HttpStatus.NOT_FOUND, request);
79 } 77 }
80 } else if (entity is _DirectoryRedirect) { 78 } else if (entity is _DirectoryRedirect) {
81 // TODO(ajohnsen): Use HttpRequest.requestedUri once 1.2 is out. 79 // TODO(ajohnsen): Use HttpRequest.requestedUri once 1.2 is out.
82 request.response.redirect(Uri.parse('${request.uri}/'), 80 request.response.redirect(Uri.parse('${request.uri}/'),
83 status: HttpStatus.MOVED_PERMANENTLY); 81 status: HttpStatus.MOVED_PERMANENTLY);
84 } else { 82 } else {
83 assert(entity == null);
85 _serveErrorPage(HttpStatus.NOT_FOUND, request); 84 _serveErrorPage(HttpStatus.NOT_FOUND, request);
86 } 85 }
86 return request.response.done;
87 }); 87 });
88 } 88 }
89 89
90 /** 90 /**
91 * Set the [callback] to override the default directory listing. The 91 * Set the [callback] to override the default directory listing. The
92 * [callback] will be called with the [Directory] to be listed and the 92 * [callback] will be called with the [Directory] to be listed and the
93 * [HttpRequest]. 93 * [HttpRequest].
94 */ 94 */
95 void set directoryHandler(void callback(Directory dir, HttpRequest request)) { 95 void set directoryHandler(void callback(Directory dir, HttpRequest request)) {
96 _dirCallback = callback; 96 _dirCallback = callback;
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
172 * is closed with error-code [HttpStatus.NOT_FOUND]. 172 * is closed with error-code [HttpStatus.NOT_FOUND].
173 */ 173 */
174 void serveFile(File file, HttpRequest request) { 174 void serveFile(File file, HttpRequest request) {
175 var response = request.response; 175 var response = request.response;
176 // TODO(ajohnsen): Set up Zone support for these errors. 176 // TODO(ajohnsen): Set up Zone support for these errors.
177 file.lastModified().then((lastModified) { 177 file.lastModified().then((lastModified) {
178 if (request.headers.ifModifiedSince != null && 178 if (request.headers.ifModifiedSince != null &&
179 !lastModified.isAfter(request.headers.ifModifiedSince)) { 179 !lastModified.isAfter(request.headers.ifModifiedSince)) {
180 response.statusCode = HttpStatus.NOT_MODIFIED; 180 response.statusCode = HttpStatus.NOT_MODIFIED;
181 response.close(); 181 response.close();
182 return; 182 return null;
183 } 183 }
184 184
185 response.headers.set(HttpHeaders.LAST_MODIFIED, lastModified); 185 response.headers.set(HttpHeaders.LAST_MODIFIED, lastModified);
186 response.headers.set(HttpHeaders.ACCEPT_RANGES, "bytes"); 186 response.headers.set(HttpHeaders.ACCEPT_RANGES, "bytes");
187 187
188 if (request.method == 'HEAD') { 188 if (request.method == 'HEAD') {
189 response.close(); 189 response.close();
190 return; 190 return null;
191 } 191 }
192 192
193 return file.length().then((length) { 193 return file.length().then((length) {
194 String range = request.headers.value("range"); 194 String range = request.headers.value("range");
195 if (range != null) { 195 if (range != null) {
196 // We only support one range, where the standard support several. 196 // We only support one range, where the standard support several.
197 Match matches = new RegExp(r"^bytes=(\d*)\-(\d*)$").firstMatch(range); 197 Match matches = new RegExp(r"^bytes=(\d*)\-(\d*)$").firstMatch(range);
198 // If the range header have the right format, handle it. 198 // If the range header have the right format, handle it.
199 if (matches != null) { 199 if (matches != null) {
200 // Serve sub-range. 200 // Serve sub-range.
(...skipping 13 matching lines...) Expand all
214 response.headers.set(HttpHeaders.CONTENT_LENGTH, end - start); 214 response.headers.set(HttpHeaders.CONTENT_LENGTH, end - start);
215 215
216 // Set 'Partial Content' status code. 216 // Set 'Partial Content' status code.
217 response.statusCode = HttpStatus.PARTIAL_CONTENT; 217 response.statusCode = HttpStatus.PARTIAL_CONTENT;
218 response.headers.set(HttpHeaders.CONTENT_RANGE, 218 response.headers.set(HttpHeaders.CONTENT_RANGE,
219 "bytes $start-${end - 1}/$length"); 219 "bytes $start-${end - 1}/$length");
220 220
221 // Pipe the 'range' of the file. 221 // Pipe the 'range' of the file.
222 file.openRead(start, end) 222 file.openRead(start, end)
223 .pipe(new _VirtualDirectoryFileStream(response, file.path)) 223 .pipe(new _VirtualDirectoryFileStream(response, file.path))
224 .catchError((_) {}); 224 .catchError((_) {
225 // TODO(kevmoo): log errors
226 });
225 return; 227 return;
226 } 228 }
227 } 229 }
228 230
229 file.openRead() 231 file.openRead()
230 .pipe(new _VirtualDirectoryFileStream(response, file.path)) 232 .pipe(new _VirtualDirectoryFileStream(response, file.path))
231 .catchError((_) {}); 233 .catchError((_) {
234 // TODO(kevmoo): log errors
235 });
232 }); 236 });
233 }).catchError((_) { 237 }).catchError((_) {
234 response.statusCode = HttpStatus.NOT_FOUND; 238 response.statusCode = HttpStatus.NOT_FOUND;
235 response.close(); 239 response.close();
236 }); 240 });
237 } 241 }
238 242
239 void _serveDirectory(Directory dir, HttpRequest request) { 243 void _serveDirectory(Directory dir, HttpRequest request) {
240 if (_dirCallback != null) { 244 if (_dirCallback != null) {
241 _dirCallback(dir, request); 245 _dirCallback(dir, request);
242 return; 246 return;
243 } 247 }
244 var response = request.response; 248 var response = request.response;
245 dir.stat().then((stats) { 249 dir.stat().then((stats) {
246 if (request.headers.ifModifiedSince != null && 250 if (request.headers.ifModifiedSince != null &&
247 !stats.modified.isAfter(request.headers.ifModifiedSince)) { 251 !stats.modified.isAfter(request.headers.ifModifiedSince)) {
248 response.statusCode = HttpStatus.NOT_MODIFIED; 252 response.statusCode = HttpStatus.NOT_MODIFIED;
249 response.close(); 253 return null;
250 return;
251 } 254 }
252 255
253 response.headers.set(HttpHeaders.LAST_MODIFIED, stats.modified); 256 response.headers.set(HttpHeaders.LAST_MODIFIED, stats.modified);
254 var path = request.uri.path; 257 var path = request.uri.path;
255 var header = 258 var header =
256 '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 259 '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
257 http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 260 http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
258 <html xmlns="http://www.w3.org/1999/xhtml"> 261 <html xmlns="http://www.w3.org/1999/xhtml">
259 <head> 262 <head>
260 <title>Index of $path</title> 263 <title>Index of $path</title>
(...skipping 28 matching lines...) Expand all
289 <td>$modified</td> 292 <td>$modified</td>
290 <td style="text-align: right">$size</td> 293 <td style="text-align: right">$size</td>
291 </tr>'''; 294 </tr>''';
292 response.write(entry); 295 response.write(entry);
293 } 296 }
294 297
295 if (path != '/') { 298 if (path != '/') {
296 add('../', null, null); 299 add('../', null, null);
297 } 300 }
298 301
299 dir.list(followLinks: true).listen((entity) { 302 return dir.list(followLinks: true)
300 // TODO(ajohnsen): Consider async dir listing. 303 .handleError((error) {
Anders Johnsen 2014/01/10 18:19:45 Why is this better than the old version. Its longe
kevmoo 2014/01/10 18:51:24 Went back to old impl...it was just personal prefe
Anders Johnsen 2014/01/10 18:53:35 Yep :)
301 if (entity is File) { 304 // TODO(kevmoo): log errors;
302 var stat = entity.statSync(); 305 })
303 add(basename(entity.path), 306 .forEach((entity) {
304 stat.modified.toString(), 307 // TODO(ajohnsen): Consider async dir listing.
305 stat.size); 308 if (entity is File) {
306 } else if (entity is Directory) { 309 var stat = entity.statSync();
307 add(basename(entity.path) + '/', 310 add(basename(entity.path),
308 entity.statSync().modified.toString(), 311 stat.modified.toString(),
309 null); 312 stat.size);
310 } 313 } else if (entity is Directory) {
311 }, onError: (e) { 314 add(basename(entity.path) + '/',
312 }, onDone: () { 315 entity.statSync().modified.toString(),
313 response.write(footer); 316 null);
314 response.close(); 317 }
315 }); 318 })
316 }, onError: (e) => response.close()); 319 .then((_) {
320 response.write(footer);
321 });
322 })
323 .catchError((error) {
324 // TODO(kevmoo): log errors
325 })
326 .whenComplete(() {
327 response.close();
328 });
317 } 329 }
318 330
319 void _serveErrorPage(int error, HttpRequest request) { 331 void _serveErrorPage(int error, HttpRequest request) {
320 var response = request.response; 332 var response = request.response;
321 response.statusCode = error; 333 response.statusCode = error;
322 if (_errorCallback != null) { 334 if (_errorCallback != null) {
323 _errorCallback(request); 335 _errorCallback(request);
324 return; 336 return;
325 } 337 }
326 // Default error page. 338 // Default error page.
(...skipping 15 matching lines...) Expand all
342 </body> 354 </body>
343 </html>'''; 355 </html>''';
344 response.write(page); 356 response.write(page);
345 response.close(); 357 response.close();
346 } 358 }
347 } 359 }
348 360
349 class _VirtualDirectoryFileStream extends StreamConsumer<List<int>> { 361 class _VirtualDirectoryFileStream extends StreamConsumer<List<int>> {
350 final HttpResponse response; 362 final HttpResponse response;
351 final String path; 363 final String path;
352 var buffer = []; 364 List<int> buffer = [];
353 365
354 _VirtualDirectoryFileStream(HttpResponse this.response, String this.path); 366 _VirtualDirectoryFileStream(HttpResponse this.response, String this.path);
355 367
356 Future addStream(Stream<List<int>> stream) { 368 Future addStream(Stream<List<int>> stream) {
357 stream.listen( 369 stream.listen(
358 (data) { 370 (data) {
359 if (buffer == null) { 371 if (buffer == null) {
360 response.add(data); 372 response.add(data);
361 return; 373 return;
362 } 374 }
(...skipping 24 matching lines...) Expand all
387 } 399 }
388 } 400 }
389 response.close(); 401 response.close();
390 }, 402 },
391 onError: response.addError); 403 onError: response.addError);
392 return response.done; 404 return response.done;
393 } 405 }
394 406
395 Future close() => new Future.value(); 407 Future close() => new Future.value();
396 408
397 void setMimeType(var bytes) { 409 void setMimeType(List<int> bytes) {
398 var mimeType = lookupMimeType(path, headerBytes: bytes); 410 var mimeType = lookupMimeType(path, headerBytes: bytes);
399 if (mimeType != null) { 411 if (mimeType != null) {
400 response.headers.contentType = ContentType.parse(mimeType); 412 response.headers.contentType = ContentType.parse(mimeType);
401 } 413 }
402 } 414 }
403 } 415 }
OLDNEW
« no previous file with comments | « no previous file | pkg/http_server/test/http_mock.dart » ('j') | pkg/http_server/test/http_mock.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698