diff --git a/docs/asciidoc/index.adoc b/docs/asciidoc/index.adoc index 2e3c706b3f..2aa1211d47 100644 --- a/docs/asciidoc/index.adoc +++ b/docs/asciidoc/index.adoc @@ -62,7 +62,7 @@ Latest Release: https://github.com/jooby-project/jooby/releases/tag/v{joobyVersi Looking for a previous version? -* Access to link:v2[2.x] documentation. See link:/migration/3.x[migrating from 2.x to 3.x] +* Access to link:v2[2.x] documentation. See link:/v3/migration/3.x[migrating from 2.x to 3.x] * Access to link:v1[1.x] documentation. ==== diff --git a/docs/asciidoc/migration/3.x.adoc b/docs/asciidoc/migration/3.x.adoc index beed7da3da..1924458b1b 100644 --- a/docs/asciidoc/migration/3.x.adoc +++ b/docs/asciidoc/migration/3.x.adoc @@ -120,4 +120,4 @@ Reactive libraries has been removed from core to his own module. |reactor|jooby-reactor |=== -All reactive libraries requires explicit handler while using script/lambda routes. More details on link:/#responses-nonblocking[NonBlocking] responses. +All reactive libraries requires explicit handler while using script/lambda routes. More details on link:/v3/#responses-nonblocking[NonBlocking] responses. diff --git a/docs/asciidoc/modules/jackson.adoc b/docs/asciidoc/modules/jackson.adoc index aef6c4d5db..b8c1c78b68 100644 --- a/docs/asciidoc/modules/jackson.adoc +++ b/docs/asciidoc/modules/jackson.adoc @@ -159,7 +159,7 @@ import io.jooby.json.JacksonModule === Provisioning Jackson Modules -Jackson module can be provided by a link:/#extensions-and-services-dependency-injection[dependency injection] framework. +Jackson module can be provided by a link:/v3/#extensions-and-services-dependency-injection[dependency injection] framework. .Provisioning Modules [source, java, role="primary"] diff --git a/docs/asciidoc/modules/modules.adoc b/docs/asciidoc/modules/modules.adoc index 135aba30f2..62c9fd67ab 100644 --- a/docs/asciidoc/modules/modules.adoc +++ b/docs/asciidoc/modules/modules.adoc @@ -14,58 +14,58 @@ configuration properties. Available modules are listed next. === Cloud - * link:/modules/awssdkv2[AWS-SDK v2]: Amazon Web Service module SDK 2. - * link:/modules/aws[AWS SDK v1]: Amazon Web Service module SDK 1. + * link:/v3/modules/awssdkv2[AWS-SDK v2]: Amazon Web Service module SDK 2. + * link:/v3/modules/aws[AWS SDK v1]: Amazon Web Service module SDK 1. === Data - * link:/modules/ebean[Ebean]: Ebean ORM module. - * link:/modules/flyway[Flyway]: Flyway migration module. - * link:/modules/graphql[GraphQL]: GraphQL Java module. - * link:/modules/hikari[HikariCP]: A high-performance JDBC connection pool. - * link:/modules/hibernate[Hibernate]: Hibernate ORM module. - * link:/modules/jdbi[Jdbi]: Jdbi module. - * link:/modules/kafka[Kafka]: Kafka module. - * link:/modules/redis[Redis]: Redis module. + * link:/v3/modules/ebean[Ebean]: Ebean ORM module. + * link:/v3/modules/flyway[Flyway]: Flyway migration module. + * link:/v3/modules/graphql[GraphQL]: GraphQL Java module. + * link:/v3/modules/hikari[HikariCP]: A high-performance JDBC connection pool. + * link:/v3/modules/hibernate[Hibernate]: Hibernate ORM module. + * link:/v3/modules/jdbi[Jdbi]: Jdbi module. + * link:/v3/modules/kafka[Kafka]: Kafka module. + * link:/v3/modules/redis[Redis]: Redis module. === Validation - * link:/modules/avaje-validator[Avaje Validator]: Avaje Validator module. - * link:/modules/hibernate-validator[Hibernate Validator]: Hibernate Validator module. + * link:/v3/modules/avaje-validator[Avaje Validator]: Avaje Validator module. + * link:/v3/modules/hibernate-validator[Hibernate Validator]: Hibernate Validator module. === Development Tools - * link:/#development[Jooby Run]: Run and hot reload your application. - * link:/modules/whoops[Whoops]: Pretty page stacktrace reporter. - * link:/modules/metrics[Metrics]: Application metrics from the excellent metrics library. + * link:/v3/#development[Jooby Run]: Run and hot reload your application. + * link:/v3/modules/whoops[Whoops]: Pretty page stacktrace reporter. + * link:/v3/modules/metrics[Metrics]: Application metrics from the excellent metrics library. === Event Bus - * link:/modules/camel[Camel]: Camel module for Jooby. + * link:/v3/modules/camel[Camel]: Camel module for Jooby. === JSON - * link:/modules/gson[Gson]: Gson module for Jooby. - * link:/modules/jackson[Jackson]: Jackson module for Jooby. - * link:/modules/yasson[JSON-B]: JSON-B module for Jooby. - * link:/modules/avaje-jsonb[Avaje-JsonB]: Avaje-JsonB module for Jooby. + * link:/v3/modules/gson[Gson]: Gson module for Jooby. + * link:/v3/modules/jackson[Jackson]: Jackson module for Jooby. + * link:/v3/modules/yasson[JSON-B]: JSON-B module for Jooby. + * link:/v3/modules/avaje-jsonb[Avaje-JsonB]: Avaje-JsonB module for Jooby. === OpenAPI - * link:/modules/openapi[OpenAPI]: OpenAPI supports. + * link:/v3/modules/openapi[OpenAPI]: OpenAPI supports. === Template Engine - * link:/modules/handlebars[Handlebars]: Handlebars template engine. - * link:/modules/jstachio[JStachio]: JStachio template engine. - * link:/modules/jte[jte]: jte template engine. - * link:/modules/freemarker[Freemarker]: Freemarker template engine. - * link:/modules/pebble[Pebble]: Pebble template engine. - * link:/modules/rocker[Rocker]: Rocker template engine. - * link:/modules/thymeleaf[Thymeleaf]: Thymeleaf template engine. + * link:/v3/modules/handlebars[Handlebars]: Handlebars template engine. + * link:/v3/modules/jstachio[JStachio]: JStachio template engine. + * link:/v3/modules/jte[jte]: jte template engine. + * link:/v3/modules/freemarker[Freemarker]: Freemarker template engine. + * link:/v3/modules/pebble[Pebble]: Pebble template engine. + * link:/v3/modules/rocker[Rocker]: Rocker template engine. + * link:/v3/modules/thymeleaf[Thymeleaf]: Thymeleaf template engine. === Security - * link:/modules/jasypt[Jasypt]: Encrypted configuration files. - * link:/modules/pac4j[Pac4j]: Security engine module. + * link:/v3/modules/jasypt[Jasypt]: Encrypted configuration files. + * link:/v3/modules/pac4j[Pac4j]: Security engine module. === Session Store - * link:/modules/caffeine[Caffeine]: In-memory session store using Caffeine cache. - * link:/modules/jwt-session-store[JWT]: JSON Web Token session store. - * link:/modules/redis#redis-http-session[Redis]: Save session data on redis. + * link:/v3/modules/caffeine[Caffeine]: In-memory session store using Caffeine cache. + * link:/v3/modules/jwt-session-store[JWT]: JSON Web Token session store. + * link:/v3/modules/redis#redis-http-session[Redis]: Save session data on redis. === Scheduler - * link:/modules/db-scheduler[DbScheduler]: Db scheduler module. - * link:/modules/quartz[Quartz]: Quartz scheduler module. + * link:/v3/modules/db-scheduler[DbScheduler]: Db scheduler module. + * link:/v3/modules/quartz[Quartz]: Quartz scheduler module. diff --git a/docs/asciidoc/packaging/packaging.adoc b/docs/asciidoc/packaging/packaging.adoc index d8165bf2cd..40cf3448bd 100644 --- a/docs/asciidoc/packaging/packaging.adoc +++ b/docs/asciidoc/packaging/packaging.adoc @@ -9,7 +9,7 @@ application. [TIP] ==== -The link:/#getting-started[jooby-cli] takes care of configures everything for single jar +The link:/v3/#getting-started[jooby-cli] takes care of configures everything for single jar distribution. Next example shows how to do it in case you created your application manually. ==== diff --git a/jooby/pom.xml b/jooby/pom.xml index 370edeeedf..456b63bce1 100644 --- a/jooby/pom.xml +++ b/jooby/pom.xml @@ -6,7 +6,7 @@ io.jooby jooby-project - 3.11.1-SNAPSHOT + 3.11.7 jooby jooby diff --git a/jooby/src/main/java/io/jooby/Route.java b/jooby/src/main/java/io/jooby/Route.java index 68d89988bc..b04d7a9dd8 100644 --- a/jooby/src/main/java/io/jooby/Route.java +++ b/jooby/src/main/java/io/jooby/Route.java @@ -26,11 +26,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import io.jooby.annotation.Transactional; -import io.jooby.exception.MethodNotAllowedException; -import io.jooby.exception.NotAcceptableException; -import io.jooby.exception.NotFoundException; -import io.jooby.exception.StatusCodeException; -import io.jooby.exception.UnsupportedMediaType; +import io.jooby.exception.*; /** * Route contains information about the HTTP method, path pattern, which content types consumes and @@ -350,6 +346,19 @@ public interface Handler extends Serializable, Aware { } }; + /** Handler for body error decoder responses. */ + public static final Handler FORM_DECODER_HANDLER = + ctx -> { + var tooManyFields = (Throwable) ctx.getAttributes().remove("__too_many_fields"); + BadRequestException cause; + if (tooManyFields != null) { + cause = new BadRequestException("Too many form fields", tooManyFields); + } else { + cause = new BadRequestException("Failed to decode HTTP body"); + } + return ctx.sendError(cause); + }; + /** Handler for {@link StatusCode#REQUEST_ENTITY_TOO_LARGE} responses. */ public static final Handler REQUEST_ENTITY_TOO_LARGE = ctx -> diff --git a/jooby/src/main/java/io/jooby/ServerOptions.java b/jooby/src/main/java/io/jooby/ServerOptions.java index a565a742ad..bb07654157 100644 --- a/jooby/src/main/java/io/jooby/ServerOptions.java +++ b/jooby/src/main/java/io/jooby/ServerOptions.java @@ -106,6 +106,9 @@ public class ServerOptions { */ private int maxRequestSize = _10MB; + /** Max number of form fields. Default: 1000. */ + private int maxFormFields = 1000; + /** The maximum size in bytes of a http request header. Default is 8kb */ private int maxHeaderSize = _8KB; @@ -131,7 +134,7 @@ public class ServerOptions { * @param conf Configuration object. * @return Server options. */ - public static @NonNull Optional from(@NonNull Config conf) { + public static Optional from(@NonNull Config conf) { if (conf.hasPath("server")) { ServerOptions options = new ServerOptions(); if (conf.hasPath("server.port")) { @@ -162,6 +165,9 @@ public class ServerOptions { if (conf.hasPath("server.maxRequestSize")) { options.setMaxRequestSize((int) conf.getMemorySize("server.maxRequestSize").toBytes()); } + if (conf.hasPath("server.maxFormFields")) { + options.setMaxFormFields(conf.getInt("server.maxFormFields")); + } if (conf.hasPath("server.workerThreads")) { options.setWorkerThreads(conf.getInt("server.workerThreads")); } @@ -445,11 +451,31 @@ public int getMaxRequestSize() { * @param maxRequestSize Max request size in bytes. * @return This options. */ - public @NonNull ServerOptions setMaxRequestSize(int maxRequestSize) { + public ServerOptions setMaxRequestSize(int maxRequestSize) { this.maxRequestSize = maxRequestSize; return this; } + /** + * Max number of form fields. Default: 1000. + * + * @return Max number of form fields. Default: 1000. + */ + public int getMaxFormFields() { + return maxFormFields; + } + + /** + * Set max number of form fields. Default: 1000. + * + * @param maxFormFields Max number of form fields. + * @return Max number of form fields. Default: 1000. + */ + public ServerOptions setMaxFormFields(int maxFormFields) { + this.maxFormFields = maxFormFields; + return this; + } + /** * The maximum size in bytes of an http request header. Exceeding the size generates a different * response across server implementations. diff --git a/jooby/src/main/java/io/jooby/SneakyThrows.java b/jooby/src/main/java/io/jooby/SneakyThrows.java index eba918fd2c..fa52c99d4a 100644 --- a/jooby/src/main/java/io/jooby/SneakyThrows.java +++ b/jooby/src/main/java/io/jooby/SneakyThrows.java @@ -899,6 +899,28 @@ public static Supplier throwingSupplier(Supplier fn) { return fn; } + /** + * Singleton supplier from existing instance. + * + * @param instance Singleton. + * @return Single supplier. + * @param Resulting value. + */ + public static Supplier singleton(V instance) { + return () -> instance; + } + + /** + * Singleton supplier from another supplier. + * + * @param fn Supplier. + * @return Single supplier. + * @param Resulting value. + */ + public static Supplier singleton(Supplier fn) { + return singleton(fn.get()); + } + /** * Factory method for {@link Function} and {@link java.util.function.Function}. * diff --git a/jooby/src/main/java/io/jooby/internal/RouterMatch.java b/jooby/src/main/java/io/jooby/internal/RouterMatch.java index cb99732c6c..b4aecb711f 100644 --- a/jooby/src/main/java/io/jooby/internal/RouterMatch.java +++ b/jooby/src/main/java/io/jooby/internal/RouterMatch.java @@ -109,7 +109,6 @@ public RouterMatch missing(String method, String path, MessageEncoder encoder) { } this.route = new Route(method, path, h); this.route.setEncoder(encoder); - // this.route.setReturnType(Context.class); return this; } } diff --git a/modules/jooby-apt/pom.xml b/modules/jooby-apt/pom.xml index a491429860..d4e3bfb29e 100644 --- a/modules/jooby-apt/pom.xml +++ b/modules/jooby-apt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-apt jooby-apt diff --git a/modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java b/modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java index a59f968558..a209fff3d8 100644 --- a/modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java +++ b/modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java @@ -113,7 +113,8 @@ public boolean process(Set annotations, RoundEnvironment context.add(router); var sourceCode = router.toSourceCode(null); var sourceLocation = router.getGeneratedFilename(); - onGeneratedSource(toJavaFileObject(sourceLocation, sourceCode)); + onGeneratedSource( + router.getGeneratedType(), toJavaFileObject(sourceLocation, sourceCode)); context.debug("router %s: %s", router.getTargetType(), router.getGeneratedType()); router.getRoutes().forEach(it -> context.debug(" %s", it)); writeSource(router, sourceLocation, sourceCode); @@ -182,7 +183,7 @@ public String toString() { }; } - protected void onGeneratedSource(JavaFileObject source) {} + protected void onGeneratedSource(String className, JavaFileObject source) {} private void doServices(Filer filer, List routers) { try { @@ -261,44 +262,40 @@ private Map buildRouteRegistry( */ private void buildRouteRegistry(Map registry, TypeElement currentType) { for (TypeElement superType : context.superTypes(currentType)) { - if (processed.add(superType)) { - // collect all declared methods - superType.getEnclosedElements().stream() - .filter(ExecutableElement.class::isInstance) - .map(ExecutableElement.class::cast) - .forEach( - method -> { - if (method.getModifiers().contains(Modifier.ABSTRACT)) { - context.debug("ignoring abstract method: %s %s", superType, method); - } else { - method.getAnnotationMirrors().stream() - .map(AnnotationMirror::getAnnotationType) - .map(DeclaredType::asElement) - .filter(TypeElement.class::isInstance) - .map(TypeElement.class::cast) - .filter(HttpMethod::hasAnnotation) - .forEach( - annotation -> { - Stream.of(currentType, superType) - .distinct() - .forEach( - routerClass -> - registry - .computeIfAbsent( - routerClass, type -> new MvcRouter(context, type)) - .put(annotation, method)); - }); - } - }); - } else { - if (!currentType.equals(superType)) { - // edge-case #1: when controller has no method and extends another class which has. - // edge-case #2: some odd usage a controller could be empty. - // See https://github.com/jooby-project/jooby/issues/3656 - if (registry.containsKey(superType)) { - registry.computeIfAbsent( - currentType, key -> new MvcRouter(key, registry.get(superType))); - } + // collect all declared methods + superType.getEnclosedElements().stream() + .filter(ExecutableElement.class::isInstance) + .map(ExecutableElement.class::cast) + .forEach( + method -> { + if (method.getModifiers().contains(Modifier.ABSTRACT)) { + context.debug("ignoring abstract method: %s %s", superType, method); + } else { + method.getAnnotationMirrors().stream() + .map(AnnotationMirror::getAnnotationType) + .map(DeclaredType::asElement) + .filter(TypeElement.class::isInstance) + .map(TypeElement.class::cast) + .filter(HttpMethod::hasAnnotation) + .forEach( + annotation -> { + Stream.of(currentType, superType) + .distinct() + .forEach( + routerClass -> + registry + .computeIfAbsent( + routerClass, type -> new MvcRouter(context, type)) + .put(annotation, method)); + }); + } + }); + if (!currentType.equals(superType)) { + // edge-case #1: when controller has no method and extends another class which has. + // edge-case #2: some odd usage a controller could be empty. + // See https://github.com/jooby-project/jooby/issues/3656 + if (registry.containsKey(superType)) { + registry.computeIfAbsent(currentType, key -> new MvcRouter(key, registry.get(superType))); } } } diff --git a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRouter.java b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRouter.java index 49456e62b6..1f5b2424b7 100644 --- a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRouter.java +++ b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRouter.java @@ -224,13 +224,22 @@ private StringBuilder constructors(String generatedName, boolean kt) { buffer, List.of(), (output, params) -> { - output - .append("this(") - .append(kt ? "" : "new ") - .append(targetType) - .append("())") - .append(semicolon(kt)) - .append(System.lineSeparator()); + if (kt) { + output + .append("this(") + .append(targetType) + .append("())") + .append(semicolon(true)) + .append(System.lineSeparator()); + } else { + output + .append("this(") + .append("io.jooby.SneakyThrows.singleton(") + .append(targetType) + .append("::new))") + .append(semicolon(false)) + .append(System.lineSeparator()); + } }); } } diff --git a/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.java b/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.java index 8beea1cc6c..3a5f01e5a6 100644 --- a/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.java +++ b/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.java @@ -9,7 +9,7 @@ public class ${generatedClassName} implements io.jooby.MvcExtension { } public ${generatedClassName}(java.util.function.Supplier<${className}> provider) { - this(ctx -> provider.get()); + this(ctx -> (${className}) provider.get()); } public ${generatedClassName}(java.util.function.Function factory) { diff --git a/modules/jooby-apt/src/test/java/io/jooby/apt/ProcessorRunner.java b/modules/jooby-apt/src/test/java/io/jooby/apt/ProcessorRunner.java index 1cf3617846..ab3c3005ef 100644 --- a/modules/jooby-apt/src/test/java/io/jooby/apt/ProcessorRunner.java +++ b/modules/jooby-apt/src/test/java/io/jooby/apt/ProcessorRunner.java @@ -27,21 +27,18 @@ public class ProcessorRunner { private static class GeneratedSourceClassLoader extends ClassLoader { - private final JavaFileObject classFile; - private final String className; + private final Map classes = new LinkedHashMap<>(); - public GeneratedSourceClassLoader(ClassLoader parent, JavaFileObject source) { + public GeneratedSourceClassLoader(ClassLoader parent, Map sources) { super(parent); - this.classFile = javac().compile(List.of(source)).generatedFiles().get(0); - this.className = source.getName().replace('/', '.').replace(".java", ""); - } - - public String getClassName() { - return className; + for (var e : sources.entrySet()) { + classes.put(e.getKey(), javac().compile(List.of(e.getValue())).generatedFiles().get(0)); + } } protected Class findClass(String name) throws ClassNotFoundException { - if (name.equals(className)) { + if (classes.containsKey(name)) { + var classFile = classes.get(name); try (var in = classFile.openInputStream()) { var bytes = in.readAllBytes(); return defineClass(name, bytes, 0, bytes.length); @@ -54,24 +51,23 @@ protected Class findClass(String name) throws ClassNotFoundException { } private static class HookJoobyProcessor extends JoobyProcessor { - private JavaFileObject source; - private String kotlinSource; + private Map javaFiles = new LinkedHashMap<>(); + private Map kotlinFiles = new LinkedHashMap<>(); public HookJoobyProcessor(Consumer console) { super((kind, message) -> console.accept(message)); } public GeneratedSourceClassLoader createClassLoader() { - Objects.requireNonNull(source); - return new GeneratedSourceClassLoader(getClass().getClassLoader(), source); + return new GeneratedSourceClassLoader(getClass().getClassLoader(), javaFiles); } public JavaFileObject getSource() { - return source; + return javaFiles.isEmpty() ? null : javaFiles.entrySet().iterator().next().getValue(); } public String getKotlinSource() { - return kotlinSource; + return kotlinFiles.entrySet().iterator().next().getValue(); } public MvcContext getContext() { @@ -79,23 +75,28 @@ public MvcContext getContext() { } @Override - protected void onGeneratedSource(JavaFileObject source) { - this.source = source; + protected void onGeneratedSource(String classname, JavaFileObject source) { + javaFiles.put(classname, source); try { // Generate kotlin source code inside the compiler scope... avoid false positive errors - this.kotlinSource = context.getRouters().get(0).toSourceCode(true); + kotlinFiles.put(classname, context.getRouters().get(0).toSourceCode(true)); } catch (IOException e) { SneakyThrows.propagate(e); } } } + private final List instances; private final HookJoobyProcessor processor; public ProcessorRunner(Object instance) throws IOException { this(instance, Map.of()); } + public ProcessorRunner(List instances) throws IOException { + this(instances, System.out::println, Map.of()); + } + public ProcessorRunner(Object instance, Consumer stdout) throws IOException { this(instance, stdout, Map.of()); } @@ -106,12 +107,19 @@ public ProcessorRunner(Object instance, Map options) throws IOEx public ProcessorRunner(Object instance, Consumer stdout, Map options) throws IOException { - this.processor = new HookJoobyProcessor(stdout::accept); + this(List.of(instance), stdout, options); + } + + public ProcessorRunner( + List instances, Consumer stdout, Map options) + throws IOException { + this.instances = instances; + this.processor = new HookJoobyProcessor(stdout); var optionsArray = options.entrySet().stream().map(e -> "-A" + e.getKey() + "=" + e.getValue()).toList(); Truth.assert_() .about(JavaSourcesSubjectFactory.javaSources()) - .that(sources(sourceNames(instance.getClass()))) + .that(sources(sourceNames(instances.stream().map(Object::getClass).toList()))) .withCompilerOptions(optionsArray.toArray(new String[0])) .processedWith(processor) .compilesWithoutError(); @@ -123,11 +131,33 @@ public ProcessorRunner withRouter(SneakyThrows.Consumer consumer) throws public ProcessorRunner withRouter(SneakyThrows.Consumer2 consumer) throws Exception { + return withRouter(instances.get(0).getClass(), consumer); + } + + public ProcessorRunner withRouter(Class routerType, SneakyThrows.Consumer consumer) + throws Exception { + return withRouter(routerType, (app, source) -> consumer.accept(app)); + } + + public ProcessorRunner withRouter( + Class routerType, SneakyThrows.Consumer2 consumer) + throws Exception { var classLoader = processor.createClassLoader(); - var factoryName = classLoader.getClassName(); - var factoryClass = (Class) classLoader.loadClass(factoryName); - var constructor = factoryClass.getDeclaredConstructor(); - var extension = constructor.newInstance(); + var factoryName = routerType.getName() + "_"; + var factoryClass = (Class) classLoader.loadClass(factoryName); + Extension extension; + try { + var constructor = factoryClass.getDeclaredConstructor(); + extension = constructor.newInstance(); + } catch (NoSuchMethodException x) { + var instance = + instances.stream() + .filter(it -> it.getClass().equals(routerType)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Not found: " + routerType)); + extension = factoryClass.getDeclaredConstructor(routerType).newInstance(instance); + } + var application = new Jooby(); application.install(extension); consumer.accept(application, processor.getSource()); @@ -146,17 +176,22 @@ public ProcessorRunner withSourceCode(SneakyThrows.Consumer consumer) { public ProcessorRunner withSourceCode(boolean kt, SneakyThrows.Consumer consumer) { consumer.accept( kt - ? processor.kotlinSource + ? processor.kotlinFiles.values().iterator().next() : Optional.ofNullable(processor.getSource()).map(Objects::toString).orElse(null)); return this; } - private String[] sourceNames(Class input) { + private String[] sourceNames(List> inputs) { List result = new ArrayList<>(); - while (input != Object.class) { - result.add(input.getName()); - input = input.getSuperclass(); - } + Set visited = new HashSet<>(); + inputs.stream() + .forEach( + input -> { + while (input != Object.class && visited.add(input)) { + result.add(input.getName()); + input = input.getSuperclass(); + } + }); return result.toArray(new String[0]); } diff --git a/modules/jooby-apt/src/test/java/tests/i3737/C3737.java b/modules/jooby-apt/src/test/java/tests/i3737/C3737.java new file mode 100644 index 0000000000..90a6956927 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3737/C3737.java @@ -0,0 +1,20 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3737; + +import io.jooby.annotation.GET; +import io.jooby.annotation.Path; + +@Path("/3737") +public class C3737 { + + public C3737() throws Exception {} + + @GET("/hello") + public String hello() { + return "hello world"; + } +} diff --git a/modules/jooby-apt/src/test/java/tests/i3737/Issue3737.java b/modules/jooby-apt/src/test/java/tests/i3737/Issue3737.java new file mode 100644 index 0000000000..91c2e6dce4 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3737/Issue3737.java @@ -0,0 +1,28 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3737; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import io.jooby.apt.ProcessorRunner; + +public class Issue3737 { + @Test + public void shouldNotFail() throws Exception { + // must compile + new ProcessorRunner(new C3737()) + .withRouter( + (app, source) -> { + var routes = app.getRoutes(); + assertNotNull(routes); + assertFalse(routes.isEmpty()); + var route = app.getRoutes().get(0); + assertNotNull(route); + }); + } +} diff --git a/modules/jooby-apt/src/test/java/tests/i3786/Base3786.java b/modules/jooby-apt/src/test/java/tests/i3786/Base3786.java new file mode 100644 index 0000000000..1fdfca7662 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3786/Base3786.java @@ -0,0 +1,30 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3786; + +import io.jooby.Context; +import io.jooby.annotation.GET; +import io.jooby.annotation.Path; +import io.jooby.annotation.QueryParam; + +@Path("/base") +public abstract class Base3786 { + + @GET + public String base() { + return "base"; + } + + @GET("/withPath") + public String withPath(@QueryParam String q) { + return "withPath"; + } + + @GET("/{id}") + public String getOne(Context ctx) { + return "base: " + ctx.path("id").value(); + } +} diff --git a/modules/jooby-apt/src/test/java/tests/i3786/C3786.java b/modules/jooby-apt/src/test/java/tests/i3786/C3786.java new file mode 100644 index 0000000000..1a044762f1 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3786/C3786.java @@ -0,0 +1,24 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3786; + +import io.jooby.Context; +import io.jooby.annotation.GET; +import io.jooby.annotation.POST; +import io.jooby.annotation.Path; + +@Path("/inherited") +public class C3786 extends Base3786 { + @GET("/childOnly") + public String childOnly(Context ctx) { + return ctx.getMethod() + ctx.getRequestPath(); + } + + @POST("/childOnly") + public String childOnlyPost(Context ctx) { + return ctx.getMethod() + ctx.getRequestPath(); + } +} diff --git a/modules/jooby-apt/src/test/java/tests/i3786/D3786.java b/modules/jooby-apt/src/test/java/tests/i3786/D3786.java new file mode 100644 index 0000000000..f83b5cb98f --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3786/D3786.java @@ -0,0 +1,24 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3786; + +import io.jooby.Context; +import io.jooby.annotation.GET; +import io.jooby.annotation.POST; +import io.jooby.annotation.Path; + +@Path("/overrideMethod") +public class D3786 extends Base3786 { + @GET("/childOnly") + public String childOnly(Context ctx) { + return ctx.getRequestPath(); + } + + @POST("/user") + public String newPath(Context q) { + return "/overrideMethod/user"; + } +} diff --git a/modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java b/modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java new file mode 100644 index 0000000000..beaa56eaf0 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java @@ -0,0 +1,54 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3786; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import io.jooby.apt.ProcessorRunner; +import io.jooby.test.MockContext; +import io.jooby.test.MockRouter; + +public class Issue3786 { + @Test + public void shouldCheckBase() throws Exception { + new ProcessorRunner(List.of(new C3786(), new D3786())) + .withRouter( + C3786.class, + (app, source) -> { + var router = new MockRouter(app); + assertEquals("base", router.get("/inherited", new MockContext()).value()); + assertEquals( + "withPath", router.get("/inherited/withPath", new MockContext()).value()); + assertEquals("base: 123", router.get("/inherited/123", new MockContext()).value()); + assertEquals( + "GET/inherited/childOnly", + router.get("/inherited/childOnly", new MockContext()).value()); + assertEquals( + "POST/inherited/childOnly", + router.post("/inherited/childOnly", new MockContext()).value()); + }) + .withRouter( + D3786.class, + (app, source) -> { + var router = new MockRouter(app); + assertEquals("base", router.get("/overrideMethod", new MockContext()).value()); + assertEquals( + "withPath", router.get("/overrideMethod/withPath", new MockContext()).value()); + assertEquals( + "base: 123", router.get("/overrideMethod/123", new MockContext()).value()); + assertEquals( + "/overrideMethod/childOnly", + router.get("/overrideMethod/childOnly", new MockContext()).value()); + assertEquals( + "/overrideMethod/user", + router.post("/overrideMethod/user", new MockContext()).value()); + }); + } +} diff --git a/modules/jooby-avaje-inject/pom.xml b/modules/jooby-avaje-inject/pom.xml index be63832efc..d040ba68f3 100644 --- a/modules/jooby-avaje-inject/pom.xml +++ b/modules/jooby-avaje-inject/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-avaje-inject jooby-avaje-inject diff --git a/modules/jooby-avaje-jsonb/pom.xml b/modules/jooby-avaje-jsonb/pom.xml index c67f6c8727..21b6dcf939 100644 --- a/modules/jooby-avaje-jsonb/pom.xml +++ b/modules/jooby-avaje-jsonb/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-avaje-jsonb jooby-avaje-jsonb diff --git a/modules/jooby-avaje-validator/pom.xml b/modules/jooby-avaje-validator/pom.xml index 4822973c37..fbd2c3a387 100644 --- a/modules/jooby-avaje-validator/pom.xml +++ b/modules/jooby-avaje-validator/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-avaje-validator jooby-avaje-validator diff --git a/modules/jooby-awssdk-v1/pom.xml b/modules/jooby-awssdk-v1/pom.xml index 49364c9af4..f0c4facfac 100644 --- a/modules/jooby-awssdk-v1/pom.xml +++ b/modules/jooby-awssdk-v1/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-awssdk-v1 jooby-awssdk-v1 diff --git a/modules/jooby-awssdk-v2/pom.xml b/modules/jooby-awssdk-v2/pom.xml index 1f515bafeb..cfd7b12226 100644 --- a/modules/jooby-awssdk-v2/pom.xml +++ b/modules/jooby-awssdk-v2/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-awssdk-v2 jooby-awssdk-v2 diff --git a/modules/jooby-bom/pom.xml b/modules/jooby-bom/pom.xml index c52bbd6531..463c586e34 100644 --- a/modules/jooby-bom/pom.xml +++ b/modules/jooby-bom/pom.xml @@ -7,14 +7,14 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 io.jooby jooby-bom jooby-bom pom - 3.11.1-SNAPSHOT + 3.11.7 Jooby (Bill of Materials) https://jooby.io diff --git a/modules/jooby-caffeine/pom.xml b/modules/jooby-caffeine/pom.xml index d9d39a098d..4fbffbf3e1 100644 --- a/modules/jooby-caffeine/pom.xml +++ b/modules/jooby-caffeine/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-caffeine jooby-caffeine diff --git a/modules/jooby-camel/pom.xml b/modules/jooby-camel/pom.xml index 3a947c1ad7..3b0308e560 100644 --- a/modules/jooby-camel/pom.xml +++ b/modules/jooby-camel/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-camel jooby-camel diff --git a/modules/jooby-cli/pom.xml b/modules/jooby-cli/pom.xml index 57dc312c36..f88caab387 100644 --- a/modules/jooby-cli/pom.xml +++ b/modules/jooby-cli/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-cli jooby-cli diff --git a/modules/jooby-commons-email/pom.xml b/modules/jooby-commons-email/pom.xml index 58462beddd..c6d6a6c66a 100644 --- a/modules/jooby-commons-email/pom.xml +++ b/modules/jooby-commons-email/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-commons-email jooby-commons-email diff --git a/modules/jooby-conscrypt/pom.xml b/modules/jooby-conscrypt/pom.xml index f2dda890fd..5b79fa9f46 100644 --- a/modules/jooby-conscrypt/pom.xml +++ b/modules/jooby-conscrypt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-conscrypt jooby-conscrypt diff --git a/modules/jooby-db-scheduler/pom.xml b/modules/jooby-db-scheduler/pom.xml index c0b6ef9ed2..3d8097494e 100644 --- a/modules/jooby-db-scheduler/pom.xml +++ b/modules/jooby-db-scheduler/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-db-scheduler jooby-db-scheduler diff --git a/modules/jooby-distribution/pom.xml b/modules/jooby-distribution/pom.xml index 8f96266a19..6b8031e1cf 100644 --- a/modules/jooby-distribution/pom.xml +++ b/modules/jooby-distribution/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-distribution jooby-distribution diff --git a/modules/jooby-ebean/pom.xml b/modules/jooby-ebean/pom.xml index 8a62e54cac..78edb85019 100644 --- a/modules/jooby-ebean/pom.xml +++ b/modules/jooby-ebean/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-ebean jooby-ebean diff --git a/modules/jooby-flyway/pom.xml b/modules/jooby-flyway/pom.xml index 8ea5a5485d..43802925e0 100644 --- a/modules/jooby-flyway/pom.xml +++ b/modules/jooby-flyway/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-flyway jooby-flyway diff --git a/modules/jooby-flyway/src/main/java/io/jooby/flyway/FlywayModule.java b/modules/jooby-flyway/src/main/java/io/jooby/flyway/FlywayModule.java index 63ca8df3ec..8642f2f0c5 100644 --- a/modules/jooby-flyway/src/main/java/io/jooby/flyway/FlywayModule.java +++ b/modules/jooby-flyway/src/main/java/io/jooby/flyway/FlywayModule.java @@ -5,14 +5,13 @@ */ package io.jooby.flyway; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; +import java.util.*; import javax.sql.DataSource; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.configuration.FluentConfiguration; +import org.flywaydb.core.api.migration.JavaMigration; import edu.umd.cs.findbugs.annotations.NonNull; import io.jooby.Extension; @@ -48,6 +47,7 @@ public class FlywayModule implements Extension { private final String name; + private List javaMigrations = List.of(); /** * Creates a new Flyway module. @@ -66,16 +66,30 @@ public FlywayModule() { this("db"); } + /** + * The manually added Java-based migrations. These are not Java-based migrations discovered + * through classpath scanning and instantiated by Flyway. Instead, these are manually added + * instances of JavaMigration. This is particularly useful when working with a dependencies, where + * you may want to instantiate the class and wire up its dependencies. + * + * @param migrations The manually added Java-based migrations. An empty array if none. + * @return This module. + */ + public FlywayModule javaMigrations(@NonNull JavaMigration... migrations) { + this.javaMigrations = List.of(migrations); + return this; + } + @Override public void install(@NonNull Jooby application) throws Exception { var environment = application.getEnvironment(); var registry = application.getServices(); var dataSource = registry.getOrNull(ServiceKey.key(DataSource.class, name)); if (dataSource == null) { - // TODO: replace with usage exception dataSource = registry.require(DataSource.class); } var configuration = new FluentConfiguration(environment.getClassLoader()); + configuration.javaMigrations(javaMigrations.toArray(new JavaMigration[0])); var defaults = environment.getProperties("flyway"); var overrides = environment.getProperties(name + ".flyway", "flyway"); diff --git a/modules/jooby-freemarker/pom.xml b/modules/jooby-freemarker/pom.xml index 1189958613..5ffcf45982 100644 --- a/modules/jooby-freemarker/pom.xml +++ b/modules/jooby-freemarker/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-freemarker jooby-freemarker diff --git a/modules/jooby-gradle-setup/pom.xml b/modules/jooby-gradle-setup/pom.xml index 6110880e0b..a4b6e25a1c 100644 --- a/modules/jooby-gradle-setup/pom.xml +++ b/modules/jooby-gradle-setup/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-gradle-setup jooby-gradle-setup diff --git a/modules/jooby-graphiql/pom.xml b/modules/jooby-graphiql/pom.xml index ed4e199f00..840c9835cc 100644 --- a/modules/jooby-graphiql/pom.xml +++ b/modules/jooby-graphiql/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-graphiql jooby-graphiql @@ -58,26 +58,26 @@ - - org.apache.maven.plugins - maven-antrun-plugin - ${maven-antrun-plugin.version} - - - - run - - generate-resources - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/modules/jooby-graphiql/src/main/resources/io/jooby/graphiql/index.html b/modules/jooby-graphiql/src/main/resources/io/jooby/graphiql/index.html new file mode 100644 index 0000000000..1a71687193 --- /dev/null +++ b/modules/jooby-graphiql/src/main/resources/io/jooby/graphiql/index.html @@ -0,0 +1,92 @@ + + + + + + + GraphiQL 5 with React 19 and GraphiQL Explorer + + + + + + + + +
+
Loading…
+
+ + diff --git a/modules/jooby-graphql/pom.xml b/modules/jooby-graphql/pom.xml index a034382138..177c3e2468 100644 --- a/modules/jooby-graphql/pom.xml +++ b/modules/jooby-graphql/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-graphql jooby-graphql diff --git a/modules/jooby-gson/pom.xml b/modules/jooby-gson/pom.xml index 0c5ebd8629..2eaa3401d3 100644 --- a/modules/jooby-gson/pom.xml +++ b/modules/jooby-gson/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-gson jooby-gson diff --git a/modules/jooby-guice/pom.xml b/modules/jooby-guice/pom.xml index 84f020dd29..a5ede3a5d0 100644 --- a/modules/jooby-guice/pom.xml +++ b/modules/jooby-guice/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-guice jooby-guice diff --git a/modules/jooby-handlebars/pom.xml b/modules/jooby-handlebars/pom.xml index 97e784d149..623d366580 100644 --- a/modules/jooby-handlebars/pom.xml +++ b/modules/jooby-handlebars/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-handlebars jooby-handlebars diff --git a/modules/jooby-hibernate-validator/pom.xml b/modules/jooby-hibernate-validator/pom.xml index fd17f1043a..29faa5af1d 100644 --- a/modules/jooby-hibernate-validator/pom.xml +++ b/modules/jooby-hibernate-validator/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-hibernate-validator jooby-hibernate-validator diff --git a/modules/jooby-hibernate/pom.xml b/modules/jooby-hibernate/pom.xml index c50d195ba4..ade04c7576 100644 --- a/modules/jooby-hibernate/pom.xml +++ b/modules/jooby-hibernate/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-hibernate jooby-hibernate diff --git a/modules/jooby-hikari/pom.xml b/modules/jooby-hikari/pom.xml index 97f5952f43..c4de2fe539 100644 --- a/modules/jooby-hikari/pom.xml +++ b/modules/jooby-hikari/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-hikari jooby-hikari diff --git a/modules/jooby-jackson/pom.xml b/modules/jooby-jackson/pom.xml index b16c61f880..8fd7305538 100644 --- a/modules/jooby-jackson/pom.xml +++ b/modules/jooby-jackson/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-jackson jooby-jackson diff --git a/modules/jooby-jasypt/pom.xml b/modules/jooby-jasypt/pom.xml index d7772da36a..861aa074c9 100644 --- a/modules/jooby-jasypt/pom.xml +++ b/modules/jooby-jasypt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-jasypt jooby-jasypt diff --git a/modules/jooby-jdbi/pom.xml b/modules/jooby-jdbi/pom.xml index db928f0fd2..2e0c1f74f9 100644 --- a/modules/jooby-jdbi/pom.xml +++ b/modules/jooby-jdbi/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-jdbi jooby-jdbi diff --git a/modules/jooby-jetty/pom.xml b/modules/jooby-jetty/pom.xml index 9271df03f9..c58f318289 100644 --- a/modules/jooby-jetty/pom.xml +++ b/modules/jooby-jetty/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-jetty jooby-jetty diff --git a/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyContext.java b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyContext.java index 6350054d9a..39cb28e15c 100644 --- a/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyContext.java +++ b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyContext.java @@ -697,7 +697,7 @@ private FileUpload register(FileUpload upload) { return upload; } - private static void formParam(Request request, Formdata form) { + private void formParam(Request request, Formdata form) { try { var params = Request.getParameters(request); for (Fields.Field param : params) { @@ -710,7 +710,19 @@ private static void formParam(Request request, Formdata form) { } } } catch (Exception ex) { - throw SneakyThrows.propagate(ex); + if (ex instanceof IllegalStateException + && ex.getMessage() != null + && ex.getMessage().startsWith("form with too many")) { + this.setAttribute("__too_many_fields", ex); + try { + Route.FORM_DECODER_HANDLER.apply(this); + } catch (Exception cause) { + throw SneakyThrows.propagate(cause); + } + throw new JettyStopPipeline(); + } else { + throw SneakyThrows.propagate(ex); + } } } } diff --git a/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyHandler.java b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyHandler.java index f3b2744f68..ac90b7d20f 100644 --- a/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyHandler.java +++ b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyHandler.java @@ -39,10 +39,14 @@ public boolean handle(Request request, Response response, Callback callback) { if (defaultHeaders) { responseHeaders.put(HttpHeader.SERVER.asString(), "J"); } - var context = - new JettyContext( - getInvocationType(), request, response, callback, router, bufferSize, maxRequestSize); - router.match(context).execute(context); + try { + var context = + new JettyContext( + getInvocationType(), request, response, callback, router, bufferSize, maxRequestSize); + router.match(context).execute(context); + } catch (JettyStopPipeline ignored) { + // handled already, + } return true; } } diff --git a/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyStopPipeline.java b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyStopPipeline.java new file mode 100644 index 0000000000..4358c174bc --- /dev/null +++ b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyStopPipeline.java @@ -0,0 +1,8 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.internal.jetty; + +public class JettyStopPipeline extends RuntimeException {} diff --git a/modules/jooby-jetty/src/main/java/io/jooby/jetty/JettyServer.java b/modules/jooby-jetty/src/main/java/io/jooby/jetty/JettyServer.java index 26903c4463..d615668270 100644 --- a/modules/jooby-jetty/src/main/java/io/jooby/jetty/JettyServer.java +++ b/modules/jooby-jetty/src/main/java/io/jooby/jetty/JettyServer.java @@ -26,7 +26,7 @@ import org.eclipse.jetty.util.thread.Invocable; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.ThreadPool; -import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler; +import org.eclipse.jetty.websocket.server.ServerWebSocketContainer; import com.typesafe.config.Config; import edu.umd.cs.findbugs.annotations.NonNull; @@ -98,10 +98,6 @@ public io.jooby.Server start(@NonNull Jooby... application) { var portInUse = options.getPort(); try { this.applications = List.of(application); - /* Set max request size attribute: */ - System.setProperty( - "org.eclipse.jetty.server.Request.maxFormContentSize", - Long.toString(options.getMaxRequestSize())); addShutdownHook(); @@ -114,7 +110,7 @@ public io.jooby.Server start(@NonNull Jooby... application) { var acceptors = 1; var selectors = options.getIoThreads(); - this.server = new Server(threadPool); + server = new Server(threadPool); server.setStopAtShutdown(false); JettyHttp2Configurer http2 = @@ -200,7 +196,8 @@ public io.jooby.Server start(@NonNull Jooby... application) { } var context = new ContextHandler(); - + context.setAttribute(FormFields.MAX_FIELDS_ATTRIBUTE, options.getMaxFormFields()); + context.setAttribute(FormFields.MAX_LENGTH_ATTRIBUTE, options.getMaxRequestSize()); boolean webSockets = application[0].getRoutes().stream().anyMatch(it -> it.getMethod().equals(Router.WS)); @@ -239,13 +236,9 @@ public io.jooby.Server start(@NonNull Jooby... application) { ? conf.getDuration("websocket.idleTimeout", TimeUnit.MILLISECONDS) : TimeUnit.MINUTES.toMillis(5); - WebSocketUpgradeHandler.from( - server, - context, - container -> { - container.setMaxTextMessageSize(maxSize); - container.setIdleTimeout(Duration.ofMillis(timeout)); - }); + var container = ServerWebSocketContainer.ensure(server, context); + container.setMaxTextMessageSize(maxSize); + container.setIdleTimeout(Duration.ofMillis(timeout)); } server.setHandler(context); server.start(); diff --git a/modules/jooby-jstachio/pom.xml b/modules/jooby-jstachio/pom.xml index 7946ce4c6c..97f81a7d91 100644 --- a/modules/jooby-jstachio/pom.xml +++ b/modules/jooby-jstachio/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-jstachio jooby-jstachio diff --git a/modules/jooby-jte/pom.xml b/modules/jooby-jte/pom.xml index c950e5935e..67359eb05e 100644 --- a/modules/jooby-jte/pom.xml +++ b/modules/jooby-jte/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-jte jooby-jte diff --git a/modules/jooby-jwt/pom.xml b/modules/jooby-jwt/pom.xml index e658dbc67b..ce2c1cc001 100644 --- a/modules/jooby-jwt/pom.xml +++ b/modules/jooby-jwt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-jwt jooby-jwt diff --git a/modules/jooby-kafka/pom.xml b/modules/jooby-kafka/pom.xml index 2aaf104d87..1d11efd5ec 100644 --- a/modules/jooby-kafka/pom.xml +++ b/modules/jooby-kafka/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-kafka jooby-kafka diff --git a/modules/jooby-kotlin/pom.xml b/modules/jooby-kotlin/pom.xml index 21747bb212..38df42ad05 100644 --- a/modules/jooby-kotlin/pom.xml +++ b/modules/jooby-kotlin/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-kotlin jooby-kotlin diff --git a/modules/jooby-log4j/pom.xml b/modules/jooby-log4j/pom.xml index 20db48cb26..d6e78fe02e 100644 --- a/modules/jooby-log4j/pom.xml +++ b/modules/jooby-log4j/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-log4j jooby-log4j diff --git a/modules/jooby-logback/pom.xml b/modules/jooby-logback/pom.xml index 992bf6bab2..1866f47f68 100644 --- a/modules/jooby-logback/pom.xml +++ b/modules/jooby-logback/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-logback jooby-logback diff --git a/modules/jooby-maven-plugin/pom.xml b/modules/jooby-maven-plugin/pom.xml index 75435d68a4..51aa522658 100644 --- a/modules/jooby-maven-plugin/pom.xml +++ b/modules/jooby-maven-plugin/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-maven-plugin jooby-maven-plugin diff --git a/modules/jooby-metrics/pom.xml b/modules/jooby-metrics/pom.xml index fd1316524f..9cd1f36146 100644 --- a/modules/jooby-metrics/pom.xml +++ b/modules/jooby-metrics/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-metrics jooby-metrics diff --git a/modules/jooby-mutiny/pom.xml b/modules/jooby-mutiny/pom.xml index 6f6e3e1380..9cec40670f 100644 --- a/modules/jooby-mutiny/pom.xml +++ b/modules/jooby-mutiny/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-mutiny jooby-mutiny diff --git a/modules/jooby-netty/pom.xml b/modules/jooby-netty/pom.xml index 20ae2e15c8..338a24311c 100644 --- a/modules/jooby-netty/pom.xml +++ b/modules/jooby-netty/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-netty jooby-netty diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java index 261592f720..c83faf3e4f 100644 --- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java +++ b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java @@ -34,7 +34,7 @@ public class NettyHandler extends ChannelInboundHandlerAdapter { private final int bufferSize; private final boolean defaultHeaders; private final long maxRequestSize; - private long contentLength; + private final int maxFormFields; private long chunkSize; private final boolean http2; private NettyContext context; @@ -43,6 +43,7 @@ public NettyHandler( NettyDateService dateService, List applications, long maxRequestSize, + int maxFormFields, int bufferSize, boolean defaultHeaders, boolean http2) { @@ -50,6 +51,7 @@ public NettyHandler( this.applications = applications; this.ctxSelector = Context.Selector.create(applications); this.maxRequestSize = maxRequestSize; + this.maxFormFields = maxFormFields; this.bufferSize = bufferSize; this.defaultHeaders = defaultHeaders; this.http2 = http2; @@ -75,29 +77,16 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { router.match(context).execute(context); } else { // possibly body: - contentLength = contentLength(req); + long contentLength = contentLength(req); if (contentLength > 0 || isTransferEncodingChunked(req)) { context.httpDataFactory = new DefaultHttpDataFactory(bufferSize); context.httpDataFactory.setBaseDir(app.getTmpdir().toString()); - context.decoder = newDecoder(req, context.httpDataFactory); + context.decoder = newDecoder(req, context.httpDataFactory, maxFormFields); } else { // no body, move on router.match(context).execute(context); } } - } else if (isLastHttpContent(msg)) { - var chunk = (HttpContent) msg; - try { - // when decoder == null, chunk is always a LastHttpContent.EMPTY, ignore it - if (context.decoder != null) { - offer(context, chunk); - Router.Match route = router.match(context); - resetDecoderState(context, !route.matches()); - route.execute(context); - } - } finally { - release(chunk); - } } else if (isHttpContent(msg)) { var chunk = (HttpContent) msg; try { @@ -109,10 +98,13 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { router.match(context).execute(context, Route.REQUEST_ENTITY_TOO_LARGE); return; } - offer(context, chunk); + if (offer(context, chunk) && isLastHttpContent(msg)) { + Router.Match route = router.match(context); + resetDecoderState(context, !route.matches()); + route.execute(context); + } } } finally { - // must be released release(chunk); } } else if (isWebSocketFrame(msg)) { @@ -172,20 +164,21 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { } } - private void offer(NettyContext context, HttpContent chunk) { + private boolean offer(NettyContext context, HttpContent chunk) { try { context.decoder.offer(chunk); - } catch (HttpPostRequestDecoder.ErrorDataDecoderException - | HttpPostRequestDecoder.TooLongFormFieldException - | HttpPostRequestDecoder.TooManyFormFieldsException x) { - resetDecoderState(context, true); - context.sendError(x, StatusCode.BAD_REQUEST); + return true; + } catch (Exception x) { + if (x instanceof HttpPostRequestDecoder.TooManyFormFieldsException) { + context.setAttribute("__too_many_fields", x); + } + router.match(context).execute(context, Route.FORM_DECODER_HANDLER); + return false; } } private void resetDecoderState(NettyContext context, boolean destroy) { chunkSize = 0; - contentLength = -1; if (destroy && context.decoder != null) { var decoder = context.decoder; var httpDataFactory = context.httpDataFactory; @@ -197,14 +190,16 @@ private void resetDecoderState(NettyContext context, boolean destroy) { } private static InterfaceHttpPostRequestDecoder newDecoder( - HttpRequest request, HttpDataFactory factory) { + HttpRequest request, HttpDataFactory factory, int maxFormFields) { String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE); if (contentType != null) { String lowerContentType = contentType.toLowerCase(); if (lowerContentType.startsWith(MediaType.MULTIPART_FORMDATA)) { - return new HttpPostMultipartRequestDecoder(factory, request, StandardCharsets.UTF_8); + return new HttpPostMultipartRequestDecoder( + factory, request, StandardCharsets.UTF_8, maxFormFields, -1); } else if (lowerContentType.startsWith(MediaType.FORM_URLENCODED)) { - return new HttpPostStandardRequestDecoder(factory, request, StandardCharsets.UTF_8); + return new HttpPostStandardRequestDecoder( + factory, request, StandardCharsets.UTF_8, maxFormFields, -1); } } return new HttpRawPostRequestDecoder(factory, request); diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java index 44b12e46cc..32123a4d77 100644 --- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java +++ b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java @@ -24,6 +24,7 @@ public class NettyPipeline extends ChannelInitializer { private final HttpDecoderConfig decoderConfig; private final List applications; private final long maxRequestSize; + private final int maxFormFields; private final int bufferSize; private final boolean defaultHeaders; private final boolean http2; @@ -36,6 +37,7 @@ public NettyPipeline( HttpDecoderConfig decoderConfig, List applications, long maxRequestSize, + int maxFormFields, int bufferSize, boolean defaultHeaders, boolean http2, @@ -46,6 +48,7 @@ public NettyPipeline( this.decoderConfig = decoderConfig; this.applications = applications; this.maxRequestSize = maxRequestSize; + this.maxFormFields = maxFormFields; this.bufferSize = bufferSize; this.defaultHeaders = defaultHeaders; this.http2 = http2; @@ -55,7 +58,7 @@ public NettyPipeline( private NettyHandler createHandler() { return new NettyHandler( - serverDate, applications, maxRequestSize, bufferSize, defaultHeaders, http2); + serverDate, applications, maxRequestSize, maxFormFields, bufferSize, defaultHeaders, http2); } @Override diff --git a/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java b/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java index eaea22c579..ce5d5a99ce 100644 --- a/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java +++ b/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java @@ -213,6 +213,7 @@ private NettyPipeline newPipeline( decoderConfig, applications, options.getMaxRequestSize(), + options.getMaxFormFields(), bufferSize, options.getDefaultHeaders(), http2, diff --git a/modules/jooby-openapi/pom.xml b/modules/jooby-openapi/pom.xml index 131f4d9ca5..ea7dd625f5 100644 --- a/modules/jooby-openapi/pom.xml +++ b/modules/jooby-openapi/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-openapi jooby-openapi @@ -56,7 +56,7 @@ com.fasterxml.jackson.core jackson-annotations - ${jackson.version} + 2.21 diff --git a/modules/jooby-pac4j/pom.xml b/modules/jooby-pac4j/pom.xml index 216e0f921a..57e54975fd 100644 --- a/modules/jooby-pac4j/pom.xml +++ b/modules/jooby-pac4j/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-pac4j jooby-pac4j diff --git a/modules/jooby-pebble/pom.xml b/modules/jooby-pebble/pom.xml index 7415699d95..f4969d7441 100644 --- a/modules/jooby-pebble/pom.xml +++ b/modules/jooby-pebble/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-pebble jooby-pebble diff --git a/modules/jooby-quartz/pom.xml b/modules/jooby-quartz/pom.xml index de6bcc51df..637dbabd7c 100644 --- a/modules/jooby-quartz/pom.xml +++ b/modules/jooby-quartz/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-quartz jooby-quartz diff --git a/modules/jooby-reactor/pom.xml b/modules/jooby-reactor/pom.xml index 6049affec4..b55a4533e0 100644 --- a/modules/jooby-reactor/pom.xml +++ b/modules/jooby-reactor/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-reactor jooby-reactor diff --git a/modules/jooby-redis/pom.xml b/modules/jooby-redis/pom.xml index c033a4a463..22d7b6b3b9 100644 --- a/modules/jooby-redis/pom.xml +++ b/modules/jooby-redis/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-redis jooby-redis diff --git a/modules/jooby-redoc/pom.xml b/modules/jooby-redoc/pom.xml index 473d9ea04a..5dc8f92f02 100644 --- a/modules/jooby-redoc/pom.xml +++ b/modules/jooby-redoc/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-redoc jooby-redoc diff --git a/modules/jooby-rocker/pom.xml b/modules/jooby-rocker/pom.xml index cd84140c81..7e473138a8 100644 --- a/modules/jooby-rocker/pom.xml +++ b/modules/jooby-rocker/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-rocker jooby-rocker diff --git a/modules/jooby-run/pom.xml b/modules/jooby-run/pom.xml index 02f1555b09..d5da81db7f 100644 --- a/modules/jooby-run/pom.xml +++ b/modules/jooby-run/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-run jooby-run diff --git a/modules/jooby-run/src/main/java/io/jooby/internal/run/JoobyModuleFinder.java b/modules/jooby-run/src/main/java/io/jooby/internal/run/JoobyModuleFinder.java index df30de02c4..c933b390f3 100644 --- a/modules/jooby-run/src/main/java/io/jooby/internal/run/JoobyModuleFinder.java +++ b/modules/jooby-run/src/main/java/io/jooby/internal/run/JoobyModuleFinder.java @@ -7,8 +7,8 @@ import static java.util.stream.Collectors.joining; import static org.jboss.modules.ResourceLoaderSpec.createResourceLoaderSpec; -import static org.jboss.modules.ResourceLoaders.createJarResourceLoader; -import static org.jboss.modules.ResourceLoaders.createPathResourceLoader; +import static org.jboss.modules.ResourceLoaders.*; +import static org.jboss.modules.filter.PathFilters.not; import java.io.File; import java.io.IOException; @@ -23,12 +23,7 @@ import java.util.Set; import java.util.jar.JarFile; -import org.jboss.modules.DependencySpec; -import org.jboss.modules.ModuleDependencySpecBuilder; -import org.jboss.modules.ModuleFinder; -import org.jboss.modules.ModuleSpec; -import org.jboss.modules.PathUtils; -import org.jboss.modules.ResourceLoaderSpec; +import org.jboss.modules.*; import org.jboss.modules.filter.PathFilters; import io.jooby.run.JoobyRun; @@ -73,8 +68,7 @@ private static Path joobyRunHook(Class loader) { } } - public static ModuleSpec createModuleSpec( - String name, Set resources, Set dependencies) { + public ModuleSpec createModuleSpec(String name, Set resources, Set dependencies) { ModuleSpec.Builder builder = newModule(name, resources); // dependencies @@ -90,7 +84,7 @@ public static ModuleSpec createModuleSpec( return builder.create(); } - private static ModuleSpec.Builder newModule(String name, Set resources) { + private ModuleSpec.Builder newModule(String name, Set resources) { try { ModuleSpec.Builder builder = ModuleSpec.build(name); // Add all JDK classes @@ -102,8 +96,20 @@ private static ModuleSpec.Builder newModule(String name, Set resources) { for (Path path : resources) { if (Files.isDirectory(path)) { - builder.addResourceRoot( - ResourceLoaderSpec.createResourceLoaderSpec(createPathResourceLoader(path))); + var resourceLoader = createPathResourceLoader(path); + if (main.equals(name)) { + resourceLoader = + createFilteredResourceLoader( + not( + it -> + // remove duplicated log configuration + (it.startsWith("logback") || it.startsWith("log4j")) + && it.endsWith(".xml") + // remove duplicated configuration + || (it.startsWith("application") && it.endsWith(".conf"))), + resourceLoader); + } + builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(resourceLoader)); } else { builder.addResourceRoot( createResourceLoaderSpec(createJarResourceLoader(new JarFile(path.toFile())))); @@ -117,7 +123,7 @@ private static ModuleSpec.Builder newModule(String name, Set resources) { @Override public String toString() { - return "classes: " + return "main: " + classes.stream().map(Path::toString).collect(joining(File.pathSeparator)) + "\nresources: " + resources.stream().map(Path::toString).collect(joining(File.pathSeparator)) diff --git a/modules/jooby-run/src/main/java/io/jooby/internal/run/JoobyMultiModuleFinder.java b/modules/jooby-run/src/main/java/io/jooby/internal/run/JoobyMultiModuleFinder.java index 64fb89378a..cfe643e832 100644 --- a/modules/jooby-run/src/main/java/io/jooby/internal/run/JoobyMultiModuleFinder.java +++ b/modules/jooby-run/src/main/java/io/jooby/internal/run/JoobyMultiModuleFinder.java @@ -16,7 +16,11 @@ /** * The new class loader since 3.x. It creates 3 modules with their own classloader: * - *

- classes: project classes - resources: project resources - jars: project dependencies + *

    + *
  • classes: project classes + *
  • resources: project resources + *
  • jars: project dependencies + *
* *

This approach reduce memory footprint allowing fast restart. */ diff --git a/modules/jooby-rxjava3/pom.xml b/modules/jooby-rxjava3/pom.xml index 32c9bada8a..9394ba6e85 100644 --- a/modules/jooby-rxjava3/pom.xml +++ b/modules/jooby-rxjava3/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-rxjava3 jooby-rxjava3 diff --git a/modules/jooby-stork/pom.xml b/modules/jooby-stork/pom.xml index a3fcf66462..c27d00637d 100644 --- a/modules/jooby-stork/pom.xml +++ b/modules/jooby-stork/pom.xml @@ -4,7 +4,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-stork diff --git a/modules/jooby-swagger-ui/pom.xml b/modules/jooby-swagger-ui/pom.xml index 7003752dd8..8c305c6ffa 100644 --- a/modules/jooby-swagger-ui/pom.xml +++ b/modules/jooby-swagger-ui/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-swagger-ui jooby-swagger-ui diff --git a/modules/jooby-test/pom.xml b/modules/jooby-test/pom.xml index 002873c27c..9ae4eafc41 100644 --- a/modules/jooby-test/pom.xml +++ b/modules/jooby-test/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-test jooby-test diff --git a/modules/jooby-thymeleaf/pom.xml b/modules/jooby-thymeleaf/pom.xml index ee23d72da4..2100f2937d 100644 --- a/modules/jooby-thymeleaf/pom.xml +++ b/modules/jooby-thymeleaf/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-thymeleaf jooby-thymeleaf diff --git a/modules/jooby-undertow/pom.xml b/modules/jooby-undertow/pom.xml index 4e62f9126d..a635673204 100644 --- a/modules/jooby-undertow/pom.xml +++ b/modules/jooby-undertow/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-undertow jooby-undertow diff --git a/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowHandler.java b/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowHandler.java index 677e91a063..88463181d4 100644 --- a/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowHandler.java +++ b/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowHandler.java @@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Optional; import io.jooby.*; import io.undertow.io.Receiver; @@ -18,6 +19,7 @@ import io.undertow.server.handlers.form.MultiPartParserDefinition; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; +import io.undertow.util.ParameterLimitException; public class UndertowHandler implements HttpHandler { protected final List applications; @@ -92,7 +94,11 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { try { parser.parse(execute(router, context)); } catch (Exception x) { - context.sendError(x, StatusCode.BAD_REQUEST); + var cause = Optional.ofNullable(x.getCause()).orElse(x); + if (cause instanceof ParameterLimitException) { + context.setAttribute("__too_many_fields", cause); + } + router.match(context).execute(context, Route.FORM_DECODER_HANDLER); } } } else { diff --git a/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowWebSocket.java b/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowWebSocket.java index 22b06de829..f9e8285a12 100644 --- a/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowWebSocket.java +++ b/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowWebSocket.java @@ -325,9 +325,7 @@ void fireConnect() { conf.hasPath("websocket.idleTimeout") ? conf.getDuration("websocket.idleTimeout", TimeUnit.MILLISECONDS) : TimeUnit.MINUTES.toMillis(5); - if (timeout > 0) { - channel.setIdleTimeout(timeout); - } + channel.setIdleTimeout(timeout); if (onConnectCallback != null) { dispatch(webSocketTask(() -> onConnectCallback.onConnect(this), true)); } else { diff --git a/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java b/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java index db3b2bfe5e..649cc9d413 100644 --- a/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java +++ b/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java @@ -64,13 +64,13 @@ protected ServerOptions defaultOptions() { return new ServerOptions().setIoThreads(ServerOptions.IO_THREADS).setServer("utow"); } - @NonNull @Override + @Override public String getName() { return "undertow"; } @Override - public @NonNull Server start(@NonNull Jooby... application) { + public Server start(@NonNull Jooby... application) { // force options to be non-null var options = getOptions(); var portInUse = options.getPort(); @@ -114,7 +114,7 @@ public String getName() { .addAll(OptionMap.create(Options.WORKER_NAME, "worker")) .getMap()); - Undertow.Builder builder = + var builder = Undertow.builder() .setBufferSize(options.getBufferSize()) /** Socket : */ @@ -123,6 +123,7 @@ public String getName() { // HTTP/1.1 is keep-alive by default, turn this option off .setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, false) .setServerOption(UndertowOptions.MAX_HEADER_SIZE, options.getMaxHeaderSize()) + .setServerOption(UndertowOptions.MAX_PARAMETERS, options.getMaxFormFields()) .setServerOption(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true) .setServerOption(UndertowOptions.ALWAYS_SET_DATE, options.getDefaultHeaders()) .setServerOption(UndertowOptions.RECORD_REQUEST_START_TIME, false) diff --git a/modules/jooby-whoops/pom.xml b/modules/jooby-whoops/pom.xml index ddc3b64fcd..2ef9f6dec4 100644 --- a/modules/jooby-whoops/pom.xml +++ b/modules/jooby-whoops/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-whoops jooby-whoops diff --git a/modules/jooby-yasson/pom.xml b/modules/jooby-yasson/pom.xml index cef4e26d2f..370c7c5f28 100644 --- a/modules/jooby-yasson/pom.xml +++ b/modules/jooby-yasson/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.1-SNAPSHOT + 3.11.7 jooby-yasson jooby-yasson diff --git a/modules/pom.xml b/modules/pom.xml index 06c2b68c72..abc2bf2947 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -4,7 +4,7 @@ io.jooby jooby-project - 3.11.1-SNAPSHOT + 3.11.7 modules diff --git a/pom.xml b/pom.xml index f79b83b8fd..ad2ea48623 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.jooby jooby-project - 3.11.1-SNAPSHOT + 3.11.7 pom jooby-project @@ -58,11 +58,11 @@ 2.3.34 - 4.4.0 + 4.5.0 1.3.7 3.2.4 - 2.19.2 - 2.13.1 + 2.21.1 + 2.13.2 3.0.1 3.0.4 2.2.1 @@ -70,76 +70,76 @@ 3.2.1 - 6.3.0 + 6.3.3 1.2 - 6.6.22.Final + 6.6.33.Final 15.11.0 - 3.49.5 - 11.8.2 + 3.51.0 + 11.15.0 24.1 6.7.1.RELEASE 2.12.1 - 4.0.0 - 3.2.2 + 4.1.0 + 3.2.3 - 1.4.4 + 1.4.6 7.0.0 - 1.5.18 - 2.25.1 + 1.5.32 + 2.25.3 2.0.17 1.6.0 - 4.2.33 + 4.2.38 - 2.1.6.Final + 2.2.0.Final - 9.8 + 9.9.1 - 2.3.18.Final - 12.0.23 - 4.2.3.Final + 2.3.23.Final + 12.1.7 + 4.2.10.Final - 2.2.34 - 2.1.31 + 2.2.45 + 2.1.39 2.0.0-rc.20 2.5.2 - 11.5 - 3.5 - 2.12 + 11.6 + 3.1 + 2.17 2.0.1.MR 3.1.1 4.0.0 - 4.9.3 + 4.9.8 - 5.1.0 - 0.12.6 - 6.1.3 - 2.5.0 + 5.3.2 + 0.13.0 + 6.3.3 + 2.5.1 9.2.1 8.0.1 - 1.12.788 - 4.11.0 + 1.12.797 + 4.15.0 1.9.3 - 2.20.0 + 2.21.0 2.2.0 @@ -147,15 +147,15 @@ true - 0.8.13 - 5.13.3 - 5.5.5 + 0.8.14 + 6.0.0 + 5.5.6 3.27.3 - 5.18.0 - 33.4.8-jre + 5.20.0 + 33.5.0-jre - 0.21.0 - 1.4.4 + 0.23.0 + 1.4.5 2.6 @@ -207,7 +207,7 @@ false yyyy-MM-dd HH:mm:ssa - 2025-07-21T10:12:15Z + 2026-03-10T16:43:31Z UTF-8 etc${file.separator}source${file.separator}formatter.sh diff --git a/tests/pom.xml b/tests/pom.xml index a1d238b386..2dd0bb8224 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -6,7 +6,7 @@ io.jooby jooby-project - 3.11.1-SNAPSHOT + 3.11.7 tests tests diff --git a/tests/src/test/java/io/jooby/i2806/Issue2806.java b/tests/src/test/java/io/jooby/i2806/Issue2806.java index 3b5ff321e5..208af9a784 100644 --- a/tests/src/test/java/io/jooby/i2806/Issue2806.java +++ b/tests/src/test/java/io/jooby/i2806/Issue2806.java @@ -5,9 +5,9 @@ */ package io.jooby.i2806; +import static io.jooby.test.TestUtil._19kb; import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.Arrays; import java.util.Map; import com.google.common.collect.ImmutableMap; @@ -22,9 +22,6 @@ public class Issue2806 { @ServerTest public void renderShouldWorkFromErrorHandlerWhenLargeRequestAreSent(ServerTestRunner runner) { - char[] chars = new char[19 * 1024]; - Arrays.fill(chars, 'S'); - String _19kb = new String(chars); runner .define( app -> { @@ -43,20 +40,32 @@ public void renderShouldWorkFromErrorHandlerWhenLargeRequestAreSent(ServerTestRu ctx.render(map); }); - app.post("/2806", ctx -> ctx.body().value("")); + app.post( + "/2806", + ctx -> { + return ctx.body().value(""); + }); app.get("/2806", ctx -> ctx.body().value("")); }) .ready( client -> { // Exceeds - client.post( - "/2806", - RequestBody.create(_19kb, MediaType.get("text/plain")), - rsp -> { - assertEquals(413, rsp.code()); - assertEquals("{\"router\":true,\"route\":true}", rsp.body().string()); - }); + client + .post("/2806", RequestBody.create(_19kb, MediaType.get("text/plain"))) + .execute( + rsp -> { + assertEquals(413, rsp.code()); + assertEquals("{\"router\":true,\"route\":true}", rsp.body().string()); + }); + + client + .get("/2806") + .execute( + rsp -> { + assertEquals(200, rsp.code()); + assertEquals("", rsp.body().string()); + }); }); } } diff --git a/tests/src/test/java/io/jooby/i3756/C3756.java b/tests/src/test/java/io/jooby/i3756/C3756.java new file mode 100644 index 0000000000..47125737e9 --- /dev/null +++ b/tests/src/test/java/io/jooby/i3756/C3756.java @@ -0,0 +1,25 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3756; + +import io.jooby.annotation.GET; +import io.jooby.annotation.Path; + +@Path("/C3756") +public class C3756 { + private final S3756 s3756; + + public C3756(S3756 s3756) { + super(); + this.s3756 = s3756; + } + + @GET + public String handle() { + s3756.accept("hello"); + return "hello"; + } +} diff --git a/tests/src/test/java/io/jooby/i3756/S3756.java b/tests/src/test/java/io/jooby/i3756/S3756.java new file mode 100644 index 0000000000..2afbf013b5 --- /dev/null +++ b/tests/src/test/java/io/jooby/i3756/S3756.java @@ -0,0 +1,11 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3756; + +public interface S3756 { + + void accept(String s); +} diff --git a/tests/src/test/java/io/jooby/i3783/Issue3783.java b/tests/src/test/java/io/jooby/i3783/Issue3783.java new file mode 100644 index 0000000000..282df458bf --- /dev/null +++ b/tests/src/test/java/io/jooby/i3783/Issue3783.java @@ -0,0 +1,45 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3783; + +import static org.junit.jupiter.api.Assertions.*; + +import io.jooby.ServerOptions; +import io.jooby.junit.ServerTest; +import io.jooby.junit.ServerTestRunner; +import okhttp3.FormBody; + +public class Issue3783 { + + @ServerTest + public void shouldAllowToSetMaxFormFields(ServerTestRunner runner) { + runner + .define( + app -> { + app.setServerOptions(new ServerOptions().setMaxFormFields(2)); + app.error((ctx, cause, code) -> ctx.send(cause.getMessage())); + app.post( + "/3723", + ctx -> { + return ctx.form("f").toList(); + }); + }) + .ready( + http -> { + http.post( + "/3723", + new FormBody.Builder() + .add("f1", "value 1") + .add("f2", "value 2") + .add("f3", "value 3") + .build(), + rsp -> { + assertEquals(400, rsp.code()); + assertEquals("Too many form fields", rsp.body().string()); + }); + }); + } +} diff --git a/tests/src/test/java/io/jooby/test/Http2Test.java b/tests/src/test/java/io/jooby/test/Http2Test.java index 3e7293aac1..851f8ea960 100644 --- a/tests/src/test/java/io/jooby/test/Http2Test.java +++ b/tests/src/test/java/io/jooby/test/Http2Test.java @@ -131,7 +131,7 @@ public void onAccept(Session session) {} Response.Builder builder = new Response.Builder(); session.newStream( frame, - new Promise.Adapter<>(), + Promise.noop(), new Stream.Listener() { @Override public void onHeaders(final Stream stream, final HeadersFrame frame) { diff --git a/tests/src/test/java/io/jooby/test/WebClient.java b/tests/src/test/java/io/jooby/test/WebClient.java index 93d622b98e..93607e41ed 100644 --- a/tests/src/test/java/io/jooby/test/WebClient.java +++ b/tests/src/test/java/io/jooby/test/WebClient.java @@ -14,10 +14,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -142,17 +139,46 @@ public void close() { public class Request { private final okhttp3.Request.Builder req; + private SneakyThrows.Consumer configurer; public Request(okhttp3.Request.Builder req) { this.req = req; } public Request prepare(SneakyThrows.Consumer configurer) { - configurer.accept(req); + this.configurer = configurer; return this; } public void execute(SneakyThrows.Consumer callback) { + execute(1, callback); + } + + public void execute(int concurrency, SneakyThrows.Consumer callback) { + if (configurer != null) { + configurer.accept(req); + } + if (concurrency > 1) { + var futures = new ArrayList>(); + for (var i = 0; i < concurrency; i++) { + futures.add( + CompletableFuture.supplyAsync( + () -> { + executeCall(callback); + return "success"; + })); + try { + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + } catch (CompletionException x) { + throw SneakyThrows.propagate(x.getCause()); + } + } + } else { + executeCall(callback); + } + } + + private void executeCall(SneakyThrows.Consumer callback) { okhttp3.Request r = req.build(); try (Response rsp = client.newCall(r).execute()) { callback.accept(rsp);