blob: bfeb7cc924df8cc8a729494eb9c605fd6fa20a7e [file] [log] [blame]
// 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:async';
import 'dart:collection';
import 'dart:io';
import 'log/log.dart';
import 'page/log_page.dart';
import 'page/stats_page.dart';
import 'page/task_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 path to the page containing statistics about the instrumentation log.
*/
static final String taskPath = '/task';
/**
* The content type for HTML responses.
*/
static final ContentType _htmlContent =
new 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.
*/
Future<HttpServer> _server;
/**
* Initialize a newly created server.
*/
WebServer(this.log);
Map<String, String> getParameterMap(HttpRequest request) {
Map<String, String> parameterMap = new HashMap<String, String>();
String query = request.uri.query;
if (query != null && query.isNotEmpty) {
List<String> pairs = query.split('&');
for (String pair in pairs) {
List<String> parts = pair.split('=');
String 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 {
StringBuffer buffer = new StringBuffer();
await request.forEach((List<int> element) {
for (int code in element) {
buffer.writeCharCode(code);
}
});
Map<String, String> valueMap = new HashMap<String, String>();
String parameters = buffer.toString();
if (parameters.isNotEmpty) {
List<String> pairs = parameters.split('&');
for (String pair in pairs) {
List<String> parts = pair.split('=');
String 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) {
StringBuffer buffer = new StringBuffer();
try {
String path = request.uri.path;
if (path == logPath) {
_writeLogPage(request, buffer);
} else if (path == statsPath) {
_writeStatsPage(request, buffer);
} else if (path == taskPath) {
_writeTaskPage(request, buffer);
} else {
_returnUnknownRequest(request);
return;
}
} on UnknownRequest {
_returnUnknownRequest(request);
return;
} catch (exception, stackTrace) {
HttpResponse response = request.response;
response.statusCode = HttpStatus.ok;
response.headers.contentType = _htmlContent;
StringBuffer buffer = new 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;
}
HttpResponse 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) {
String 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) {
HttpResponse response = request.response;
response.statusCode = HttpStatus.notFound;
response.headers.contentType =
new 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) {
Map<String, String> parameterMap = getParameterMap(request);
String groupId = parameterMap['group'];
String startIndex = parameterMap['start'];
LogPage page = new 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) {
if (stackTrace != null) {
String 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) {
new StatsPage(log).writePage(buffer);
}
void _writeTaskPage(HttpRequest request, StringBuffer buffer) {
Map<String, String> parameterMap = getParameterMap(request);
String analysisStart = parameterMap['analysisStart'];
String start = parameterMap['start'];
TaskPage page = new TaskPage(log);
if (analysisStart == null) {
throw new UnknownRequest();
}
page.analysisStart = int.parse(analysisStart);
if (start != null) {
page.pageStart = int.parse(start);
} else {
page.pageStart = 0;
}
page.pageLength = 25;
page.writePage(buffer);
}
}