| // 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; | |
| /// The number of log entries to show on a result page. | |
| final int pageLength; | |
| /// Initialize a newly created server. | |
| new(this.log, {required this.pageLength}); | |
| 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 = pageLength; | |
| 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); | |
| } | |
| } |