From f948c45f4ebe00531f858e289d17664bc2edd496 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 15 Jul 2019 11:11:26 -0400 Subject: [PATCH] performance improvements --- .../jboss/resteasy/spi/ResourceInvoker.java | 3 + .../core/AcceptHeaderByFileSuffixFilter.java | 3 +- .../resteasy/core/ResourceLocatorInvoker.java | 9 ++ .../resteasy/core/ResourceMethodInvoker.java | 10 ++ .../resteasy/core/registry/Expression.java | 4 + .../resteasy/core/registry/MatchCache.java | 55 ++++++++ .../core/registry/MethodExpression.java | 2 +- .../resteasy/core/registry/RootNode.java | 24 +++- .../resteasy/core/registry/SegmentNode.java | 52 +++----- .../jboss/resteasy/mock/MockHttpRequest.java | 55 ++++---- .../plugins/server/servlet/ServletUtil.java | 10 +- .../resteasy/specimpl/ResteasyUriInfo.java | 94 ++++++++++---- .../doseta/DigitalSigningInterceptor.java | 5 +- .../plugins/server/netty/NettyUtil.java | 8 +- .../plugins/server/vertx/VertxUtil.java | 17 ++- .../basic/resource/UriInfoSimpleResource.java | 1 + .../test/resource/path/TrailingSlashTest.java | 8 +- .../request/ContainerRequestContextTest.java | 3 +- .../resteasy/test/resource/BenchmarkTest.java | 122 ++++++++++++++++++ .../test/resource/TrailingSlashTest.java | 10 +- 20 files changed, 376 insertions(+), 119 deletions(-) create mode 100644 resteasy-core/src/main/java/org/jboss/resteasy/core/registry/MatchCache.java create mode 100644 testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/resource/BenchmarkTest.java diff --git a/resteasy-core-spi/src/main/java/org/jboss/resteasy/spi/ResourceInvoker.java b/resteasy-core-spi/src/main/java/org/jboss/resteasy/spi/ResourceInvoker.java index 7f119246c13..7214d906a9b 100644 --- a/resteasy-core-spi/src/main/java/org/jboss/resteasy/spi/ResourceInvoker.java +++ b/resteasy-core-spi/src/main/java/org/jboss/resteasy/spi/ResourceInvoker.java @@ -16,4 +16,7 @@ public interface ResourceInvoker CompletionStage invoke(HttpRequest request, HttpResponse response, Object target); Method getMethod(); + + // optimizations + boolean hasProduces(); } diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/core/AcceptHeaderByFileSuffixFilter.java b/resteasy-core/src/main/java/org/jboss/resteasy/core/AcceptHeaderByFileSuffixFilter.java index b464015a800..40864d0bd2b 100644 --- a/resteasy-core/src/main/java/org/jboss/resteasy/core/AcceptHeaderByFileSuffixFilter.java +++ b/resteasy-core/src/main/java/org/jboss/resteasy/core/AcceptHeaderByFileSuffixFilter.java @@ -46,11 +46,10 @@ public void setLanguageMappings(Map languageMappings) @Override public void filter(ContainerRequestContext requestContext) throws IOException { - if (mediaTypeMappings == null && languageMappings == null) + if ((mediaTypeMappings == null || mediaTypeMappings.isEmpty()) && (languageMappings == null || languageMappings.isEmpty())) { return; } - URI uri = requestContext.getUriInfo().getRequestUri(); String rawPath = uri.getRawPath(); int lastSegment = rawPath.lastIndexOf('/'); diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceLocatorInvoker.java b/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceLocatorInvoker.java index f52797713f4..4c3f9e74d65 100644 --- a/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceLocatorInvoker.java +++ b/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceLocatorInvoker.java @@ -16,6 +16,7 @@ import org.jboss.resteasy.util.GetRestful; import javax.ws.rs.NotFoundException; +import javax.ws.rs.Produces; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -34,6 +35,7 @@ public class ResourceLocatorInvoker implements ResourceInvoker protected ResteasyProviderFactory providerFactory; protected ResourceLocator method; protected ConcurrentHashMap, LocatorRegistry> cachedSubresources = new ConcurrentHashMap, LocatorRegistry>(); + protected final boolean hasProduces; public ResourceLocatorInvoker(final ResourceFactory resource, final InjectorFactory injector, final ResteasyProviderFactory providerFactory, final ResourceLocator locator) { @@ -42,8 +44,15 @@ public ResourceLocatorInvoker(final ResourceFactory resource, final InjectorFact this.providerFactory = providerFactory; this.method = locator; this.methodInjector = injector.createMethodInjector(locator, providerFactory); + hasProduces = method.getMethod().isAnnotationPresent(Produces.class) || method.getMethod().getClass().isAnnotationPresent(Produces.class); } + @Override + public boolean hasProduces() { + return hasProduces; + } + + protected CompletionStage createResource(HttpRequest request, HttpResponse response) { return this.resource.createResource(request, response, providerFactory) diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodInvoker.java b/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodInvoker.java index 99a55fd6733..da79392598b 100644 --- a/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodInvoker.java +++ b/resteasy-core/src/main/java/org/jboss/resteasy/core/ResourceMethodInvoker.java @@ -35,6 +35,7 @@ import org.jboss.resteasy.util.DynamicFeatureContextDelegate; import javax.ws.rs.ProcessingException; +import javax.ws.rs.Produces; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.container.DynamicFeature; @@ -86,6 +87,9 @@ public class ResourceMethodInvoker implements ResourceInvoker, JaxrsInterceptorR protected ResourceInfo resourceInfo; protected boolean expectsBody; + protected final boolean hasProduces; + + @@ -167,6 +171,12 @@ protected void initializeUtils() isSse = true; method.markAsynchronous(); } + hasProduces = method.getMethod().isAnnotationPresent(Produces.class) || method.getMethod().getClass().isAnnotationPresent(Produces.class); + } + + @Override + public boolean hasProduces() { + return hasProduces; } // spec section 9.3 Server API: diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/Expression.java b/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/Expression.java index f1b683dcd83..13a7af32e5f 100644 --- a/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/Expression.java +++ b/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/Expression.java @@ -103,6 +103,10 @@ protected static int groupCount(String regex) return groupCount; } + public boolean isStatic() { + return groups.isEmpty(); + } + public int getNumGroups() { return groups.size(); diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/MatchCache.java b/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/MatchCache.java new file mode 100644 index 00000000000..35683224235 --- /dev/null +++ b/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/MatchCache.java @@ -0,0 +1,55 @@ +package org.jboss.resteasy.core.registry; + + +import org.jboss.resteasy.specimpl.ResteasyUriInfo; +import org.jboss.resteasy.spi.HttpRequest; +import org.jboss.resteasy.spi.ResourceInvoker; + +import javax.ws.rs.core.MediaType; +import java.util.List; +import java.util.Objects; + +public class MatchCache { + public MediaType chosen; + public SegmentNode.Match match; + public ResourceInvoker invoker; + + public static class Key { + public String path; + public int start; + public String method; + public MediaType contentType; + public List accepts; + + public Key(final HttpRequest request, final int start) { + String matchingPath = ((ResteasyUriInfo) request.getUri()).getMatchingPath(); + this.path = start == 0 ? matchingPath : matchingPath.substring(start); + this.start = start; + this.method = request.getHttpMethod(); + this.contentType = request.getHttpHeaders().getMediaType(); + this.accepts = request.getHttpHeaders().getAcceptableMediaTypes(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Key key = (Key) o; + boolean b = start == key.start && + path.equals(key.path) && + method.equals(key.method) && + Objects.equals(contentType, key.contentType); + if (!b) return false; + if (accepts.isEmpty() && key.accepts.isEmpty()) return true; + if (accepts.size() != key.accepts.size()) return false; + // todo improve this + return b && + accepts.equals(key.accepts); + } + + @Override + public int hashCode() { + return Objects.hash(path, start, method); + } + } +} diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/MethodExpression.java b/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/MethodExpression.java index 02c78d1e179..4b6ef51f294 100644 --- a/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/MethodExpression.java +++ b/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/MethodExpression.java @@ -45,7 +45,7 @@ public MethodExpression(final SegmentNode parent, final String segment, final Re this.invoker = invoker; } - public void populatePathParams(HttpRequest request, Matcher matcher, String path) + public void populatePathParams(HttpRequest request, Matcher matcher, String path) { ResteasyUriInfo uriInfo = (ResteasyUriInfo) request.getUri(); for (Group group : groups) diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/RootNode.java b/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/RootNode.java index c775a787111..102866696bc 100644 --- a/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/RootNode.java +++ b/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/RootNode.java @@ -9,6 +9,9 @@ import java.lang.reflect.Method; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static org.jboss.resteasy.core.registry.SegmentNode.RESTEASY_CHOSEN_ACCEPT; /** * @author Bill Burke @@ -19,6 +22,7 @@ public class RootNode protected SegmentNode root = new SegmentNode(""); protected int size = 0; protected MultivaluedMap bounded = new MultivaluedHashMap(); + protected ConcurrentHashMap cache = new ConcurrentHashMap<>(); public int getSize() { @@ -38,9 +42,27 @@ public MultivaluedMap getBounded() return rtn; } + private static boolean CACHE = true; + public ResourceInvoker match(HttpRequest request, int start) { - return root.match(request, start); + if (!CACHE) { + return root.match(request, start).invoker; + } + MatchCache.Key key = new MatchCache.Key(request, start); + MatchCache match = cache.get(key); + if (match != null) { + //System.out.println("*** cache hit: " + key.method + " " + key.path); + request.setAttribute(RESTEASY_CHOSEN_ACCEPT, match.chosen); + } else { + match = root.match(request, start); + if (match.match != null && match.match.expression.getNumGroups() == 0 && match.invoker instanceof ResourceMethodInvoker) { + //System.out.println("*** caching: " + key.method + " " + key.path); + match.match = null; + cache.putIfAbsent(key, match); + } + } + return match.invoker; } public void removeBinding(String path, Method method) diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/SegmentNode.java b/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/SegmentNode.java index 227b8b09d87..067e7f40a35 100644 --- a/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/SegmentNode.java +++ b/resteasy-core/src/main/java/org/jboss/resteasy/core/registry/SegmentNode.java @@ -17,12 +17,10 @@ import javax.ws.rs.NotAllowedException; import javax.ws.rs.NotFoundException; import javax.ws.rs.NotSupportedException; -import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -73,7 +71,7 @@ public Match(final MethodExpression expression, final Matcher matcher) } } - public ResourceInvoker match(HttpRequest request, int start) + public MatchCache match(HttpRequest request, int start) { String path = ((ResteasyUriInfo) request.getUri()).getMatchingPath(); RESTEasyTracingLogger logger = RESTEasyTracingLogger.getInstance(request); @@ -104,6 +102,8 @@ public ResourceInvoker match(HttpRequest request, int start) ResourceInvoker invoker = expression.getInvoker(); if (invoker instanceof ResourceLocatorInvoker) { + MatchCache ctx = new MatchCache(); + ctx.invoker = invoker; ResteasyUriInfo uriInfo = (ResteasyUriInfo) request.getUri(); int length = matcher.start(expression.getNumGroups() + 1); if (length == -1) @@ -135,7 +135,7 @@ public ResourceInvoker match(HttpRequest request, int start) } expression.populatePathParams(request, matcher, path); logger.log("MATCH_LOCATOR", invoker.getMethod()); - return invoker; + return ctx; } else { @@ -150,10 +150,10 @@ public ResourceInvoker match(HttpRequest request, int start) { throw new NotFoundException(Messages.MESSAGES.couldNotFindResourceForFullPath(request.getUri().getRequestUri())); } - Match match = match(matches, request.getHttpMethod(), request); - match.expression.populatePathParams(request, match.matcher, path); - logger.log("MATCH_PATH_SELECTED", match.expression.getRegex()); - return match.expression.getInvoker(); + MatchCache match = match(matches, request.getHttpMethod(), request); + match.match.expression.populatePathParams(request, match.match.matcher, path); + logger.log("MATCH_PATH_SELECTED", match.match.expression.getRegex()); + return match; } @@ -302,29 +302,7 @@ public MediaType getAcceptType() || "qs".equals(name)) continue; params.put(name, entry.getValue()); } - Annotation[] annotations = match.expression.invoker.getMethod().getAnnotations(); - boolean hasProduces = false; - for (Annotation annotation : annotations) - { - if (annotation instanceof Produces) - { - hasProduces = true; - break; - } - } - if (!hasProduces) - { - annotations = match.expression.invoker.getMethod().getClass().getAnnotations(); - for (Annotation annotation : annotations) - { - if (annotation instanceof Produces) - { - hasProduces = true; - break; - } - } - } - if (hasProduces) + if (match.expression.invoker.hasProduces()) { params.put(RESTEASY_SERVER_HAS_PRODUCES, "true"); } @@ -373,7 +351,7 @@ public int compareTo(SortEntry o) } } - public Match match(List matches, String httpMethod, HttpRequest request) + public MatchCache match(List matches, String httpMethod, HttpRequest request) { MediaType contentType = request.getHttpHeaders().getMediaType(); @@ -511,7 +489,6 @@ else if (!consumeMatch) for (SortFactor consume : consumeCombo) { - final Method m = match.expression.getInvoker().getMethod(); sortList.add(new SortEntry(match, consume, sortFactor, produce)); } } @@ -528,8 +505,13 @@ else if (!consumeMatch) { LogMessages.LOGGER.multipleMethodsMatch(requestToString(request), mm); } - request.setAttribute(RESTEASY_CHOSEN_ACCEPT, sortEntry.getAcceptType()); - return sortEntry.match; + MediaType acceptType = sortEntry.getAcceptType(); + request.setAttribute(RESTEASY_CHOSEN_ACCEPT, acceptType); + MatchCache ctx = new MatchCache(); + ctx.chosen = acceptType; + ctx.match = sortEntry.match; + ctx.invoker = sortEntry.match.expression.invoker; + return ctx; } protected void addExpression(MethodExpression expression) diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/mock/MockHttpRequest.java b/resteasy-core/src/main/java/org/jboss/resteasy/mock/MockHttpRequest.java index ad2738439ab..502164ee704 100644 --- a/resteasy-core/src/main/java/org/jboss/resteasy/mock/MockHttpRequest.java +++ b/resteasy-core/src/main/java/org/jboss/resteasy/mock/MockHttpRequest.java @@ -1,23 +1,5 @@ package org.jboss.resteasy.mock; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import javax.ws.rs.core.Cookie; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.UriBuilder; - import org.jboss.resteasy.plugins.server.BaseHttpRequest; import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.jboss.resteasy.specimpl.ResteasyHttpHeaders; @@ -30,6 +12,22 @@ import org.jboss.resteasy.util.HttpHeaderNames; import org.jboss.resteasy.util.ReadFromStream; +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -58,20 +56,23 @@ protected static MockHttpRequest initWithUri(String uri) throws URISyntaxExcepti return initWithUri(absoluteUri, baseUri); } + public static MockHttpRequest create(String httpMethod, String absolute, String query, String contextPath) { + MockHttpRequest request = new MockHttpRequest(); + request.httpHeaders = new ResteasyHttpHeaders(new CaseInsensitiveMap()); + if (query != null && query.length() > 0) { + absolute = absolute + "?" + query; + } + request.uri = new ResteasyUriInfo(absolute, contextPath); + request.httpMethod = httpMethod; + return request; + } + private static MockHttpRequest initWithUri(URI absoluteUri, URI baseUri) { if (baseUri == null) baseUri = EMPTY_URI; MockHttpRequest request = new MockHttpRequest(); request.httpHeaders = new ResteasyHttpHeaders(new CaseInsensitiveMap()); - //request.uri = new UriInfoImpl(absoluteUri, absoluteUri, absoluteUri.getPath(), absoluteUri.getQuery(), PathSegmentImpl.parseSegments(absoluteUri.getPath())); - - // remove query part - URI absolutePath = UriBuilder.fromUri(absoluteUri).replaceQuery(null).build(); - // path must be relative to the application's base uri - URI relativeUri = baseUri.relativize(absoluteUri); - relativeUri = UriBuilder.fromUri(relativeUri.getRawPath()).replaceQuery(absoluteUri.getRawQuery()).build(); - - request.uri = new ResteasyUriInfo(absoluteUri.toString(), absoluteUri.getRawQuery(), baseUri.getRawPath()); + request.uri = new ResteasyUriInfo(absoluteUri.toString(), baseUri.getRawPath()); return request; } diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/servlet/ServletUtil.java b/resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/servlet/ServletUtil.java index 5ef5e2e0d34..43f7cdd3119 100644 --- a/resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/servlet/ServletUtil.java +++ b/resteasy-core/src/main/java/org/jboss/resteasy/plugins/server/servlet/ServletUtil.java @@ -32,7 +32,15 @@ public static ResteasyUriInfo extractUriInfo(HttpServletRequest request, String contextPath += "/"; contextPath += servletPrefix; } - return new ResteasyUriInfo(request.getRequestURL(), request.getQueryString(), contextPath); + String queryString = request.getQueryString(); + StringBuffer builder = request.getRequestURL(); + String absolute; + if (queryString != null && queryString.length() > 0) { + absolute = request.getRequestURL().append('?').append(queryString).toString(); + } else { + absolute = request.getRequestURL().toString(); + } + return new ResteasyUriInfo(absolute, contextPath); } public static ResteasyHttpHeaders extractHttpHeaders(HttpServletRequest request) diff --git a/resteasy-core/src/main/java/org/jboss/resteasy/specimpl/ResteasyUriInfo.java b/resteasy-core/src/main/java/org/jboss/resteasy/specimpl/ResteasyUriInfo.java index c385b791ba2..844ac1f293a 100644 --- a/resteasy-core/src/main/java/org/jboss/resteasy/specimpl/ResteasyUriInfo.java +++ b/resteasy-core/src/main/java/org/jboss/resteasy/specimpl/ResteasyUriInfo.java @@ -29,8 +29,8 @@ public class ResteasyUriInfo implements UriInfo private String path; private String encodedPath; private String matchingPath; - private MultivaluedMap queryParameters = new MultivaluedMapImpl<>(); - private MultivaluedMap encodedQueryParameters = new MultivaluedMapImpl<>(); + private MultivaluedMap queryParameters; + private MultivaluedMap encodedQueryParameters; private MultivaluedMap pathParameters; private MultivaluedMap encodedPathParameters; private MultivaluedMap pathParameterPathSegments; @@ -45,11 +45,53 @@ public class ResteasyUriInfo implements UriInfo private List encodedMatchedUris; private List encodedMatchedPaths = new LinkedList(); private List ancestors; + private String absoluteString; + private String contextPath; + private int queryIdx; + private int endPath; + private int pathStart; - public ResteasyUriInfo(final CharSequence absoluteUri, final String queryString, final String contextPath) - { - initialize(absoluteUri, queryString, contextPath); + public ResteasyUriInfo(final String absoluteUri, final String contextPath) { + initialize(absoluteUri, contextPath); + } + + protected void initialize(String absoluteUri, String contextPath) { + this.absoluteString = absoluteUri; + this.contextPath = contextPath; + + int pathIdx = absoluteUri.indexOf('/'); + if (pathIdx > 0 && absoluteUri.length() > 3) { + if (absoluteUri.charAt(pathIdx - 1) == ':' && absoluteUri.charAt(pathIdx + 1) == '/') { + pathIdx = pathIdx + 2; + int tmp = absoluteUri.indexOf('/', pathIdx); + if (tmp > -1) pathIdx = tmp; + } + } + queryIdx = pathIdx > -1 ? absoluteUri.indexOf('?', pathIdx) : absoluteUri.indexOf('?'); + endPath = queryIdx > -1 ? queryIdx : absoluteUri.length(); + pathStart = pathIdx > -1 ? pathIdx : 0; + String tmpEncodedPath = pathStart >= 0 && endPath > pathStart ? absoluteUri.substring(pathStart, endPath) : ""; + encodedPath = PathHelper.getEncodedPathInfo(tmpEncodedPath, contextPath); + + if (encodedPath.length() == 0 || encodedPath.charAt(0) != '/') + { + encodedPath = "/" + encodedPath; + } + path = Encode.decodePath(encodedPath); + processPath(); + } + + private void processUris() { + requestURI = URI.create(absoluteString); + absolutePath = queryIdx < 0 ? requestURI : URI.create(absoluteString.substring(0, queryIdx)); + baseURI = absolutePath; + String tmpContextPath = contextPath; + if (!tmpContextPath.endsWith("/")) tmpContextPath += "/"; + if (!tmpContextPath.startsWith("/")) tmpContextPath = "/" + tmpContextPath; + String baseString = absoluteString.substring(0, pathStart); + baseString += tmpContextPath; + baseURI = URI.create(baseString); } protected void initialize(CharSequence absoluteUri, String queryString, String contextPath) @@ -79,6 +121,8 @@ protected void initialize(CharSequence absoluteUri, String queryString, String c processPath(); } + + public ResteasyUriInfo(final URI base, final URI relative) { String b = base.toString(); @@ -120,7 +164,6 @@ protected void processPath() { pathSegments.add(new PathSegmentImpl(((PathSegmentImpl) segment).getOriginal(), true)); } - extractParameters(requestURI.getRawQuery()); if (parse.hasMatrixParams) extractMatchingPath(encodedPathSegments); else { @@ -190,6 +233,7 @@ public String getMatchingPath() */ public void setRequestUri(URI relative) { + if (baseURI == null) processUris(); setUri(baseURI, relative); } @@ -217,32 +261,35 @@ public List getPathSegments(boolean decode) public URI getRequestUri() { + if (requestURI == null) processUris(); return requestURI; } public UriBuilder getRequestUriBuilder() { - return UriBuilder.fromUri(requestURI); + return UriBuilder.fromUri(getRequestUri()); } public URI getAbsolutePath() { + if (absolutePath == null) processUris(); return absolutePath; } public UriBuilder getAbsolutePathBuilder() { - return UriBuilder.fromUri(absolutePath); + return UriBuilder.fromUri(getAbsolutePath()); } public URI getBaseUri() { + if (baseURI == null) processUris(); return baseURI; } public UriBuilder getBaseUriBuilder() { - return UriBuilder.fromUri(baseURI); + return UriBuilder.fromUri(getBaseUri()); } public MultivaluedMap getPathParameters() @@ -296,11 +343,17 @@ public MultivaluedMap getPathParameters(boolean decode) public MultivaluedMap getQueryParameters() { + if (queryParameters == null) { + extractParameters(); + } return new UnmodifiableMultivaluedMap<>(queryParameters); } protected MultivaluedMap getEncodedQueryParameters() { + if (encodedQueryParameters == null) { + extractParameters(); + } return new UnmodifiableMultivaluedMap<>(encodedQueryParameters); } @@ -311,25 +364,16 @@ public MultivaluedMap getQueryParameters(boolean decode) else return getEncodedQueryParameters(); } - public void clearQueryParameters(boolean decode) { - if (decode) clearQueryParameters(); - else clearEncodedQueryParameters(); - } - - private void clearQueryParameters() { - if (queryParameters != null) { - queryParameters.clear(); - } - } - - private void clearEncodedQueryParameters() { - if (encodedQueryParameters != null) { - encodedQueryParameters.clear(); - } + private void clearQueryParameters(boolean decode) { + queryParameters = null; + encodedQueryParameters = null; } - protected void extractParameters(String queryString) + protected void extractParameters() { + queryParameters = new MultivaluedMapImpl<>(); + encodedQueryParameters = new MultivaluedMapImpl<>(); + String queryString = getRequestUri().getRawQuery(); if (queryString == null || queryString.equals("")) return; String[] params = queryString.split("&"); diff --git a/security/resteasy-crypto/src/main/java/org/jboss/resteasy/security/doseta/DigitalSigningInterceptor.java b/security/resteasy-crypto/src/main/java/org/jboss/resteasy/security/doseta/DigitalSigningInterceptor.java index 7ac0ea97ee7..d431d8f672f 100644 --- a/security/resteasy-crypto/src/main/java/org/jboss/resteasy/security/doseta/DigitalSigningInterceptor.java +++ b/security/resteasy-crypto/src/main/java/org/jboss/resteasy/security/doseta/DigitalSigningInterceptor.java @@ -9,6 +9,7 @@ import java.security.PrivateKey; import java.security.SignatureException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.annotation.Priority; @@ -40,13 +41,13 @@ public class DigitalSigningInterceptor implements WriterInterceptor, ContainerRe protected List getHeaders(MultivaluedMap headers) { - List list = new ArrayList(); List signatures = headers.get(DKIMSignature.DKIM_SIGNATURE); if (signatures == null || signatures.isEmpty()) { - return list; + return Collections.EMPTY_LIST; } + List list = new ArrayList(); for (Object obj : signatures) { diff --git a/server-adapters/resteasy-netty4/src/main/java/org/jboss/resteasy/plugins/server/netty/NettyUtil.java b/server-adapters/resteasy-netty4/src/main/java/org/jboss/resteasy/plugins/server/netty/NettyUtil.java index 3d874c29e74..f0b1b2e05de 100644 --- a/server-adapters/resteasy-netty4/src/main/java/org/jboss/resteasy/plugins/server/netty/NettyUtil.java +++ b/server-adapters/resteasy-netty4/src/main/java/org/jboss/resteasy/plugins/server/netty/NettyUtil.java @@ -11,7 +11,6 @@ import javax.ws.rs.core.Cookie; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -25,7 +24,6 @@ public class NettyUtil { public static ResteasyUriInfo extractUriInfo(HttpRequest request, String contextPath, String protocol) { - String host = request.headers().get(HttpHeaderNames.HOST, "unknown"); String uri = request.uri(); String uriString; @@ -34,11 +32,11 @@ public static ResteasyUriInfo extractUriInfo(HttpRequest request, String context if (uri.startsWith(protocol + "://")) { uriString = uri; } else { + String host = request.headers().get(HttpHeaderNames.HOST, "unknown"); uriString = protocol + "://" + host + uri; } - URI absoluteURI = URI.create(uriString); - return new ResteasyUriInfo(uriString, absoluteURI.getRawQuery(), contextPath); + return new ResteasyUriInfo(uriString, contextPath); } public static ResteasyHttpHeaders extractHttpHeaders(HttpRequest request) @@ -49,8 +47,6 @@ public static ResteasyHttpHeaders extractHttpHeaders(HttpRequest request) Map cookies = extractCookies(requestHeaders); headers.setCookies(cookies); - // test parsing should throw an exception on error - headers.testParsing(); return headers; } diff --git a/server-adapters/resteasy-vertx/src/main/java/org/jboss/resteasy/plugins/server/vertx/VertxUtil.java b/server-adapters/resteasy-vertx/src/main/java/org/jboss/resteasy/plugins/server/vertx/VertxUtil.java index a0d36deea37..e002bea31e8 100644 --- a/server-adapters/resteasy-vertx/src/main/java/org/jboss/resteasy/plugins/server/vertx/VertxUtil.java +++ b/server-adapters/resteasy-vertx/src/main/java/org/jboss/resteasy/plugins/server/vertx/VertxUtil.java @@ -11,7 +11,6 @@ import javax.ws.rs.core.Cookie; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -25,11 +24,6 @@ public class VertxUtil { public static ResteasyUriInfo extractUriInfo(HttpServerRequest req, String contextPath) { - String host = req.host(); - if (host == null) - { - host = "unknown"; - } String uri = req.absoluteURI(); String protocol = req.scheme(); @@ -39,13 +33,18 @@ public static ResteasyUriInfo extractUriInfo(HttpServerRequest req, String conte if (uri.startsWith(protocol + "://")) { uriString = uri; - } else + } + else { + String host = req.host(); + if (host == null) + { + host = "unknown"; + } uriString = protocol + "://" + host + uri; } - URI absoluteURI = URI.create(uriString); - return new ResteasyUriInfo(uriString, absoluteURI.getRawQuery(), contextPath); + return new ResteasyUriInfo(uriString, contextPath); } public static ResteasyHttpHeaders extractHttpHeaders(HttpServerRequest request) diff --git a/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/resource/basic/resource/UriInfoSimpleResource.java b/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/resource/basic/resource/UriInfoSimpleResource.java index fbb9964ca10..207449b1838 100644 --- a/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/resource/basic/resource/UriInfoSimpleResource.java +++ b/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/resource/basic/resource/UriInfoSimpleResource.java @@ -33,6 +33,7 @@ public String get(@Context UriInfo info, @QueryParam("abs") String abs) { logger.info("BASE URI: " + info.getBaseUri()); logger.info("Request URI: " + info.getRequestUri()); + logger.info("Absolute URI: " + info.getAbsolutePath()); Assert.assertEquals(base.getPath(), info.getBaseUri().getPath()); Assert.assertEquals("/simple", info.getPath()); return "CONTENT"; diff --git a/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/resource/path/TrailingSlashTest.java b/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/resource/path/TrailingSlashTest.java index f51c146ec83..a36e84f477d 100644 --- a/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/resource/path/TrailingSlashTest.java +++ b/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/resource/path/TrailingSlashTest.java @@ -92,12 +92,12 @@ void doTwoArgConstructorTest(URI base, URI relative, String path) throws URISynt */ @Test public void threeArgConstructorTest() throws Exception { - doThreeArgConstructorTest("http://localhost/abc", "/abc"); - doThreeArgConstructorTest("http://localhost/abc/", "/abc/"); + doTwoArgConstructorTest("http://localhost/abc", "/abc"); + doTwoArgConstructorTest("http://localhost/abc/", "/abc/"); } - void doThreeArgConstructorTest(String s, String path) throws URISyntaxException { - ResteasyUriInfo ruri = new ResteasyUriInfo(s, "", ""); + void doTwoArgConstructorTest(String s, String path) throws URISyntaxException { + ResteasyUriInfo ruri = new ResteasyUriInfo(s, ""); URI uri = new URI(s); Assert.assertEquals(ERROR_MSG, path, ruri.getPath()); Assert.assertEquals(ERROR_MSG, path, ruri.getPath(true)); diff --git a/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/request/ContainerRequestContextTest.java b/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/request/ContainerRequestContextTest.java index f36e48ebec2..2f74c3cbe95 100644 --- a/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/request/ContainerRequestContextTest.java +++ b/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/request/ContainerRequestContextTest.java @@ -50,7 +50,8 @@ public void testQueryParamatersClear() throws URISyntaxException { expected.put("foo", Collections.singletonList("foo")); expected.put("bar", Collections.singletonList("bar")); - assertEquals("Wrong parameter in getUriInfo response", expected, containerRequestContext.getUriInfo().getQueryParameters()); + MultivaluedMap queryParameters = containerRequestContext.getUriInfo().getQueryParameters(); + assertEquals("Wrong parameter in getUriInfo response", expected, queryParameters); containerRequestContext.setRequestUri(new URI("http://foo.bar")); logger.info("request uri: " + containerRequestContext.getUriInfo().getRequestUri()); diff --git a/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/resource/BenchmarkTest.java b/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/resource/BenchmarkTest.java new file mode 100644 index 00000000000..b80ee169f02 --- /dev/null +++ b/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/resource/BenchmarkTest.java @@ -0,0 +1,122 @@ +package org.jboss.resteasy.test.resource; + +import org.jboss.resteasy.core.ResteasyContext; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.jboss.resteasy.specimpl.ResteasyUriInfo; +import org.jboss.resteasy.spi.Dispatcher; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Configurable; +import javax.ws.rs.core.UriInfo; + +/** + * @tpSubChapter Profiler helper tests + * @tpChapter Unit tests + * @tpTestCaseDetails Use to benchmark with a profiler + * @tpSince RESTEasy 4.1.1 + */ +public class BenchmarkTest { + + private static Dispatcher dispatcher; + + @BeforeClass + public static void BeforeClass() { + dispatcher = MockDispatcherFactory.createDispatcher(); + dispatcher.getRegistry().addPerRequestResource(HelloResource.class); + } + + @Before + public void before() { + ResteasyContext.getContextDataMap().put(Configurable.class, dispatcher.getProviderFactory()); + } + + @Path("/hello") + public static class HelloResource { + @GET + @Produces("text/plain") + public String hello() { + return "hello world"; + } + + @POST + @Produces("text/plain") + @Consumes("text/plain") + public String hello(String name) { + return "Hello " + name; + } + + } + + + //@Test + public void testPerRequest() throws Exception + { + System.gc(); + //System.out.println("Starting in"); + for (int i = 30; i >= 1; i--) { + //System.out.println(i); + Thread.sleep(1000); + } + //System.out.println("start"); + testRaw(); + //System.out.println("done"); + Thread.sleep(30000); + } + + private static final int first = 14442; // 2000000 + private static final int BENCH = 4000000; + private static final int PROFILE = 2000; + //@Test + public void testRaw() { + long start = System.currentTimeMillis(); + for (int i = 0; i < 5; i++) { + MockHttpRequest request = MockHttpRequest.create("GET", "/hello", "", ""); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + } + long end = System.currentTimeMillis() - start; + //System.out.println("time took: " + end); + } + + //@Test + public void testOne() { + MockHttpRequest request = MockHttpRequest.create("GET", "/hello", "", ""); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + UriInfo uriInfo = request.getUri(); + + } + + //@Test + public void testUriInfoOptimized() { + long start = System.currentTimeMillis(); + for (int i = 0; i < BENCH; i++) { + ResteasyUriInfo uriInfo = new ResteasyUriInfo("http://localhost:8080/hello", ""); + uriInfo.getMatchingPath(); + } + long end = System.currentTimeMillis() - start; + //System.out.println("time took: " + end); + + } + + @Test + public void testContextPath() { + ResteasyUriInfo uriInfo = new ResteasyUriInfo("http://localhost:8080/hello/world", "/hello"); + Assert.assertEquals("/world", uriInfo.getMatchingPath()); + uriInfo = new ResteasyUriInfo("http://localhost:8080/hello/world", "/hello"); + Assert.assertEquals("/world", uriInfo.getMatchingPath()); + Assert.assertEquals("/world", uriInfo.getPath()); + + } + +} diff --git a/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/resource/TrailingSlashTest.java b/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/resource/TrailingSlashTest.java index 0416bf3783e..beded237d2c 100644 --- a/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/resource/TrailingSlashTest.java +++ b/testsuite/unit-tests/src/test/java/org/jboss/resteasy/test/resource/TrailingSlashTest.java @@ -62,13 +62,13 @@ void doTwoArgConstructorTest(URI base, URI relative, String path) throws URISynt * @tpSince RESTEasy 3.0.17 */ @Test - public void threeArgConstructorTest() throws Exception { - doThreeArgConstructorTest("http://localhost/abc", "/abc"); - doThreeArgConstructorTest("http://localhost/abc/", "/abc/"); + public void twoStringArgConstructorTest() throws Exception { + doTwoStringArgConstructorTest("http://localhost/abc", "/abc"); + doTwoStringArgConstructorTest("http://localhost/abc/", "/abc/"); } - void doThreeArgConstructorTest(String s, String path) throws URISyntaxException { - ResteasyUriInfo ruri = new ResteasyUriInfo(s, "", ""); + void doTwoStringArgConstructorTest(String s, String path) throws URISyntaxException { + ResteasyUriInfo ruri = new ResteasyUriInfo(s, ""); URI uri = new URI(s); Assert.assertEquals(ERROR_MSG, path, ruri.getPath()); Assert.assertEquals(ERROR_MSG, path, ruri.getPath(true));