blob: 44498802c910f665364941e945479066723a5c78 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.submarine.server;
import org.apache.log4j.PropertyConfigurator;
import org.apache.submarine.server.rest.provider.YamlEntityProvider;
import org.apache.submarine.server.security.SecurityFactory;
import org.apache.submarine.server.security.SecurityProvider;
import org.apache.submarine.server.workbench.websocket.NotebookServer;
import org.apache.submarine.server.websocket.WebSocketServer;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.ServiceLocatorFactory;
import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.submarine.commons.utils.SubmarineConfiguration;
import org.apache.submarine.commons.utils.SubmarineConfVars;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.EnumSet;
import java.util.Optional;
public class SubmarineServer extends ResourceConfig {
private static final Logger LOG = LoggerFactory.getLogger(SubmarineServer.class);
private static final long SERVERTIMESTAMP = System.currentTimeMillis();
public static Server jettyWebServer;
public static ServiceLocator sharedServiceLocator;
private static WebAppContext webApp;
private static final SubmarineConfiguration conf = SubmarineConfiguration.getInstance();
public static long getServerTimeStamp() {
return SERVERTIMESTAMP;
}
public static void main(String[] args) throws InterruptedException {
PropertyConfigurator.configure(ClassLoader.getSystemResource("log4j.properties"));
LOG.info("Submarine server Host: " + conf.getServerAddress());
if (!conf.useSsl()) {
LOG.info("Submarine server Port: " + conf.getServerPort());
} else {
LOG.info("Submarine server SSL Port: " + conf.getServerSslPort());
}
jettyWebServer = setupJettyServer(conf);
// Web UI
HandlerList handlers = new HandlerList();
webApp = setupWebAppContext(handlers, conf);
jettyWebServer.setHandler(handlers);
// Add
sharedServiceLocator = ServiceLocatorFactory.getInstance().create("shared-locator");
ServiceLocatorUtilities.enableImmediateScope(sharedServiceLocator);
ServiceLocatorUtilities.bind(
sharedServiceLocator,
new AbstractBinder() {
@Override
protected void configure() {
bindAsContract(NotebookServer.class)
.to(WebSocketServlet.class)
.in(Singleton.class);
bindAsContract(WebSocketServer.class)
.to(WebSocketServlet.class)
.in(Singleton.class);
}
});
setupRestApiContextHandler(webApp, conf);
// Notebook server
setupNotebookServer(webApp, conf, sharedServiceLocator);
// Cluster Server
// Cluster Server is useless for submarine now. Shield it to improve performance.
// setupClusterServer();
setupWebSocketServer(webApp, conf, sharedServiceLocator);
startServer();
}
@Inject
public SubmarineServer() {
packages("org.apache.submarine.server.workbench.rest",
"org.apache.submarine.server.rest"
);
register(YamlEntityProvider.class);
}
private static void startServer() throws InterruptedException {
LOG.info("Starting submarine server");
try {
// Instantiates SubmarineServer
jettyWebServer.start();
} catch (Exception e) {
LOG.error("Error while running jettyServer", e);
System.exit(-1);
}
LOG.info("Done, submarine server started");
Runtime.getRuntime()
.addShutdownHook(
new Thread(
() -> {
LOG.info("Shutting down Submarine Server ... ");
try {
jettyWebServer.stop();
Thread.sleep(3000);
} catch (InterruptedException e) {
LOG.error("Interrupted exception:", e);
} catch (Exception e) {
LOG.error("Error while stopping servlet container", e);
}
LOG.info("Bye");
}));
jettyWebServer.join();
}
private static void setupRestApiContextHandler(WebAppContext webapp, SubmarineConfiguration conf) {
final ServletHolder servletHolder =
new ServletHolder(new org.glassfish.jersey.servlet.ServletContainer());
servletHolder.setInitParameter("javax.ws.rs.Application", SubmarineServer.class.getName());
servletHolder.setName("rest");
servletHolder.setForcedPath("rest");
webapp.setSessionHandler(new SessionHandler());
webapp.addServlet(servletHolder, "/api/*");
}
private static WebAppContext setupWebAppContext(HandlerList handlers,
SubmarineConfiguration conf) {
WebAppContext webApp = new WebAppContext();
webApp.setContextPath("/");
File warPath = new File(conf.getString(SubmarineConfVars.ConfVars.WORKBENCH_WEB_WAR));
LOG.info("workbench web war file path is {}.",
conf.getString(SubmarineConfVars.ConfVars.WORKBENCH_WEB_WAR));
if (warPath.isDirectory()) {
// Development mode, read from FS
webApp.setResourceBase(warPath.getPath());
webApp.setParentLoaderPriority(true);
} else {
// use packaged WAR
webApp.setWar(warPath.getAbsolutePath());
File warTempDirectory = new File("webapps");
warTempDirectory.mkdir();
webApp.setTempDirectory(warTempDirectory);
}
webApp.addServlet(new ServletHolder(new DefaultServlet()), "/");
// When requesting the workbench page, the content of index.html needs to be returned,
// otherwise a 404 error will be displayed
// NOTE: If you modify the workbench directory in the front-end URL,
// you need to modify the `/workbench/*` here.
webApp.addServlet(new ServletHolder(RefreshServlet.class), "/user/*");
webApp.addServlet(new ServletHolder(RefreshServlet.class), "/workbench/*");
// add security filter
Optional<SecurityProvider> securityProvider = SecurityFactory.getSecurityProvider();
if (securityProvider.isPresent()) {
Class<Filter> filterClass = securityProvider.get().getFilterClass();
LOG.info("Add {} to support auth", filterClass);
webApp.addFilter(filterClass, "/*", EnumSet.of(DispatcherType.REQUEST));
}
handlers.setHandlers(new Handler[]{webApp});
return webApp;
}
private static Server setupJettyServer(SubmarineConfiguration conf) {
ThreadPool threadPool =
new QueuedThreadPool(conf.getInt(SubmarineConfVars.ConfVars.SUBMARINE_SERVER_JETTY_THREAD_POOL_MAX),
conf.getInt(SubmarineConfVars.ConfVars.SUBMARINE_SERVER_JETTY_THREAD_POOL_MIN),
conf.getInt(SubmarineConfVars.ConfVars.SUBMARINE_SERVER_JETTY_THREAD_POOL_TIMEOUT));
final Server server = new Server(threadPool);
ServerConnector connector;
if (conf.useSsl()) {
LOG.debug("Enabling SSL for submarine Server on port " + conf.getServerSslPort());
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecureScheme("https");
httpConfig.setSecurePort(conf.getServerSslPort());
httpConfig.setOutputBufferSize(32768);
httpConfig.setResponseHeaderSize(8192);
httpConfig.setSendServerVersion(true);
HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
SecureRequestCustomizer src = new SecureRequestCustomizer();
httpsConfig.addCustomizer(src);
connector = new ServerConnector(
server,
new SslConnectionFactory(getSslContextFactory(conf), HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(httpsConfig));
} else {
connector = new ServerConnector(server);
}
configureRequestHeaderSize(conf, connector);
// Set some timeout options to make debugging easier.
int timeout = 1000 * 30;
connector.setIdleTimeout(timeout);
connector.setHost(conf.getServerAddress());
if (conf.useSsl()) {
connector.setPort(conf.getServerSslPort());
} else {
connector.setPort(conf.getServerPort());
}
server.addConnector(connector);
return server;
}
private static void setupNotebookServer(WebAppContext webapp,
SubmarineConfiguration conf, ServiceLocator serviceLocator) {
String maxTextMessageSize = conf.getWebsocketMaxTextMessageSize();
final ServletHolder servletHolder =
new ServletHolder(serviceLocator.getService(NotebookServer.class));
servletHolder.setInitParameter("maxTextMessageSize", maxTextMessageSize);
final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
webapp.addServlet(servletHolder, "/wss/*");
}
private static void setupWebSocketServer(WebAppContext webapp,
SubmarineConfiguration conf, ServiceLocator serviceLocator) {
String maxTextMessageSize = conf.getWebsocketMaxTextMessageSize();
final ServletHolder notebookServletHolder =
new ServletHolder(serviceLocator.getService(WebSocketServer.class));
notebookServletHolder.setInitParameter("maxTextMessageSize", maxTextMessageSize);
final ServletHolder experimentServletHolder =
new ServletHolder(serviceLocator.getService(WebSocketServer.class));
experimentServletHolder.setInitParameter("maxTextMessageSize", maxTextMessageSize);
final ServletHolder environmentServletHolder =
new ServletHolder(serviceLocator.getService(WebSocketServer.class));
environmentServletHolder.setInitParameter("maxTextMessageSize", maxTextMessageSize);
final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
webapp.addServlet(notebookServletHolder, "/ws/notebook/*");
webapp.addServlet(experimentServletHolder, "/ws/experiment/*");
webapp.addServlet(environmentServletHolder, "/ws/environment/*");
}
private static SslContextFactory getSslContextFactory(SubmarineConfiguration conf) {
SslContextFactory sslContextFactory = new SslContextFactory();
// Set keystore
sslContextFactory.setKeyStorePath(conf.getKeyStorePath());
sslContextFactory.setKeyStoreType(conf.getKeyStoreType());
sslContextFactory.setKeyStorePassword(conf.getKeyStorePassword());
sslContextFactory.setKeyManagerPassword(conf.getKeyManagerPassword());
if (conf.useClientAuth()) {
sslContextFactory.setNeedClientAuth(conf.useClientAuth());
// Set truststore
sslContextFactory.setTrustStorePath(conf.getTrustStorePath());
sslContextFactory.setTrustStoreType(conf.getTrustStoreType());
sslContextFactory.setTrustStorePassword(conf.getTrustStorePassword());
}
return sslContextFactory;
}
private static void configureRequestHeaderSize(
SubmarineConfiguration conf, ServerConnector connector) {
HttpConnectionFactory cf =
(HttpConnectionFactory) connector.getConnectionFactory(HttpVersion.HTTP_1_1.toString());
int requestHeaderSize = conf.getJettyRequestHeaderSize();
cf.getHttpConfiguration().setRequestHeaderSize(requestHeaderSize);
}
// SUBMARINE-422. Fix refreshing page returns 404 error
// Because the workbench is developed using angular,
// the adjustment of angular WEB pages is completely controlled by the front end,
// so when you manually refresh a specific page in the browser,
// the browser will send the request for this page to the back-end service,
// but the back-end service only In response to API requests, it will cause the front end to display 404.
// The solution is to find that not all API requests directly return the content of the index page,
// so that the front end will automatically perform correct page routing processing.
public static class RefreshServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
response.encodeRedirectURL("/");
response.setStatus(HttpServletResponse.SC_OK);
File warPath = new File(conf.getString(SubmarineConfVars.ConfVars.WORKBENCH_WEB_WAR));
File indexFile = null;
if (warPath.isDirectory()) {
// Development mode, read from FS
indexFile = new File(warPath.getAbsolutePath() + "/index.html");
} else {
// Product mode, read from war file
File warFile = webApp.getTempDirectory();
if (!warFile.exists()) {
throw new ServletException("Can't found war directory!");
}
indexFile = new File(warFile.getAbsolutePath() + "/webapp/index.html");
}
// If index.html does not exist, throw ServletException
if (!(indexFile.isFile() && indexFile.exists())) {
throw new ServletException("Can't found index html!");
}
StringBuilder sbIndexBuf = new StringBuilder();
try (InputStreamReader reader =
new InputStreamReader(new FileInputStream(indexFile), "GBK");
BufferedReader bufferedReader = new BufferedReader(reader);) {
String lineTxt = null;
while ((lineTxt = bufferedReader.readLine()) != null) {
sbIndexBuf.append(lineTxt);
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
response.getWriter().print(sbIndexBuf.toString());
}
}
}