| OLD | NEW |
| 1 // Copyright (c) 2012, 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 library http_server; | 5 library http_server; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:io'; | 8 import 'dart:io'; |
| 9 import 'dart:isolate'; | 9 import 'dart:isolate'; |
| 10 import 'dart:uri'; | 10 import 'dart:uri'; |
| 11 import 'test_suite.dart'; // For TestUtils. | 11 import 'test_suite.dart'; // For TestUtils. |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 94 int get crossOriginPort => _serverList[1].port; | 94 int get crossOriginPort => _serverList[1].port; |
| 95 | 95 |
| 96 /** | 96 /** |
| 97 * [startServers] will start two Http servers. | 97 * [startServers] will start two Http servers. |
| 98 * The first server listens on [port] and sets | 98 * The first server listens on [port] and sets |
| 99 * "Access-Control-Allow-Origin: *" | 99 * "Access-Control-Allow-Origin: *" |
| 100 * The second server listens on [crossOriginPort] and sets | 100 * The second server listens on [crossOriginPort] and sets |
| 101 * "Access-Control-Allow-Origin: client:port1 | 101 * "Access-Control-Allow-Origin: client:port1 |
| 102 * "Access-Control-Allow-Credentials: true" | 102 * "Access-Control-Allow-Credentials: true" |
| 103 */ | 103 */ |
| 104 void startServers(String host, {int port: 0, int crossOriginPort: 0}) { | 104 Future startServers(String host, {int port: 0, int crossOriginPort: 0}) { |
| 105 _startHttpServer(host, port: port); | 105 return _startHttpServer(host, port: port).then((_) { |
| 106 _startHttpServer(host, | 106 _startHttpServer(host, |
| 107 port: crossOriginPort, | 107 port: crossOriginPort, |
| 108 allowedPort:_serverList[0].port); | 108 allowedPort:_serverList[0].port); |
| 109 }); |
| 109 } | 110 } |
| 110 | 111 |
| 111 String httpServerCommandline() { | 112 String httpServerCommandline() { |
| 112 var dart = TestUtils.dartTestExecutable.toNativePath(); | 113 var dart = TestUtils.dartTestExecutable.toNativePath(); |
| 113 var dartDir = TestUtils.dartDir(); | 114 var dartDir = TestUtils.dartDir(); |
| 114 var script = dartDir.join(new Path("tools/testing/dart/http_server.dart")); | 115 var script = dartDir.join(new Path("tools/testing/dart/http_server.dart")); |
| 115 var buildDirectory = _buildDirectory.toNativePath(); | 116 var buildDirectory = _buildDirectory.toNativePath(); |
| 116 var csp = useContentSecurityPolicy ? '--csp ' : ''; | 117 var csp = useContentSecurityPolicy ? '--csp ' : ''; |
| 117 | 118 |
| 118 return '$dart $script -p $port -c $crossOriginPort $csp' | 119 return '$dart $script -p $port -c $crossOriginPort $csp' |
| 119 '--build-directory=$buildDirectory'; | 120 '--build-directory=$buildDirectory'; |
| 120 } | 121 } |
| 121 | 122 |
| 122 void stopServers() { | 123 void stopServers() { |
| 123 for (var server in _serverList) { | 124 for (var server in _serverList) { |
| 124 server.close(); | 125 server.close(); |
| 125 } | 126 } |
| 126 } | 127 } |
| 127 | 128 |
| 128 void _startHttpServer(String host, {int port: 0, int allowedPort: -1}) { | 129 Future _startHttpServer(String host, {int port: 0, int allowedPort: -1}) { |
| 129 var httpServer = new HttpServer(); | 130 return HttpServer.bind(host, port).then((HttpServer httpServer) { |
| 130 httpServer.onError = (e) { | 131 httpServer.listen((HttpRequest request) { |
| 131 DebugLogger.error('HttpServer: an error occured: $e'); | 132 if (request.uri.path == "/echo") { |
| 132 }; | 133 _handleEchoRequest(request, request.response); |
| 133 httpServer.defaultRequestHandler = (request, response) { | 134 } else { |
| 134 _handleFileOrDirectoryRequest(request, response, allowedPort); | 135 _handleFileOrDirectoryRequest( |
| 135 }; | 136 request, request.response, allowedPort); |
| 136 httpServer.addRequestHandler( | 137 } |
| 137 (req) => req.path == "/echo", _handleEchoRequest); | 138 }, |
| 138 | 139 onError: (e) { |
| 139 httpServer.listen(host, port); | 140 DebugLogger.error('HttpServer: an error occured: $e'); |
| 140 _serverList.add(httpServer); | 141 }); |
| 142 _serverList.add(httpServer); |
| 143 }); |
| 141 } | 144 } |
| 142 | 145 |
| 143 void _handleFileOrDirectoryRequest(HttpRequest request, | 146 void _handleFileOrDirectoryRequest(HttpRequest request, |
| 144 HttpResponse response, | 147 HttpResponse response, |
| 145 int allowedPort) { | 148 int allowedPort) { |
| 146 var path = _getFilePathFromRequestPath(request.path); | 149 var path = _getFilePathFromRequestPath(request.uri.path); |
| 147 if (path != null) { | 150 if (path != null) { |
| 148 var file = new File.fromPath(path); | 151 var file = new File.fromPath(path); |
| 149 file.exists().then((exists) { | 152 file.exists().then((exists) { |
| 150 if (exists) { | 153 if (exists) { |
| 151 _sendFileContent(request, response, allowedPort, path, file); | 154 _sendFileContent(request, response, allowedPort, path, file); |
| 152 } else { | 155 } else { |
| 153 var directory = new Directory.fromPath(path); | 156 var directory = new Directory.fromPath(path); |
| 154 directory.exists().then((exists) { | 157 directory.exists().then((exists) { |
| 155 if (exists) { | 158 if (exists) { |
| 156 _listDirectory(directory).then((entries) { | 159 _listDirectory(directory).then((entries) { |
| 157 _sendDirectoryListing(entries, request, response); | 160 _sendDirectoryListing(entries, request, response); |
| 158 }); | 161 }); |
| 159 } else { | 162 } else { |
| 160 _sendNotFound(request, response); | 163 _sendNotFound(request, response); |
| 161 } | 164 } |
| 162 }); | 165 }); |
| 163 } | 166 } |
| 164 }); | 167 }); |
| 165 } else { | 168 } else { |
| 166 if (request.path == '/') { | 169 if (request.uri.path == '/') { |
| 167 var entries = [new _Entry('root_dart', 'root_dart/'), | 170 var entries = [new _Entry('root_dart', 'root_dart/'), |
| 168 new _Entry('root_build', 'root_build/'), | 171 new _Entry('root_build', 'root_build/'), |
| 169 new _Entry('echo', 'echo')]; | 172 new _Entry('echo', 'echo')]; |
| 170 _sendDirectoryListing(entries, request, response); | 173 _sendDirectoryListing(entries, request, response); |
| 171 } else { | 174 } else { |
| 172 _sendNotFound(request, response); | 175 _sendNotFound(request, response); |
| 173 } | 176 } |
| 174 } | 177 } |
| 175 } | 178 } |
| 176 | 179 |
| 177 void _handleEchoRequest(HttpRequest request, HttpResponse response) { | 180 void _handleEchoRequest(HttpRequest request, HttpResponse response) { |
| 178 response.headers.set("Access-Control-Allow-Origin", "*"); | 181 response.headers.set("Access-Control-Allow-Origin", "*"); |
| 179 request.inputStream.pipe(response.outputStream); | 182 request.pipe(response); |
| 183 response.done.catchError((e) { |
| 184 DebugLogger.warning( |
| 185 'HttpServer: error while closing the response stream: $e'); |
| 186 }); |
| 180 } | 187 } |
| 181 | 188 |
| 182 Path _getFilePathFromRequestPath(String urlRequestPath) { | 189 Path _getFilePathFromRequestPath(String urlRequestPath) { |
| 183 // Go to the top of the file to see an explanation of the URL path scheme. | 190 // Go to the top of the file to see an explanation of the URL path scheme. |
| 184 var requestPath = new Path(urlRequestPath.substring(1)).canonicalize(); | 191 var requestPath = new Path(urlRequestPath.substring(1)).canonicalize(); |
| 185 var pathSegments = requestPath.segments(); | 192 var pathSegments = requestPath.segments(); |
| 186 if (pathSegments.length > 0) { | 193 if (pathSegments.length > 0) { |
| 187 var basePath; | 194 var basePath; |
| 188 var relativePath; | 195 var relativePath; |
| 189 if (pathSegments[0] == PREFIX_BUILDDIR) { | 196 if (pathSegments[0] == PREFIX_BUILDDIR) { |
| (...skipping 18 matching lines...) Expand all Loading... |
| 208 return basePath.join(relativePath); | 215 return basePath.join(relativePath); |
| 209 } | 216 } |
| 210 } | 217 } |
| 211 return null; | 218 return null; |
| 212 } | 219 } |
| 213 | 220 |
| 214 Future<List<_Entry>> _listDirectory(Directory directory) { | 221 Future<List<_Entry>> _listDirectory(Directory directory) { |
| 215 var completer = new Completer(); | 222 var completer = new Completer(); |
| 216 var entries = []; | 223 var entries = []; |
| 217 | 224 |
| 218 directory.list() | 225 directory.list().listen( |
| 219 ..onFile = (filepath) { | 226 (FileSystemEntity fse) { |
| 220 var filename = new Path(filepath).filename; | 227 var filename = new Path(fse.path).filename; |
| 221 entries.add(new _Entry(filename, filename)); | 228 if (fse is File) { |
| 222 } | 229 entries.add(new _Entry(filename, filename)); |
| 223 ..onDir = (dirpath) { | 230 } else if (fse is Directory) { |
| 224 var filename = new Path(dirpath).filename; | 231 entries.add(new _Entry(filename, '$filename/')); |
| 225 entries.add(new _Entry(filename, '$filename/')); | 232 } |
| 226 } | 233 }, |
| 227 ..onDone = (_) { | 234 onDone: () { |
| 228 completer.complete(entries); | 235 completer.complete(entries); |
| 229 }; | 236 }); |
| 230 return completer.future; | 237 return completer.future; |
| 231 } | 238 } |
| 232 | 239 |
| 233 void _sendDirectoryListing(List<_Entry> entries, | 240 void _sendDirectoryListing(List<_Entry> entries, |
| 234 HttpRequest request, | 241 HttpRequest request, |
| 235 HttpResponse response) { | 242 HttpResponse response) { |
| 236 response.headers.set('Content-Type', 'text/html'); | 243 response.headers.set('Content-Type', 'text/html'); |
| 237 var header = '''<!DOCTYPE html> | 244 var header = '''<!DOCTYPE html> |
| 238 <html> | 245 <html> |
| 239 <head> | 246 <head> |
| 240 <title>${request.path}</title> | 247 <title>${request.uri.path}</title> |
| 241 </head> | 248 </head> |
| 242 <body> | 249 <body> |
| 243 <code> | 250 <code> |
| 244 <div>${request.path}</div> | 251 <div>${request.uri.path}</div> |
| 245 <hr/> | 252 <hr/> |
| 246 <ul>'''; | 253 <ul>'''; |
| 247 var footer = ''' | 254 var footer = ''' |
| 248 </ul> | 255 </ul> |
| 249 </code> | 256 </code> |
| 250 </body> | 257 </body> |
| 251 </html>'''; | 258 </html>'''; |
| 252 | 259 |
| 253 | 260 |
| 254 entries.sort(); | 261 entries.sort(); |
| 255 response.outputStream.writeString(header); | 262 response.write(header); |
| 256 for (var entry in entries) { | 263 for (var entry in entries) { |
| 257 response.outputStream.writeString( | 264 response.write( |
| 258 '<li><a href="${new Path(request.path).append(entry.name)}">' | 265 '<li><a href="${new Path(request.uri.path).append(entry.name)}">' |
| 259 '${entry.displayName}</a></li>'); | 266 '${entry.displayName}</a></li>'); |
| 260 } | 267 } |
| 261 response.outputStream.writeString(footer); | 268 response.write(footer); |
| 262 response.outputStream.close(); | 269 response.close(); |
| 270 response.done.catchError((e) { |
| 271 DebugLogger.warning( |
| 272 'HttpServer: error while closing the response stream: $e'); |
| 273 }); |
| 263 } | 274 } |
| 264 | 275 |
| 265 void _sendFileContent(HttpRequest request, | 276 void _sendFileContent(HttpRequest request, |
| 266 HttpResponse response, | 277 HttpResponse response, |
| 267 int allowedPort, | 278 int allowedPort, |
| 268 Path path, | 279 Path path, |
| 269 File file) { | 280 File file) { |
| 270 if (allowedPort != -1) { | 281 if (allowedPort != -1) { |
| 271 var origin = new Uri(request.headers.value('Origin')); | 282 var origin = new Uri(request.headers.value('Origin')); |
| 272 // Allow loading from http://*:$allowedPort in browsers. | 283 // Allow loading from http://*:$allowedPort in browsers. |
| (...skipping 16 matching lines...) Expand all Loading... |
| 289 response.headers.set(header, "script-src 'self'; object-src 'self'"); | 300 response.headers.set(header, "script-src 'self'; object-src 'self'"); |
| 290 } | 301 } |
| 291 } | 302 } |
| 292 if (path.filename.endsWith('.html')) { | 303 if (path.filename.endsWith('.html')) { |
| 293 response.headers.set('Content-Type', 'text/html'); | 304 response.headers.set('Content-Type', 'text/html'); |
| 294 } else if (path.filename.endsWith('.js')) { | 305 } else if (path.filename.endsWith('.js')) { |
| 295 response.headers.set('Content-Type', 'application/javascript'); | 306 response.headers.set('Content-Type', 'application/javascript'); |
| 296 } else if (path.filename.endsWith('.dart')) { | 307 } else if (path.filename.endsWith('.dart')) { |
| 297 response.headers.set('Content-Type', 'application/dart'); | 308 response.headers.set('Content-Type', 'application/dart'); |
| 298 } | 309 } |
| 299 file.openInputStream().pipe(response.outputStream); | 310 file.openRead().pipe(response); |
| 311 response.done.catchError((e) { |
| 312 DebugLogger.warning( |
| 313 'HttpServer: error while closing the response stream: $e'); |
| 314 }); |
| 300 } | 315 } |
| 301 | 316 |
| 302 void _sendNotFound(HttpRequest request, HttpResponse response) { | 317 void _sendNotFound(HttpRequest request, HttpResponse response) { |
| 303 // NOTE: Since some tests deliberately try to access non-existent files. | 318 // NOTE: Since some tests deliberately try to access non-existent files. |
| 304 // We might want to remove this warning (otherwise it will show | 319 // We might want to remove this warning (otherwise it will show |
| 305 // up in the debug.log every time). | 320 // up in the debug.log every time). |
| 306 DebugLogger.warning('HttpServer: could not find file for request path: ' | 321 if (request.uri.path != "/favicon.ico") { |
| 307 '"${request.path}"'); | 322 DebugLogger.warning('HttpServer: could not find file for request path: ' |
| 323 '"${request.uri.path}"'); |
| 324 } |
| 308 response.statusCode = HttpStatus.NOT_FOUND; | 325 response.statusCode = HttpStatus.NOT_FOUND; |
| 309 try { | 326 response.close(); |
| 310 response.outputStream.close(); | 327 response.done.catchError((e) { |
| 311 } catch (e) { | 328 DebugLogger.warning( |
| 312 if (e is StreamException) { | 329 'HttpServer: error while closing the response stream: $e'); |
| 313 DebugLogger.warning('HttpServer: error while closing the response ' | 330 }); |
| 314 'stream: $e'); | |
| 315 } else { | |
| 316 throw e; | |
| 317 } | |
| 318 } | |
| 319 } | 331 } |
| 320 } | 332 } |
| 321 | 333 |
| 322 // Helper class for displaying directory listings. | 334 // Helper class for displaying directory listings. |
| 323 class _Entry { | 335 class _Entry { |
| 324 final String name; | 336 final String name; |
| 325 final String displayName; | 337 final String displayName; |
| 326 | 338 |
| 327 _Entry(this.name, this.displayName); | 339 _Entry(this.name, this.displayName); |
| 328 | 340 |
| 329 int compareTo(_Entry other) { | 341 int compareTo(_Entry other) { |
| 330 return name.compareTo(other.name); | 342 return name.compareTo(other.name); |
| 331 } | 343 } |
| 332 } | 344 } |
| OLD | NEW |