package org.smartly.packages.http.impl; import org.eclipse.jetty.server.*; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.json.JSONArray; import org.json.JSONObject; import org.smartly.Smartly; import org.smartly.commons.logging.Level; import org.smartly.commons.logging.Logger; import org.smartly.commons.logging.util.LoggingUtils; import org.smartly.commons.util.*; import org.smartly.packages.http.impl.handlers.SmartlyShutdownHandler; import org.smartly.packages.http.impl.util.vtool.AppTool; import org.smartly.packages.velocity.impl.VLCManager; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.MultipartConfigElement; import javax.servlet.Servlet; import java.io.IOException; import java.util.EnumSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; /** * Web Server Wrapper * <p/> * More about Jetty here: * http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ */ public class WebServer extends AbstractHttpServer { // -------------------------------------------------------------------- // c o n s t a n t s // -------------------------------------------------------------------- private static final String DEF_KEYSTORE = "/keystore"; public static final String PARAM_KEYSTORE = "keystore"; public static final String PARAM_REQUEST_BUFFER = "request_buffer"; public static final String PARAM_RESPONSE_BUFFER = "response_buffer"; public static final String PARAM_SECURE_PORT = "secure_port"; public static final String PARAM_SECURE_SCHEME = "secure_scheme"; public static final String PARAM_CONNECTORS = "connectors"; public static final String PARAM_ENABLED = "enabled"; public static final String PARAM_PORT = "port"; public static final String PARAM_MAX_IDLE = "max_idle"; public static final String PARAM_KEY_PASSWORD = "key_password"; public static final String PARAM_KEY_MANAGER_PASSWORD = "key_manager_password"; public static final String PARAM_HANDLERS = "handlers"; public static final String PARAM_CHAIN = "chain"; public static final String PARAM_ENDPOINTS = "endpoints"; public static final String PARAM_ENDPOINT = "endpoint"; public static final String PARAM_SERVLETS = "servlets"; public static final String PARAM_DATA = "data"; public static final String PARAM_MULTIPART = "multipart"; public static final String PARAM_MULTIPART_LOCATION = "multipart_location"; public static final String PARAM_MULTIPART_MAX_FILE = "multipart_max_file"; public static final String PARAM_MULTIPART_MAX_REQUEST = "multipart_max_request"; public static final String PARAM_MULTIPART_FILE_THRESHOLD = "multipart_file_threshold"; // -------------------------------------------------------------------- // f i e l d s // -------------------------------------------------------------------- private Connector[] _connectors; // -------------------------------------------------------------------- // c o n s t r u c t o r // -------------------------------------------------------------------- public WebServer(final String docRoot, final JSONObject configuration) { super(docRoot, configuration); } @Override public void start(final boolean join) { try { // start only if not launched from test-unit or admin mode if (!Smartly.isTestUnitMode() && !Smartly.isAdminMode()) { final String jettyHome = super.getRoot(); // absolute path final String sslRoot = super.getSslRootPath(); final JSONObject configuration = super.getConfiguration(); //-- init connectors --// _connectors = initConnectors(super.getJetty(), sslRoot, configuration); super.getJetty().setConnectors(_connectors); //-- init handlers --// final Handler handler = initHandlers(this, configuration); if (null != handler) { super.getJetty().setHandler(handler); } // init velocity engine initVelocity(jettyHome); //-- start jetty --// super.start(join); } } catch (Throwable t) { super.triggerOnError("Error starting server", t); // try stop server try { super.stop(); } catch (Throwable t2) { super.triggerOnError("Error stopping server", t2); } } } @Override public String toString() { final StringBuilder result = new StringBuilder(); result.append(super.getJetty().toString()); return result.toString(); } // -------------------------------------------------------------------- // p r i v a t e // -------------------------------------------------------------------- private static void initVelocity(final String absoluteDocRoot) { VLCManager.getInstance().getEngine().setFileResourceLoaderPath(absoluteDocRoot); //-- APPLICATION TOOL (DON'T override) --// VLCManager.getInstance().getToolbox().add(AppTool.NAME, AppTool.class, null, true); } private static Logger staticLogger() { return LoggingUtils.getLogger(WebServer.class); } private static String mkdirs(final String absolutePath) { try { FileUtils.mkdirs(absolutePath); } catch (Throwable t) { staticLogger().log(Level.SEVERE, null, t); } return absolutePath; } private static Connector[] initConnectors(final Server server, final String sslRoot, final JSONObject configuration) { // configuration params final int request_buffer = JsonWrapper.getInt(configuration, PARAM_REQUEST_BUFFER, 8112); final int response_buffer = JsonWrapper.getInt(configuration, PARAM_RESPONSE_BUFFER, 32768); final int secure_port = JsonWrapper.getInt(configuration, PARAM_SECURE_PORT, 8443); final String secure_scheme = JsonWrapper.getString(configuration, PARAM_SECURE_SCHEME, "https"); final JSONObject connectors = JsonWrapper.getJSON(configuration, PARAM_CONNECTORS); final Iterator<String> keys = connectors.keys(); final List<Connector> result = new LinkedList<Connector>(); // http configuration final HttpConfiguration http_config = new HttpConfiguration(); http_config.setSecureScheme(secure_scheme); http_config.setSecurePort(secure_port); http_config.setOutputBufferSize(response_buffer); http_config.setRequestHeaderSize(request_buffer); while (keys.hasNext()) { final String key = keys.next(); if ("http".equalsIgnoreCase(key)) { final JSONObject connector = JsonWrapper.getJSON(connectors, key); if (null != connector && JsonWrapper.getBoolean(connector, PARAM_ENABLED)) { final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(http_config)); http.setPort(JsonWrapper.getInt(connector, PARAM_PORT, 8080)); http.setIdleTimeout(JsonWrapper.getInt(connector, PARAM_MAX_IDLE, 30000)); result.add(http); } } else if ("ssl".equalsIgnoreCase(key)) { final JSONObject connector = JsonWrapper.getJSON(connectors, key); if (null != connector && JsonWrapper.getBoolean(connector, PARAM_ENABLED)) { final String key_store = JsonWrapper.getString(connector, PARAM_KEYSTORE, DEF_KEYSTORE); mkdirs(sslRoot); final String keySorePath = PathUtils.join(sslRoot, key_store); // SSL Context Factory for HTTPS and SPDY SslContextFactory sslContextFactory = new SslContextFactory(); sslContextFactory.setKeyStorePath(keySorePath); sslContextFactory.setKeyStorePassword(JsonWrapper.getString(connector, PARAM_KEY_PASSWORD)); sslContextFactory.setKeyManagerPassword(JsonWrapper.getString(connector, PARAM_KEY_MANAGER_PASSWORD)); // HTTPS Configuration HttpConfiguration https_config = new HttpConfiguration(http_config); https_config.addCustomizer(new SecureRequestCustomizer()); // HTTPS connector ServerConnector https = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(https_config)); https.setPort(JsonWrapper.getInt(connector, PARAM_PORT, 8443)); https.setIdleTimeout(JsonWrapper.getInt(connector, PARAM_MAX_IDLE, 30000)); result.add(https); } } } return result.toArray(new Connector[result.size()]); } /** * Returns main Handler. It's a HandlerList. * A Handler List is a Handler Collection that calls each handler in turn until * either an exception is thrown, * the response is committed or * the request.isHandled() returns true. * It can be used to combine handlers that conditionally handle a request. * * @param server * @param configuration * @return */ private static Handler initHandlers(final WebServer server, final JSONObject configuration) { final String shutdownToken = JsonWrapper.getString(configuration, "shutdown_token", "smartly"); final JSONObject handlers = JsonWrapper.getJSON(configuration, PARAM_HANDLERS); if (null != handlers && handlers.length() > 0) { //-- file handler --// final HandlerList main = initChainHandlers(server, JsonWrapper.getJSON(handlers, PARAM_CHAIN)); //-- shutdown handler --// main.addHandler(new SmartlyShutdownHandler(server, shutdownToken)); //-- add endpoints (servlets and rest) --// final ContextHandlerCollection endpoints = initContextHandlers(server, JsonWrapper.getJSON(handlers, PARAM_ENDPOINTS)); if (null != endpoints) { main.addHandler(endpoints); } return main; } return null; } private static HandlerList initChainHandlers(final WebServer server, final JSONObject chain) { final HandlerList list = new HandlerList(); if (null != chain && JsonWrapper.getBoolean(chain, PARAM_ENABLED)) { final List<JSONObject> data = JsonWrapper.toListOfJSONObject(JsonWrapper.getArray(chain, PARAM_DATA)); if (!CollectionUtils.isEmpty(data)) { for (final JSONObject file : data) { final Handler handler = createHandler(server, file); if (null != handler) { list.addHandler(handler); } } } } return list; } private static ContextHandlerCollection initContextHandlers(final WebServer server, final JSONObject endpoints) { final ContextHandlerCollection result = new ContextHandlerCollection(); if (null != endpoints && JsonWrapper.getBoolean(endpoints, PARAM_ENABLED)) { // rest loadRestHandlers(result, server, JsonWrapper.toListOfJSONObject(JsonWrapper.getArray(endpoints, PARAM_DATA))); // servlets final ServletContextHandler servlets = initServletHandlers(server, JsonWrapper.getJSON(endpoints, PARAM_SERVLETS)); if (null != servlets) { result.addHandler(servlets); } } return result; } private static void loadRestHandlers(final ContextHandlerCollection list, final WebServer server, final List<JSONObject> data) { if (!CollectionUtils.isEmpty(data)) { for (final JSONObject file : data) { final Handler handler = createHandler(server, file); if (null != handler) { list.addHandler(handler); } } } } private static Handler createHandler(final WebServer server, final JSONObject file) { if (null != file) { try { final String className = JsonWrapper.getString(file, "class"); final String[] welcomes = JsonWrapper.toArrayOfString(JsonWrapper.getArray(file, "welcome")); final String contextPath = JsonWrapper.getString(file, "endpoint"); final Object params = JsonWrapper.get(file, "params"); if (StringUtils.hasText(className)) { final Class handlerClass = ClassLoaderUtils.forName(className); if (null == handlerClass) { throw new Exception(FormatUtils.format("Class not found '{0}'", className)); } final Object instance = null != params ? ClassLoaderUtils.newInstance(handlerClass, new Class[]{Object.class}, new Object[]{params}) : ClassLoaderUtils.newInstance(handlerClass); if (instance instanceof Handler) { // server BeanUtils.setValueIfAny(instance, "server", server); // doc_root BeanUtils.setValueIfAny(instance, "resourceBase", server.getRoot()); // welcome files if (null != welcomes && welcomes.length > 0) { BeanUtils.setValueIfAny(instance, "welcomeFiles", welcomes); } // contextPath for context handlers if (StringUtils.hasText(contextPath)) { BeanUtils.setValueIfAny(instance, "contextPath", contextPath); } return (Handler) instance; } } } catch (Throwable t) { staticLogger().log(Level.SEVERE, null, t); } } return null; } private static ServletContextHandler initServletHandlers(final WebServer server, final JSONObject servlets) { if (null != servlets && JsonWrapper.getBoolean(servlets, PARAM_ENABLED)) { // creates context final String contextPath = JsonWrapper.getString(servlets, PARAM_ENDPOINT, "/"); final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); context.setContextPath(contextPath); // creates servlets final JSONArray servletData = JsonWrapper.getArray(servlets, PARAM_DATA); if (null != servletData && servletData.length() > 0) { for (int i = 0; i < servletData.length(); i++) { final JSONObject servletJSON = servletData.optJSONObject(i); final Object handler = createServletOrFilter(server, servletJSON); if (null != handler) { final String pathSpec = JsonWrapper.getString(servletJSON, PARAM_ENDPOINT); final boolean isMultipart = JsonWrapper.getBoolean(servletJSON, PARAM_MULTIPART); final String location = server.getWorkSpacePath(JsonWrapper.getString(servletJSON, PARAM_MULTIPART_LOCATION)); final long maxFileSize = JsonWrapper.getLong(servletJSON, PARAM_MULTIPART_MAX_FILE, 1048576); // 1Mb final long maxRequestSize = JsonWrapper.getLong(servletJSON, PARAM_MULTIPART_MAX_REQUEST, maxFileSize); final int fileSizeThreshold = JsonWrapper.getInt(servletJSON, PARAM_MULTIPART_FILE_THRESHOLD, 262144); if (handler instanceof Servlet) { // servlet final ServletHolder sh = new ServletHolder((Servlet) handler); if (isMultipart) { final MultipartConfigElement config = new MultipartConfigElement( location, // location (String) maxFileSize, // maxFileSize (long) maxRequestSize, // maxRequestSize (long) fileSizeThreshold); // fileSizeThreshold (int) sh.getRegistration().setMultipartConfig(config); sh.getRegistration().setInitParameter(PARAM_MULTIPART_LOCATION, location); sh.getRegistration().setInitParameter(PARAM_MULTIPART_MAX_FILE, maxFileSize + ""); sh.getRegistration().setInitParameter(PARAM_MULTIPART_MAX_REQUEST, maxRequestSize + ""); sh.getRegistration().setInitParameter(PARAM_MULTIPART_FILE_THRESHOLD, fileSizeThreshold + ""); } context.addServlet(sh, pathSpec); server.registerEndPoint(pathSpec); } else if (handler instanceof Filter) { // filter final FilterHolder holder = new FilterHolder((Filter) handler); if (isMultipart) { holder.getRegistration().setInitParameter(PARAM_MULTIPART_LOCATION, location); holder.getRegistration().setInitParameter(PARAM_MULTIPART_MAX_FILE, maxFileSize + ""); holder.getRegistration().setInitParameter(PARAM_MULTIPART_MAX_REQUEST, maxRequestSize + ""); holder.getRegistration().setInitParameter(PARAM_MULTIPART_FILE_THRESHOLD, fileSizeThreshold + ""); holder.getRegistration().setInitParameter("deleteFiles", "true"); holder.getRegistration().setInitParameter("fileOutputBuffer", fileSizeThreshold + ""); holder.getRegistration().setInitParameter("maxFileSize", maxFileSize + ""); holder.getRegistration().setInitParameter("maxRequestSize", maxRequestSize + ""); holder.getRegistration().setInitParameter("maxFormKeys", "1000"); } context.addFilter(holder, pathSpec, EnumSet.allOf(DispatcherType.class)); server.registerEndPoint(pathSpec); } else { staticLogger().log(Level.WARNING, FormatUtils.format("UNSUPPORTED HANDLER. " + "Only Servlets and Filter are admitted. Handler '{0}' is not supported " + "and will not be installed on path '{1}'.", handler.getClass().getName(), pathSpec)); } } } } return context; } return null; } private static Object createServletOrFilter(final WebServer server, final JSONObject servlet) { if (null != servlet) { try { final String className = JsonWrapper.getString(servlet, "class"); if (StringUtils.hasText(className)) { final Class servletClass = ClassLoaderUtils.forName(className); if (null != servletClass) { final Object params = JsonWrapper.get(servlet, "params"); final Object instance; if (null != params && StringUtils.hasText(params.toString())) { instance = newInstance(servletClass, params); } else { instance = newInstance(servletClass, servlet); } // server BeanUtils.setValueIfAny(instance, "server", server); // doc_root BeanUtils.setValueIfAny(instance, "resourceBase", server.getRoot()); return instance; } else { staticLogger().log(Level.WARNING, FormatUtils.format("Servlet not found: '{0}'", className)); } } } catch (Throwable t) { staticLogger().log(Level.SEVERE, null, t); } } return null; } private static Object newInstance(final Class aclass, final Object params) throws Exception { try { return ClassLoaderUtils.newInstance(aclass, new Class[]{Object.class}, new Object[]{params}); } catch (Throwable ignored) { return ClassLoaderUtils.newInstance(aclass); } } private static FilterHolder createFilter(final WebServer server, final JSONObject filter) { if (null != filter) { try { final String className = JsonWrapper.getString(filter, "class"); if (StringUtils.hasText(className)) { final Class servletClass = ClassLoaderUtils.forName(className); if (null != servletClass) { final Object params = JsonWrapper.get(filter, "params"); final Object instance; if (null != params) { instance = ClassLoaderUtils.newInstance(servletClass, new Class[]{Object.class}, new Object[]{params}); } else { instance = ClassLoaderUtils.newInstance(servletClass); } if (instance instanceof Servlet) { // doc_root BeanUtils.setValueIfAny(instance, "resourceBase", server.getRoot()); return new FilterHolder((Filter) instance); } } else { staticLogger().log(Level.WARNING, FormatUtils.format("Servlet not found: '{0}'", className)); } } } catch (Throwable t) { staticLogger().log(Level.SEVERE, null, t); } } return null; } // -------------------------------------------------------------------- // L A U N C H E R // -------------------------------------------------------------------- public static boolean isEnabled() { return Smartly.getConfiguration().getBoolean("http.webserver.enabled"); } public static WebServer create() throws IOException { final JSONObject configuration = Smartly.getConfiguration().getJSONObject("http.webserver"); final String docRoot = JsonWrapper.getString(configuration, "root"); final String absoluteDocRoot = Smartly.getAbsolutePath(docRoot); // ensure resource base exists FileUtils.mkdirs(absoluteDocRoot); //-- start the web server --// return new WebServer(absoluteDocRoot, configuration); } public static WebServer launch(final boolean join) throws IOException { if (isEnabled()) { //-- start the web server --// final WebServer server = create(); server.start(join); return server; } return null; } }