All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.fbk.knowledgestore.server.http.HttpServer Maven / Gradle / Ivy

Go to download

The HTTP server module (ks-server-http) implements the Web API of the KnowledgeStore, which includes the two CRUD and SPARQL endpoints. The CRUD Endpoint supports the retrieval and manipulation of semi-structured data about resource, mention, entity and axiom records (encoded in RDF, possibly using JSONLD), and the upload / download of resource representation. The SPARQL Endpoint supports SPARQL SELECT, CONSTRUCT, DESCRIBE and ASK queries according to the W3C SPARQL protocol. The two endpoints are implemented on top of a component implementing the KnowledgeStore Java API (the Store interface), which can be either the the KnowledgeStore frontend (ks-frontend) or the Java Client. The implementation of the module is based on the Jetty Web sever (run in embedded mode) and the Jersey JAX-RS implementation. Reference documentation of the Web API is automatically generated using the Enunciate tool.

There is a newer version: 1.7.1
Show newest version
package eu.fbk.knowledgestore.server.http;

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;

import javax.annotation.Nullable;

import com.google.common.base.Charsets;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.io.Resources;

import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ConnectorStatistics;
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.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.access.jetty.RequestLogImpl;

import eu.fbk.knowledgestore.ForwardingKnowledgeStore;
import eu.fbk.knowledgestore.KnowledgeStore;
import eu.fbk.knowledgestore.Session;
import eu.fbk.knowledgestore.data.Data;
import eu.fbk.knowledgestore.runtime.Component;
import eu.fbk.knowledgestore.server.http.jaxrs.Application;
import eu.fbk.knowledgestore.server.http.jaxrs.Files;
import eu.fbk.knowledgestore.server.http.jaxrs.Mentions;
import eu.fbk.knowledgestore.server.http.jaxrs.Root;
import eu.fbk.knowledgestore.server.http.jaxrs.Sparql;

// TODO: check
// https://jersey.java.net/apidocs/2.5.1/jersey/org/glassfish/jersey/server/filter/UriConnegFilter.html

// TODO: add DOSFilter and QOSFilter from jetty-servlets

// TODO: getters and tostring

