blob: 2dc05894eeb8de0a23c659f215923d0e21011aa4 [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:convert' show utf8;
import 'dart:io';
import '../client_context.dart';
import 'context_registry.dart';
void _info(String message) {
final formattedMessage = '${DateTime.now()}: $message';
stderr.writeln(formattedMessage);
}
class AppEngineHttpServer {
final ContextRegistry _contextRegistry;
final String _hostname;
final int _port;
final bool _shared;
final Completer _shutdownCompleter = Completer();
int _pendingRequests = 0;
HttpServer? _httpServer;
AppEngineHttpServer(this._contextRegistry,
{String hostname = '0.0.0.0', int port = 8080, bool shared = false})
: _hostname = hostname,
_port = port,
_shared = shared;
Future get done => _shutdownCompleter.future;
void run(
Function(HttpRequest request, ClientContext context) applicationHandler, {
void Function(InternetAddress address, int port)? onAcceptingConnections,
}) {
final serviceHandlers = {
'/_ah/start': _start,
'/_ah/health': _health,
'/_ah/stop': _stop
};
HttpServer.bind(_hostname, _port, shared: _shared)
.then((HttpServer server) {
_httpServer = server;
if (onAcceptingConnections != null) {
onAcceptingConnections(server.address, server.port);
}
server.listen((HttpRequest request) {
// Default handling is sending the request to the application.
dynamic Function(HttpRequest, ClientContext)? handler = applicationHandler;
// Check if the request path is one of the service handlers.
final String path = request.uri.path;
for (final pattern in serviceHandlers.keys) {
if (path.startsWith(pattern)) {
handler = serviceHandlers[pattern];
break;
}
}
_pendingRequests++;
final context = _contextRegistry.add(request);
request.response.done.whenComplete(() {
_contextRegistry.remove(request);
});
request.response.done.catchError((error) {
if (!_contextRegistry.isDevelopmentEnvironment) {
_info('Error while handling response: $error');
}
_pendingRequests--;
_checkShutdown();
});
handler!(request, context);
});
});
}
void _start(HttpRequest request, _) {
request.drain().then((_) {
_sendResponse(request.response, HttpStatus.ok, 'ok');
});
}
void _health(HttpRequest request, _) {
request.drain().then((_) {
_sendResponse(request.response, HttpStatus.ok, 'ok');
});
}
void _stop(HttpRequest request, _) {
request.drain().then((_) {
if (_httpServer != null) {
_httpServer!.close().then((_) {
_httpServer = null;
_sendResponse(request.response, HttpStatus.ok, 'ok');
});
} else {
_sendResponse(request.response, HttpStatus.conflict, 'fail');
}
});
}
void _checkShutdown() {
if (_pendingRequests == 0 && _httpServer == null) {
_shutdownCompleter.complete();
}
}
void _sendResponse(HttpResponse response, int statusCode, String message) {
final data = utf8.encode(message);
response.headers.contentType =
ContentType('text', 'plain', charset: 'utf-8');
response.headers.set('Cache-Control', 'no-cache');
response.headers.set('Server', _hostname);
response
..contentLength = data.length
..statusCode = statusCode
..add(data)
..close();
}
}