blob: cc68c34acb49e7d23c1459b1dfb8528e1ea89ff5 [file] [log] [blame]
// Copyright (c) 2014, 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:io';
import 'package:analysis_server/src/socket_server.dart';
import 'package:analysis_server/src/status/diagnostics.dart';
/// Instances of the class [AbstractHttpHandler] handle HTTP requests.
abstract class AbstractHttpHandler {
/// Handle a GET request received by the HTTP server.
void handleGetRequest(HttpRequest request);
/// Handle a POST request received by the HTTP server.
void handlePostRequest(HttpRequest request);
/// Handle a request to upgrade to a WebSocket.
void handleWebSocketRequest(HttpRequest request);
}
/// Instances of the class [HttpServer] implement a simple HTTP server. The
/// server:
///
/// - listens for an UPGRADE request in order to start an analysis server
/// - serves diagnostic information as html pages
class HttpAnalysisServer {
/// Number of lines of print output to capture.
static const int MAX_PRINT_BUFFER_LENGTH = 1000;
/// An object that can handle either a WebSocket connection or a connection
/// to the client over stdio.
final AbstractSocketServer _socketServer;
/// An object that can handle HTTP requests.
late final AbstractHttpHandler _httpHandler = DiagnosticsSite(
_socketServer,
_printBuffer,
);
/// Future that is completed with the HTTP server once it is running.
Future<HttpServer>? _serverFuture;
/// Last PRINT_BUFFER_LENGTH lines printed.
final List<String> _printBuffer = <String>[];
/// Initialize a newly created HTTP server.
HttpAnalysisServer(this._socketServer);
/// Return the port this server is bound to.
Future<int?> get boundPort async {
return (await _serverFuture)?.port;
}
void close() {
_serverFuture?.then((HttpServer server) {
server.close();
});
}
/// Record that the given line was printed out by the analysis server.
void recordPrint(String line) {
_printBuffer.add(line);
if (_printBuffer.length > MAX_PRINT_BUFFER_LENGTH) {
_printBuffer.removeRange(
0,
_printBuffer.length - MAX_PRINT_BUFFER_LENGTH,
);
}
}
/// Begin serving HTTP requests over the given port.
Future<int?> serveHttp([int? initialPort]) async {
if (_serverFuture != null) {
return boundPort;
}
try {
_serverFuture = HttpServer.bind(
InternetAddress.loopbackIPv4,
initialPort ?? 0,
);
var server = (await _serverFuture)!;
_handleServer(server);
return server.port;
} catch (ignore) {
// If we can't bind to the specified port, don't remember the broken
// server.
_serverFuture = null;
return null;
}
}
/// Attach a listener to a newly created HTTP server.
void _handleServer(HttpServer httpServer) {
httpServer.listen((HttpRequest request) {
if (WebSocketTransformer.isUpgradeRequest(request) &&
// For WebSockets, verify we're same origin (since the browser would
// not).
request.headers.value('origin') == request.requestedUri.origin) {
_httpHandler.handleWebSocketRequest(request);
} else if (request.method == 'GET') {
_httpHandler.handleGetRequest(request);
} else if (request.method == 'POST') {
_httpHandler.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;
response.write('Not found');
response.close();
}
}