Skip to content

Commit 3b2aeb1

Browse files
committed
Header parsing optimization in cookie, authentication and caching protocol code
1 parent 9128f74 commit 3b2aeb1

4 files changed

Lines changed: 69 additions & 94 deletions

File tree

httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.apache.hc.core5.http.HttpHeaders;
4444
import org.apache.hc.core5.http.HttpRequest;
4545
import org.apache.hc.core5.http.HttpResponse;
46+
import org.apache.hc.core5.http.message.MessageSupport;
4647
import org.apache.hc.core5.http.message.ParserCursor;
4748
import org.apache.hc.core5.util.Args;
4849
import org.apache.hc.core5.util.CharArrayBuffer;
@@ -110,38 +111,23 @@ protected CacheControlHeaderParser() {
110111

111112
public void parse(final Iterator<Header> headerIterator, final BiConsumer<String, String> consumer) {
112113
while (headerIterator.hasNext()) {
113-
final Header header = headerIterator.next();
114-
final CharArrayBuffer buffer;
115-
final Tokenizer.Cursor cursor;
116-
if (header instanceof FormattedHeader) {
117-
buffer = ((FormattedHeader) header).getBuffer();
118-
cursor = new Tokenizer.Cursor(((FormattedHeader) header).getValuePos(), buffer.length());
119-
} else {
120-
final String s = header.getValue();
121-
if (s == null) {
122-
continue;
123-
}
124-
buffer = new CharArrayBuffer(s.length());
125-
buffer.append(s);
126-
cursor = new Tokenizer.Cursor(0, buffer.length());
127-
}
128-
129-
// Parse the header
130-
while (!cursor.atEnd()) {
131-
final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
132-
String value = null;
133-
if (!cursor.atEnd()) {
134-
final int valueDelim = buffer.charAt(cursor.getPos());
135-
cursor.updatePos(cursor.getPos() + 1);
136-
if (valueDelim == EQUAL_CHAR) {
137-
value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
138-
if (!cursor.atEnd()) {
139-
cursor.updatePos(cursor.getPos() + 1);
114+
MessageSupport.parseHeader(headerIterator.next(), (buffer, cursor) -> {
115+
while (!cursor.atEnd()) {
116+
final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
117+
String value = null;
118+
if (!cursor.atEnd()) {
119+
final int valueDelim = buffer.charAt(cursor.getPos());
120+
cursor.updatePos(cursor.getPos() + 1);
121+
if (valueDelim == EQUAL_CHAR) {
122+
value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
123+
if (!cursor.atEnd()) {
124+
cursor.updatePos(cursor.getPos() + 1);
125+
}
140126
}
141127
}
128+
consumer.accept(name, value);
142129
}
143-
consumer.accept(name, value);
144-
}
130+
});
145131
}
146132
}
147133

@@ -172,17 +158,13 @@ public final ResponseCacheControl parseResponse(final Iterator<Header> headerIte
172158
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_CACHE)) {
173159
builder.setNoCache(true);
174160
if (value != null) {
175-
final Tokenizer.Cursor valCursor = new ParserCursor(0, value.length());
161+
final ParserCursor valCursor = new ParserCursor(0, value.length());
176162
final Set<String> noCacheFields = new HashSet<>();
177-
while (!valCursor.atEnd()) {
178-
final String token = tokenParser.parseToken(value, valCursor, VALUE_DELIMS);
163+
MessageSupport.parseTokens(value, valCursor, token -> {
179164
if (!TextUtils.isBlank(token)) {
180165
noCacheFields.add(token);
181166
}
182-
if (!valCursor.atEnd()) {
183-
valCursor.updatePos(valCursor.getPos() + 1);
184-
}
185-
}
167+
});
186168
builder.setNoCacheFields(noCacheFields);
187169
}
188170
} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_STORE)) {

httpclient5/src/main/java/org/apache/hc/client5/http/impl/cookie/RFC6265CookieSpec.java

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@
4545
import org.apache.hc.client5.http.cookie.MalformedCookieException;
4646
import org.apache.hc.core5.annotation.Contract;
4747
import org.apache.hc.core5.annotation.ThreadingBehavior;
48-
import org.apache.hc.core5.http.FormattedHeader;
4948
import org.apache.hc.core5.http.Header;
5049
import org.apache.hc.core5.http.ParseException;
5150
import org.apache.hc.core5.http.message.BufferedHeader;
51+
import org.apache.hc.core5.http.message.MessageSupport;
5252
import org.apache.hc.core5.util.Args;
5353
import org.apache.hc.core5.util.CharArrayBuffer;
5454
import org.apache.hc.core5.util.Tokenizer;
@@ -109,43 +109,65 @@ static String getDefaultDomain(final CookieOrigin origin) {
109109
public final List<Cookie> parse(final Header header, final CookieOrigin origin) throws MalformedCookieException {
110110
Args.notNull(header, "Header");
111111
Args.notNull(origin, "Cookie origin");
112-
if (!header.getName().equalsIgnoreCase("Set-Cookie")) {
113-
throw new MalformedCookieException("Unrecognized cookie header: '" + header + "'");
112+
final RawCookie rawCookie = MessageSupport.parserHeaderValue(header, this::parseCookie);
113+
if (rawCookie == null) {
114+
throw new MalformedCookieException("Cookie value is invalid");
114115
}
115-
final CharArrayBuffer buffer;
116-
final Tokenizer.Cursor cursor;
117-
if (header instanceof FormattedHeader) {
118-
buffer = ((FormattedHeader) header).getBuffer();
119-
cursor = new Tokenizer.Cursor(((FormattedHeader) header).getValuePos(), buffer.length());
120-
} else {
121-
final String s = header.getValue();
122-
if (s == null) {
123-
throw new MalformedCookieException("Header value is null");
116+
117+
final BasicClientCookie cookie = new BasicClientCookie(rawCookie.name, rawCookie.value);
118+
cookie.setPath(getDefaultPath(origin));
119+
cookie.setDomain(getDefaultDomain(origin));
120+
cookie.setCreationDate(Instant.now());
121+
122+
for (final Map.Entry<String, String> entry: rawCookie.attribMap.entrySet()) {
123+
final String paramName = entry.getKey();
124+
final String paramValue = entry.getValue();
125+
cookie.setAttribute(paramName, paramValue);
126+
final CookieAttributeHandler handler = this.attribHandlerMap.get(paramName);
127+
if (handler != null) {
128+
handler.parse(cookie, paramValue);
124129
}
125-
buffer = new CharArrayBuffer(s.length());
126-
buffer.append(s);
127-
cursor = new Tokenizer.Cursor(0, buffer.length());
128130
}
131+
return Collections.singletonList(cookie);
132+
}
133+
134+
static class RawCookie {
135+
136+
final String name;
137+
final String value;
138+
final Map<String, String> attribMap;
139+
140+
RawCookie(final String name, final String value, final Map<String, String> attribMap) {
141+
this.name = name;
142+
this.value = value;
143+
this.attribMap = attribMap;
144+
}
145+
146+
@Override
147+
public String toString() {
148+
return name + " = " + value + " " + attribMap;
149+
}
150+
151+
}
152+
153+
private RawCookie parseCookie(final CharSequence buffer,
154+
final Tokenizer.Cursor cursor) {
129155
final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
130156
if (name.isEmpty()) {
131-
return Collections.emptyList();
157+
return null;
132158
}
133159
if (cursor.atEnd()) {
134-
return Collections.emptyList();
160+
return null;
135161
}
136162
final int valueDelim = buffer.charAt(cursor.getPos());
137163
cursor.updatePos(cursor.getPos() + 1);
138164
if (valueDelim != '=') {
139-
throw new MalformedCookieException("Cookie value is invalid: '" + header + "'");
165+
return null;
140166
}
141167
final String value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
142168
if (!cursor.atEnd()) {
143169
cursor.updatePos(cursor.getPos() + 1);
144170
}
145-
final BasicClientCookie cookie = new BasicClientCookie(name, value);
146-
cookie.setPath(getDefaultPath(origin));
147-
cookie.setDomain(getDefaultDomain(origin));
148-
cookie.setCreationDate(Instant.now());
149171

150172
final Map<String, String> attribMap = new LinkedHashMap<>();
151173
while (!cursor.atEnd()) {
@@ -162,24 +184,14 @@ public final List<Cookie> parse(final Header header, final CookieOrigin origin)
162184
}
163185
}
164186
}
165-
cookie.setAttribute(paramName, paramValue);
166187
attribMap.put(paramName, paramValue);
167188
}
168189
// Ignore 'Expires' if 'Max-Age' is present
169190
if (attribMap.containsKey(Cookie.MAX_AGE_ATTR)) {
170191
attribMap.remove(Cookie.EXPIRES_ATTR);
171192
}
172193

173-
for (final Map.Entry<String, String> entry: attribMap.entrySet()) {
174-
final String paramName = entry.getKey();
175-
final String paramValue = entry.getValue();
176-
final CookieAttributeHandler handler = this.attribHandlerMap.get(paramName);
177-
if (handler != null) {
178-
handler.parse(cookie, paramValue);
179-
}
180-
}
181-
182-
return Collections.singletonList(cookie);
194+
return new RawCookie(name, value, attribMap);
183195
}
184196

185197
@Override

httpclient5/src/main/java/org/apache/hc/client5/http/protocol/NextNonceInterceptor.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
import org.apache.hc.core5.annotation.Contract;
3131
import org.apache.hc.core5.annotation.ThreadingBehavior;
3232
import org.apache.hc.core5.http.EntityDetails;
33-
import org.apache.hc.core5.http.FormattedHeader;
3433
import org.apache.hc.core5.http.Header;
3534
import org.apache.hc.core5.http.HttpResponse;
3635
import org.apache.hc.core5.http.HttpResponseInterceptor;
36+
import org.apache.hc.core5.http.message.MessageSupport;
3737
import org.apache.hc.core5.http.message.ParserCursor;
3838
import org.apache.hc.core5.http.protocol.HttpContext;
3939
import org.apache.hc.core5.util.Args;
@@ -101,16 +101,7 @@ public void process(final HttpResponse response, final EntityDetails entity, fin
101101

102102
final Header header = response.getFirstHeader(AUTHENTICATION_INFO_HEADER);
103103
if (header != null) {
104-
final String nextNonce;
105-
if (header instanceof FormattedHeader) {
106-
final CharSequence buf = ((FormattedHeader) header).getBuffer();
107-
final ParserCursor cursor = new ParserCursor(((FormattedHeader) header).getValuePos(), buf.length());
108-
nextNonce = parseNextNonce(buf, cursor);
109-
} else {
110-
final CharSequence headerValue = header.getValue();
111-
final ParserCursor cursor = new ParserCursor(0, headerValue.length());
112-
nextNonce = parseNextNonce(headerValue, cursor);
113-
}
104+
final String nextNonce = MessageSupport.parserHeaderValue(header, this::parseNextNonce);
114105
if (!TextUtils.isBlank(nextNonce)) {
115106
HttpClientContext.castOrCreate(context).setNextNonce(nextNonce);
116107
}

httpclient5/src/test/java/org/apache/hc/client5/http/impl/cookie/TestRFC6265CookieSpec.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,24 +85,14 @@ void testParseCookieQuotedValue() throws Exception {
8585
Assertions.assertEquals("stuff", cookie.getAttribute("this"));
8686
}
8787

88-
@Test
89-
void testParseCookieWrongHeader() {
90-
final RFC6265CookieSpec cookiespec = new RFC6265CookieSpec();
91-
92-
final Header header = new BasicHeader("Set-Cookie2", "blah");
93-
final CookieOrigin origin = new CookieOrigin("host", 80, "/path/", true);
94-
Assertions.assertThrows(MalformedCookieException.class, () ->
95-
cookiespec.parse(header, origin));
96-
}
97-
9888
@Test
9989
void testParseCookieMissingName() throws Exception {
10090
final RFC6265CookieSpec cookiespec = new RFC6265CookieSpec();
10191

10292
final Header header = new BasicHeader("Set-Cookie", "=blah ; this = stuff;");
10393
final CookieOrigin origin = new CookieOrigin("host", 80, "/path/", true);
104-
final List<Cookie> cookies = cookiespec.parse(header, origin);
105-
Assertions.assertEquals(0, cookies.size());
94+
Assertions.assertThrows(MalformedCookieException.class, () ->
95+
cookiespec.parse(header, origin));
10696
}
10797

10898
@Test
@@ -111,8 +101,8 @@ void testParseCookieMissingValue1() throws Exception {
111101

112102
final Header header = new BasicHeader("Set-Cookie", "blah");
113103
final CookieOrigin origin = new CookieOrigin("host", 80, "/path/", true);
114-
final List<Cookie> cookies = cookiespec.parse(header, origin);
115-
Assertions.assertEquals(0, cookies.size());
104+
Assertions.assertThrows(MalformedCookieException.class, () ->
105+
cookiespec.parse(header, origin));
116106
}
117107

118108
@Test

0 commit comments

Comments
 (0)