Skip to content

Commit c776670

Browse files
authored
Fix missing Accept/Allow headers in 415/405 responses with sub-routers (#2863)
When using sub-routers, the Accept header was empty in 415 (Unsupported Media Type) responses and the Allow header was empty in 405 (Method Not Allowed) responses. This occurred because RoutingContextWrapper accumulated allowed content types and methods but didn't propagate them to the inner context when delegating. Added addAllowedMethods() and addAllowedContentTypes() methods to RoutingContextInternal to properly synchronize this state between wrapper and inner contexts. Some portions of this content were created with the assistance of Claude Code. Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
1 parent 2de6b32 commit c776670

File tree

5 files changed

+104
-1
lines changed

5 files changed

+104
-1
lines changed

vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextDecorator.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ public RoutingContextInternal setMatchFailure(int matchFailure) {
5050
return decoratedContext.setMatchFailure(matchFailure);
5151
}
5252

53+
@Override
54+
public RoutingContextInternal addAllowedMethods(java.util.Set<HttpMethod> allowedMethods) {
55+
return decoratedContext.addAllowedMethods(allowedMethods);
56+
}
57+
58+
@Override
59+
public RoutingContextInternal addAllowedContentTypes(java.util.Set<MIMEHeader> allowedContentTypes) {
60+
return decoratedContext.addAllowedContentTypes(allowedContentTypes);
61+
}
62+
5363
@Override
5464
public int addBodyEndHandler(Handler<Void> handler) {
5565
return decoratedContext.addBodyEndHandler(handler);

vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextImplBase.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,18 @@ public synchronized RoutingContextInternal setMatchFailure(int matchFailure) {
117117
return this;
118118
}
119119

120+
@Override
121+
public synchronized RoutingContextInternal addAllowedMethods(Set<HttpMethod> allowedMethods) {
122+
this.allowedMethods.addAll(allowedMethods);
123+
return this;
124+
}
125+
126+
@Override
127+
public synchronized RoutingContextInternal addAllowedContentTypes(Set<MIMEHeader> allowedContentTypes) {
128+
this.allowedContentTypes.addAll(allowedContentTypes);
129+
return this;
130+
}
131+
120132
@Override
121133
public String mountPoint() {
122134
return mountPoint;

vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextInternal.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,22 @@ public interface RoutingContextInternal extends RoutingContext {
5858
*/
5959
RoutingContextInternal setMatchFailure(int matchFailure);
6060

61+
/**
62+
* adds allowed methods to the context for 405 responses.
63+
*
64+
* @param allowedMethods the allowed methods to add
65+
* @return fluent self
66+
*/
67+
RoutingContextInternal addAllowedMethods(java.util.Set<io.vertx.core.http.HttpMethod> allowedMethods);
68+
69+
/**
70+
* adds allowed content types to the context for 415 responses.
71+
*
72+
* @param allowedContentTypes the allowed content types to add
73+
* @return fluent self
74+
*/
75+
RoutingContextInternal addAllowedContentTypes(java.util.Set<io.vertx.ext.web.MIMEHeader> allowedContentTypes);
76+
6177
/**
6278
* @return the current router this context is being routed through. All routingContext is associated with a router and
6379
* never returns null.

vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextWrapper.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,10 @@ public UserContext userContext() {
186186
public void next() {
187187
if (!super.iterateNext()) {
188188
// We didn't route request to anything so go to parent,
189-
// but also propagate the current status
189+
// but also propagate the current status and accumulated allowed methods/content types
190190
inner.setMatchFailure(matchFailure);
191+
inner.addAllowedMethods(allowedMethods);
192+
inner.addAllowedContentTypes(allowedContentTypes);
191193
inner.next();
192194
}
193195
}

vertx-web/src/test/java/io/vertx/ext/web/tests/SubRouterTest.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,4 +784,67 @@ private void assertRouterErrorHandlers(String name, Router router, HttpResponseS
784784
router.errorHandler(status.code(), ctx -> ctx.response().setStatusCode(status.code()).end(handlerKey));
785785
testRequest(HttpMethod.GET, path, status.code(), status.reasonPhrase(), handlerKey);
786786
}
787+
788+
@Test
789+
public void testSubRouterConsumes() throws Exception {
790+
Router subRouter = Router.router(vertx);
791+
792+
router.route("/api*").subRouter(subRouter);
793+
794+
subRouter.route("/resource").consumes("application/json").handler(rc -> rc.response().end("OK"));
795+
796+
// Test successful content type match
797+
testRequestWithContentType(HttpMethod.POST, "/api/resource", "application/json", 200, "OK");
798+
799+
// Test 415 response - Accept header should contain allowed content type from sub-router
800+
testRequestWithContentType(HttpMethod.POST, "/api/resource", "text/xml", 415, "Unsupported Media Type",
801+
res -> assertEquals("application/json", res.getHeader("Accept")));
802+
}
803+
804+
@Test
805+
public void testSubRouterConsumesMultiple() throws Exception {
806+
Router subRouter = Router.router(vertx);
807+
808+
router.route("/api*").subRouter(subRouter);
809+
810+
subRouter.route("/resource")
811+
.consumes("application/json")
812+
.consumes("text/html; charset=utf-8")
813+
.handler(rc -> rc.response().end("OK"));
814+
815+
// Test successful content type match
816+
testRequestWithContentType(HttpMethod.POST, "/api/resource", "application/json", 200, "OK");
817+
testRequestWithContentType(HttpMethod.POST, "/api/resource", "text/html; charset=utf-8", 200, "OK");
818+
819+
// Test 415 response - Accept header should contain both allowed content types from sub-router
820+
testRequestWithContentType(HttpMethod.POST, "/api/resource", "text/xml", 415, "Unsupported Media Type",
821+
res -> {
822+
String acceptHeader = res.getHeader("Accept");
823+
assertNotNull(acceptHeader);
824+
assertTrue(acceptHeader.contains("application/json"));
825+
assertTrue(acceptHeader.contains("text/html; charset=utf-8"));
826+
});
827+
}
828+
829+
@Test
830+
public void testSubRouterMethodNotAllowed() throws Exception {
831+
Router subRouter = Router.router(vertx);
832+
833+
router.route("/api*").subRouter(subRouter);
834+
835+
subRouter.post("/resource").handler(rc -> rc.response().end("OK"));
836+
subRouter.put("/resource").handler(rc -> rc.response().end("OK"));
837+
838+
// Test successful method match
839+
testRequest(HttpMethod.POST, "/api/resource", 200, "OK");
840+
testRequest(HttpMethod.PUT, "/api/resource", 200, "OK");
841+
842+
// Test 405 response - Allow header should contain allowed methods from sub-router
843+
testRequest(HttpMethod.GET, "/api/resource", null, res -> {
844+
String allowHeader = res.getHeader("Allow");
845+
assertNotNull(allowHeader);
846+
assertTrue(allowHeader.contains("POST"));
847+
assertTrue(allowHeader.contains("PUT"));
848+
}, 405, "Method Not Allowed", null);
849+
}
787850
}

0 commit comments

Comments
 (0)