Skip to content

Commit 94a9800

Browse files
committed
feature: support multiple application on single server
- Implement multiapp deploy on servers - TODO: doc - ref #3645
1 parent a4a10a6 commit 94a9800

File tree

20 files changed

+429
-161
lines changed

20 files changed

+429
-161
lines changed

jooby/src/main/java/io/jooby/Context.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,38 @@
4646
*/
4747
public interface Context extends Registry {
4848

49+
interface Selector {
50+
Jooby select(List<Jooby> applications, String path);
51+
52+
static Selector create(List<Jooby> applications) {
53+
return applications.size() == 1 ? single() : multiple();
54+
}
55+
56+
static Selector multiple() {
57+
return (applications, path) -> {
58+
var defaultApp = applications.get(0);
59+
for (var app : applications) {
60+
var contextPath = app.getContextPath();
61+
if ("/".equals(contextPath)) {
62+
defaultApp = app;
63+
} else if (path.startsWith(contextPath)) {
64+
return app;
65+
}
66+
}
67+
return defaultApp;
68+
};
69+
}
70+
71+
static Selector single() {
72+
return new Selector() {
73+
@Override
74+
public Jooby select(List<Jooby> applications, String path) {
75+
return applications.get(0);
76+
}
77+
};
78+
}
79+
}
80+
4981
/** Constant for default HTTP port. */
5082
int PORT = 80;
5183

jooby/src/main/java/io/jooby/Jooby.java

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import static java.util.Collections.singletonList;
99
import static java.util.Objects.requireNonNull;
10-
import static java.util.Spliterators.spliteratorUnknownSize;
1110
import static java.util.stream.StreamSupport.stream;
1211

1312
import java.io.IOException;
@@ -30,7 +29,6 @@
3029
import java.util.ServiceConfigurationError;
3130
import java.util.ServiceLoader;
3231
import java.util.Set;
33-
import java.util.Spliterator;
3432
import java.util.concurrent.Executor;
3533
import java.util.concurrent.ExecutorService;
3634
import java.util.concurrent.atomic.AtomicBoolean;
@@ -149,7 +147,9 @@ public Jooby() {
149147
* Server options or <code>null</code>.
150148
*
151149
* @return Server options or <code>null</code>.
150+
* @deprecated Use {@link Server#getOptions()}
152151
*/
152+
@Deprecated(since = "3.8.0", forRemoval = true)
153153
public @Nullable ServerOptions getServerOptions() {
154154
return serverOptions;
155155
}
@@ -159,7 +159,9 @@ public Jooby() {
159159
*
160160
* @param serverOptions Server options.
161161
* @return This application.
162+
* @deprecated Use {@link Server#setOptions(ServerOptions)}
162163
*/
164+
@Deprecated(since = "3.8.0", forRemoval = true)
163165
public @NonNull Jooby setServerOptions(@NonNull ServerOptions serverOptions) {
164166
this.serverOptions = serverOptions;
165167
return this;
@@ -970,10 +972,12 @@ public Jooby setStartupSummary(List<StartupSummary> startupSummary) {
970972
* etc..
971973
*
972974
* @return Server.
975+
* @deprecated Use {@link Server#start(Jooby[])}
973976
*/
977+
@Deprecated(since = "3.8.0", forRemoval = true)
974978
public @NonNull Server start() {
975979
if (server == null) {
976-
this.server = loadServer();
980+
this.server = Server.loadServer();
977981
}
978982
if (!server.getLoggerOff().isEmpty()) {
979983
this.server = MutedServer.mute(this.server);
@@ -1004,31 +1008,6 @@ public Jooby setStartupSummary(List<StartupSummary> startupSummary) {
10041008
}
10051009
}
10061010

1007-
/**
1008-
* Load server from classpath using {@link ServiceLoader}.
1009-
*
1010-
* @return A server.
1011-
*/
1012-
private Server loadServer() {
1013-
List<Server> servers =
1014-
stream(
1015-
spliteratorUnknownSize(
1016-
ServiceLoader.load(Server.class).iterator(), Spliterator.ORDERED),
1017-
false)
1018-
.toList();
1019-
if (servers.isEmpty()) {
1020-
throw new StartupException("Server not found.");
1021-
}
1022-
if (servers.size() > 1) {
1023-
List<String> names =
1024-
servers.stream()
1025-
.map(it -> it.getClass().getSimpleName().toLowerCase())
1026-
.collect(Collectors.toList());
1027-
getLog().warn("Multiple servers found {}. Using: {}", names, names.get(0));
1028-
}
1029-
return servers.get(0);
1030-
}
1031-
10321011
/**
10331012
* Call back method that indicates application was deploy it in the given server.
10341013
*
@@ -1277,28 +1256,78 @@ public static void runApp(
12771256
@NonNull String[] args,
12781257
@NonNull ExecutionMode executionMode,
12791258
@NonNull Supplier<Jooby> provider) {
1280-
createApp(args, executionMode, provider).start();
1259+
runApp(args, Server.loadServer(), executionMode, List.of(provider));
12811260
}
12821261

12831262
/**
12841263
* Setup default environment, logging (logback or log4j2) and run application.
12851264
*
12861265
* @param args Application arguments.
1287-
* @param executionMode Application execution mode.
12881266
* @param provider Application provider.
1289-
* @return Application.
12901267
*/
1291-
public static Jooby createApp(
1268+
public static void runApp(@NonNull String[] args, @NonNull List<Supplier<Jooby>> provider) {
1269+
runApp(args, Server.loadServer(), ExecutionMode.DEFAULT, provider);
1270+
}
1271+
1272+
/**
1273+
* Setup default environment, logging (logback or log4j2) and run application.
1274+
*
1275+
* @param args Application arguments.
1276+
* @param executionMode Execution mode.
1277+
* @param provider Application provider.
1278+
*/
1279+
public static void runApp(
12921280
@NonNull String[] args,
12931281
@NonNull ExecutionMode executionMode,
1294-
@NonNull Supplier<Jooby> provider) {
1282+
@NonNull List<Supplier<Jooby>> provider) {
1283+
runApp(args, Server.loadServer(), executionMode, provider);
1284+
}
12951285

1296-
configurePackage(provider.getClass().getPackage());
1286+
/**
1287+
* Setup default environment, logging (logback or log4j2) and run application.
1288+
*
1289+
* @param args Application arguments.
1290+
* @param server Server.
1291+
* @param provider Application provider.
1292+
*/
1293+
public static void runApp(
1294+
@NonNull String[] args, @NonNull Server server, @NonNull List<Supplier<Jooby>> provider) {
1295+
runApp(args, server, ExecutionMode.DEFAULT, provider);
1296+
}
12971297

1298-
/** Dump command line as system properties. */
1298+
/**
1299+
* Setup default environment, logging (logback or log4j2) and run application.
1300+
*
1301+
* @param args Application arguments.
1302+
* @param server Server.
1303+
* @param executionMode Execution mode.
1304+
* @param provider Application provider.
1305+
*/
1306+
public static void runApp(
1307+
@NonNull String[] args,
1308+
@NonNull Server server,
1309+
@NonNull ExecutionMode executionMode,
1310+
@NonNull List<Supplier<Jooby>> provider) {
1311+
/* Dump command line as system properties. */
12991312
parseArguments(args).forEach(System::setProperty);
1313+
var apps = new ArrayList<Jooby>();
1314+
for (var factory : provider) {
1315+
apps.add(createApp(executionMode, factory));
1316+
}
1317+
server.start(apps.toArray(new Jooby[0]));
1318+
}
13001319

1301-
/** Find application.env: */
1320+
/**
1321+
* Setup default environment, logging (logback or log4j2) and run application.
1322+
*
1323+
* @param executionMode Application execution mode.
1324+
* @param provider Application provider.
1325+
* @return Application.
1326+
*/
1327+
public static Jooby createApp(
1328+
@NonNull ExecutionMode executionMode, @NonNull Supplier<Jooby> provider) {
1329+
configurePackage(provider.getClass().getPackage());
1330+
/* Find application.env: */
13021331
String logfile =
13031332
LoggingService.configure(
13041333
provider.getClass().getClassLoader(), new EnvironmentOptions().getActiveNames());

jooby/src/main/java/io/jooby/LoggingService.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.stream.StreamSupport;
1717

1818
import edu.umd.cs.findbugs.annotations.NonNull;
19+
import edu.umd.cs.findbugs.annotations.Nullable;
1920

2021
/**
2122
* Describe the underlying logging system. Jooby provides two implementation: jooby-logback and
@@ -62,7 +63,7 @@ public interface LoggingService {
6263
* @param names Actives environment names. Useful for choosing an environment specific logging
6364
* configuration file.
6465
*/
65-
static String configure(@NonNull ClassLoader classLoader, @NonNull List<String> names) {
66+
static @Nullable String configure(@NonNull ClassLoader classLoader, @NonNull List<String> names) {
6667
// Supported well-know implementation
6768
String[] keys = {"logback.configurationFile", "log4j.configurationFile"};
6869
for (String key : keys) {
@@ -177,7 +178,7 @@ private static List<Object> logFile(
177178
return result;
178179
}
179180

180-
private static String property(String name) {
181+
private static @Nullable String property(String name) {
181182
return System.getProperty(name, System.getenv(name));
182183
}
183184
}

jooby/src/main/java/io/jooby/Router.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -794,11 +794,11 @@ default Object execute(@NonNull Context context) {
794794
* <p>If no match exists this method returns a route with a <code>404</code> handler. See {@link
795795
* Route#NOT_FOUND}.
796796
*
797-
* @param method Method to match.
797+
* @param pattern Pattern to match.
798798
* @param path Path to match.
799799
* @return A route match result.
800800
*/
801-
boolean match(@NonNull String method, @NonNull String path);
801+
boolean match(@NonNull String pattern, @NonNull String path);
802802

803803
/* Error handler: */
804804

jooby/src/main/java/io/jooby/Server.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,28 @@
77

88
import static java.util.Collections.emptyList;
99
import static java.util.Collections.singletonList;
10+
import static java.util.Spliterators.spliteratorUnknownSize;
11+
import static java.util.stream.StreamSupport.stream;
1012

1113
import java.io.EOFException;
1214
import java.io.IOException;
1315
import java.net.BindException;
1416
import java.nio.channels.ClosedChannelException;
1517
import java.util.List;
1618
import java.util.Optional;
19+
import java.util.ServiceLoader;
20+
import java.util.Spliterator;
1721
import java.util.concurrent.CopyOnWriteArrayList;
1822
import java.util.concurrent.Executor;
1923
import java.util.concurrent.atomic.AtomicBoolean;
2024
import java.util.function.Predicate;
25+
import java.util.stream.Collectors;
26+
27+
import org.slf4j.LoggerFactory;
2128

2229
import edu.umd.cs.findbugs.annotations.NonNull;
2330
import edu.umd.cs.findbugs.annotations.Nullable;
31+
import io.jooby.exception.StartupException;
2432
import io.jooby.internal.MutedServer;
2533

2634
/**
@@ -145,7 +153,7 @@ protected void addShutdownHook() {
145153
* @param application Application to start.
146154
* @return This server.
147155
*/
148-
@NonNull Server start(@NonNull Jooby application);
156+
@NonNull Server start(@NonNull Jooby... application);
149157

150158
/**
151159
* Utility method to turn off odd logger. This help to ensure same startup log lines across server
@@ -219,4 +227,30 @@ static boolean isAddressInUse(@Nullable Throwable cause) {
219227
}
220228
return false;
221229
}
230+
231+
/**
232+
* Load server from classpath using {@link ServiceLoader}.
233+
*
234+
* @return A server.
235+
*/
236+
static Server loadServer() {
237+
List<Server> servers =
238+
stream(
239+
spliteratorUnknownSize(
240+
ServiceLoader.load(Server.class).iterator(), Spliterator.ORDERED),
241+
false)
242+
.toList();
243+
if (servers.isEmpty()) {
244+
throw new StartupException("Server not found.");
245+
}
246+
if (servers.size() > 1) {
247+
List<String> names =
248+
servers.stream()
249+
.map(it -> it.getClass().getSimpleName().toLowerCase())
250+
.collect(Collectors.toList());
251+
var log = LoggerFactory.getLogger(servers.get(0).getClass());
252+
log.warn("Multiple servers found {}. Using: {}", names, names.get(0));
253+
}
254+
return servers.get(0);
255+
}
222256
}

jooby/src/main/java/io/jooby/internal/MutedServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public static Server mute(Server server, String... logger) {
6565
return delegate.getOptions();
6666
}
6767

68-
@NonNull public Server start(@NonNull Jooby application) {
68+
@NonNull public Server start(@NonNull Jooby... application) {
6969
loggingService.logOff(mute, () -> delegate.start(application));
7070
return delegate;
7171
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal.jetty;
7+
8+
import java.util.List;
9+
import java.util.Map;
10+
11+
import org.eclipse.jetty.server.Handler;
12+
import org.eclipse.jetty.server.Request;
13+
import org.eclipse.jetty.server.Response;
14+
import org.eclipse.jetty.util.Callback;
15+
16+
public class PrefixHandler extends Handler.Abstract {
17+
private final List<Map.Entry<String, Handler>> mapping;
18+
private int defaultHandlerIndex;
19+
20+
public PrefixHandler(List<Map.Entry<String, Handler>> mapping) {
21+
this.mapping = mapping;
22+
this.defaultHandlerIndex = 0;
23+
for (int i = 0; i < mapping.size(); i++) {
24+
if (mapping.get(i).getKey().equals("/")) {
25+
defaultHandlerIndex = i;
26+
break;
27+
}
28+
}
29+
}
30+
31+
@Override
32+
public boolean handle(Request request, Response response, Callback callback) throws Exception {
33+
for (Map.Entry<String, Handler> e : mapping) {
34+
var path = request.getHttpURI().getPath();
35+
if (path.startsWith(e.getKey())) {
36+
return e.getValue().handle(request, response, callback);
37+
}
38+
}
39+
return mapping.get(defaultHandlerIndex).getValue().handle(request, response, callback);
40+
}
41+
}

0 commit comments

Comments
 (0)