You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2026/08/16 11:21:22 UTC
(httpcomponents-client) 01/01: Header parsing optimization in cookie, authentication and caching protocol code
This is an automated email from the ASF dual-hosted git repository.
ok2c pushed a commit to branch feature/parsing_optimization
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git
commit 3b2aeb10f6998bf4fc1d91608a88cadad04c13f1
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sun Aug 16 12:08:29 2026 +0200
Header parsing optimization in cookie, authentication and caching protocol code
---
.../http/impl/cache/CacheControlHeaderParser.java | 54 +++++----------
.../http/impl/cookie/RFC6265CookieSpec.java | 78 +++++++++++++---------
.../http/protocol/NextNonceInterceptor.java | 13 +---
.../http/impl/cookie/TestRFC6265CookieSpec.java | 18 ++---
4 files changed, 69 insertions(+), 94 deletions(-)
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java
index 6aae25450..58d9762ec 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java
@@ -43,6 +43,7 @@
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.message.ParserCursor;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.CharArrayBuffer;
@@ -110,38 +111,23 @@ protected CacheControlHeaderParser() {
public void parse(final Iterator<Header> headerIterator, final BiConsumer<String, String> consumer) {
while (headerIterator.hasNext()) {
- final Header header = headerIterator.next();
- final CharArrayBuffer buffer;
- final Tokenizer.Cursor cursor;
- if (header instanceof FormattedHeader) {
- buffer = ((FormattedHeader) header).getBuffer();
- cursor = new Tokenizer.Cursor(((FormattedHeader) header).getValuePos(), buffer.length());
- } else {
- final String s = header.getValue();
- if (s == null) {
- continue;
- }
- buffer = new CharArrayBuffer(s.length());
- buffer.append(s);
- cursor = new Tokenizer.Cursor(0, buffer.length());
- }
-
- // Parse the header
- while (!cursor.atEnd()) {
- final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
- String value = null;
- if (!cursor.atEnd()) {
- final int valueDelim = buffer.charAt(cursor.getPos());
- cursor.updatePos(cursor.getPos() + 1);
- if (valueDelim == EQUAL_CHAR) {
- value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
- if (!cursor.atEnd()) {
- cursor.updatePos(cursor.getPos() + 1);
+ MessageSupport.parseHeader(headerIterator.next(), (buffer, cursor) -> {
+ while (!cursor.atEnd()) {
+ final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
+ String value = null;
+ if (!cursor.atEnd()) {
+ final int valueDelim = buffer.charAt(cursor.getPos());
+ cursor.updatePos(cursor.getPos() + 1);
+ if (valueDelim == EQUAL_CHAR) {
+ value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
+ if (!cursor.atEnd()) {
+ cursor.updatePos(cursor.getPos() + 1);
+ }
}
}
+ consumer.accept(name, value);
}
- consumer.accept(name, value);
- }
+ });
}
}
@@ -172,17 +158,13 @@ public final ResponseCacheControl parseResponse(final Iterator<Header> headerIte
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_CACHE)) {
builder.setNoCache(true);
if (value != null) {
- final Tokenizer.Cursor valCursor = new ParserCursor(0, value.length());
+ final ParserCursor valCursor = new ParserCursor(0, value.length());
final Set<String> noCacheFields = new HashSet<>();
- while (!valCursor.atEnd()) {
- final String token = tokenParser.parseToken(value, valCursor, VALUE_DELIMS);
+ MessageSupport.parseTokens(value, valCursor, token -> {
if (!TextUtils.isBlank(token)) {
noCacheFields.add(token);
}
- if (!valCursor.atEnd()) {
- valCursor.updatePos(valCursor.getPos() + 1);
- }
- }
+ });
builder.setNoCacheFields(noCacheFields);
}
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_STORE)) {
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265CookieSpec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265CookieSpec.java
index c15c78808..4a442c8d3 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265CookieSpec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265CookieSpec.java
@@ -45,10 +45,10 @@
import org.apache.hc.client5.http.cookie.MalformedCookieException;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
-import org.apache.hc.core5.http.FormattedHeader;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.message.BufferedHeader;
+import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.CharArrayBuffer;
import org.apache.hc.core5.util.Tokenizer;
@@ -109,43 +109,65 @@ static String getDefaultDomain(final CookieOrigin origin) {
public final List<Cookie> parse(final Header header, final CookieOrigin origin) throws MalformedCookieException {
Args.notNull(header, "Header");
Args.notNull(origin, "Cookie origin");
- if (!header.getName().equalsIgnoreCase("Set-Cookie")) {
- throw new MalformedCookieException("Unrecognized cookie header: '" + header + "'");
+ final RawCookie rawCookie = MessageSupport.parserHeaderValue(header, this::parseCookie);
+ if (rawCookie == null) {
+ throw new MalformedCookieException("Cookie value is invalid");
}
- final CharArrayBuffer buffer;
- final Tokenizer.Cursor cursor;
- if (header instanceof FormattedHeader) {
- buffer = ((FormattedHeader) header).getBuffer();
- cursor = new Tokenizer.Cursor(((FormattedHeader) header).getValuePos(), buffer.length());
- } else {
- final String s = header.getValue();
- if (s == null) {
- throw new MalformedCookieException("Header value is null");
+
+ final BasicClientCookie cookie = new BasicClientCookie(rawCookie.name, rawCookie.value);
+ cookie.setPath(getDefaultPath(origin));
+ cookie.setDomain(getDefaultDomain(origin));
+ cookie.setCreationDate(Instant.now());
+
+ for (final Map.Entry<String, String> entry: rawCookie.attribMap.entrySet()) {
+ final String paramName = entry.getKey();
+ final String paramValue = entry.getValue();
+ cookie.setAttribute(paramName, paramValue);
+ final CookieAttributeHandler handler = this.attribHandlerMap.get(paramName);
+ if (handler != null) {
+ handler.parse(cookie, paramValue);
}
- buffer = new CharArrayBuffer(s.length());
- buffer.append(s);
- cursor = new Tokenizer.Cursor(0, buffer.length());
}
+ return Collections.singletonList(cookie);
+ }
+
+ static class RawCookie {
+
+ final String name;
+ final String value;
+ final Map<String, String> attribMap;
+
+ RawCookie(final String name, final String value, final Map<String, String> attribMap) {
+ this.name = name;
+ this.value = value;
+ this.attribMap = attribMap;
+ }
+
+ @Override
+ public String toString() {
+ return name + " = " + value + " " + attribMap;
+ }
+
+ }
+
+ private RawCookie parseCookie(final CharSequence buffer,
+ final Tokenizer.Cursor cursor) {
final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
if (name.isEmpty()) {
- return Collections.emptyList();
+ return null;
}
if (cursor.atEnd()) {
- return Collections.emptyList();
+ return null;
}
final int valueDelim = buffer.charAt(cursor.getPos());
cursor.updatePos(cursor.getPos() + 1);
if (valueDelim != '=') {
- throw new MalformedCookieException("Cookie value is invalid: '" + header + "'");
+ return null;
}
final String value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
if (!cursor.atEnd()) {
cursor.updatePos(cursor.getPos() + 1);
}
- final BasicClientCookie cookie = new BasicClientCookie(name, value);
- cookie.setPath(getDefaultPath(origin));
- cookie.setDomain(getDefaultDomain(origin));
- cookie.setCreationDate(Instant.now());
final Map<String, String> attribMap = new LinkedHashMap<>();
while (!cursor.atEnd()) {
@@ -162,7 +184,6 @@ public final List<Cookie> parse(final Header header, final CookieOrigin origin)
}
}
}
- cookie.setAttribute(paramName, paramValue);
attribMap.put(paramName, paramValue);
}
// Ignore 'Expires' if 'Max-Age' is present
@@ -170,16 +191,7 @@ public final List<Cookie> parse(final Header header, final CookieOrigin origin)
attribMap.remove(Cookie.EXPIRES_ATTR);
}
- for (final Map.Entry<String, String> entry: attribMap.entrySet()) {
- final String paramName = entry.getKey();
- final String paramValue = entry.getValue();
- final CookieAttributeHandler handler = this.attribHandlerMap.get(paramName);
- if (handler != null) {
- handler.parse(cookie, paramValue);
- }
- }
-
- return Collections.singletonList(cookie);
+ return new RawCookie(name, value, attribMap);
}
@Override
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/NextNonceInterceptor.java b/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/NextNonceInterceptor.java
index a5bb2a471..d68aed719 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/NextNonceInterceptor.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/NextNonceInterceptor.java
@@ -30,10 +30,10 @@
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.EntityDetails;
-import org.apache.hc.core5.http.FormattedHeader;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpResponseInterceptor;
+import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.message.ParserCursor;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.util.Args;
@@ -101,16 +101,7 @@ public void process(final HttpResponse response, final EntityDetails entity, fin
final Header header = response.getFirstHeader(AUTHENTICATION_INFO_HEADER);
if (header != null) {
- final String nextNonce;
- if (header instanceof FormattedHeader) {
- final CharSequence buf = ((FormattedHeader) header).getBuffer();
- final ParserCursor cursor = new ParserCursor(((FormattedHeader) header).getValuePos(), buf.length());
- nextNonce = parseNextNonce(buf, cursor);
- } else {
- final CharSequence headerValue = header.getValue();
- final ParserCursor cursor = new ParserCursor(0, headerValue.length());
- nextNonce = parseNextNonce(headerValue, cursor);
- }
+ final String nextNonce = MessageSupport.parserHeaderValue(header, this::parseNextNonce);
if (!TextUtils.isBlank(nextNonce)) {
HttpClientContext.castOrCreate(context).setNextNonce(nextNonce);
}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestRFC6265CookieSpec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestRFC6265CookieSpec.java
index 6bb9ad14f..56897c9b8 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestRFC6265CookieSpec.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestRFC6265CookieSpec.java
@@ -85,24 +85,14 @@ void testParseCookieQuotedValue() throws Exception {
Assertions.assertEquals("stuff", cookie.getAttribute("this"));
}
- @Test
- void testParseCookieWrongHeader() {
- final RFC6265CookieSpec cookiespec = new RFC6265CookieSpec();
-
- final Header header = new BasicHeader("Set-Cookie2", "blah");
- final CookieOrigin origin = new CookieOrigin("host", 80, "/path/", true);
- Assertions.assertThrows(MalformedCookieException.class, () ->
- cookiespec.parse(header, origin));
- }
-
@Test
void testParseCookieMissingName() throws Exception {
final RFC6265CookieSpec cookiespec = new RFC6265CookieSpec();
final Header header = new BasicHeader("Set-Cookie", "=blah ; this = stuff;");
final CookieOrigin origin = new CookieOrigin("host", 80, "/path/", true);
- final List<Cookie> cookies = cookiespec.parse(header, origin);
- Assertions.assertEquals(0, cookies.size());
+ Assertions.assertThrows(MalformedCookieException.class, () ->
+ cookiespec.parse(header, origin));
}
@Test
@@ -111,8 +101,8 @@ void testParseCookieMissingValue1() throws Exception {
final Header header = new BasicHeader("Set-Cookie", "blah");
final CookieOrigin origin = new CookieOrigin("host", 80, "/path/", true);
- final List<Cookie> cookies = cookiespec.parse(header, origin);
- Assertions.assertEquals(0, cookies.size());
+ Assertions.assertThrows(MalformedCookieException.class, () ->
+ cookiespec.parse(header, origin));
}
@Test