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

org.graylog2.bootstrap.preflight.PreflightJerseyService Maven / Gradle / Ivy

There is a newer version: 6.1.4
Show newest version
/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * .
 */
package org.graylog2.bootstrap.preflight;

import com.codahale.metrics.InstrumentedExecutorService;
import com.codahale.metrics.MetricRegistry;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.jakarta.rs.base.JsonMappingExceptionMapper;
import com.fasterxml.jackson.jakarta.rs.json.JacksonXmlBindJsonProvider;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.HostAndPort;
import com.google.common.util.concurrent.AbstractIdleService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.ext.ContextResolver;
import org.apache.commons.codec.digest.DigestUtils;
import org.glassfish.grizzly.http.CompressionConfig;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.model.Resource;
import org.graylog2.Configuration;
import org.graylog2.bootstrap.preflight.web.BasicAuthFilter;
import org.graylog2.configuration.HttpConfiguration;
import org.graylog2.rest.MoreMediaTypes;
import org.graylog2.shared.rest.exceptionmappers.AnyExceptionClassMapper;
import org.graylog2.shared.rest.exceptionmappers.JacksonPropertyExceptionMapper;
import org.graylog2.shared.rest.exceptionmappers.JsonProcessingExceptionMapper;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jakarta.inject.Inject;

import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import static com.codahale.metrics.MetricRegistry.name;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Objects.requireNonNull;

public class PreflightJerseyService extends AbstractIdleService {
    private static final Logger LOG = LoggerFactory.getLogger(PreflightJerseyService.class);

    private final HttpConfiguration configuration;
    private final Set> systemRestResources;

    private final ObjectMapper objectMapper;
    private final MetricRegistry metricRegistry;
    private final Configuration localConfiguration;
    private final PreflightConfigService preflightConfigService;

    private HttpServer apiHttpServer = null;

    @Inject
    public PreflightJerseyService(final HttpConfiguration httpConfiguration,
                                  final Configuration localConfiguration,
                                  @PreflightRestResourcesBinding final Set> systemRestResources,
                                  ObjectMapper objectMapper,
                                  MetricRegistry metricRegistry,
                                  PreflightConfigService preflightConfigService) {
        this.configuration = requireNonNull(httpConfiguration, "configuration");
        this.localConfiguration = requireNonNull(localConfiguration, "localConfiguration");
        this.preflightConfigService = requireNonNull(preflightConfigService, "preflightConfigService");
        this.systemRestResources = systemRestResources;
        this.objectMapper = requireNonNull(objectMapper, "objectMapper");
        this.metricRegistry = requireNonNull(metricRegistry, "metricRegistry");
    }

    @Override
    protected void startUp() throws Exception {
        // we need to work around the change introduced in https://github.com/GrizzlyNIO/grizzly-mirror/commit/ba9beb2d137e708e00caf7c22603532f753ec850
        // because the PooledMemoryManager which is default now uses 10% of the heap no matter what
        System.setProperty("org.glassfish.grizzly.DEFAULT_MEMORY_MANAGER", "org.glassfish.grizzly.memory.HeapMemoryManager");
        startUpApi();
    }

    @Override
    protected void shutDown() throws Exception {
        shutdownHttpServer(apiHttpServer, configuration.getHttpBindAddress());
    }

    private void shutdownHttpServer(HttpServer httpServer, HostAndPort bindAddress) {
        if (httpServer != null && httpServer.isStarted()) {
            LOG.info("Shutting down HTTP listener at <{}>", bindAddress);
            httpServer.shutdownNow();
        }
    }

    private void startUpApi() throws Exception {
        final SSLEngineConfigurator sslEngineConfigurator = null;

        final HostAndPort bindAddress = configuration.getHttpBindAddress();
        final String contextPath = configuration.getHttpPublishUri().getPath();
        final URI listenUri = new URI(
                configuration.getUriScheme(),
                null,
                bindAddress.getHost(),
                bindAddress.getPort(),
                isNullOrEmpty(contextPath) ? "/" : contextPath,
                null,
                null
        );

        apiHttpServer = setUp(
                listenUri,
                sslEngineConfigurator,
                configuration.getHttpThreadPoolSize(),
                configuration.getHttpSelectorRunnersCount(),
                configuration.getHttpMaxHeaderSize(),
                configuration.isHttpEnableGzip(),
                Set.of());

        apiHttpServer.start();

        final var username = localConfiguration.getRootUsername();
        final var preflightPassword = preflightConfigService.getPreflightPassword();
        logBanner(bindAddress, username, preflightPassword);
    }

