com.linecorp.armeria.server.Server Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of armeria-shaded Show documentation
Show all versions of armeria-shaded Show documentation
Asynchronous HTTP/2 RPC/REST client/server library built on top of Java 8, Netty, Thrift and GRPC (armeria-shaded)
/*
* Copyright 2015 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 java.util.Objects.requireNonNull;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.linecorp.armeria.common.SessionProtocol;
import com.linecorp.armeria.common.metric.MeterIdPrefix;
import com.linecorp.armeria.common.util.EventLoopGroups;
import com.linecorp.armeria.common.util.StartStopSupport;
import com.linecorp.armeria.internal.ChannelUtil;
import com.linecorp.armeria.internal.ConnectionLimitingHandler;
import com.linecorp.armeria.internal.PathAndQuery;
import com.linecorp.armeria.internal.TransportType;
import io.micrometer.core.instrument.MeterRegistry;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.handler.ssl.SslContext;
import io.netty.util.DomainNameMapping;
import io.netty.util.concurrent.FastThreadLocalThread;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.ImmediateEventExecutor;
/**
* Listens to {@link ServerPort}s and delegates client requests to {@link Service}s.
*
* @see ServerBuilder
*/
public final class Server implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(Server.class);
private final ServerConfig config;
@Nullable
private final DomainNameMapping sslContexts;
private final StartStopSupport startStop = new ServerStartStopSupport();
private final Set serverChannels = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Map activePorts = new ConcurrentHashMap<>();
private final Map unmodifiableActivePorts =
Collections.unmodifiableMap(activePorts);
private final ConnectionLimitingHandler connectionLimitingHandler;
@Nullable
private volatile ServerPort primaryActivePort;
@Nullable
private ServerBootstrap serverBootstrap;
Server(ServerConfig config, @Nullable DomainNameMapping sslContexts) {
this.config = requireNonNull(config, "config");
this.sslContexts = sslContexts;
config.setServer(this);
// Invoke the serviceAdded() method in Service so that it can keep the reference to this Server or
// add a listener to it.
config.serviceConfigs().forEach(cfg -> ServiceCallbackInvoker.invokeServiceAdded(cfg, cfg.service()));
connectionLimitingHandler = new ConnectionLimitingHandler(config.maxNumConnections());
// Server-wide cache metrics.
final MeterIdPrefix idPrefix = new MeterIdPrefix("armeria.server.parsedPathCache");
PathAndQuery.registerMetrics(config.meterRegistry(), idPrefix);
}
/**
* Returns the configuration of this {@link Server}.
*/
public ServerConfig config() {
return config;
}
/**
* Returns the hostname of the default {@link VirtualHost}, which is the hostname of the machine unless
* configured explicitly via {@link ServerBuilder#defaultVirtualHost(VirtualHost)}.
*/
public String defaultHostname() {
return config().defaultVirtualHost().defaultHostname();
}
/**
* Returns all {@link ServerPort}s that this {@link Server} is listening to.
*
* @return a {@link Map} whose key is the bind address and value is {@link ServerPort}.
* an empty {@link Map} if this {@link Server} did not start.
*
* @see Server#activePort()
*/
public Map activePorts() {
return unmodifiableActivePorts;
}
/**
* Returns the primary {@link ServerPort} that this {@link Server} is listening to. This method is useful
* when a {@link Server} listens to only one {@link ServerPort}.
*
* @return {@link Optional#empty()} if this {@link Server} did not start
*/
public Optional activePort() {
return Optional.ofNullable(primaryActivePort);
}
@Nullable
@VisibleForTesting
ServerBootstrap serverBootstrap() {
return serverBootstrap;
}
/**
* Returns the {@link MeterRegistry} that collects various stats.
*/
public MeterRegistry meterRegistry() {
return config().meterRegistry();
}
/**
* Adds the specified {@link ServerListener} to this {@link Server}, so that it is notified when the state
* of this {@link Server} changes. This method is useful when you want to initialize/destroy the resources
* associated with a {@link Service}:
* {@code
* > public class MyService extends SimpleService {
* > @Override
* > public void serviceAdded(Server server) {
* > server.addListener(new ServerListenerAdapter() {
* > @Override
* > public void serverStarting() {
* > ... initialize ...
* > }
* >
* > @Override
* > public void serverStopped() {
* > ... destroy ...
* > }
* > }
* > }
* > }
* }
*/
public void addListener(ServerListener listener) {
startStop.addListener(requireNonNull(listener, "listener"));
}
/**
* Removes the specified {@link ServerListener} from this {@link Server}, so that it is not notified
* anymore when the state of this {@link Server} changes.
*/
public boolean removeListener(ServerListener listener) {
return startStop.removeListener(requireNonNull(listener, "listener"));
}
/**
* Starts this {@link Server} to listen to the {@link ServerPort}s specified in the {@link ServerConfig}.
* Note that the startup procedure is asynchronous and thus this method returns immediately. To wait until
* this {@link Server} is fully started up, wait for the returned {@link CompletableFuture}:
* {@code
* ServerBuilder builder = new ServerBuilder();
* ...
* Server server = builder.build();
* server.start().get();
* }
*/
public CompletableFuture start() {
return startStop.start(true);
}
/**
* Stops this {@link Server} to close all active {@link ServerPort}s. Note that the shutdown procedure is
* asynchronous and thus this method returns immediately. To wait until this {@link Server} is fully
* shut down, wait for the returned {@link CompletableFuture}:
* {@code
* Server server = ...;
* server.stop().get();
* }
*/
public CompletableFuture stop() {
return startStop.stop();
}
/**
* Returns a {@link EventLoop} from the worker group. This can be used for, e.g., scheduling background
* tasks for the lifetime of the {@link Server} using
* {@link EventLoop#scheduleAtFixedRate(Runnable, long, long, TimeUnit)}. It is very important that these
* tasks do not block as this would block all requests in the server on that {@link EventLoop}.
*/
public EventLoop nextEventLoop() {
return config().workerGroup().next();
}
/**
* A shortcut to {@link #stop() stop().get()}.
*/
@Override
public void close() {
startStop.close();
}
/**
* Returns the number of open connections on this {@link Server}.
*/
public int numConnections() {
return connectionLimitingHandler.numConnections();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("config", config())
.add("activePorts", activePorts())
.add("state", startStop)
.toString();
}
private final class ServerStartStopSupport extends StartStopSupport {
private volatile GracefulShutdownSupport gracefulShutdownSupport = GracefulShutdownSupport.disabled();
ServerStartStopSupport() {
super(GlobalEventExecutor.INSTANCE);
}
@Override
protected CompletionStage doStart() throws Exception {
if (config().gracefulShutdownQuietPeriod().isZero()) {
gracefulShutdownSupport = GracefulShutdownSupport.disabled();
} else {
gracefulShutdownSupport =
GracefulShutdownSupport.create(config().gracefulShutdownQuietPeriod(),
config().blockingTaskExecutor());
}
// Initialize the server sockets asynchronously.
final CompletableFuture future = new CompletableFuture<>();
final List ports = config().ports();
final AtomicInteger remainingPorts = new AtomicInteger(ports.size());
for (final ServerPort p: ports) {
doStart(p).addListener(new ServerPortStartListener(remainingPorts, future, p));
}
setupServerMetrics();
return future;
}
private ChannelFuture doStart(ServerPort port) {
final ServerBootstrap b = new ServerBootstrap();
serverBootstrap = b;
config.channelOptions().forEach((k, v) -> {
@SuppressWarnings("unchecked")
final ChannelOption
© 2015 - 2025 Weber Informatics LLC | Privacy Policy