public class HttpServer extends ForwardingKnowledgeStore implements Component {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpServer.class);

    private static final String DEFAULT_HOST = "0.0.0.0";

    private static final String DEFAULT_PATH = "/";

    private static final int DEFAULT_HTTP_PORT = 8080;

    private static final int DEFAULT_HTTPS_PORT = 8443;

    private static final int DEFAULT_ACCEPTORS = -1; // -1 = platform specific

    private static final int DEFAULT_SELECTORS = -1; // -1 = platform specific

    private static final KeystoreConfig DEFAULT_KEYSTORE_CONFIG = new KeystoreConfig(
            HttpServer.class.getResource("HttpServer.jks").toString(), "kspass", null, null);

    private static final String DEFAULT_REALM = "KnowledgeStore";

    private static final UIConfig DEFAULT_UI_CONFIG = UIConfig.builder().build();

    private static final long STOP_TIMEOUT = 1000; // wait 1 s before forcing closure

    private final KnowledgeStore delegate;

    private final Server server;

    private HttpServer(final Builder builder) {
        this.delegate = Preconditions.checkNotNull(builder.delegate);
        this.server = createJettyServer(builder);
    }

    @Override
    protected KnowledgeStore delegate() {
        return this.delegate;
    }

    @Override
    public void init() throws IOException {

        if (this.delegate instanceof Component) {
            ((Component) this.delegate).init();
        }

        try {
            this.server.start();
            if (LOGGER.isInfoEnabled()) {
                final StringBuilder builder = new StringBuilder("Jetty ").append(
                        Server.getVersion()).append(" started, listening on ");
                builder.append(((ServerConnector) this.server.getConnectors()[0]).getHost());
                builder.append(", port(s)");
                for (final Connector connector : this.server.getConnectors()) {
                    builder.append(" ").append(((ServerConnector) connector).getPort());
                }
                builder.append(", base '").append(this.server.getAttribute("base")).append('\'');
                LOGGER.info(builder.toString());
            }

        } catch (final Exception ex) {
            throw ex instanceof RuntimeException ? (RuntimeException) ex
                    : new RuntimeException(ex);
        }
    }

    @Override
    public Session newSession() throws IllegalStateException {
        return this.delegate.newSession();
    }

    @Override
    public Session newSession(final String username, final String password)
            throws IllegalStateException {
        return this.delegate.newSession(username, password);
    }

    @Override
    public boolean isClosed() {
        return this.delegate.isClosed();
    }

    @Override
    public void close() {
        try {
            this.server.setStopTimeout(STOP_TIMEOUT);
            this.server.stop();
            LOGGER.info("Jetty {} stopped", Server.getVersion());
        } catch (final Exception ex) {
            // Just log a warning without additional detail, as Jetty already prints log info
            LOGGER.warn("Jetty {} stopped (with errors)", Server.getVersion());
        } finally {
            this.delegate.close();
        }
    }

    private Server createJettyServer(final Builder builder) {

        // Retrieve common attributes
        final String host = MoreObjects.firstNonNull(builder.host, DEFAULT_HOST);
        String base = MoreObjects.firstNonNull(builder.path, DEFAULT_PATH);
        final int acceptors = MoreObjects.firstNonNull(builder.acceptors, DEFAULT_ACCEPTORS);
        final int selectors = MoreObjects.firstNonNull(builder.selectors, DEFAULT_SELECTORS);
        final boolean debug = Boolean.TRUE.equals(builder.debug);
        base = base.startsWith("/") ? base : "/" + base;

        // Retrieve the scheduler
        final ExecutorService scheduler = Data.getExecutor();

        // Create HTTP server
        final Server server = new Server(new ExecutorThreadPool(scheduler) {

            @Override
            protected void doStop() throws Exception {
                // Do nothing (default behaviour is shutting down the supplied scheduler, which is
                // something we do not want.
            }

        });
        server.setDumpAfterStart(false);
        server.setDumpBeforeStop(false);
        server.setStopAtShutdown(true);
        // server.setSendDateHeader(true);
        // server.setSendServerVersion(true); // custom header sent

        // enable JMX support
        final MBeanContainer jmxContainer = new MBeanContainer(
                ManagementFactory.getPlatformMBeanServer());
        server.addBean(jmxContainer);

        // configure security
        final SecurityConfig sc = builder.securityConfig;
        String realm = DEFAULT_REALM;
        Set anonymousRoles = SecurityConfig.ALL_ROLES;
        if (sc != null) {
            realm = MoreObjects.firstNonNull(sc.getRealm(), realm);
            anonymousRoles = sc.getAnonymousRoles();
            final String userdb = sc.getUserdbURL().toString();
            final HashLoginService login = new HashLoginService();
            login.setName(sc.getRealm());
            login.setConfig(userdb);
            login.setRefreshInterval(userdb.startsWith("file://") ? 60000 : 0);
            server.addBean(login);
        }

        // add HTTP and HTTPS connectors
        final int httpPort = MoreObjects.firstNonNull(builder.httpPort, DEFAULT_HTTP_PORT);
        final int httpsPort = MoreObjects.firstNonNull(builder.httpsPort, DEFAULT_HTTPS_PORT);
        if (httpPort > 0) {
            Preconditions.checkArgument(httpPort < 65536, "Invalid HTTP port %s", httpPort);
            server.addConnector(createServerConnector(server, host, httpPort, acceptors,
                    selectors, createHttpConnectionFactory(httpsPort, false)));
        }
        if (httpsPort > 0) {
            Preconditions.checkArgument(httpsPort < 65536 && httpsPort != httpPort,
                    "Invalid HTTPS port %s", httpsPort);
            final KeystoreConfig kc = MoreObjects.firstNonNull(builder.keystoreConfig,
                    DEFAULT_KEYSTORE_CONFIG);
            server.addConnector(createServerConnector(server, host, httpsPort, acceptors,
                    selectors, createSSLConnectionFactory(kc),
                    createHttpConnectionFactory(httpsPort, true)));
        }

        // configure Webapp handler
        final WebAppContext webappHandler = new WebAppContext();
        webappHandler.setThrowUnavailableOnStartupException(true);
        webappHandler.setTempDirectory(new File(System.getProperty("java.io.tmpdir") + "/"
                + UUID.randomUUID().toString()));
        webappHandler.setResourceBase(getClass().getClassLoader()
                .getResource("eu/fbk/knowledgestore/server/http/jaxrs").toExternalForm());
        webappHandler.setParentLoaderPriority(true);
        webappHandler.setMaxFormContentSize(Integer.MAX_VALUE);
        webappHandler.setDescriptor(createWebXml(realm, anonymousRoles, debug).toString());
        webappHandler.setConfigurationDiscovered(false);
        webappHandler.setCompactPath(true);
        webappHandler.setClassLoader(getClass().getClassLoader());
        webappHandler.setContextPath(base);
        webappHandler.getServletContext().setAttribute(Application.STORE_ATTRIBUTE, this);
        webappHandler.getServletContext().setAttribute(Application.TRACING_ATTRIBUTE, debug);
        webappHandler.getServletContext().setAttribute(Application.UI_ATTRIBUTE,
                MoreObjects.firstNonNull(builder.uiConfig, DEFAULT_UI_CONFIG));
        webappHandler.getServletContext().setAttribute(
                Application.RESOURCE_ATTRIBUTE,
                ImmutableList.of(Root.class, Files.class,
                        eu.fbk.knowledgestore.server.http.jaxrs.Resources.class, Mentions.class,
                        // Entities.class, Axioms.class, Match.class,
                        Sparql.class));

        // configure request logging using logback access
        RequestLogHandler requestLogHandler = null;
        if (builder.logConfigLocation != null) {
            final RequestLogImpl requestLog = new RequestLogImpl();
            requestLog.setQuiet(true);
            requestLog.setResource((builder.logConfigLocation.startsWith("/") ? "" : "/")
                    + builder.logConfigLocation);
            requestLogHandler = new RequestLogHandler();
            requestLogHandler.setRequestLog(requestLog);
        }

        // add proxy pass reverse support
        final Handler handler = webappHandler;

        // configure request statistics collector (accessible via JMX)
        final StatisticsHandler statHandler = new StatisticsHandler();

        // configure CLF logging
        if (requestLogHandler == null) {
            statHandler.setHandler(handler);
        } else {
            final HandlerCollection multiHandler = new HandlerCollection();
            multiHandler.setHandlers(new Handler[] { handler, requestLogHandler });
            statHandler.setHandler(multiHandler);
        }

        server.setHandler(statHandler);
        server.setAttribute("base", base);

        // return configured server
        return server;
    }

    private ServerConnector createServerConnector(final Server server, final String host,
            final int port, final int acceptors, final int selectors,
            final ConnectionFactory... connectionFactories) {

        final ServerConnector connector = new ServerConnector(server, null, null, null, acceptors,
                selectors, connectionFactories);
        connector.setHost(host);
        connector.setPort(port);
        connector.setReuseAddress(true); // to avoid respawning issues with supervisord
        connector.addBean(new ConnectorStatistics());
        return connector;
    }

    private HttpConnectionFactory createHttpConnectionFactory(final int httpsPort,
            final boolean customizer) {

        final HttpConfiguration config = new HttpConfiguration();
        if (httpsPort > 0) {
            config.setSecureScheme("https");
            config.setSecurePort(httpsPort);
            if (customizer) {
                config.addCustomizer(new SecureRequestCustomizer());
            }
        }

        config.setOutputBufferSize(32 * 1024);
        config.setRequestHeaderSize(8 * 1024);
        config.setResponseHeaderSize(8 * 1024);
        config.setSendServerVersion(true);
        config.setSendDateHeader(true);
        final HttpConnectionFactory factory = new HttpConnectionFactory(config);
        return factory;
    }

    private SslConnectionFactory createSSLConnectionFactory(final KeystoreConfig keystore) {

        final Resource keystoreResource = Resource.newResource(keystore.getURL());

        final SslContextFactory contextFactory = new SslContextFactory();
        contextFactory.setEnableCRLDP(false);
        contextFactory.setEnableOCSP(false);
        contextFactory.setNeedClientAuth(false);
        contextFactory.setWantClientAuth(false);
        contextFactory.setValidateCerts(false);
        contextFactory.setValidatePeerCerts(false);
        contextFactory.setRenegotiationAllowed(false); // avoid vulnerability
        contextFactory.setSessionCachingEnabled(true); // optimization
        contextFactory.setKeyStoreResource(keystoreResource);
        contextFactory.setKeyStorePassword(keystore.getPassword());
        contextFactory.setCertAlias(keystore.getAlias());
        contextFactory.setKeyStoreType(keystore.getType());
        contextFactory.setIncludeCipherSuites("TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
                "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA",
                "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
                "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA"); // check RFC 5430

        final SslConnectionFactory factory = new SslConnectionFactory(contextFactory, "http/1.1");
        return factory;
    }

    private URL createWebXml(final String realmName,
            final Iterable anonymousRoles, final boolean debug) {
        try {
            // load web.xml from classpath
            String webxml = Resources.toString(HttpServer.class.getResource("HttpServer.web.xml"),
                    Charsets.UTF_8);

            // disable TeeFilter if not in debug mode
            if (!debug) {
                int index = webxml.indexOf("TeeFilter");
                final int start1 = webxml.lastIndexOf("", index);
                final int end1 = webxml.indexOf("", index) + 9;
                index = webxml.indexOf("TeeFilter", end1);
                final int start2 = webxml.lastIndexOf("", index);
                final int end2 = webxml.indexOf("", index) + 17;
                webxml = webxml.substring(0, start1) + webxml.substring(end1, start2)
                        + webxml.substring(end2);
            }

            // replace references to 
            StringBuilder builder = new StringBuilder();
            int index = 0;
            int last = 0;
            while ((index = webxml.indexOf("", last)) >= 0) {
                index = index + 12;
                builder.append(webxml.substring(last, index));
                builder.append(realmName);
                index = webxml.indexOf("", index);
                last = index + 13;
                builder.append(webxml.substring(index, last));
            }
            builder.append(webxml.substring(last));
            webxml = builder.toString();

            // disable security constraints for anonymous roles
            builder = new StringBuilder();
            last = 0;
            while ((index = webxml.indexOf("", last)) >= 0) {
                builder.append(webxml.substring(last, index));
                last = webxml.indexOf("", index) + 22;
                final int start1 = index;
                int end1 = -1;
                final Set roles = Sets.newHashSet();
                int start2 = index + 21;
                while (true) {
                    index = webxml.indexOf("", start2);
                    if (index < 0 || index >= last) {
                        break;
                    }
                    end1 = end1 >= 0 ? end1 : index;
                    index += 11;
                    final int roleStart = index;
                    index = webxml.indexOf("", index);
                    roles.add(webxml.substring(roleStart, index));
                    start2 = index + 12;
                }
                last = webxml.indexOf("", start2) + 22;
                for (final String role : anonymousRoles) {
                    roles.remove(role);
                }
                if (!roles.isEmpty()) {
                    builder.append(webxml.substring(start1, end1));
                    for (final String role : roles) {
                        builder.append("").append(role).append("");
                    }
                    builder.append(webxml.substring(start2, last));
                }
            }
            builder.append(webxml.substring(last));
            webxml = builder.toString();

            // save filtered web.xml to temporary file, returning associated URL
            final File file = File.createTempFile("knowledgestore_", ".web.xml");
            file.deleteOnExit();
            com.google.common.io.Files.write(webxml, file, Charsets.UTF_8);
            return file.toURI().toURL();

        } catch (final Exception ex) {
            throw new Error("Cannot configure web.xml descriptor: " + ex.getMessage(), ex);
        }
    }

    public static Builder builder(final KnowledgeStore delegate) {
        return new Builder(delegate);
    }

    public static class Builder {

        final KnowledgeStore delegate;

        @Nullable
        String host;

        @Nullable
        Integer httpPort;

        @Nullable
        Integer httpsPort;

        @Nullable
        String path;

        @Nullable
        String proxyHttpRoot; // TODO: handle this

        @Nullable
        String proxyHttpsRoot; // TODO: handle this

        @Nullable
        KeystoreConfig keystoreConfig;

        @Nullable
        Integer acceptors;

        @Nullable
        Integer selectors;

        @Nullable
        SecurityConfig securityConfig;

        @Nullable
        UIConfig uiConfig;

        @Nullable
        Boolean debug;

        @Nullable
        String logConfigLocation;

        Builder(final KnowledgeStore store) {
            this.delegate = Preconditions.checkNotNull(store);
        }

        public Builder host(@Nullable final String host) {
            this.host = host;
            return this;
        }

        public Builder httpPort(@Nullable final Integer httpPort) {
            this.httpPort = httpPort;
            return this;
        }

        public Builder httpsPort(@Nullable final Integer httpsPort) {
            this.httpsPort = httpsPort;
            return this;
        }

        public Builder path(@Nullable final String path) {
            this.path = path;
            return this;
        }

        public Builder proxyHttpRoot(@Nullable final String proxyHttpRoot) {
            this.proxyHttpRoot = proxyHttpRoot;
            return this;
        }

        public Builder proxyHttpsRoot(@Nullable final String proxyHttpsRoot) {
            this.proxyHttpsRoot = proxyHttpsRoot;
            return this;
        }

        public Builder acceptors(@Nullable final Integer acceptors) {
            this.acceptors = acceptors;
            return this;
        }

        public Builder selectors(@Nullable final Integer selectors) {
            this.selectors = selectors;
            return this;
        }

        public Builder keystoreConfig(@Nullable final KeystoreConfig keystoreConfig) {
            this.keystoreConfig = keystoreConfig;
            return this;
        }

        public Builder securityConfig(@Nullable final SecurityConfig securityConfig) {
            this.securityConfig = securityConfig;
            return this;
        }

        public Builder uiConfig(@Nullable final UIConfig uiConfig) {
            this.uiConfig = uiConfig;
            return this;
        }

        public Builder debug(@Nullable final Boolean debug) {
            this.debug = debug;
            return this;
        }

        public Builder logConfigLocation(@Nullable final String logConfigLocation) {
            this.logConfigLocation = logConfigLocation;
            return this;
        }

        public HttpServer build() {
            return new HttpServer(this);
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy