From cbbb0c6ed94b48b335b1ab4545445c6282010abf Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 9 Mar 2021 18:17:20 +0200 Subject: [PATCH] Optimize Accepts header validation in the common case The common case of having multiple Accepts headers validate against a single @Produces media type shows up often (as both browsers and benchmarks send 5 values in the Accepts header) --- .../server/handlers/ClassRoutingHandler.java | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java index 8ba2f4100f5ea..243bcc039ca63 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java @@ -118,12 +118,46 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti if (target.value.getProduces() != null) { String accepts = serverRequest.getRequestHeader(HttpHeaders.ACCEPT); if ((accepts != null) && !accepts.equals(MediaType.WILDCARD)) { - if (!accepts.contains(",") && target.value.getProduces().getSortedOriginalMediaTypes().length == 1) { // the point of this branch is to eliminate the list creation and sorting + int commaIndex = accepts.indexOf(','); + boolean multipleAcceptsValues = commaIndex >= 0; + MediaType[] producesMediaTypes = target.value.getProduces().getSortedOriginalMediaTypes(); + if (!multipleAcceptsValues && (producesMediaTypes.length == 1)) { + // the point of this branch is to eliminate any list creation or string indexing as none is needed MediaType acceptsMediaType = MediaType.valueOf(accepts.trim()); - MediaType providedMediaType = target.value.getProduces().getSortedOriginalMediaTypes()[0]; + MediaType providedMediaType = producesMediaTypes[0]; if (!providedMediaType.isCompatible(acceptsMediaType)) { throw new NotAcceptableException(); } + } else if (multipleAcceptsValues && (producesMediaTypes.length == 1)) { + // this is fairly common case, so we want it to be as fast as possible + // we do that by manually splitting the accepts header and immediately checking + // if the value is compatible with the produces media type + boolean compatible = false; + int begin = 0; + + do { + String acceptPart; + if (commaIndex == -1) { // this is the case where we are checking the remainder of the string + acceptPart = accepts.substring(begin); + } else { + acceptPart = accepts.substring(begin, commaIndex); + } + if (producesMediaTypes[0].isCompatible(toMediaType(acceptPart.trim()))) { + compatible = true; + break; + } else if (commaIndex == -1) { // we have reached the end and not found any compatible media types + break; + } + begin = commaIndex + 1; // the next part will start at the character after the comma + if (begin >= (accepts.length() - 1)) { // if we have reached this point, then are no compatible media types + break; + } + commaIndex = accepts.indexOf(',', begin); + } while (true); + + if (!compatible) { + throw new NotAcceptableException(); + } } else { // don't use any of the JAX-RS stuff from the various MediaType helper as we want to be as performant as possible List acceptsMediaTypes; @@ -137,7 +171,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti } else { acceptsMediaTypes = Collections.singletonList(toMediaType(accepts)); } - if (MediaTypeHelper.getFirstMatch(Arrays.asList(target.value.getProduces().getSortedOriginalMediaTypes()), + if (MediaTypeHelper.getFirstMatch(Arrays.asList(producesMediaTypes), acceptsMediaTypes) == null) { throw new NotAcceptableException(); }