Skip to content

Commit

Permalink
feat: make ServerVerticle handler configurable
Browse files Browse the repository at this point in the history
The `ServerVerticle` attaches request handlers to the route. The request
handlers attached to the route cannot be changed at the moment. This
change makes the request handlers that the `ServerVerticle` attaches to
the route configurable.

A `RoutingHandlerFactory` is used to create an instance of the
`Handler<RoutingContext>` class. In the `ServerConfig`, a list of
`RoutingHandlerFactory` class names is used to specify the
`RoutingHandlerFactory`. The handler created by the
`RoutingHandlerFactory` is then added to the root route. The request
handlers are added in the order specified in the `ServerConfig` list.
  • Loading branch information
halber authored and s4heid committed Aug 10, 2022
1 parent e0f366c commit deff579
Show file tree
Hide file tree
Showing 22 changed files with 717 additions and 89 deletions.
25 changes: 25 additions & 0 deletions docs/ServerVerticle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# ServerVerticle configuration

## Configuration

### Handler factories

Handler factories are used to register routing handlers on the root router in the ServerVerticle. A handler factory must
implement the `io.neonbee.internal.handler.factories.RoutingHandlerFactory` interface and must be configured in the
handlerFactories section of the ServerVerticle configuration. The configured handlerFactories are loaded and added in
the configured order.
The order of handler factories has to take the priority of the returned handlers into account.
See `io.vertx.ext.web.impl.RouteState.weight` If the order is violated, an Exception is thrown.

#### Default configuration

```yaml
handlerFactories:
- io.neonbee.internal.handler.factories.LoggerHandlerFactory
- io.neonbee.internal.handler.factories.InstanceInfoHandlerFactory
- io.neonbee.internal.handler.factories.CorrelationIdHandlerFactory
- io.neonbee.internal.handler.factories.TimeoutHandlerFactory
- io.neonbee.internal.handler.factories.SessionHandlerFactory
- io.neonbee.internal.handler.factories.CacheControlHandlerFactory
- io.neonbee.internal.handler.factories.DisallowingFileUploadBodyHandlerFactory
```
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,14 @@ config:
provider:
# the authentication provider to be set for this handler# any of: HTDIGEST, HTPASSWD, JDBC, JWT, MONGO, OAUTH2, mandatory attribute
type: string
# ... more authentication provider options (see the specific provider implementations)
# ... more authentication provider options (see the specific provider implementations)