    private void logBanner(HostAndPort bindAddress, String username, String preflightPassword) {
        var banner = """

                                                                             ---
                                                                             ---
                                                                             ---
                    ########  ###   ######### ##########   ####         #### ---         .----               ----
                  ###############   ###################### #####       ####  ---      ------------       .----------- --
                 #####     ######   #####              #### ####      ####   ---     ---        ---     ---        -----
                ####         ####   ####       ############  ####     ####   ---    --           ---   ---           ---
                ###           ###   ####     ##############   ####   ####    ---   ---            --   --             --
                ####         ####   ####    ####       ####    #### ####     ---   ---            --   --            .--
                #####       #####   ####    ####       ####     #######      ---    ---          ---   ---           ---
                 ################   ####     ##############     ######-       --     ----      ----      ---       -----
                   ##############   ####      #############      #####        -----   -----------         ----------  --
                             ####                                ####                                                ---
                #####       ####                                ####                                     -          .--
                  #############                                ####                                     -----     ----
                     ######                                   ####                                          -------

                ========================================================================================================

                It seems you are starting Graylog for the first time. To set up a fresh install, a setup interface has
                been started. You must log in to it to perform the initial configuration and continue.

                Initial configuration is accessible at %s, with username '%s' and password '%s'.
                Try clicking on http://%s:%s@%s

                ========================================================================================================
                """.formatted(bindAddress, username, preflightPassword, username, preflightPassword, bindAddress);

        LOG.info(banner);
    }

    private ResourceConfig buildResourceConfig(final Set additionalResources) {
        return new ResourceConfig()
                .property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true)
                .property(ServerProperties.WADL_FEATURE_DISABLE, true)
                .property(ServerProperties.MEDIA_TYPE_MAPPINGS, mediaTypeMappings())
                .registerClasses(
                        JacksonXmlBindJsonProvider.class,
                        JsonProcessingExceptionMapper.class,
                        JsonMappingExceptionMapper.class,
                        JacksonPropertyExceptionMapper.class,
                        AnyExceptionClassMapper.class
                )
                // Replacing this with a lambda leads to missing subtypes - https://github.com/Graylog2/graylog2-server/pull/10617#discussion_r630236360
                .register(new ContextResolver() {
                    @Override
                    public ObjectMapper getContext(Class type) {
                        return objectMapper;
                    }
                })
                .register(MultiPartFeature.class)
                .registerClasses(systemRestResources)
                .registerResources(additionalResources)
                .register(createBasicAuthFilter(localConfiguration, preflightConfigService));
    }

    @NotNull
    private BasicAuthFilter createBasicAuthFilter(Configuration localConfiguration, PreflightConfigService preflightConfigService) {
        final String username = localConfiguration.getRootUsername();
        final String preflightPassword = preflightConfigService.getPreflightPassword();
        return new BasicAuthFilter(username, DigestUtils.sha256Hex(preflightPassword), "preflight-config");
    }

    private Map mediaTypeMappings() {
        return ImmutableMap.of(
                "json", MediaType.APPLICATION_JSON_TYPE,
                "ndjson", MoreMediaTypes.APPLICATION_NDJSON_TYPE,
                "csv", MoreMediaTypes.TEXT_CSV_TYPE,
                "log", MoreMediaTypes.TEXT_PLAIN_TYPE
        );
    }

    private HttpServer setUp(URI listenUri,
                             SSLEngineConfigurator sslEngineConfigurator,
                             int threadPoolSize,
                             int selectorRunnersCount,
                             int maxHeaderSize,
                             boolean enableGzip,
                             Set additionalResources) {
        final ResourceConfig resourceConfig = buildResourceConfig(additionalResources);
        final HttpServer httpServer = GrizzlyHttpServerFactory.createHttpServer(
                listenUri,
                resourceConfig,
                sslEngineConfigurator != null,
                sslEngineConfigurator,
                false);

        final NetworkListener listener = httpServer.getListener("grizzly");
        listener.setMaxHttpHeaderSize(maxHeaderSize);

        final ExecutorService workerThreadPoolExecutor = instrumentedExecutor(
                "http-worker-executor",
                "http-worker-%d",
                threadPoolSize);
        listener.getTransport().setWorkerThreadPool(workerThreadPoolExecutor);

        // The Grizzly default value is equal to `Runtime.getRuntime().availableProcessors()` which doesn't make
        // sense for Graylog because we are not mainly a web server.
        // See "Selector runners count" at https://grizzly.java.net/bestpractices.html for details.
        listener.getTransport().setSelectorRunnersCount(selectorRunnersCount);


        if (enableGzip) {
            final CompressionConfig compressionConfig = listener.getCompressionConfig();
            compressionConfig.setCompressionMode(CompressionConfig.CompressionMode.ON);
            compressionConfig.setCompressionMinSize(512);
        }

        return httpServer;
    }

    private ExecutorService instrumentedExecutor(final String executorName,
                                                 final String threadNameFormat,
                                                 int poolSize) {
        final ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setNameFormat(threadNameFormat)
                .setDaemon(true)
                .build();

        return new InstrumentedExecutorService(
                Executors.newFixedThreadPool(poolSize, threadFactory),
                metricRegistry,
                name(PreflightJerseyService.class, executorName));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy