Fix cookie date parsing, close #6016

Motivation:
* RFC6265 defines its own parser which is different from RFC1123 (it accepts RFC1123 format but also other ones). Basically, it's very lax on delimiters, ignores day of week and timezone. Currently, ClientCookieDecoder uses HttpHeaderDateFormat underneath, and can't parse valid cookies such as Github ones whose expires attribute looks like "Sun, 27 Nov 2016 19:37:15 -0000"
* ServerSideCookieEncoder currently uses HttpHeaderDateFormat underneath for formatting expires field, and it's slow.

Modifications:
* Introduce HttpHeaderDateFormatter that correctly implement RFC6265
* Use HttpHeaderDateFormatter in ClientCookieDecoder and ServerCookieEncoder
* Deprecate HttpHeaderDateFormat

Result:
* Proper RFC6265 dates support
* Faster ServerCookieEncoder and ClientCookieDecoder
* Faster tool for handling headers such as "Expires" and "Date"
This commit is contained in:
Stephane Landelle 2016-11-14 12:33:03 +01:00 committed by Norman Maurer
parent 0bc30a123e
commit edc4842309
8 changed files with 663 additions and 26 deletions

View File

@ -32,7 +32,9 @@ import java.util.TimeZone;
* <li>Sunday, 06-Nov-94 08:49:37 GMT: obsolete specification</li>
* <li>Sun Nov 6 08:49:37 1994: obsolete specification</li>
* </ul>
* @deprecated Use {@link HttpHeaderDateFormatter} instead
*/
@Deprecated
public final class HttpHeaderDateFormat extends SimpleDateFormat {
private static final long serialVersionUID = -925286159755905325L;

View File

@ -0,0 +1,443 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project 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 io.netty.handler.codec.http;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.util.AsciiString;
import io.netty.util.concurrent.FastThreadLocal;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
/**
* A formatter for HTTP header dates, such as "Expires" and "Date" headers, or "expires" field in "Set-Cookie".
*
* On the parsing side, it honors RFC6265 (so it supports RFC1123).
* Note that:
* <ul>
* <li>Day of week is ignored and not validated</li>
* <li>Timezone is ignored, as RFC6265 assumes UTC</li>
* </ul>
* If you're looking for a date format that validates day of week, or supports other timezones, consider using
* java.util.DateTimeFormatter.RFC_1123_DATE_TIME.
*
* On the formatting side, it uses RFC1123 format.
*
* @see <a href="https://tools.ietf.org/html/rfc6265#section-5.1.1">RFC6265</a> for the parsing side
* @see <a href="https://tools.ietf.org/html/rfc1123#page-55">RFC1123</a> for the encoding side.
*/
public final class HttpHeaderDateFormatter {
private static final BitSet DELIMITERS = new BitSet();
static {
DELIMITERS.set(0x09);
for (char c = 0x20; c <= 0x2F; c++) {
DELIMITERS.set(c);
}
for (char c = 0x3B; c <= 0x40; c++) {
DELIMITERS.set(c);
}
for (char c = 0x5B; c <= 0x60; c++) {
DELIMITERS.set(c);
}
for (char c = 0x7B; c <= 0x7E; c++) {
DELIMITERS.set(c);
}
}
private static final String[] DAY_OF_WEEK_TO_SHORT_NAME =
new String[]{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
private static final String[] CALENDAR_MONTH_TO_SHORT_NAME =
new String[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
private static final FastThreadLocal<HttpHeaderDateFormatter> INSTANCES =
new FastThreadLocal<HttpHeaderDateFormatter>() {
@Override
protected HttpHeaderDateFormatter initialValue() {
return new HttpHeaderDateFormatter();
}
};
/**
* Parse some text into a {@link Date}, according to RFC6265
* @param txt text to parse
* @return a {@link Date}, or null if text couldn't be parsed
*/
public static Date parse(CharSequence txt) {
return parse(txt, 0, txt.length());
}
/**
* Parse some text into a {@link Date}, according to RFC6265
* @param txt text to parse
* @param start the start index inside <code>txt</code>
* @param end the end index inside <code>txt</code>
* @return a {@link Date}, or null if text couldn't be parsed
*/
public static Date parse(CharSequence txt, int start, int end) {
int length = end - start;
if (length == 0) {
return null;
} else if (length < 0) {
throw new IllegalArgumentException("Can't have end < start");
} else if (length > 64) {
throw new IllegalArgumentException("Can't parse more than 64 chars," +
"looks like a user error or a malformed header");
}
return formatter().parse0(checkNotNull(txt, "txt"), start, end);
}
/**
* Format a {@link Date} into RFC1123 format
* @param date the date to format
* @return a RFC1123 string
*/
public static String format(Date date) {
return formatter().format0(checkNotNull(date, "date"));
}
/**
* Append a {@link Date} to a {@link StringBuilder} into RFC1123 format
* @param date the date to format
* @param sb the StringBuilder
* @return the same StringBuilder
*/
public static StringBuilder append(Date date, StringBuilder sb) {
return formatter().append0(checkNotNull(date, "date"), checkNotNull(sb, "sb"));
}
private static HttpHeaderDateFormatter formatter() {
HttpHeaderDateFormatter formatter = INSTANCES.get();
formatter.reset();
return formatter;
}
// delimiter = %x09 / %x20-2F / %x3B-40 / %x5B-60 / %x7B-7E
private static boolean isDelim(char c) {
return DELIMITERS.get(c);
}
private static boolean isDigit(char c) {
return c >= 48 && c <= 57;
}
private static int getNumericalValue(char c) {
return c - 48;
}
private final GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
private final StringBuilder sb = new StringBuilder(29); // Sun, 27 Nov 2016 19:37:15 GMT
private boolean timeFound;
private int hours;
private int minutes;
private int seconds;
private boolean dayOfMonthFound;
private int dayOfMonth;
private boolean monthFound;
private int month;
private boolean yearFound;
private int year;
private HttpHeaderDateFormatter() {
reset();
}
public void reset() {
timeFound = false;
hours = -1;
minutes = -1;
seconds = -1;
dayOfMonthFound = false;
dayOfMonth = -1;
monthFound = false;
month = -1;
yearFound = false;
year = -1;
cal.set(Calendar.MILLISECOND, 0);
sb.setLength(0);
}
private boolean tryParseTime(CharSequence txt, int tokenStart, int tokenEnd) {
int len = tokenEnd - tokenStart;
// h:m:s to hh:mm:ss
if (len < 5 || len > 8) {
return false;
}
int localHours = -1;
int localMinutes = -1;
int localSeconds = -1;
int currentPartNumber = 0;
int currentPartValue = 0;
for (int i = tokenStart; i < tokenEnd; i++) {
char c = txt.charAt(i);
if (isDigit(c)) {
currentPartValue = currentPartValue * 10 + getNumericalValue(c);
} else if (c == ':') {
if (currentPartValue == 0) {
// invalid :: (nothing in between)
return false;
}
switch (currentPartNumber) {
case 0:
// flushing hours
localHours = currentPartValue;
currentPartValue = 0;
currentPartNumber++;
break;
case 1:
// flushing minutes
localMinutes = currentPartValue;
currentPartValue = 0;
currentPartNumber++;
break;
default:
// invalid, too many :
return false;
}
} else {
// invalid char
return false;
}
}
if (currentPartValue > 0) {
// pending seconds
localSeconds = currentPartValue;
}
if (localHours >= 0 && localMinutes >= 0 && localSeconds >= 0) {
hours = localHours;
minutes = localMinutes;
seconds = localSeconds;
return true;
}
return false;
}
private boolean tryParseDayOfMonth(CharSequence txt, int tokenStart, int tokenEnd) {
int len = tokenEnd - tokenStart;
if (len == 1) {
char c0 = txt.charAt(tokenStart);
if (isDigit(c0)) {
dayOfMonth = getNumericalValue(c0);
return true;
}
} else if (len == 2) {
char c0 = txt.charAt(tokenStart);
char c1 = txt.charAt(tokenStart + 1);
if (isDigit(c0) && isDigit(c1)) {
dayOfMonth = getNumericalValue(c0) * 10 + getNumericalValue(c1);
return true;
}
}
return false;
}
private static boolean matchMonth(String month, CharSequence txt, int tokenStart) {
return AsciiString.regionMatchesAscii(month, true, 0, txt, tokenStart, 3);
}
private boolean tryParseMonth(CharSequence txt, int tokenStart, int tokenEnd) {
int len = tokenEnd - tokenStart;
if (len != 3) {
return false;
}
if (matchMonth("Jan", txt, tokenStart)) {
month = Calendar.JANUARY;
} else if (matchMonth("Feb", txt, tokenStart)) {
month = Calendar.FEBRUARY;
} else if (matchMonth("Mar", txt, tokenStart)) {
month = Calendar.MARCH;
} else if (matchMonth("Apr", txt, tokenStart)) {
month = Calendar.APRIL;
} else if (matchMonth("May", txt, tokenStart)) {
month = Calendar.MAY;
} else if (matchMonth("Jun", txt, tokenStart)) {
month = Calendar.JUNE;
} else if (matchMonth("Jul", txt, tokenStart)) {
month = Calendar.JULY;
} else if (matchMonth("Aug", txt, tokenStart)) {
month = Calendar.AUGUST;
} else if (matchMonth("Sep", txt, tokenStart)) {
month = Calendar.SEPTEMBER;
} else if (matchMonth("Oct", txt, tokenStart)) {
month = Calendar.OCTOBER;
} else if (matchMonth("Nov", txt, tokenStart)) {
month = Calendar.NOVEMBER;
} else if (matchMonth("Dec", txt, tokenStart)) {
month = Calendar.DECEMBER;
} else {
return false;
}
return true;
}
private boolean tryParseYear(CharSequence txt, int tokenStart, int tokenEnd) {
int len = tokenEnd - tokenStart;
if (len == 2) {
char c0 = txt.charAt(tokenStart);
char c1 = txt.charAt(tokenStart + 1);
if (isDigit(c0) && isDigit(c1)) {
year = getNumericalValue(c0) * 10 + getNumericalValue(c1);
return true;
}
} else if (len == 4) {
char c0 = txt.charAt(tokenStart);
char c1 = txt.charAt(tokenStart + 1);
char c2 = txt.charAt(tokenStart + 2);
char c3 = txt.charAt(tokenStart + 3);
if (isDigit(c0) && isDigit(c1) && isDigit(c2) && isDigit(c3)) {
year = getNumericalValue(c0) * 1000 +
getNumericalValue(c1) * 100 +
getNumericalValue(c2) * 10 +
getNumericalValue(c3);
return true;
}
}
return false;
}
private boolean parseToken(CharSequence txt, int tokenStart, int tokenEnd) {
// return true if all parts are found
if (!timeFound) {
timeFound = tryParseTime(txt, tokenStart, tokenEnd);
if (timeFound) {
return dayOfMonthFound && monthFound && yearFound;
}
}
if (!dayOfMonthFound) {
dayOfMonthFound = tryParseDayOfMonth(txt, tokenStart, tokenEnd);
if (dayOfMonthFound) {
return timeFound && monthFound && yearFound;
}
}
if (!monthFound) {
monthFound = tryParseMonth(txt, tokenStart, tokenEnd);
if (monthFound) {
return timeFound && dayOfMonthFound && yearFound;
}
}
if (!yearFound) {
yearFound = tryParseYear(txt, tokenStart, tokenEnd);
}
return timeFound && dayOfMonthFound && monthFound && yearFound;
}
private Date parse0(CharSequence txt, int start, int end) {
boolean allPartsFound = parse1(txt, start, end);
return allPartsFound && normalizeAndValidate() ? computeDate() : null;
}
private boolean parse1(CharSequence txt, int start, int end) {
// return true if all parts are found
int tokenStart = -1;
for (int i = start; i < end; i++) {
char c = txt.charAt(i);
if (isDelim(c)) {
if (tokenStart != -1) {
// terminate token
if (parseToken(txt, tokenStart, i)) {
return true;
}
tokenStart = -1;
}
} else if (tokenStart == -1) {
// start new token
tokenStart = i;
}
}
// terminate trailing token
return tokenStart != -1 && parseToken(txt, tokenStart, txt.length());
}
private boolean normalizeAndValidate() {
if (dayOfMonth < 1
|| dayOfMonth > 31
|| hours > 23
|| minutes > 59
|| seconds > 59) {
return false;
}
if (year >= 70 && year <= 99) {
year += 1900;
} else if (year >= 0 && year < 70) {
year += 2000;
} else if (year < 1601) {
// invalid value
return false;
}
return true;
}
private Date computeDate() {
cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.YEAR, year);
cal.set(Calendar.HOUR_OF_DAY, hours);
cal.set(Calendar.MINUTE, minutes);
cal.set(Calendar.SECOND, seconds);
return cal.getTime();
}
private String format0(Date date) {
append0(date, sb);
return sb.toString();
}
private StringBuilder append0(Date date, StringBuilder sb) {
cal.setTime(date);
sb.append(DAY_OF_WEEK_TO_SHORT_NAME[cal.get(Calendar.DAY_OF_WEEK) - 1]).append(", ");
sb.append(cal.get(Calendar.DAY_OF_MONTH)).append(' ');
sb.append(CALENDAR_MONTH_TO_SHORT_NAME[cal.get(Calendar.MONTH)]).append(' ');
sb.append(cal.get(Calendar.YEAR)).append(' ');
appendZeroLeftPadded(cal.get(Calendar.HOUR_OF_DAY), sb).append(':');
appendZeroLeftPadded(cal.get(Calendar.MINUTE), sb).append(':');
return appendZeroLeftPadded(cal.get(Calendar.SECOND), sb).append(" GMT");
}
private static StringBuilder appendZeroLeftPadded(int value, StringBuilder sb) {
if (value < 10) {
sb.append('0');
}
return sb.append(value);
}
}

View File

@ -15,13 +15,12 @@
*/
package io.netty.handler.codec.http.cookie;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.handler.codec.http.HttpHeaderDateFormatter;
import io.netty.handler.codec.http.HttpHeaderDateFormat;
import java.text.ParsePosition;
import java.util.Date;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie decoder to be used client side.
*
@ -168,16 +167,13 @@ public final class ClientCookieDecoder extends CookieDecoder {
// max age has precedence over expires
if (maxAge != Long.MIN_VALUE) {
return maxAge;
} else {
String expires = computeValue(expiresStart, expiresEnd);
if (expires != null) {
Date expiresDate = HttpHeaderDateFormat.get().parse(expires, new ParsePosition(0));
} else if (isValueDefined(expiresStart, expiresEnd)) {
Date expiresDate = HttpHeaderDateFormatter.parse(header, expiresStart, expiresEnd);
if (expiresDate != null) {
long maxAgeMillis = expiresDate.getTime() - System.currentTimeMillis();
return maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0 ? 1 : 0);
}
}
}
return Long.MIN_VALUE;
}
@ -254,8 +250,12 @@ public final class ClientCookieDecoder extends CookieDecoder {
}
}
private static boolean isValueDefined(int valueStart, int valueEnd) {
return valueStart != -1 && valueStart != valueEnd;
}
private String computeValue(int valueStart, int valueEnd) {
return valueStart == -1 || valueStart == valueEnd ? null : header.substring(valueStart, valueEnd);
return isValueDefined(valueStart, valueEnd) ? header.substring(valueStart, valueEnd) : null;
}
}
}

