// 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 _server; /// Initialize a newly created server. WebServer(this.log); Map getParameterMap(HttpRequest request) { Map parameterMap = HashMap(); 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> getValueMap(HttpRequest request) async { var buffer = StringBuffer(); await request.forEach((List element) { for (var code in element) { buffer.writeCharCode(code); } }); Map valueMap = HashMap(); 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('

Exception while composing page:

'); buffer.write('

$exception

'); buffer.write('

'); _writeStackTrace(buffer, stackTrace); buffer.write('

'); 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 _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( '

Page not found: "${request.uri.path}".

'); 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('#', '
#'); if (trace.startsWith('
#')) { trace = trace.substring(4); } sink.write('

'); sink.write(trace); sink.write('

'); } void _writeStatsPage(HttpRequest request, StringBuffer buffer) { StatsPage(log).writePage(buffer); } }