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

com.linecorp.armeria.server.AbstractVirtualHostBuilder Maven / Gradle / Ivy

Go to download

Asynchronous HTTP/2 RPC/REST client/server library built on top of Java 8, Netty, Thrift and GRPC (armeria-shaded)

There is a newer version: 0.75.0
Show newest version
/*
 * Copyright 2016 LINE Corporation
 *
 * LINE Corporation 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:
 *
 *   https://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.linecorp.armeria.server;

import static com.linecorp.armeria.server.VirtualHost.ensureHostnamePatternMatchesDefaultHostname;
import static com.linecorp.armeria.server.VirtualHost.normalizeDefaultHostname;
import static com.linecorp.armeria.server.VirtualHost.normalizeHostnamePattern;
import static java.util.Objects.requireNonNull;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

import javax.annotation.Nullable;
import javax.net.ssl.SSLException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableList;

import com.linecorp.armeria.common.Flags;
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.common.MediaTypeSet;
import com.linecorp.armeria.common.SessionProtocol;
import com.linecorp.armeria.internal.crypto.BouncyCastleKeyFactoryProvider;
import com.linecorp.armeria.server.AnnotatedHttpServiceFactory.AnnotatedHttpServiceElement;
import com.linecorp.armeria.server.annotation.ExceptionHandlerFunction;
import com.linecorp.armeria.server.annotation.RequestConverterFunction;
import com.linecorp.armeria.server.annotation.ResponseConverterFunction;

import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.SelfSignedCertificate;

/**
 * Contains information for the build of the virtual host.
 *
 * @see ChainedVirtualHostBuilder
 * @see VirtualHostBuilder
 */
@SuppressWarnings("rawtypes")
abstract class AbstractVirtualHostBuilder {

    private static final Logger logger = LoggerFactory.getLogger(AbstractVirtualHostBuilder.class);

    private static final ApplicationProtocolConfig HTTPS_ALPN_CFG = new ApplicationProtocolConfig(
            Protocol.ALPN,
            // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers.
            SelectorFailureBehavior.NO_ADVERTISE,
            // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers.
            SelectedListenerFailureBehavior.ACCEPT,
            ApplicationProtocolNames.HTTP_2,
            ApplicationProtocolNames.HTTP_1_1);

    private static final String LOCAL_HOSTNAME;

    static {
        // Try the '/usr/bin/hostname' command first, which is more reliable.
        Process process = null;
        String hostname = null;
        try {
            process = Runtime.getRuntime().exec("hostname");
            final String line = new BufferedReader(new InputStreamReader(process.getInputStream())).readLine();
            if (line == null) {
                logger.debug("The 'hostname' command returned nothing; " +
                             "using InetAddress.getLocalHost() instead");
            } else {
                hostname = normalizeDefaultHostname(line.trim());
                logger.info("Hostname: {} (from 'hostname' command)", hostname);
            }
        } catch (Exception e) {
            logger.debug("Failed to get the hostname using the 'hostname' command; " +
                         "using InetAddress.getLocalHost() instead", e);
        } finally {
            if (process != null) {
                process.destroy();
            }
        }

        if (hostname == null) {
            try {
                hostname = normalizeDefaultHostname(InetAddress.getLocalHost().getHostName());
                logger.info("Hostname: {} (from InetAddress.getLocalHost())", hostname);
            } catch (Exception e) {
                hostname = "localhost";
                logger.warn("Failed to get the hostname using InetAddress.getLocalHost(); " +
                            "using 'localhost' instead", e);
            }
        }

        LOCAL_HOSTNAME = hostname;
    }

    private final String defaultHostname;
    private final String hostnamePattern;
    private final List services = new ArrayList<>();
    @Nullable
    private SslContext sslContext;
    @Nullable
    private Function, Service> decorator;

    /**
     * Creates a new {@link VirtualHostBuilder} whose hostname pattern is {@code "*"} (match-all).
     */
    AbstractVirtualHostBuilder() {
        this(LOCAL_HOSTNAME, "*");
    }

