blob: bd16a252a6494094deab91becc7fe5fd57a8a685 [file] [log] [blame]
/*
* Copyright 2013-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.httpserver;
import com.facebook.buck.io.ProjectFilesystem;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import java.util.List;
import java.util.Map;
/**
* A WebSocket server that reports events of buck.
*/
public class WebServer {
private static final String INDEX_CONTEXT_PATH = "/";
private static final String STATIC_CONTEXT_PATH = "/static";
private static final String TRACE_CONTEXT_PATH = "/trace";
private static final String TRACES_CONTEXT_PATH = "/traces";
private static final String TRACE_DATA_CONTEXT_PATH = "/tracedata";
private Optional<Integer> port;
private final ProjectFilesystem projectFilesystem;
private final String staticContentDirectory;
private final Server server;
private final StreamingWebSocketServlet streamingWebSocketServlet;
/**
* @param port If 0, then an <a href="http://en.wikipedia.org/wiki/Ephemeral_port">
* ephemeral port</a> will be assigned. Use {@link #getPort()} to find out which port is being
* used.
*/
public WebServer(
int port,
ProjectFilesystem projectFilesystem,
String staticContentDirectory) {
this.projectFilesystem = projectFilesystem;
this.staticContentDirectory = staticContentDirectory;
this.port = Optional.absent();
this.server = new Server(port);
this.streamingWebSocketServlet = new StreamingWebSocketServlet();
}
public Optional<Integer> getPort() {
if (!port.isPresent()) {
for (Connector connector : server.getConnectors()) {
if (connector instanceof ServerConnector) {
port = Optional.of(((ServerConnector) connector).getLocalPort());
break;
}
}
}
return port;
}
public WebServerBuckEventListener createListener() {
return new WebServerBuckEventListener(this);
}
public StreamingWebSocketServlet getStreamingWebSocketServlet() {
return streamingWebSocketServlet;
}
public synchronized void start() throws WebServerException {
if (server.isStarted()) {
return;
}
// Package up all of the handlers into a ContextHandlerCollection to serve as the handler for
// the server.
List<? extends Handler> handlers = createHandlers();
ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.setHandlers(handlers.toArray(new Handler[0]));
server.setHandler(contexts);
try {
server.start();
} catch (Exception e) {
throw new WebServerException("Cannot start Websocket server.", e);
}
}
@VisibleForTesting
List<ContextHandler> createHandlers() {
Map<String, Handler> contextPathToHandler = Maps.newHashMap();
contextPathToHandler.put(INDEX_CONTEXT_PATH, new TemplateHandler(new IndexHandlerDelegate()));
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setResourceBase(staticContentDirectory);
contextPathToHandler.put(STATIC_CONTEXT_PATH, resourceHandler);
// Handlers for traces.
TracesHelper tracesHelper = new TracesHelper(projectFilesystem);
contextPathToHandler.put(TRACE_CONTEXT_PATH, new TemplateHandler(
new TraceHandlerDelegate(tracesHelper)));
contextPathToHandler.put(TRACES_CONTEXT_PATH, new TemplateHandler(
new TracesHandlerDelegate(tracesHelper)));
contextPathToHandler.put(TRACE_DATA_CONTEXT_PATH, new TraceDataHandler(tracesHelper));
ImmutableList.Builder<ContextHandler> handlers = ImmutableList.builder();
for (Map.Entry<String, Handler> entry : contextPathToHandler.entrySet()) {
String contextPath = entry.getKey();
Handler handler = entry.getValue();
ContextHandler contextHandler = new ContextHandler(contextPath);
contextHandler.setHandler(handler);
handlers.add(contextHandler);
}
// Create a handler that acts as a WebSocket server.
ServletContextHandler servletContextHandler = new ServletContextHandler(
/* parent */ server,
/* contextPath */ "/comet",
/* sessions */ true,
/* security */ false);
servletContextHandler.addServlet(new ServletHolder(streamingWebSocketServlet), "/echo");
handlers.add(servletContextHandler);
return handlers.build();
}
public synchronized void stop() throws WebServerException {
if (!server.isRunning()) {
return;
}
try {
server.stop();
} catch (Exception e) {
throw new WebServerException("Cannot stop Websocket server.", e);
}
}
@SuppressWarnings("serial")
public static class WebServerException extends Exception {
public WebServerException(String message, Exception clause) {
super(message, clause);
}
}
}