Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 * A [VirtualDirectory] can serve files and directory-listing from a root path, | 8 * A [VirtualDirectory] can serve files and directory-listing from a root path, |
| 9 * to [HttpRequest]s. | 9 * to [HttpRequest]s. |
| 10 * | 10 * |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 39 * Serve a [Stream] of [HttpRequest]s, in this [VirtualDirectory]. | 39 * Serve a [Stream] of [HttpRequest]s, in this [VirtualDirectory]. |
| 40 */ | 40 */ |
| 41 void serve(Stream<HttpRequest> requests); | 41 void serve(Stream<HttpRequest> requests); |
| 42 | 42 |
| 43 /** | 43 /** |
| 44 * Serve a single [HttpRequest], in this [VirtualDirectory]. | 44 * Serve a single [HttpRequest], in this [VirtualDirectory]. |
| 45 */ | 45 */ |
| 46 void serveRequest(HttpRequest request); | 46 void serveRequest(HttpRequest request); |
| 47 | 47 |
| 48 /** | 48 /** |
| 49 * Set the [callback] to override the default directory listing. The | |
| 50 * [callback] will be called with the [Directory] to be listed and the | |
| 51 * [HttpRequest]. | |
| 52 */ | |
| 53 void setDirectoryHandler(void callback(Directory dir, HttpRequest request)); | |
| 54 | |
| 55 /** | |
| 49 * Set the [callback] to override the error page handler. When [callback] is | 56 * Set the [callback] to override the error page handler. When [callback] is |
| 50 * invoked, the `statusCode` property of the response is set. | 57 * invoked, the `statusCode` property of the response is set. |
| 51 */ | 58 */ |
| 52 void setErrorPageHandler(void callback(HttpRequest request)); | 59 void setErrorPageHandler(void callback(HttpRequest request)); |
| 53 } | 60 } |
| 54 | 61 |
| 55 class _VirtualDirectory implements VirtualDirectory { | 62 class _VirtualDirectory implements VirtualDirectory { |
| 56 final String root; | 63 final String root; |
| 57 | 64 |
| 58 bool allowDirectoryListing = false; | 65 bool allowDirectoryListing = false; |
| 59 bool followLinks = true; | 66 bool followLinks = true; |
| 60 | 67 |
| 61 Function _errorCallback; | 68 Function _errorCallback; |
| 69 Function _dirCallback; | |
| 62 | 70 |
| 63 _VirtualDirectory(this.root); | 71 _VirtualDirectory(this.root); |
| 64 | 72 |
| 65 void serve(Stream<HttpRequest> requests) { | 73 void serve(Stream<HttpRequest> requests) { |
| 66 requests.listen(serveRequest); | 74 requests.listen(serveRequest); |
| 67 } | 75 } |
| 68 | 76 |
| 69 void serveRequest(HttpRequest request) { | 77 void serveRequest(HttpRequest request) { |
| 70 var path = new Path(request.uri.path).canonicalize(); | 78 var path = new Path(request.uri.path).canonicalize(); |
| 71 | 79 |
| 72 if (!path.isAbsolute) { | 80 if (!path.isAbsolute) { |
| 73 return _serveErrorPage(HttpStatus.NOT_FOUND, request); | 81 return _serveErrorPage(HttpStatus.NOT_FOUND, request); |
| 74 } | 82 } |
| 75 | 83 |
| 76 _locateResource(new Path('.'), path.segments()) | 84 _locateResource(new Path('.'), path.segments()) |
| 77 .then((entity) { | 85 .then((entity) { |
| 78 if (entity == null) { | 86 if (entity == null) { |
| 79 _serveErrorPage(HttpStatus.NOT_FOUND, request); | 87 _serveErrorPage(HttpStatus.NOT_FOUND, request); |
| 80 return; | 88 return; |
| 81 } | 89 } |
| 82 if (entity is File) { | 90 if (entity is File) { |
| 83 _serveFile(entity, request); | 91 _serveFile(entity, request); |
| 92 } else if (entity is Directory) { | |
| 93 _serveDirectory(entity, request); | |
| 84 } else { | 94 } else { |
| 85 _serveErrorPage(HttpStatus.NOT_FOUND, request); | 95 _serveErrorPage(HttpStatus.NOT_FOUND, request); |
| 86 } | 96 } |
| 87 }); | 97 }); |
| 88 } | 98 } |
| 89 | 99 |
| 100 void setDirectoryHandler(void callback(Directory dir, HttpRequest request)) { | |
| 101 _dirCallback = callback; | |
| 102 } | |
| 103 | |
| 90 void setErrorPageHandler(void callback(HttpRequest request)) { | 104 void setErrorPageHandler(void callback(HttpRequest request)) { |
| 91 _errorCallback = callback; | 105 _errorCallback = callback; |
| 92 } | 106 } |
| 93 | 107 |
| 94 Future<FileSystemEntity> _locateResource(Path path, | 108 Future<FileSystemEntity> _locateResource(Path path, |
| 95 Iterable<String> segments) { | 109 Iterable<String> segments) { |
| 96 Path fullPath() => new Path(root).join(path); | 110 Path fullPath() => new Path(root).join(path); |
| 97 return FileSystemEntity.type(fullPath().toNativePath(), followLinks: false) | 111 return FileSystemEntity.type(fullPath().toNativePath(), followLinks: false) |
| 98 .then((type) { | 112 .then((type) { |
| 99 switch (type) { | 113 switch (type) { |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 112 } | 126 } |
| 113 break; | 127 break; |
| 114 | 128 |
| 115 case FileSystemEntityType.LINK: | 129 case FileSystemEntityType.LINK: |
| 116 if (followLinks) { | 130 if (followLinks) { |
| 117 return new Link.fromPath(fullPath()).target() | 131 return new Link.fromPath(fullPath()).target() |
| 118 .then((target) { | 132 .then((target) { |
| 119 var targetPath = new Path(target).canonicalize(); | 133 var targetPath = new Path(target).canonicalize(); |
| 120 if (targetPath.isAbsolute) return null; | 134 if (targetPath.isAbsolute) return null; |
| 121 targetPath = path.directoryPath.join(targetPath) | 135 targetPath = path.directoryPath.join(targetPath) |
| 122 .canonicalize(); | 136 .canonicalize(); |
|
Bill Hesse
2013/06/28 12:42:26
Nit: Break line at = ?
Anders Johnsen
2013/06/28 12:49:36
Done.
| |
| 123 if (targetPath.segments().isEmpty || | 137 if (targetPath.segments().isEmpty || |
| 124 targetPath.segments().first == '..') return null; | 138 targetPath.segments().first == '..') return null; |
| 139 if (segments.isEmpty) { | |
| 140 return _locateResource(targetPath, []); | |
| 141 } | |
| 125 return _locateResource(targetPath.append(segments.first), | 142 return _locateResource(targetPath.append(segments.first), |
| 126 segments.skip(1)); | 143 segments.skip(1)); |
| 127 }); | 144 }); |
| 128 } | 145 } |
| 129 break; | 146 break; |
| 130 } | 147 } |
| 131 // Return `null` on fall-through, to indicate NOT_FOUND. | 148 // Return `null` on fall-through, to indicate NOT_FOUND. |
| 132 return null; | 149 return null; |
| 133 }); | 150 }); |
| 134 } | 151 } |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 190 | 207 |
| 191 file.openRead() | 208 file.openRead() |
| 192 .pipe(new _VirtualDirectoryFileStream(response, file.path)) | 209 .pipe(new _VirtualDirectoryFileStream(response, file.path)) |
| 193 .catchError((_) {}); | 210 .catchError((_) {}); |
| 194 }); | 211 }); |
| 195 }).catchError((_) { | 212 }).catchError((_) { |
| 196 response.close(); | 213 response.close(); |
| 197 }); | 214 }); |
| 198 } | 215 } |
| 199 | 216 |
| 217 void _serveDirectory(Directory dir, HttpRequest request) { | |
| 218 if (_dirCallback != null) { | |
| 219 _dirCallback(dir, request); | |
| 220 return; | |
| 221 } | |
| 222 var response = request.response; | |
| 223 dir.stat().then((stats) { | |
| 224 if (request.headers.ifModifiedSince != null && | |
| 225 !stats.modified.isAfter(request.headers.ifModifiedSince)) { | |
| 226 response.statusCode = HttpStatus.NOT_MODIFIED; | |
| 227 response.close(); | |
| 228 return; | |
| 229 } | |
| 230 | |
| 231 response.headers.set(HttpHeaders.LAST_MODIFIED, stats.modified); | |
| 232 var path = request.uri.path; | |
| 233 var header = | |
| 234 '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | |
| 235 http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
| 236 <html xmlns="http://www.w3.org/1999/xhtml"> | |
| 237 <head> | |
| 238 <title>Index of $path</title> | |
| 239 </head> | |
| 240 <body> | |
| 241 <h1>Index of $path</h1> | |
| 242 <table> | |
| 243 <tr> | |
| 244 <td>Name</td> | |
| 245 <td>Last modified</td> | |
| 246 <td>Size</td> | |
| 247 </tr> | |
| 248 '''; | |
| 249 var server = response.headers.value(HttpHeaders.SERVER); | |
| 250 if (server == null) server = ""; | |
| 251 var footer = | |
| 252 '''</table> | |
| 253 $server | |
| 254 </body> | |
| 255 </html> | |
| 256 '''; | |
| 257 | |
| 258 response.write(header); | |
| 259 | |
| 260 void add(String name, String modified, var size) { | |
| 261 if (size == null) size = "-"; | |
| 262 if (modified == null) modified = ""; | |
| 263 var p = new Path(path).append(name).canonicalize().toString(); | |
| 264 var entry = | |
| 265 ''' <tr> | |
| 266 <td><a href="$p">$name</a></td> | |
| 267 <td>$modified</td> | |
| 268 <td style="text-align: right">$size</td> | |
| 269 </tr>'''; | |
| 270 response.write(entry); | |
| 271 } | |
| 272 | |
| 273 if (path != '/') { | |
| 274 add('../', null, null); | |
| 275 } | |
| 276 | |
| 277 dir.list(followLinks: true).listen((entity) { | |
| 278 // TODO(ajohnsen): Consider async dir listing. | |
| 279 if (entity is File) { | |
| 280 add(new Path(entity.path).filename, | |
| 281 entity.statSync().modified.toString(), | |
| 282 entity.lengthSync()); | |
|
Bill Hesse
2013/06/28 12:42:26
length is part of stat, so you should just call st
Anders Johnsen
2013/06/28 12:49:36
Done.
| |
| 283 } else if (entity is Directory) { | |
| 284 add(new Path(entity.path).filename + '/', | |
| 285 entity.statSync().modified.toString(), | |
| 286 null); | |
| 287 } | |
| 288 }, onError: (e) { | |
| 289 }, onDone: () { | |
| 290 response.write(footer); | |
| 291 response.close(); | |
| 292 }); | |
| 293 }, onError: (e) => response.close()); | |
| 294 } | |
| 295 | |
| 200 void _serveErrorPage(int error, HttpRequest request) { | 296 void _serveErrorPage(int error, HttpRequest request) { |
| 201 var response = request.response; | 297 var response = request.response; |
| 202 response.statusCode = error; | 298 response.statusCode = error; |
| 203 if (_errorCallback != null) { | 299 if (_errorCallback != null) { |
| 204 _errorCallback(request); | 300 _errorCallback(request); |
| 205 return; | 301 return; |
| 206 } | 302 } |
| 207 // Default error page. | 303 // Default error page. |
| 208 var path = request.uri.path; | 304 var path = request.uri.path; |
| 209 var reason = response.reasonPhrase; | 305 var reason = response.reasonPhrase; |
| 210 response.write( | 306 |
| 211 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n'); | |
| 212 response.writeln( | |
| 213 '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'); | |
| 214 response.writeln('<html xmlns="http://www.w3.org/1999/xhtml">'); | |
| 215 response.writeln('<head>'); | |
| 216 response.writeln('<title>$reason: $path</title>'); | |
| 217 response.writeln('</head>'); | |
| 218 response.writeln('<body>'); | |
| 219 response.writeln('<h1>Error $error at \'$path\': $reason</h1>'); | |
| 220 var server = response.headers.value(HttpHeaders.SERVER); | 307 var server = response.headers.value(HttpHeaders.SERVER); |
| 221 if (server != null) { | 308 if (server == null) server = ""; |
| 222 response.writeln(server); | 309 var page = |
| 223 } | 310 '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
| 224 response.writeln('</body>'); | 311 http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
| 225 response.writeln('</html>'); | 312 <html xmlns="http://www.w3.org/1999/xhtml"> |
| 313 <head> | |
| 314 <title>$reason: $path</title> | |
| 315 </head> | |
| 316 <body> | |
| 317 <h1>Error $error at \'$path\': $reason</h1> | |
| 318 $server | |
| 319 </body> | |
| 320 </html>'''; | |
| 321 response.write(page); | |
| 226 response.close(); | 322 response.close(); |
| 227 } | 323 } |
| 228 } | 324 } |
| 229 | 325 |
| 230 class _VirtualDirectoryFileStream extends StreamConsumer<List<int>> { | 326 class _VirtualDirectoryFileStream extends StreamConsumer<List<int>> { |
| 231 final HttpResponse response; | 327 final HttpResponse response; |
| 232 final String path; | 328 final String path; |
| 233 var buffer = []; | 329 var buffer = []; |
| 234 | 330 |
| 235 _VirtualDirectoryFileStream(HttpResponse this.response, String this.path); | 331 _VirtualDirectoryFileStream(HttpResponse this.response, String this.path); |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 275 | 371 |
| 276 Future close() => new Future.value(); | 372 Future close() => new Future.value(); |
| 277 | 373 |
| 278 void setMimeType(var bytes) { | 374 void setMimeType(var bytes) { |
| 279 var mimeType = lookupMimeType(path, headerBytes: bytes); | 375 var mimeType = lookupMimeType(path, headerBytes: bytes); |
| 280 if (mimeType != null) { | 376 if (mimeType != null) { |
| 281 response.headers.contentType = ContentType.parse(mimeType); | 377 response.headers.contentType = ContentType.parse(mimeType); |
| 282 } | 378 } |
| 283 } | 379 } |
| 284 } | 380 } |
| OLD | NEW |