| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:collection'; |
| import 'dart:io'; |
| |
| import 'log/log.dart'; |
| import 'page/log_page.dart'; |
| import 'page/stats_page.dart'; |
| |
| /// An exception that is thrown when a request is received that cannot be |
| /// handled. |
| class UnknownRequest implements Exception {} |
| |
| /// A simple web server. |
| class WebServer { |
| /// The path to the page containing a single page from the instrumentation |
| /// log. |
| static final String logPath = '/log'; |
| |
| /// The path to the page containing statistics about the instrumentation log. |
| static final String statsPath = '/stats'; |
| |
| /// The content type for HTML responses. |
| static final ContentType _htmlContent = |
| ContentType('text', 'html', charset: 'utf-8'); |
| |
| /// The instrumentation log being served up. |
| final InstrumentationLog log; |
| |
| /// Future that is completed with the HTTP server once it is running. |
| late Future<HttpServer> _server; |
| |
| /// Initialize a newly created server. |
| WebServer(this.log); |
| |
| Map<String, String> getParameterMap(HttpRequest request) { |
| Map<String, String> parameterMap = HashMap<String, String>(); |
| var query = request.uri.query; |
| if (query.isNotEmpty) { |
| var pairs = query.split('&'); |
| for (var pair in pairs) { |
| var parts = pair.split('='); |
| var value = parts[1].trim(); |
| value = value.replaceAll('+', ' '); |
| parameterMap[parts[0].trim()] = value; |
| } |
| } |
| return parameterMap; |
| } |
| |
| /// Return a table mapping the names of properties to the values of those |
| /// properties that is extracted from the given HTTP [request]. |
| Future<Map<String, String>> getValueMap(HttpRequest request) async { |
| var buffer = StringBuffer(); |
| await request.forEach((List<int> element) { |
| for (var code in element) { |
| buffer.writeCharCode(code); |
| } |
| }); |
| Map<String, String> valueMap = HashMap<String, String>(); |
| var parameters = buffer.toString(); |
| if (parameters.isNotEmpty) { |
| var pairs = parameters.split('&'); |
| for (var pair in pairs) { |
| var parts = pair.split('='); |
| var value = parts[1].trim(); |
| value = value.replaceAll('+', ' '); |
| valueMap[parts[0].trim()] = value; |
| } |
| } |
| return valueMap; |
| } |
| |
| /// Begin serving HTTP requests over the given [port]. |
| void serveHttp(int port) { |
| _server = HttpServer.bind(InternetAddress.loopbackIPv4, port); |
| _server.then(_handleServer).catchError((_) { |
| /* Ignore errors. */ |
| }); |
| } |
| |
| /// Handle a GET [request] received by the HTTP server. |
| void _handleGetRequest(HttpRequest request) { |
| var buffer = StringBuffer(); |
| try { |
| var path = request.uri.path; |
| if (path == logPath) { |
| _writeLogPage(request, buffer); |
| } else if (path == statsPath) { |
| _writeStatsPage(request, buffer); |
| } else { |
| _returnUnknownRequest(request); |
| return; |
| } |
| } on UnknownRequest { |
| _returnUnknownRequest(request); |
| return; |
| } catch (exception, stackTrace) { |
| var response = request.response; |
| response.statusCode = HttpStatus.ok; |
| response.headers.contentType = _htmlContent; |
| var buffer = StringBuffer(); |
| buffer.write('<p><b>Exception while composing page:</b></p>'); |
| buffer.write('<p>$exception</p>'); |
| buffer.write('<p>'); |
| _writeStackTrace(buffer, stackTrace); |
| buffer.write('</p>'); |
| response.write(buffer.toString()); |
| response.close(); |
| return; |
| } |
| |
| var response = request.response; |
| response.statusCode = HttpStatus.ok; |
| response.headers.contentType = _htmlContent; |
| response.write(buffer.toString()); |
| response.close(); |
| } |
| |
| /// Handle a POST [request] received by the HTTP server. |
| Future<void> _handlePostRequest(HttpRequest request) async { |
| _returnUnknownRequest(request); |
| } |
| |
| /// Attach a listener to a newly created HTTP server. |
| void _handleServer(HttpServer httpServer) { |
| httpServer.listen((HttpRequest request) { |
| var method = request.method; |
| if (method == 'GET') { |
| _handleGetRequest(request); |
| } else if (method == 'POST') { |
| _handlePostRequest(request); |
| } else { |
| _returnUnknownRequest(request); |
| } |
| }); |
| } |
| |
| /// Return an error in response to an unrecognized request received by the |
| /// HTTP server. |
| void _returnUnknownRequest(HttpRequest request) { |
| var response = request.response; |
| response.statusCode = HttpStatus.notFound; |
| response.headers.contentType = |
| ContentType('text', 'html', charset: 'utf-8'); |
| response.write( |
| '<html><head></head><body><h3>Page not found: "${request.uri.path}".</h3></body></html>'); |
| response.close(); |
| } |
| |
| void _writeLogPage(HttpRequest request, StringBuffer buffer) { |
| var parameterMap = getParameterMap(request); |
| var groupId = parameterMap['group']; |
| var startIndex = parameterMap['start']; |
| var page = LogPage(log); |
| page.selectedGroup = EntryGroup.withId(groupId ?? 'nonTask')!; |
| if (startIndex != null) { |
| page.pageStart = int.parse(startIndex); |
| } else { |
| page.pageStart = 0; |
| } |
| page.pageLength = 25; |
| page.writePage(buffer); |
| } |
| |
| /// Write a representation of the given [stackTrace] to the given [sink]. |
| void _writeStackTrace(StringSink sink, StackTrace stackTrace) { |
| var trace = stackTrace.toString().replaceAll('#', '<br>#'); |
| if (trace.startsWith('<br>#')) { |
| trace = trace.substring(4); |
| } |
| sink.write('<p>'); |
| sink.write(trace); |
| sink.write('</p>'); |
| } |
| |
| void _writeStatsPage(HttpRequest request, StringBuffer buffer) { |
| StatsPage(log).writePage(buffer); |
| } |
| } |