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