# default handler factories. The order of handler factories has to take the priority of the returned handlers into account.
handlerFactories:
- io.neonbee.internal.handler.factories.LoggerHandlerFactory
- io.neonbee.internal.handler.factories.InstanceInfoHandlerFactory
- io.neonbee.internal.handler.factories.CorrelationIdHandlerFactory
- io.neonbee.internal.handler.factories.TimeoutHandlerFactory
- io.neonbee.internal.handler.factories.SessionHandlerFactory
- io.neonbee.internal.handler.factories.CacheControlHandlerFactory
- io.neonbee.internal.handler.factories.DisallowingFileUploadBodyHandlerFactory
15 changes: 15 additions & 0 deletions src/generated/java/io/neonbee/config/ServerConfigConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, ServerC
obj.setErrorHandlerTemplate((String) member.getValue());
}
break;
case "handlerFactoriesClassNames":
if (member.getValue() instanceof JsonArray) {
java.util.ArrayList<java.lang.String> list = new java.util.ArrayList<>();
((Iterable<Object>) member.getValue()).forEach(item -> {
if (item instanceof String)
list.add((String) item);
});
obj.setHandlerFactoriesClassNames(list);
}
break;
case "sessionCookieName":
if (member.getValue() instanceof String) {
obj.setSessionCookieName((String) member.getValue());
Expand Down Expand Up @@ -104,6 +114,11 @@ static void toJson(ServerConfig obj, java.util.Map<String, Object> json) {
if (obj.getErrorHandlerTemplate() != null) {
json.put("errorHandlerTemplate", obj.getErrorHandlerTemplate());
}
if (obj.getHandlerFactoriesClassNames() != null) {
JsonArray array = new JsonArray();
obj.getHandlerFactoriesClassNames().forEach(item -> array.add(item));
json.put("handlerFactoriesClassNames", array);
}
if (obj.getSessionCookieName() != null) {
json.put("sessionCookieName", obj.getSessionCookieName());
}
Expand Down
51 changes: 48 additions & 3 deletions src/main/java/io/neonbee/config/ServerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
import io.neonbee.endpoint.odatav4.ODataV4Endpoint;
import io.neonbee.endpoint.raw.RawEndpoint;
import io.neonbee.internal.handler.DefaultErrorHandler;
import io.neonbee.internal.handler.factories.CacheControlHandlerFactory;
import io.neonbee.internal.handler.factories.CorrelationIdHandlerFactory;
import io.neonbee.internal.handler.factories.DisallowingFileUploadBodyHandlerFactory;
import io.neonbee.internal.handler.factories.InstanceInfoHandlerFactory;
import io.neonbee.internal.handler.factories.LoggerHandlerFactory;
import io.neonbee.internal.handler.factories.RoutingHandlerFactory;
import io.neonbee.internal.handler.factories.SessionHandlerFactory;
import io.neonbee.internal.handler.factories.TimeoutHandlerFactory;
import io.neonbee.internal.json.ImmutableJsonObject;
import io.neonbee.internal.verticle.ServerVerticle;
import io.vertx.codegen.annotations.DataObject;
Expand Down Expand Up @@ -108,13 +116,16 @@
@DataObject(generateConverter = true, publicConverter = false)
@SuppressWarnings({ "PMD.ExcessivePublicCount", "PMD.CyclomaticComplexity", "PMD.TooManyMethods", "PMD.GodClass" })
public class ServerConfig extends HttpServerOptions {

public enum SessionHandling {
/**
* No session handling.
*/
NONE,
/**
* Local session handling or in clustered operation on each cluster node.
*
* Sessions are stored locally in a shared local map and only available on this instance.
*/
LOCAL,
/**
Expand Down Expand Up @@ -196,15 +207,24 @@ public String getCorrelationId(RoutingContext routingContext) {
*/
public static final String DEFAULT_SESSION_COOKIE_NAME = "neonbee-web.session";

/**
* List of instances of {@link RoutingHandlerFactory} that are loaded by default.
*/
public static final List<String> DEFAULT_HANDLER_FACTORIES_CLASS_NAMES =
List.of(LoggerHandlerFactory.class.getName(), InstanceInfoHandlerFactory.class.getName(),
CorrelationIdHandlerFactory.class.getName(), TimeoutHandlerFactory.class.getName(),
SessionHandlerFactory.class.getName(), CacheControlHandlerFactory.class.getName(),
DisallowingFileUploadBodyHandlerFactory.class.getName());

private static final String PROPERTY_PORT = "port";

private static final List<EndpointConfig> DEFAULT_ENDPOINT_CONFIGS = Collections.unmodifiableList(Stream
.of(RawEndpoint.class, ODataV4Endpoint.class, MetricsEndpoint.class, HealthEndpoint.class)
.map(endpointClass -> new EndpointConfig().setType(endpointClass.getName())).collect(Collectors.toList()));

private static final ImmutableBiMap<String, String> REPHRASE_MAP =
ImmutableBiMap.of("endpointConfigs", "endpoints", "authChainConfig", "authenticationChain",
"errorHandlerClassName", "errorHandler", "errorHandlerTemplate", "errorTemplate");
private static final ImmutableBiMap<String, String> REPHRASE_MAP = ImmutableBiMap.of("endpointConfigs", "endpoints",
"authChainConfig", "authenticationChain", "errorHandlerClassName", "errorHandler", "errorHandlerTemplate",
"errorTemplate", "handlerFactoriesClassNames", "handlerFactories");

private int timeout = DEFAULT_TIMEOUT;

Expand All @@ -220,6 +240,8 @@ public String getCorrelationId(RoutingContext routingContext) {

private List<AuthHandlerConfig> authChainConfig;

private List<String> handlerFactoriesClassNames = DEFAULT_HANDLER_FACTORIES_CLASS_NAMES;

private String errorHandlerClassName;

private String errorHandlerTemplate;
Expand Down Expand Up @@ -412,6 +434,29 @@ public ServerConfig setAuthChainConfig(List<AuthHandlerConfig> authChainConfig)
return this;
}

/**
* Returns a list of {@link RoutingHandlerFactory} names used to instantiate the handler objects that will be added
* to the root route. The handlers are added to the route in the order in which they are specified. The class must
* implement the {@link RoutingHandlerFactory} interface and must provide a default constructor.
*
* @return list of {@link RoutingHandlerFactory} names.
*/
public List<String> getHandlerFactoriesClassNames() {
return handlerFactoriesClassNames;
}

/**
* Set a custom list of {@link RoutingHandlerFactory} names.
*
* @param handlerFactoriesClassNames the list of {@link RoutingHandlerFactory} names.
* @return the {@link ServerConfig} for chaining
*/
@Fluent
public ServerConfig setHandlerFactoriesClassNames(List<String> handlerFactoriesClassNames) {
this.handlerFactoriesClassNames = handlerFactoriesClassNames;
return this;
}

/**
* Returns a custom error handler class name, which is instantiated as failure handler of the
* {@link ServerVerticle}. The {@link DefaultErrorHandler} is used in case no value is supplied. The class must
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.neonbee.internal.handler.factories;

import static io.vertx.core.Future.succeededFuture;

import io.neonbee.internal.handler.CacheControlHandler;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;

/**
* Create the {@link CacheControlHandler}.
*/
public class CacheControlHandlerFactory implements RoutingHandlerFactory {

@Override
public Future<Handler<RoutingContext>> createHandler() {
return succeededFuture(new CacheControlHandler());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.neonbee.internal.handler.factories;

import static io.vertx.core.Future.succeededFuture;

import io.neonbee.NeonBee;
import io.neonbee.config.ServerConfig;
import io.neonbee.internal.handler.CorrelationIdHandler;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;

/**
* Create the {@link CorrelationIdHandler}.
*/
public class CorrelationIdHandlerFactory implements RoutingHandlerFactory {

@Override
public Future<Handler<RoutingContext>> createHandler() {
ServerConfig serverConfig = NeonBee.get().getServerConfig();
return succeededFuture(new CorrelationIdHandler(serverConfig.getCorrelationStrategy()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.neonbee.internal.handler.factories;

import static io.vertx.core.Future.succeededFuture;

import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;

/**
* Create A {@link BodyHandler} that disallows file uploads.
*/
public class DisallowingFileUploadBodyHandlerFactory implements RoutingHandlerFactory {

@Override
public Future<Handler<RoutingContext>> createHandler() {
return succeededFuture(BodyHandler.create(false /* do not handle file uploads */));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.neonbee.internal.handler.factories;

import static io.vertx.core.Future.succeededFuture;

import io.neonbee.internal.handler.InstanceInfoHandler;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;

/**
* Create the {@link InstanceInfoHandler}.
*/
public class InstanceInfoHandlerFactory implements RoutingHandlerFactory {

@Override
public Future<Handler<RoutingContext>> createHandler() {
return succeededFuture(new InstanceInfoHandler());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.neonbee.internal.handler.factories;

import static io.vertx.core.Future.succeededFuture;

import io.neonbee.internal.handler.LoggerHandler;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;

/**
* Create the {@link LoggerHandler}.
*/
public class LoggerHandlerFactory implements RoutingHandlerFactory {

@Override
public Future<Handler<RoutingContext>> createHandler() {
return succeededFuture(new LoggerHandler());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.neonbee.internal.handler.factories;

import io.neonbee.internal.verticle.ServerVerticle;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;

/**
* Factory interface for creating routing handlers.
*
* This interface is evaluated when {@link ServerVerticle} is started. The returned handlers will be added to the root
* route.
*/
public interface RoutingHandlerFactory {

/**
* Returns an instance of the {@link Handler} object to add to the main route.
*
* @return the {@link Handler} object
*/
Future<Handler<RoutingContext>> createHandler();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.neonbee.internal.handler.factories;

import java.util.Optional;

import com.google.common.annotations.VisibleForTesting;

import io.neonbee.NeonBee;
import io.neonbee.config.ServerConfig;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.PlatformHandler;
import io.vertx.ext.web.handler.SessionHandler;
import io.vertx.ext.web.sstore.ClusteredSessionStore;
import io.vertx.ext.web.sstore.LocalSessionStore;
import io.vertx.ext.web.sstore.SessionStore;

/**
* Creates a SessionHandler.
*
* If ServerConfig.SessionHandling is set to NONE a no-operation handler is returned, which does not perform any session
* handling.
*/
public class SessionHandlerFactory implements RoutingHandlerFactory {

@VisibleForTesting
static final class NoOpHandler implements PlatformHandler {
@Override
public void handle(RoutingContext routingContext) {
routingContext.next();
}
}

@Override
public Future<Handler<RoutingContext>> createHandler() {
ServerConfig config = NeonBee.get().getServerConfig();
Handler<RoutingContext> sh =
createSessionStore(NeonBee.get().getVertx(), config.getSessionHandling()).map(SessionHandler::create)
.map(sessionHandler -> sessionHandler.setSessionCookieName(config.getSessionCookieName()))
.map(sessionHandler -> (Handler<RoutingContext>) sessionHandler).orElseGet(NoOpHandler::new);
return Future.succeededFuture(sh);
}

/**
* Creates a {@linkplain SessionStore} based on the given {@linkplain ServerConfig} to use either local or clustered
* session handling. If no session handling should be used, an empty optional is returned.
*
* @param vertx the Vert.x instance to create the {@linkplain SessionStore} for
* @param sessionHandling the session handling type
* @return an optional session store, suitable for the given Vert.x instance and based on the provided config value
* (none/local/clustered). In case the session handling is set to clustered, but Vert.x does not run in
* clustered mode, fallback to the local session handling.
*/
@VisibleForTesting
static Optional<SessionStore> createSessionStore(Vertx vertx, ServerConfig.SessionHandling sessionHandling) {
switch (sessionHandling) {
case LOCAL:
return Optional.of(LocalSessionStore.create(vertx));
case CLUSTERED:
if (!vertx.isClustered()) { // Behaves like clustered in case that instance isn't clustered
return Optional.of(LocalSessionStore.create(vertx));
}
return Optional.of(ClusteredSessionStore.create(vertx));
default: /* nothing to do here, no session handling, so neither add a cookie, nor a session handler */
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.neonbee.internal.handler.factories;

import static io.vertx.core.Future.succeededFuture;

import java.util.concurrent.TimeUnit;

import io.neonbee.NeonBee;
import io.neonbee.config.ServerConfig;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.TimeoutHandler;

/**
* Create the {@link TimeoutHandler}.
*/
public class TimeoutHandlerFactory implements RoutingHandlerFactory {

@Override
public Future<Handler<RoutingContext>> createHandler() {
ServerConfig serverConfig = NeonBee.get().getServerConfig();
return succeededFuture(TimeoutHandler.create(TimeUnit.SECONDS.toMillis(serverConfig.getTimeout()),
serverConfig.getTimeoutStatusCode()));
}
}
Loading

0 comments on commit deff579

Please sign in to comment.