View File

@ -20,7 +20,9 @@ import static io.netty.handler.codec.http.cookie.CookieUtil.addQuoted;
import static io.netty.handler.codec.http.cookie.CookieUtil.stringBuilder;
import static io.netty.handler.codec.http.cookie.CookieUtil.stripTrailingSeparator;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.handler.codec.http.HttpHeaderDateFormat;
import io.netty.handler.codec.http.HttpHeaderDateFormatter;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.HttpRequest;
import java.util.ArrayList;
@ -102,7 +104,11 @@ public final class ServerCookieEncoder extends CookieEncoder {
if (cookie.maxAge() != Long.MIN_VALUE) {
add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge());
Date expires = new Date(cookie.maxAge() * 1000 + System.currentTimeMillis());
add(buf, CookieHeaderNames.EXPIRES, HttpHeaderDateFormat.get().format(expires));
buf.append(CookieHeaderNames.EXPIRES);
buf.append((char) HttpConstants.EQUALS);
HttpHeaderDateFormatter.append(expires, buf);
buf.append((char) HttpConstants.SEMICOLON);
buf.append((char) HttpConstants.SP);
}
if (cookie.path() != null) {

View File

@ -0,0 +1,105 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project 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 io.netty.handler.codec.http;
import org.junit.Test;
import java.util.Date;
import static org.junit.Assert.*;
import static io.netty.handler.codec.http.HttpHeaderDateFormatter.*;
public class HttpHeaderDateFormatterTest {
/**
* This date is set at "06 Nov 1994 08:49:37 GMT", from
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html">examples in RFC documentation</a>
*/
private static final long TIMESTAMP = 784111777000L;
private static final Date DATE = new Date(TIMESTAMP);
@Test
public void testParseWithSingleDigitDay() {
assertEquals(DATE, parse("Sun, 6 Nov 1994 08:49:37 GMT"));
}
@Test
public void testParseWithDoubleDigitDay() {
assertEquals(DATE, parse("Sun, 06 Nov 1994 08:49:37 GMT"));
}
@Test
public void testParseWithDashSeparatorSingleDigitDay() {
assertEquals(DATE, parse("Sunday, 06-Nov-94 08:49:37 GMT"));
}
@Test
public void testParseWithSingleDoubleDigitDay() {
assertEquals(DATE, parse("Sunday, 6-Nov-94 08:49:37 GMT"));
}
@Test
public void testParseWithoutGMT() {
assertEquals(DATE, parse("Sun Nov 6 08:49:37 1994"));
}
@Test
public void testParseWithFunkyTimezone() {
assertEquals(DATE, parse("Sun Nov 6 08:49:37 1994 -0000"));
}
@Test
public void testParseWithSingleDigitHourMinutesAndSecond() {
assertEquals(DATE, parse("Sunday, 6-Nov-94 8:49:37 GMT"));
}
@Test
public void testParseWithSingleDigitTime() {
assertEquals(DATE, parse("Sunday, 6 Nov 1994 8:49:37 GMT"));
Date _08_09_37 = new Date(TIMESTAMP - 40 * 60 * 1000);
assertEquals(_08_09_37, parse("Sunday, 6 Nov 1994 8:9:37 GMT"));
assertEquals(_08_09_37, parse("Sunday, 6 Nov 1994 8:09:37 GMT"));
Date _08_09_07 = new Date(TIMESTAMP - (40 * 60 + 30) * 1000);
assertEquals(_08_09_07, parse("Sunday, 6 Nov 1994 8:9:7 GMT"));
assertEquals(_08_09_07, parse("Sunday, 6 Nov 1994 8:9:07 GMT"));
}
@Test
public void testParseInvalidInput() {
// missing field
assertNull(parse("Sun, Nov 1994 08:49:37 GMT"));
assertNull(parse("Sun, 6 1994 08:49:37 GMT"));
assertNull(parse("Sun, 6 Nov 08:49:37 GMT"));
assertNull(parse("Sun, 6 Nov 1994 :49:37 GMT"));
assertNull(parse("Sun, 6 Nov 1994 49:37 GMT"));
assertNull(parse("Sun, 6 Nov 1994 08::37 GMT"));
assertNull(parse("Sun, 6 Nov 1994 08:37 GMT"));
assertNull(parse("Sun, 6 Nov 1994 08:49: GMT"));
assertNull(parse("Sun, 6 Nov 1994 08:49 GMT"));
//invalid value
assertNull(parse("Sun, 6 FOO 1994 08:49:37 GMT"));
assertNull(parse("Sun, 36 Nov 1994 08:49:37 GMT"));
assertNull(parse("Sun, 6 Nov 1994 28:49:37 GMT"));
assertNull(parse("Sun, 6 Nov 1994 08:69:37 GMT"));
assertNull(parse("Sun, 6 Nov 1994 08:49:67 GMT"));
}
@Test
public void testFormat() {
assertEquals("Sun, 6 Nov 1994 08:49:37 GMT", format(DATE));
}
}

View File

@ -15,17 +15,9 @@
*/
package io.netty.handler.codec.http.cookie;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import io.netty.handler.codec.http.HttpHeaderDateFormatter;
import org.junit.Test;
import io.netty.handler.codec.http.HttpHeaderDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
@ -33,12 +25,14 @@ import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;
import static org.junit.Assert.*;
public class ClientCookieDecoderTest {
@Test
public void testDecodingSingleCookieV0() {
String cookieString = "myCookie=myValue;expires=XXX;path=/apathsomewhere;domain=.adomainsomewhere;secure;";
cookieString = cookieString.replace("XXX", HttpHeaderDateFormat.get()
.format(new Date(System.currentTimeMillis() + 50000)));
cookieString = cookieString.replace("XXX",
HttpHeaderDateFormatter.format(new Date(System.currentTimeMillis() + 50000)));
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
assertNotNull(cookie);

View File

@ -0,0 +1,36 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project 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 io.netty.microbench.http;
import io.netty.handler.codec.http.cookie.ClientCookieDecoder;
import io.netty.handler.codec.http.cookie.Cookie;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import java.util.concurrent.TimeUnit;
@OutputTimeUnit(TimeUnit.SECONDS)
public class ClientCookieDecoderBenchmark {
private static final String COOKIE_STRING =
"__Host-user_session_same_site=fgfMsM59vJTpZg88nxqKkIhgOt0ADF8LX8wjMMbtcb4IJMufWCnCcXORhbo9QMuyiybdtx; " +
"path=/; expires=Mon, 28 Nov 2016 13:56:01 GMT; secure; HttpOnly";
@Benchmark
public Cookie decodeCookieWithRfc1123ExpiresField() {
return ClientCookieDecoder.STRICT.decode(COOKIE_STRING);
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project 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 io.netty.microbench.http;
import io.netty.handler.codec.http.HttpHeaderDateFormat;
import io.netty.handler.codec.http.HttpHeaderDateFormatter;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@OutputTimeUnit(TimeUnit.SECONDS)
public class HttpHeaderDateFormatterBenchmark {
private static final String DATE_STRING = "Sun, 27 Nov 2016 19:18:46 GMT";
private static final Date DATE = new Date(784111777000L);
@Benchmark
public Date parseHttpHeaderDateFormatter() {
return HttpHeaderDateFormatter.parse(DATE_STRING);
}
@Benchmark
public Date parseHttpHeaderDateFormat() throws Exception {
return HttpHeaderDateFormat.get().parse(DATE_STRING);
}
@Benchmark
public String formatHttpHeaderDateFormatter() {
return HttpHeaderDateFormatter.format(DATE);
}
@Benchmark
public String formatHttpHeaderDateFormat() throws Exception {
return HttpHeaderDateFormat.get().format(DATE);
}
}