diff --git a/jooby/pom.xml b/jooby/pom.xml
index 98bbfee975..6d7c9e856c 100644
--- a/jooby/pom.xml
+++ b/jooby/pom.xml
@@ -6,7 +6,7 @@
io.jooby
jooby-project
- 3.11.2
+ 3.11.3
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/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 366e2a548e..10f49d9f6d 100644
--- a/modules/jooby-apt/pom.xml
+++ b/modules/jooby-apt/pom.xml
@@ -6,7 +6,7 @@
io.jooby
modules
- 3.11.2
+ 3.11.3
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 extends TypeElement> 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/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