Skip to content

Commit 07c4ff6

Browse files
authored
Merge pull request #3646 from jooby-project/multiapp
feature: support multiple application on single server
2 parents 3690338 + dae4445 commit 07c4ff6

File tree

29 files changed

+578
-207
lines changed

29 files changed

+578
-207
lines changed

docs/asciidoc/problem-details.adoc

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
== Problem Details
1+
=== Problem Details
22

33
Most APIs have a way to report problems and errors, helping the user understand when something went wrong and what the issue is.
44
The method used depends on the API’s style, technology, and design.
@@ -12,7 +12,7 @@ If it suits the API’s needs, using this standard benefits both designers and u
1212

1313
`Jooby` provides built-in support for `Problem Details`.
1414

15-
=== Set up ProblemDetails
15+
==== Set up ProblemDetails
1616

1717
To enable the `ProblemDetails`, simply add the following line to your configuration:
1818

@@ -44,11 +44,11 @@ problem.details {
4444
<3> You can optionally mute some exceptions logging completely.
4545

4646

47-
=== Creating problems
47+
==== Creating problems
4848

4949
`HttpProblem` class represents the `RFC 7807` model. It is the main entity you need to work with to produce the problem.
5050

51-
==== Static helpers
51+
===== Static helpers
5252

5353
There are several handy static methods to produce a simple `HttpProblem`:
5454

@@ -111,7 +111,7 @@ Resulting response:
111111
}
112112
----
113113

114-
==== Builder
114+
===== Builder
115115

116116
Use builder to create a rich problem instance with all properties:
117117

@@ -126,7 +126,7 @@ throw HttpProblem.builder()
126126
.build();
127127
----
128128

129-
=== Adding extra parameters
129+
==== Adding extra parameters
130130

131131
`RFC 7807` has a simple extension model: APIs are free to add any other properties to the problem details object, so all properties other than the five ones listed above are extensions.
132132

@@ -163,7 +163,7 @@ Resulting response:
163163
}
164164
----
165165

166-
=== Adding headers
166+
==== Adding headers
167167

168168
Some `HTTP` codes (like `413` or `426`) require additional response headers, or it may be required by third-party system/integration. `HttpProblem` support additional headers in response:
169169

@@ -177,7 +177,7 @@ throw HttpProblem.builder()
177177
.build();
178178
----
179179

180-
=== Respond with errors details
180+
==== Respond with errors details
181181

182182
`RFC 9457` finally described how errors should be delivered in HTTP APIs.
183183
It is basically another extension `errors` on a root level. Adding errors is straight-forward using `error()` or `errors()` for bulk addition in builder:
@@ -214,7 +214,7 @@ In response:
214214
If you need to enrich errors with more information feel free to extend `HttpProblem.Error` and make your custom errors model.
215215
====
216216

217-
=== Custom `Exception` to `HttpProblem`
217+
==== Custom `Exception` to `HttpProblem`
218218

219219
Apparently, you may already have many custom `Exception` classes in the codebase, and you want to make them `Problem Details` compliant without complete re-write. You can achieve this by implementing `HttpProblemMappable` interface. It allows you to control how exceptions should be transformed into `HttpProblem` if default behaviour doesn't suite your needs:
220220

@@ -233,7 +233,7 @@ public class MyException implements HttpProblemMappable {
233233
}
234234
----
235235

236-
=== Custom Problems
236+
==== Custom Problems
237237

238238
Extending `HttpProblem` and utilizing builder functionality makes it really easy:
239239

@@ -255,7 +255,7 @@ public class OutOfStockProblem extends HttpProblem {
255255
}
256256
----
257257

258-
=== Custom Exception Handlers
258+
==== Custom Exception Handlers
259259

260260
All the features described above should give you ability to rely solely on built-in global error handler. But, in case you still need custom exception handler for some reason, you still can do it:
261261

docs/asciidoc/routing.adoc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,6 +1362,56 @@ Output:
13621362

13631363
Done {love}!
13641364

1365+
=== Multiple routers
1366+
1367+
This model let you `run` multiple applications on single server instance. Each application
1368+
works like a standalone application, they don't share any kind of services.
1369+
1370+
.Multiple routers
1371+
[source, java,role="primary"]
1372+
----
1373+
public class Foo extends Jooby {
1374+
{
1375+
setContextPath("/foo");
1376+
get("/hello", ctx -> ...);
1377+
}
1378+
}
1379+
1380+
public class Bar extends Jooby {
1381+
{
1382+
setContextPath("/bar");
1383+
get("/hello", ctx -> ...);
1384+
}
1385+
}
1386+
1387+
import static io.jooby.Jooby.runApp;
1388+
1389+
public class MultiApp {
1390+
public static void main(String[] args) {
1391+
runApp(args, List.of(Foo::new, Bar::new));
1392+
}
1393+
}
1394+
----
1395+
1396+
.Kotlin
1397+
[source, kotlin,role="secondary"]
1398+
----
1399+
1400+
import io.jooby.kt.Kooby.runApp
1401+
1402+
fun main(args: Array<String>) {
1403+
runApp(args, ::Foo, ::Bar)
1404+
}
1405+
----
1406+
1407+
You write your application as always and them you deploy them using the `runApp` method.
1408+
1409+
[IMPORTANT]
1410+
====
1411+
Due to nature of logging framework (static loading and initialization) the logging bootstrap
1412+
might not work as you expected. It is recommend to use just the `logback.xml` or `log4j.xml` file.
1413+
====
1414+
13651415
=== Options
13661416

13671417
include::router-hidden-method.adoc[]

docs/pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<properties>
1313
<application.class>io.jooby.adoc.DocApp</application.class>
14-
<jooby.version>3.2.0</jooby.version>
14+
<jooby.version>3.7.0</jooby.version>
1515
<maven.compiler.source>17</maven.compiler.source>
1616
<maven.compiler.target>17</maven.compiler.target>
1717
<maven.compiler.release>17</maven.compiler.release>
@@ -20,7 +20,7 @@
2020
<dependencies>
2121
<dependency>
2222
<groupId>io.jooby</groupId>
23-
<artifactId>jooby-undertow</artifactId>
23+
<artifactId>jooby-netty</artifactId>
2424
<version>${jooby.version}</version>
2525
</dependency>
2626

@@ -51,7 +51,7 @@
5151
<dependency>
5252
<groupId>org.asciidoctor</groupId>
5353
<artifactId>asciidoctorj</artifactId>
54-
<version>2.5.7</version>
54+
<version>3.0.0</version>
5555
</dependency>
5656

5757
<dependency>
@@ -63,7 +63,7 @@
6363
<dependency>
6464
<groupId>io.methvin</groupId>
6565
<artifactId>directory-watcher</artifactId>
66-
<version>0.18.0</version>
66+
<version>0.19.0</version>
6767
</dependency>
6868

6969
<!-- zt-exec -->

docs/src/main/java/io/jooby/adoc/DocGenerator.java

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -251,30 +251,30 @@ private static void processModule(
251251

252252
private static Options createOptions(Path basedir, Path outdir, String version, String title)
253253
throws IOException {
254-
Attributes attributes = new Attributes();
254+
var attributes = Attributes.builder();
255255

256-
attributes.setAttribute("love", "&#9825;");
257-
attributes.setAttribute("docinfo", "shared");
258-
attributes.setTitle(title == null ? "jooby: do more! more easily!!" : "jooby: " + title);
259-
attributes.setTableOfContents(Placement.LEFT);
260-
attributes.setAttribute("toclevels", "3");
256+
attributes.attribute("love", "&#9825;");
257+
attributes.attribute("docinfo", "shared");
258+
attributes.title(title == null ? "jooby: do more! more easily!!" : "jooby: " + title);
259+
attributes.tableOfContents(Placement.LEFT);
260+
attributes.attribute("toclevels", "3");
261261
attributes.setAnchors(true);
262-
attributes.setAttribute("sectlinks", "");
263-
attributes.setSectionNumbers(true);
264-
attributes.setAttribute("sectnumlevels", "3");
265-
attributes.setLinkAttrs(true);
266-
attributes.setNoFooter(true);
267-
attributes.setAttribute("idprefix", "");
268-
attributes.setAttribute("idseparator", "-");
269-
attributes.setIcons("font");
270-
attributes.setAttribute("description", "The modular micro web framework for Java");
271-
attributes.setAttribute(
262+
attributes.attribute("sectlinks", "");
263+
attributes.sectionNumbers(true);
264+
attributes.attribute("sectnumlevels", "3");
265+
attributes.linkAttrs(true);
266+
attributes.noFooter(true);
267+
attributes.attribute("idprefix", "");
268+
attributes.attribute("idseparator", "-");
269+
attributes.icons("font");
270+
attributes.attribute("description", "The modular micro web framework for Java");
271+
attributes.attribute(
272272
"keywords", "Java, Modern, Micro, Web, Framework, Reactive, Lightweight, Microservices");
273-
attributes.setImagesDir("images");
274-
attributes.setSourceHighlighter("highlightjs");
275-
attributes.setAttribute("highlightjsdir", "js");
276-
attributes.setAttribute("highlightjs-theme", "agate");
277-
attributes.setAttribute("favicon", "images/favicon96.png");
273+
attributes.imagesDir("images");
274+
attributes.sourceHighlighter("highlightjs");
275+
attributes.attribute("highlightjsdir", "js");
276+
attributes.attribute("highlightjs-theme", "agate");
277+
attributes.attribute("favicon", "images/favicon96.png");
278278

279279
// versions:
280280
Document pom =
@@ -283,21 +283,20 @@ private static Options createOptions(Path basedir, Path outdir, String version,
283283
var tagName = tag.tagName();
284284
var value = tag.text().trim();
285285
Stream.of(tagName, tagName.replaceAll("[.-]", "_"), tagName.replaceAll("[.-]", "-"), toJavaName(tagName))
286-
.forEach(key -> attributes.setAttribute(key, value));
286+
.forEach(key -> attributes.attribute(key, value));
287287
});
288288

289-
attributes.setAttribute("joobyVersion", version);
290-
attributes.setAttribute("date", DateTimeFormatter.ISO_INSTANT.format(Instant.now()));
289+
attributes.attribute("joobyVersion", version);
290+
attributes.attribute("date", DateTimeFormatter.ISO_INSTANT.format(Instant.now()));
291291

292292
OptionsBuilder options = Options.builder();
293293
options.backend("html");
294294

295-
options.attributes(attributes);
295+
options.attributes(attributes.build());
296296
options.baseDir(basedir.toAbsolutePath().toFile());
297297
options.docType("book");
298298
options.toDir(outdir.toFile());
299299
options.mkDirs(true);
300-
options.destinationDir(outdir.resolve("site").toFile());
301300
options.safe(SafeMode.UNSAFE);
302301
return options.build();
303302
}

docs/src/main/java/io/jooby/adoc/JavadocProcessor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.util.stream.Collectors;
1414

1515
import org.asciidoctor.ast.ContentNode;
16+
import org.asciidoctor.ast.PhraseNode;
17+
import org.asciidoctor.ast.StructuralNode;
1618
import org.asciidoctor.extension.InlineMacroProcessor;
1719

1820
public class JavadocProcessor extends InlineMacroProcessor {
@@ -22,8 +24,7 @@ public JavadocProcessor(String name) {
2224
}
2325

2426
@Override
25-
public Object process(ContentNode parent, String clazz, Map<String, Object> attributes) {
26-
27+
public PhraseNode process(StructuralNode parent, String clazz, Map<String, Object> attributes) {
2728
StringBuilder link =
2829
new StringBuilder("https://www.javadoc.io/doc/io.jooby/jooby/latest/io.jooby/io/jooby/");
2930
StringBuilder text = new StringBuilder();

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

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

49+
/** Select an application base on context path prefix matching a provided path. */
50+
interface Selector {
51+
/**
52+
* Select an application base on context path prefix matching a provided path.
53+
*
54+
* @param applications List of applications.
55+
* @param path Path to match.
56+
* @return Best match application.
57+
*/
58+
Jooby select(List<Jooby> applications, String path);
59+
60+
static Selector create(List<Jooby> applications) {
61+
return applications.size() == 1 ? single() : multiple();
62+
}
63+
64+
/**
65+
* Select an application the best match a given path. If none matches it returns the application
66+
* that has no context path <code>/</code> or the first of the list.
67+
*
68+
* @return Best match application.
69+
*/
70+
static Selector multiple() {
71+
return (applications, path) -> {
72+
var defaultApp = applications.get(0);
73+
for (var app : applications) {
74+
var contextPath = app.getContextPath();
75+
if ("/".equals(contextPath)) {
76+
defaultApp = app;
77+
} else if (path.startsWith(contextPath)) {
78+
return app;
79+
}
80+
}
81+
return defaultApp;
82+
};
83+
}
84+
85+
private static Selector single() {
86+
return (applications, path) -> applications.get(0);
87+
}
88+
}
89+
4990
/** Constant for default HTTP port. */
5091
int PORT = 80;
5192

0 commit comments

Comments
 (0)