This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new ca4ca0dab0 Optimize conversion of method from bytes to String
ca4ca0dab0 is described below
commit ca4ca0dab0e5117e0a0936a3914fb24f77e9d6a7
Author: Mark Thomas <[email protected]>
AuthorDate: Thu Sep 11 08:48:07 2025 +0100
Optimize conversion of method from bytes to String
The savings are small but noticeable in performance tests - particularly
with embedded that doesn't use the StringCache by default.
Because HttpServlet calls HttpServletRequest.getmethod(), nearly every
request will convert the method to a String so it is more efficient to
do the conversion early and store it as a String rather than use
MessageBytes.
Unknown methods do attract a small performance penalty but there should
be very few of those.
---
.../catalina/authenticator/FormAuthenticator.java | 6 +-
.../apache/catalina/connector/CoyoteAdapter.java | 6 +-
.../apache/catalina/connector/OutputBuffer.java | 4 +-
java/org/apache/catalina/connector/Request.java | 2 +-
.../catalina/core/ApplicationPushBuilder.java | 2 +-
java/org/apache/coyote/Request.java | 27 ++++
java/org/apache/coyote/RequestInfo.java | 2 +-
java/org/apache/coyote/ajp/AjpProcessor.java | 8 +-
.../apache/coyote/http11/Http11InputBuffer.java | 3 +-
java/org/apache/coyote/http11/Http11Processor.java | 5 +-
java/org/apache/coyote/http2/Stream.java | 10 +-
java/org/apache/coyote/http2/StreamProcessor.java | 3 +-
java/org/apache/tomcat/util/http/Method.java | 149 +++++++++++++++++++++
test/org/apache/tomcat/util/http/TestMethod.java | 43 ++++++
.../tomcat/util/http/TestMethodPerformance.java | 67 +++++++++
webapps/docs/changelog.xml | 4 +
16 files changed, 315 insertions(+), 26 deletions(-)
diff --git a/java/org/apache/catalina/authenticator/FormAuthenticator.java
b/java/org/apache/catalina/authenticator/FormAuthenticator.java
index f5f8bebf14..d1e6b5fe17 100644
--- a/java/org/apache/catalina/authenticator/FormAuthenticator.java
+++ b/java/org/apache/catalina/authenticator/FormAuthenticator.java
@@ -448,7 +448,7 @@ public class FormAuthenticator extends AuthenticatorBase {
// Always use GET for the login page, regardless of the method used
String oldMethod = request.getMethod();
- request.getCoyoteRequest().method().setString("GET");
+ request.getCoyoteRequest().setMethod("GET");
RequestDispatcher disp =
context.getServletContext().getRequestDispatcher(loginPage);
try {
@@ -464,7 +464,7 @@ public class FormAuthenticator extends AuthenticatorBase {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
msg);
} finally {
// Restore original method so that it is written into access log
- request.getCoyoteRequest().method().setString(oldMethod);
+ request.getCoyoteRequest().setMethod(oldMethod);
}
}
@@ -632,7 +632,7 @@ public class FormAuthenticator extends AuthenticatorBase {
request.getCoyoteRequest().setContentType(contentType);
}
- request.getCoyoteRequest().method().setString(method);
+ request.getCoyoteRequest().setMethod(method);
// The method, URI, queryString and protocol are normally stored as
// bytes in the HttpInputBuffer and converted lazily to String. At this
// point, the method has already been set as String in the line above
diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java
b/java/org/apache/catalina/connector/CoyoteAdapter.java
index b6328e39f1..260f0dc369 100644
--- a/java/org/apache/catalina/connector/CoyoteAdapter.java
+++ b/java/org/apache/catalina/connector/CoyoteAdapter.java
@@ -597,7 +597,7 @@ public class CoyoteAdapter implements Adapter {
// Check for ping OPTIONS * request
if (undecodedURI.equals("*")) {
- if (req.method().equals("OPTIONS")) {
+ if ("OPTIONS".equals(req.getMethod())) {
StringBuilder allow = new StringBuilder();
allow.append("GET, HEAD, POST, PUT, DELETE, OPTIONS");
// Trace if allowed
@@ -616,7 +616,7 @@ public class CoyoteAdapter implements Adapter {
MessageBytes decodedURI = req.decodedURI();
// Filter CONNECT method
- if (req.method().equals("CONNECT")) {
+ if ("CONNECT".equals(req.getMethod())) {
response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,
sm.getString("coyoteAdapter.connect"));
} else {
// No URI for CONNECT requests
@@ -813,7 +813,7 @@ public class CoyoteAdapter implements Adapter {
}
// Filter TRACE method
- if (!connector.getAllowTrace() && req.method().equals("TRACE")) {
+ if (!connector.getAllowTrace() && "TRACE".equals(req.getMethod())) {
Wrapper wrapper = request.getWrapper();
StringBuilder header = null;
if (wrapper != null) {
diff --git a/java/org/apache/catalina/connector/OutputBuffer.java
b/java/org/apache/catalina/connector/OutputBuffer.java
index 3055735f90..9d0a3c08be 100644
--- a/java/org/apache/catalina/connector/OutputBuffer.java
+++ b/java/org/apache/catalina/connector/OutputBuffer.java
@@ -238,8 +238,8 @@ public class OutputBuffer extends Writer {
// - the content length has not been explicitly set
// AND
// - some content has been written OR this is NOT a HEAD request
- if ((!coyoteResponse.isCommitted()) &&
(coyoteResponse.getContentLengthLong() == -1) &&
- ((bb.remaining() > 0 ||
!coyoteResponse.getRequest().method().equals("HEAD")))) {
+ if (!coyoteResponse.isCommitted() &&
coyoteResponse.getContentLengthLong() == -1 &&
+ (bb.remaining() > 0 ||
!"HEAD".equals(coyoteResponse.getRequest().getMethod()))) {
coyoteResponse.setContentLength(bb.remaining());
}
diff --git a/java/org/apache/catalina/connector/Request.java
b/java/org/apache/catalina/connector/Request.java
index d879232636..55ee01df3c 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -2199,7 +2199,7 @@ public class Request implements HttpServletRequest {
@Override
public String getMethod() {
- return coyoteRequest.method().toStringType();
+ return coyoteRequest.getMethod();
}
diff --git a/java/org/apache/catalina/core/ApplicationPushBuilder.java
b/java/org/apache/catalina/core/ApplicationPushBuilder.java
index 371d8aa072..a633b96a7d 100644
--- a/java/org/apache/catalina/core/ApplicationPushBuilder.java
+++ b/java/org/apache/catalina/core/ApplicationPushBuilder.java
@@ -293,7 +293,7 @@ public class ApplicationPushBuilder implements PushBuilder {
org.apache.coyote.Request pushTarget = new org.apache.coyote.Request();
- pushTarget.method().setString(method);
+ pushTarget.setMethod(method);
// The next three are implied by the Javadoc getPath()
pushTarget.serverName().setString(baseRequest.getServerName());
pushTarget.setServerPort(baseRequest.getServerPort());
diff --git a/java/org/apache/coyote/Request.java
b/java/org/apache/coyote/Request.java
index 2874bf457c..7ed5ef4cd8 100644
--- a/java/org/apache/coyote/Request.java
+++ b/java/org/apache/coyote/Request.java
@@ -29,6 +29,7 @@ import javax.servlet.ReadListener;
import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.buf.UDecoder;
+import org.apache.tomcat.util.http.Method;
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.http.Parameters;
import org.apache.tomcat.util.http.ServerCookies;
@@ -300,10 +301,36 @@ public final class Request {
return schemeMB;
}
+ /**
+ * Get a MessageBytes instance that holds the current request's HTTP
method.
+ *
+ * @return a MessageBytes instance that holds the current request's HTTP
method.
+ *
+ * @deprecated Use {@link #getMethod()}, {@link Request#setMethod(String)}
and {@link #setMethod(byte[], int, int)}
+ */
+ @Deprecated
public MessageBytes method() {
return methodMB;
}
+ public void setMethod(String method) {
+ methodMB.setString(method);
+ }
+
+ public void setMethod(byte[] buf, int start, int len) {
+ String method = Method.bytesToString(buf, start, len);
+ if (method == null) {
+ methodMB.setBytes(buf, start, len);
+ method = methodMB.toStringType();
+ } else {
+ methodMB.setString(method);
+ }
+ }
+
+ public String getMethod() {
+ return methodMB.toStringType();
+ }
+
public MessageBytes requestURI() {
return uriMB;
}
diff --git a/java/org/apache/coyote/RequestInfo.java
b/java/org/apache/coyote/RequestInfo.java
index dae6918ce7..6b3622aaca 100644
--- a/java/org/apache/coyote/RequestInfo.java
+++ b/java/org/apache/coyote/RequestInfo.java
@@ -63,7 +63,7 @@ public class RequestInfo {
// This is useful for long-running requests only
public String getMethod() {
- return req.method().toString();
+ return req.getMethod();
}
public String getCurrentUri() {
diff --git a/java/org/apache/coyote/ajp/AjpProcessor.java
b/java/org/apache/coyote/ajp/AjpProcessor.java
index 455e8e48b6..9b74273dba 100644
--- a/java/org/apache/coyote/ajp/AjpProcessor.java
+++ b/java/org/apache/coyote/ajp/AjpProcessor.java
@@ -646,7 +646,7 @@ public class AjpProcessor extends AbstractProcessor {
byte methodCode = requestHeaderMessage.getByte();
if (methodCode != Constants.SC_M_JK_STORED) {
String methodName = Constants.getMethodForCode(methodCode - 1);
- request.method().setString(methodName);
+ request.setMethod(methodName);
}
requestHeaderMessage.getBytes(request.protocol());
@@ -831,7 +831,9 @@ public class AjpProcessor extends AbstractProcessor {
break;
case Constants.SC_A_STORED_METHOD:
- requestHeaderMessage.getBytes(request.method());
+ requestHeaderMessage.getBytes(tmpMB);
+ ByteChunk tmpBC = tmpMB.getByteChunk();
+ request.setMethod(tmpBC.getBytes(), tmpBC.getStart(),
tmpBC.getLength());
break;
case Constants.SC_A_SECRET:
@@ -929,7 +931,7 @@ public class AjpProcessor extends AbstractProcessor {
// Responses with certain status codes and/or methods are not
permitted to include a response body.
int statusCode = response.getStatus();
if (statusCode < 200 || statusCode == 204 || statusCode == 205 ||
statusCode == 304 ||
- request.method().equals("HEAD")) {
+ "HEAD".equals(request.getMethod())) {
// No entity body
swallowResponse = true;
}
diff --git a/java/org/apache/coyote/http11/Http11InputBuffer.java
b/java/org/apache/coyote/http11/Http11InputBuffer.java
index d99d7d5eeb..9903301011 100644
--- a/java/org/apache/coyote/http11/Http11InputBuffer.java
+++ b/java/org/apache/coyote/http11/Http11InputBuffer.java
@@ -395,8 +395,7 @@ public class Http11InputBuffer implements InputBuffer,
ApplicationBufferHandler
chr = byteBuffer.get();
if (chr == Constants.SP || chr == Constants.HT) {
space = true;
- request.method().setBytes(byteBuffer.array(),
parsingRequestLineStart,
- pos - parsingRequestLineStart);
+ request.setMethod(byteBuffer.array(),
parsingRequestLineStart, pos - parsingRequestLineStart);
} else if (!HttpParser.isToken(chr)) {
// Avoid unknown protocol triggering an additional error
request.protocol().setString(Constants.HTTP_11);
diff --git a/java/org/apache/coyote/http11/Http11Processor.java
b/java/org/apache/coyote/http11/Http11Processor.java
index 6eb8e36e93..3f042ea177 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -502,7 +502,7 @@ public class Http11Processor extends AbstractProcessor {
// Transfer the minimal information required for the copy of the
Request
// that is passed to the HTTP upgrade process
dest.decodedURI().duplicate(source.decodedURI());
- dest.method().duplicate(source.method());
+ dest.setMethod(source.getMethod());
dest.getMimeHeaders().duplicate(source.getMimeHeaders());
dest.requestURI().duplicate(source.requestURI());
dest.queryString().duplicate(source.queryString());
@@ -901,8 +901,7 @@ public class Http11Processor extends AbstractProcessor {
}
}
- MessageBytes methodMB = request.method();
- boolean head = methodMB.equals("HEAD");
+ boolean head = "HEAD".equals(request.getMethod());
if (head) {
// Any entity body, if present, should not be sent
outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
diff --git a/java/org/apache/coyote/http2/Stream.java
b/java/org/apache/coyote/http2/Stream.java
index ab041be128..cf328662ec 100644
--- a/java/org/apache/coyote/http2/Stream.java
+++ b/java/org/apache/coyote/http2/Stream.java
@@ -371,8 +371,8 @@ class Stream extends AbstractNonZeroStream implements
HeaderEmitter {
switch (name) {
case ":method": {
- if (coyoteRequest.method().isNull()) {
- coyoteRequest.method().setString(value);
+ if (coyoteRequest.getMethod() == null) {
+ coyoteRequest.setMethod(value);
} else {
throw new HpackException(
sm.getString("stream.header.duplicate",
getConnectionId(), getIdAsString(), ":method"));
@@ -552,8 +552,8 @@ class Stream extends AbstractNonZeroStream implements
HeaderEmitter {
final boolean receivedEndOfHeaders() throws ConnectionException {
- if (coyoteRequest.method().isNull() || coyoteRequest.scheme().isNull()
||
- !coyoteRequest.method().equals("CONNECT") &&
coyoteRequest.requestURI().isNull()) {
+ if (coyoteRequest.getMethod() == null ||
coyoteRequest.scheme().isNull() ||
+ !"CONNECT".equals(coyoteRequest.getMethod()) &&
coyoteRequest.requestURI().isNull()) {
throw new
ConnectionException(sm.getString("stream.header.required", getConnectionId(),
getIdAsString()),
Http2Error.PROTOCOL_ERROR);
}
@@ -862,7 +862,7 @@ class Stream extends AbstractNonZeroStream implements
HeaderEmitter {
return;
}
// Set the special HTTP/2 headers
-
request.getMimeHeaders().addValue(":method").duplicate(request.method());
+
request.getMimeHeaders().addValue(":method").setString(request.getMethod());
request.getMimeHeaders().addValue(":scheme").duplicate(request.scheme());
StringBuilder path = new
StringBuilder(request.requestURI().toString());
if (!request.queryString().isNull()) {
diff --git a/java/org/apache/coyote/http2/StreamProcessor.java
b/java/org/apache/coyote/http2/StreamProcessor.java
index 74de02eb7d..624746903a 100644
--- a/java/org/apache/coyote/http2/StreamProcessor.java
+++ b/java/org/apache/coyote/http2/StreamProcessor.java
@@ -522,8 +522,7 @@ class StreamProcessor extends AbstractProcessor implements
NonPipeliningProcesso
HttpParser httpParser = ((AbstractHttp11Protocol<?>)
handler.getProtocol().getHttp11Protocol()).getHttpParser();
// Method name must be a token
- String method = request.method().toString();
- if (!HttpParser.isToken(method)) {
+ if (!HttpParser.isToken(request.getMethod())) {
return false;
}
diff --git a/java/org/apache/tomcat/util/http/Method.java
b/java/org/apache/tomcat/util/http/Method.java
new file mode 100644
index 0000000000..31411c6171
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/Method.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http;
+
+public class Method {
+
+ // Standard HTTP methods supported by HttpServlet
+ public static final String GET = "GET";
+ public static final String POST = "POST";
+ public static final String PUT = "PUT";
+ public static final String PATCH = "PATCH";
+ public static final String HEAD = "HEAD";
+ public static final String OPTIONS = "OPTIONS";
+ public static final String DELETE = "DELETE";
+ public static final String TRACE = "TRACE";
+ // Additional WebDAV methods
+ public static final String PROPFIND = "PROPFIND";
+ public static final String PROPPATCH = "PROPPATCH";
+ public static final String MKCOL = "MKCOL";
+ public static final String COPY = "COPY";
+ public static final String MOVE = "MOVE";
+ public static final String LOCK = "LOCK";
+ public static final String UNLOCK = "UNLOCK";
+ // Other methods recognised by Tomcat
+ public static final String CONNECT = "CONNECT";
+
+
+ /**
+ * Provides optimised conversion from bytes to Strings for known HTTP
methods. The bytes are assumed to be an
+ * ISO-8859-1 encoded representation of an HTTP method. The method is not
validated as being a token, but only valid
+ * HTTP method names will be returned.
+ * <p>
+ * Doing in this way is ~10x faster than using MessageBytes.toStringType()
saving ~40ns per request which is ~1% of
+ * the processing time for a minimal "Hello World" type servlet. For
non-standard methods there is an additional
+ * overhead of ~2.5ns per request.
+ * <p>
+ * Pretty much every request ends up converting the method to a String so
it is more efficient to do this straight
+ * away and always use Strings.
+ *
+ * @param buf The byte buffer containing the HTTP method to convert
+ * @param start The first byte of the HTTP method
+ * @param len The number of bytes to convert
+ *
+ * @return The HTTP method as a String or {@code null} if the method is
not recognised.
+ */
+ public static String bytesToString(byte[] buf, int start, int len) {
+ switch (buf[start]) {
+ case 'G': {
+ if (len == 3 && buf[start + 1] == 'E' && buf[start + 2] ==
'T') {
+ return GET;
+ }
+ break;
+ }
+ case 'P': {
+ if (len == 4 && buf[start + 1] == 'O' && buf[start + 2] == 'S'
&& buf[start + 3] == 'T') {
+ return POST;
+ } else if (len == 3 && buf[start + 1] == 'U' && buf[start + 2]
== 'T') {
+ return PUT;
+ } else if (len == 5 && buf[start + 1] == 'A' && buf[start + 2]
== 'T' && buf[start + 3] == 'C' &&
+ buf[start + 4] == 'H') {
+ return PATCH;
+ } else if (len == 8 && buf[start + 1] == 'R' && buf[start + 2]
== 'O' && buf[start + 3] == 'P' &&
+ buf[start + 4] == 'F' && buf[start + 5] == 'I' &&
buf[start + 6] == 'N' &&
+ buf[start + 7] == 'D') {
+ return PROPFIND;
+ } else if (len == 9 && buf[start + 1] == 'R' && buf[start + 2]
== 'O' && buf[start + 3] == 'P' &&
+ buf[start + 4] == 'P' && buf[start + 5] == 'A' &&
buf[start + 6] == 'T' &&
+ buf[start + 7] == 'C' && buf[start + 8] == 'H') {
+ return PROPPATCH;
+ }
+ break;
+ }
+ case 'H': {
+ if (len == 4 && buf[start + 1] == 'E' && buf[start + 2] == 'A'
&& buf[start + 3] == 'D') {
+ return HEAD;
+ }
+ break;
+ }
+ case 'O': {
+ if (len == 7 && buf[start + 1] == 'P' && buf[start + 2] == 'T'
&& buf[start + 3] == 'I' &&
+ buf[start + 4] == 'O' && buf[start + 5] == 'N' &&
buf[start + 6] == 'S') {
+ return OPTIONS;
+ }
+ break;
+ }
+ case 'D': {
+ if (len == 6 && buf[start + 1] == 'E' && buf[start + 2] == 'L'
&& buf[start + 3] == 'E' &&
+ buf[start + 4] == 'T' && buf[start + 5] == 'E') {
+ return DELETE;
+ }
+ break;
+ }
+ case 'T': {
+ if (len == 5 && buf[start + 1] == 'R' && buf[start + 2] == 'A'
&& buf[start + 3] == 'C' &&
+ buf[start + 4] == 'E') {
+ return TRACE;
+ }
+ break;
+ }
+ case 'M': {
+ if (len == 5 && buf[start + 1] == 'K' && buf[start + 2] == 'C'
&& buf[start + 3] == 'O' &&
+ buf[start + 4] == 'L') {
+ return MKCOL;
+ } else if (len == 4 && buf[start + 1] == 'O' && buf[start + 2]
== 'V' && buf[start + 3] == 'E') {
+ return MOVE;
+ }
+ break;
+ }
+ case 'C': {
+ if (len == 4 && buf[start + 1] == 'O' && buf[start + 2] == 'P'
&& buf[start + 3] == 'Y') {
+ return COPY;
+ } else if (len == 7 && buf[start + 1] == 'O' && buf[start + 2]
== 'N' && buf[start + 3] == 'N' &&
+ buf[start + 4] == 'E' && buf[start + 5] == 'C' &&
buf[start + 6] == 'T') {
+ return CONNECT;
+ }
+ break;
+ }
+ case 'L': {
+ if (len == 4 && buf[start + 1] == 'O' && buf[start + 2] == 'C'
&& buf[start + 3] == 'K') {
+ return LOCK;
+ }
+ break;
+ }
+ case 'U': {
+ if (len == 6 && buf[start + 1] == 'N' && buf[start + 2] == 'L'
&& buf[start + 3] == 'O' &&
+ buf[start + 4] == 'C' && buf[start + 5] == 'K') {
+ return UNLOCK;
+ }
+ break;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/test/org/apache/tomcat/util/http/TestMethod.java
b/test/org/apache/tomcat/util/http/TestMethod.java
new file mode 100644
index 0000000000..a5fc7b7c28
--- /dev/null
+++ b/test/org/apache/tomcat/util/http/TestMethod.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestMethod {
+
+ /*
+ * Not testing performance. Just checking that there are no errors in the
parsing code.
+ */
+ @Test
+ public void testHttpMethodParsing() {
+ List<String> methods = Arrays.asList(Method.GET, Method.POST,
Method.PUT, Method.PATCH, Method.HEAD,
+ Method.OPTIONS, Method.DELETE, Method.TRACE, Method.PROPPATCH,
Method.PROPFIND, Method.MKCOL,
+ Method.COPY, Method.MOVE, Method.LOCK, Method.UNLOCK,
Method.CONNECT);
+
+ for (String method : methods) {
+ byte[] bytes = method.getBytes(StandardCharsets.ISO_8859_1);
+ String result = Method.bytesToString(bytes, 0, bytes.length);
+ Assert.assertEquals(method, result);
+ }
+ }
+}
diff --git a/test/org/apache/tomcat/util/http/TestMethodPerformance.java
b/test/org/apache/tomcat/util/http/TestMethodPerformance.java
new file mode 100644
index 0000000000..e2e3212b4e
--- /dev/null
+++ b/test/org/apache/tomcat/util/http/TestMethodPerformance.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http;
+
+import java.nio.charset.StandardCharsets;
+
+import org.junit.Test;
+
+import org.apache.tomcat.util.buf.MessageBytes;
+
+public class TestMethodPerformance {
+
+ private static final int LOOPS = 6;
+ private static final int ITERATIONS = 100000000;
+
+ private static final String INPUT = "GET
/context-path/servlet-path/path-info HTTP/1.1";
+ private static final byte[] INPUT_BYTES =
INPUT.getBytes(StandardCharsets.UTF_8);
+
+ private static MessageBytes mb = MessageBytes.newInstance();
+
+ @Test
+ public void testGetMethodPerformance() throws Exception {
+
+ for (int j = 0; j < LOOPS; j++) {
+ long start = System.nanoTime();
+ for (int i = 0; i < ITERATIONS; i++) {
+ mb.setBytes(INPUT_BYTES, 0, 3);
+ mb.toStringType();
+ }
+ long duration = System.nanoTime() - start;
+
+ if (j > 0) {
+ System.out.println("MessageBytes conversion took :" + duration
+ "ns");
+ }
+ }
+
+ for (int j = 0; j < LOOPS; j++) {
+ long start = System.nanoTime();
+ for (int i = 0; i < ITERATIONS; i++) {
+ String method = Method.bytesToString(INPUT_BYTES, 0, 3);
+ if (method == null) {
+ mb.setBytes(INPUT_BYTES, 0, 5);
+ mb.toStringType();
+ }
+ }
+ long duration = System.nanoTime() - start;
+
+ if (j > 0) {
+ System.out.println("Optimized conversion took :" + duration +
"ns");
+ }
+ }
+ }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 1bcb24f6f3..928288e1fb 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -131,6 +131,10 @@
additional PQC certificates defined with type <code>MLDSA</code> are
added to contexts which use classic certificates. (jfclere/remm)
</update>
+ <add>
+ Optimize the conversion of HTTP method from byte form to String form.
+ (markt)
+ </add>
</changelog>
</subsection>
<subsection name="Web applications">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]