fbf331e0dd
Change-Id: I42b4a44fd6a2197e499e8623274b3cd1a4b5556f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/394003 Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Bob Nystrom <rnystrom@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
193 lines
5.8 KiB
Dart
193 lines
5.8 KiB
Dart
// 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.
|
|
WebServer(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);
|
|
}
|
|
}
|