    /**
     * Creates a new {@link VirtualHostBuilder} with the specified hostname pattern.
     */
    AbstractVirtualHostBuilder(String hostnamePattern) {
        hostnamePattern = normalizeHostnamePattern(hostnamePattern);

        if ("*".equals(hostnamePattern)) {
            defaultHostname = LOCAL_HOSTNAME;
        } else if (hostnamePattern.startsWith("*.")) {
            defaultHostname = hostnamePattern.substring(2);
        } else {
            defaultHostname = hostnamePattern;
        }

        this.hostnamePattern = hostnamePattern;
    }

    /**
     * Creates a new {@link VirtualHostBuilder} with
     * the default host name and the specified hostname pattern.
     */
    AbstractVirtualHostBuilder(String defaultHostname, String hostnamePattern) {
        requireNonNull(defaultHostname, "defaultHostname");

        defaultHostname = normalizeDefaultHostname(defaultHostname);
        hostnamePattern = normalizeHostnamePattern(hostnamePattern);
        ensureHostnamePatternMatchesDefaultHostname(hostnamePattern, defaultHostname);

        this.defaultHostname = defaultHostname;
        this.hostnamePattern = hostnamePattern;
    }

    /**
     * Configures SSL or TLS of this {@link VirtualHost} with the specified {@link SslContext}.
     */
    public B tls(SslContext sslContext) {
        this.sslContext = VirtualHost.validateSslContext(requireNonNull(sslContext, "sslContext"));
        return self();
    }

    /**
     * Configures SSL or TLS of this {@link VirtualHost} with the specified {@code keyCertChainFile}
     * and cleartext {@code keyFile}.
     */
    public B tls(File keyCertChainFile, File keyFile) throws SSLException {
        tls(keyCertChainFile, keyFile, null);
        return self();
    }

    /**
     * Configures SSL or TLS of this {@link VirtualHost} with the specified {@code keyCertChainFile},
     * {@code keyFile} and {@code keyPassword}.
     */
    public B tls(File keyCertChainFile, File keyFile, @Nullable String keyPassword) throws SSLException {
        if (!keyCertChainFile.exists()) {
            throw new SSLException("non-existent certificate chain file: " + keyCertChainFile);
        }
        if (!keyCertChainFile.canRead()) {
            throw new SSLException("cannot read certificate chain file: " + keyCertChainFile);
        }
        if (!keyFile.exists()) {
            throw new SSLException("non-existent key file: " + keyFile);
        }
        if (!keyFile.canRead()) {
            throw new SSLException("cannot read key file: " + keyFile);
        }

        final SslContext sslCtx;

        try {
            sslCtx = BouncyCastleKeyFactoryProvider.call(() -> {
                final SslContextBuilder builder =
                        SslContextBuilder.forServer(keyCertChainFile, keyFile, keyPassword);

                builder.sslProvider(Flags.useOpenSsl() ? SslProvider.OPENSSL : SslProvider.JDK);
                builder.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE);
                builder.applicationProtocolConfig(HTTPS_ALPN_CFG);

                return builder.build();
            });
        } catch (RuntimeException | SSLException e) {
            throw e;
        } catch (Exception e) {
            throw new SSLException("failed to configure TLS: " + e, e);
        }

