blob: 7d407c94c473e97c4a5fde1e894249d987cbe121 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. 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:isolate';
import 'package:devtools_server/src/client_manager.dart';
import 'package:path/path.dart' as path;
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as shelf;
import 'package:shelf_static/shelf_static.dart';
import 'package:sse/server/sse_handler.dart';
/// Default [shelf.Handler] for serving DevTools files.
///
/// This serves files out from the build results of running a pub build of the
/// DevTools project.
Future<shelf.Handler> defaultHandler(ClientManager clients) async {
final resourceUri = await Isolate.resolvePackageUri(
Uri(scheme: 'package', path: 'devtools/devtools.dart'));
final packageDir = path.dirname(path.dirname(resourceUri.toFilePath()));
// Default static handler for all non-package requests.
final buildDir = path.join(packageDir, 'build');
final buildHandler = createStaticHandler(
buildDir,
defaultDocument: 'index.html',
);
// The packages folder is renamed in the pub package so this handler serves
// out of the `pack` folder.
final packagesDir = path.join(packageDir, 'build', 'pack');
final packHandler = createStaticHandler(
packagesDir,
defaultDocument: 'index.html',
);
final sseHandler = SseHandler(Uri.parse('/api/sse'))
..connections.rest.listen(clients.acceptClient);
// Make a handler that delegates based on path.
final handler = (shelf.Request request) {
if (request.url.path.startsWith('packages/')) {
// request.change here will strip the `packages` prefix from the path
// so it's relative to packHandler's root.
return packHandler(request.change(path: 'packages'));
}
if (request.url.path.startsWith('api/sse')) {
return sseHandler.handler(request);
}
// The API handler takes all other calls to api/.
if (ServerApi.canHandle(request)) {
return ServerApi.handle(request);
}
return buildHandler(request);
};
return handler;
}
/// The DevTools server API.
///
/// This defines endpoints that serve all requests that come in over api/.
class ServerApi {
/// Determines whether or not [request] is an API call.
static bool canHandle(shelf.Request request) {
return request.url.path.startsWith('api/');
}
/// Handles all requests.
///
/// To override an API call, pass in a subclass of [ServerApi].
static FutureOr<shelf.Response> handle(
shelf.Request request, [
ServerApi api,
]) {
api ??= ServerApi();
switch (request.url.path) {
case 'api/logScreenView':
return api.logScreenView(request);
default:
return api.notImplemented(request);
}
}
/// Logs a page view in the DevTools server.
///
/// In the open-source version of DevTools, Google Analytics handles this
/// without any need to involve the server.
FutureOr<shelf.Response> logScreenView(shelf.Request request) =>
notImplemented(request);
/// A [shelf.Response] for API calls that have not been implemented in this
/// server.
///
/// This is a no-op 204 No Content response because returning 404 Not Found
/// creates unnecessary noise in the console.
FutureOr<shelf.Response> notImplemented(shelf.Request request) =>
shelf.Response(204);
}