/**
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby;
import static java.util.Collections.singletonList;
import java.io.EOFException;
import java.io.IOException;
import java.net.BindException;
import java.nio.channels.ClosedChannelException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Web server contract. Defines operations to start, join and stop a web server. Jooby comes
* with three web server implementation: Jetty, Netty and Undertow.
*
* A web server is automatically discovered using the Java Service Loader API. All you need is to
* add the server dependency to your project classpath.
*
* When service loader is not an option or do you want to manually bootstrap a server:
*
*
{@code
*
* Server server = new Netty(); // or Jetty or Utow
*
* App app = new App();
*
* server.start(app);
*
* ...
*
* server.stop();
*
* }
*
* @author edgar
* @since 2.0.0
*/
public interface Server {
/**
* Base class for server.
*/
abstract class Base implements Server {
private static final Predicate CONNECTION_LOST = cause -> {
if (cause instanceof IOException) {
String message = cause.getMessage();
if (message != null) {
String msg = message.toLowerCase();
return msg.contains("reset by peer")
|| msg.contains("broken pipe")
|| msg.contains("forcibly closed")
|| msg.contains("connection reset");
}
}
return (cause instanceof ClosedChannelException) || (cause instanceof EOFException);
};
private static final Predicate ADDRESS_IN_USE = cause ->
(cause instanceof BindException)
|| (Optional.ofNullable(cause)
.map(Throwable::getMessage)
.map(String::toLowerCase)
.filter(msg -> msg.contains("address already in use"))
.isPresent()
);
private static final List> connectionLostListeners =
new CopyOnWriteArrayList<>(singletonList(CONNECTION_LOST));
private static final List> addressInUseListeners =
new CopyOnWriteArrayList<>(singletonList(ADDRESS_IN_USE));
private static final boolean useShutdownHook = Boolean
.parseBoolean(System.getProperty("jooby.useShutdownHook", "true"));
private AtomicBoolean stopping = new AtomicBoolean();
protected void fireStart(@Nonnull List applications, @Nonnull Executor defaultWorker) {
for (Jooby app : applications) {
app.setDefaultWorker(defaultWorker).start(this);
}
}
protected void fireReady(@Nonnull List applications) {
for (Jooby app : applications) {
app.ready(this);
}
}
protected void fireStop(@Nonnull List applications) {
if (stopping.compareAndSet(false, true)) {
if (applications != null) {
for (Jooby app : applications) {
app.stop();
}
}
}
}
protected void addShutdownHook() {
if (useShutdownHook) {
Runtime.getRuntime().addShutdownHook(new Thread(this::stop));
}
}
}
/**
* Set server options.
*
* @param options Server options.
* @return This server.
*/
@Nonnull Server setOptions(@Nonnull ServerOptions options);
/**
* Get server options.
*
* @return Server options.
*/
@Nonnull ServerOptions getOptions();
/**
* Start an application.
*
* @param application Application to start.
* @return This server.
*/
@Nonnull Server start(@Nonnull Jooby application);
/**
* Block current thread.
*
* @deprecated This method is planned to be removed in next major release (3.x)
*/
@Deprecated
@Nonnull default void join() {
try {
Thread.currentThread().join();
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
}
}
/**
* Stop the server.
*
* @return Stop the server.
*/
@Nonnull Server stop();
/**
* Add a connection lost predicate. On unexpected exception, this method allows to customize which
* error are considered connection lost. If the error is considered a connection lost, no log
* statement will be emitted by the application.
*
* @param predicate Customize connection lost error.
*/
static void addConnectionLost(@Nonnull Predicate predicate) {
Base.connectionLostListeners.add(predicate);
}
/**
* Add an address in use predicate. On unexpected exception, this method allows to customize which
* error are considered address in use. If the error is considered an address in use, no log
* statement will be emitted by the application.
*
* @param predicate Customize connection lost error.
*/
static void addAddressInUse(@Nonnull Predicate predicate) {
Base.addressInUseListeners.add(predicate);
}
/**
* Test whenever the given exception is a connection-lost. Connection-lost exceptions don't
* generate log.error statements.
*
* @param cause Exception to check.
* @return True for connection lost.
*/
static boolean connectionLost(@Nullable Throwable cause) {
for (Predicate connectionLost : Base.connectionLostListeners) {
if (connectionLost.test(cause)) {
return true;
}
}
return false;
}
/**
* Whenever the given exception is an address already in use. This probably won't work in none
* English locale systems.
*
* @param cause Exception to check.
* @return True address alaredy in use.
*/
static boolean isAddressInUse(@Nullable Throwable cause) {
for (Predicate addressInUse : Base.addressInUseListeners) {
if (addressInUse.test(cause)) {
return true;
}
}
return false;
}
}