        tls(sslCtx);
        return self();
    }

    /**
     * Configures SSL or TLS of this {@link VirtualHost} with an auto-generated self-signed certificate.
     * Note: You should never use this in production but only for a testing purpose.
     *
     * @throws CertificateException if failed to generate a self-signed certificate
     */
    public B tlsSelfSigned() throws SSLException, CertificateException {
        final SelfSignedCertificate ssc = new SelfSignedCertificate(defaultHostname);
        return tls(ssc.certificate(), ssc.privateKey());
    }

    /**
     * Sets the {@link SslContext} of this {@link VirtualHost}.
     *
     * @deprecated Use {@link #tls(SslContext)}.
     */
    @Deprecated
    public B sslContext(SslContext sslContext) {
        return tls(sslContext);
    }

    /**
     * Sets the {@link SslContext} of this {@link VirtualHost} from the specified {@link SessionProtocol},
     * {@code keyCertChainFile} and cleartext {@code keyFile}.
     *
     * @deprecated Use {@link #tls(File, File)}.
     */
    @Deprecated
    public B sslContext(
            SessionProtocol protocol, File keyCertChainFile, File keyFile) throws SSLException {
        sslContext(protocol, keyCertChainFile, keyFile, null);
        return self();
    }

    /**
     * Sets the {@link SslContext} of this {@link VirtualHost} from the specified {@link SessionProtocol},
     * {@code keyCertChainFile}, {@code keyFile} and {@code keyPassword}.
     *
     * @deprecated Use {@link #tls(File, File, String)}.
     */
    @Deprecated
    public B sslContext(
            SessionProtocol protocol,
            File keyCertChainFile, File keyFile, @Nullable String keyPassword) throws SSLException {

        if (requireNonNull(protocol, "protocol") != SessionProtocol.HTTPS) {
            throw new IllegalArgumentException("unsupported protocol: " + protocol);
        }

        return tls(keyCertChainFile, keyFile, keyPassword);
    }

    /**
     * Binds the specified {@link Service} at the specified path pattern.
     *
     * @deprecated Use {@link #service(String, Service)} instead.
     */
    @Deprecated
    public B serviceAt(String pathPattern, Service service) {
        return service(pathPattern, service);
    }

    /**
     * Binds the specified {@link Service} under the specified directory.
     */
    public B serviceUnder(String pathPrefix, Service service) {
        service(PathMapping.ofPrefix(pathPrefix), service);
        return self();
    }

    /**
     * Binds the specified {@link Service} at the specified path pattern. e.g.
     * 
    *
  • {@code /login} (no path parameters)
  • *
  • {@code /users/{userId}} (curly-brace style)
  • *
  • {@code /list/:productType/by/:ordering} (colon style)
  • *
  • {@code exact:/foo/bar} (exact match)
  • *
  • {@code prefix:/files} (prefix match)
  • *
  • glob:/~*/downloads/** (glob pattern)
  • *
  • {@code regex:^/files/(?.*)$} (regular expression)
  • *
* * @throws IllegalArgumentException if the specified path pattern is invalid */ public B service(String pathPattern, Service service) { service(PathMapping.of(pathPattern), service); return self(); } /** * Binds the specified {@link Service} at the specified {@link PathMapping}. */ public B service(PathMapping pathMapping, Service service) { services.add(new ServiceConfig(pathMapping, service, null)); return self(); } /** * Binds the specified {@link Service} at the specified {@link PathMapping}. * * @deprecated Use a logging framework integration such as {@code RequestContextExportingAppender} in * {@code armeria-logback}. */ @Deprecated public B service(PathMapping pathMapping, Service service, String loggerName) { services.add(new ServiceConfig(pathMapping, service, loggerName)); return self(); } /** * Binds the specified {@link ServiceWithPathMappings} at multiple {@link PathMapping}s. */ public > B service(T serviceWithPathMappings) { return service(serviceWithPathMappings, Function.identity()); } /** * Decorates and binds the specified {@link ServiceWithPathMappings} at multiple {@link PathMapping}s. */ public , R extends Service> B service(T serviceWithPathMappings, Function decorator) { requireNonNull(serviceWithPathMappings, "serviceWithPathMappings"); requireNonNull(serviceWithPathMappings.pathMappings(), "serviceWithPathMappings.pathMappings()"); requireNonNull(decorator, "decorator"); final Service decorated = decorator.apply(serviceWithPathMappings); serviceWithPathMappings.pathMappings().forEach(pathMapping -> service(pathMapping, decorated)); return self(); } /** * Binds the specified annotated service object under the path prefix {@code "/"}. */ public B annotatedService(Object service) { return annotatedService("/", service, Function.identity(), ImmutableList.of()); } /** * Binds the specified annotated service object under the path prefix {@code "/"}. * * @param exceptionHandlersAndConverters instances of {@link ExceptionHandlerFunction}, * {@link RequestConverterFunction} and/or * {@link ResponseConverterFunction} */ public B annotatedService(Object service, Object... exceptionHandlersAndConverters) { return annotatedService("/", service, Function.identity(), ImmutableList.copyOf(requireNonNull(exceptionHandlersAndConverters, "exceptionHandlersAndConverters"))); } /** * Binds the specified annotated service object under the path prefix {@code "/"}. * * @param exceptionHandlersAndConverters instances of {@link ExceptionHandlerFunction}, * {@link RequestConverterFunction} and/or * {@link ResponseConverterFunction} */ public B annotatedService(Object service, Function, ? extends Service> decorator, Object... exceptionHandlersAndConverters) { return annotatedService("/", service, decorator, requireNonNull(exceptionHandlersAndConverters, "exceptionHandlersAndConverters")); } /** * Binds the specified annotated service object under the specified path prefix. */ public B annotatedService(String pathPrefix, Object service) { return annotatedService(pathPrefix, service, Function.identity(), ImmutableList.of()); } /** * Binds the specified annotated service object under the specified path prefix. * * @param exceptionHandlersAndConverters instances of {@link ExceptionHandlerFunction}, * {@link RequestConverterFunction} and/or * {@link ResponseConverterFunction} */ public B annotatedService(String pathPrefix, Object service, Object... exceptionHandlersAndConverters) { return annotatedService(pathPrefix, service, Function.identity(), ImmutableList.copyOf(requireNonNull(exceptionHandlersAndConverters, "exceptionHandlersAndConverters"))); } /** * Binds the specified annotated service object under the specified path prefix. * * @param exceptionHandlersAndConverters instances of {@link ExceptionHandlerFunction}, * {@link RequestConverterFunction} and/or * {@link ResponseConverterFunction} */ public B annotatedService(String pathPrefix, Object service, Function, ? extends Service> decorator, Object... exceptionHandlersAndConverters) { return annotatedService(pathPrefix, service, decorator, ImmutableList.copyOf(requireNonNull(exceptionHandlersAndConverters, "exceptionHandlersAndConverters"))); } /** * Binds the specified annotated service object under the specified path prefix. * * @param exceptionHandlersAndConverters an iterable object of {@link ExceptionHandlerFunction}, * {@link RequestConverterFunction} and/or * {@link ResponseConverterFunction} */ public B annotatedService(String pathPrefix, Object service, Function, ? extends Service> decorator, Iterable exceptionHandlersAndConverters) { requireNonNull(pathPrefix, "pathPrefix"); requireNonNull(service, "service"); requireNonNull(decorator, "decorator"); requireNonNull(exceptionHandlersAndConverters, "exceptionHandlersAndConverters"); final List elements = AnnotatedHttpServiceFactory.find(pathPrefix, service, exceptionHandlersAndConverters); elements.forEach(e -> service(e.pathMapping(), decorator.apply(e.decorator().apply(e.service())))); return self(); } /** * Decorates all {@link Service}s with the specified {@code decorator}. * * @param decorator the {@link Function} that decorates a {@link Service} * @param the type of the {@link Service} being decorated * @param the type of the {@link Service} {@code decorator} will produce */ public , R extends Service> B decorator(Function decorator) { requireNonNull(decorator, "decorator"); @SuppressWarnings("unchecked") final Function, Service> castDecorator = (Function, Service>) decorator; if (this.decorator != null) { this.decorator = this.decorator.andThen(castDecorator); } else { this.decorator = castDecorator; } return self(); } @SuppressWarnings("unchecked") final B self() { return (B) this; } /** * Returns a newly-created {@link VirtualHost} based on the properties of this builder and the services * added to this builder. */ protected VirtualHost build() { final List producibleTypes = new ArrayList<>(); services.forEach(e -> { final PathMapping mapping = e.pathMapping(); if (mapping instanceof HttpHeaderPathMapping) { // Collect producible media types over this virtual host. producibleTypes.addAll(((HttpHeaderPathMapping) mapping).produceTypes()); } }); final VirtualHost virtualHost = new VirtualHost(defaultHostname, hostnamePattern, sslContext, services, new MediaTypeSet(producibleTypes)); return decorator != null ? virtualHost.decorate(decorator) : virtualHost; } @Override public String toString() { return VirtualHost.toString(getClass(), defaultHostname, hostnamePattern, sslContext, services); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy