Clean up following #6016
Motivation: * DefaultHeaders from netty-codec has some duplicated logic for header date parsing * Several classes keep on using deprecated HttpHeaderDateFormat Modifications: * Move HttpHeaderDateFormatter to netty-codec and rename it into HeaderDateFormatter * Make DefaultHeaders use HeaderDateFormatter * Replace HttpHeaderDateFormat usage with HeaderDateFormatter Result: Faster and more consistent code
This commit is contained in:
parent
930633350d
commit
f755e58463
@ -18,13 +18,15 @@ package io.netty.handler.codec.http;
|
|||||||
import static io.netty.handler.codec.http.CookieUtil.firstInvalidCookieNameOctet;
|
import static io.netty.handler.codec.http.CookieUtil.firstInvalidCookieNameOctet;
|
||||||
import static io.netty.handler.codec.http.CookieUtil.firstInvalidCookieValueOctet;
|
import static io.netty.handler.codec.http.CookieUtil.firstInvalidCookieValueOctet;
|
||||||
import static io.netty.handler.codec.http.CookieUtil.unwrapValue;
|
import static io.netty.handler.codec.http.CookieUtil.unwrapValue;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.DateFormatter;
|
||||||
import io.netty.handler.codec.http.cookie.CookieHeaderNames;
|
import io.netty.handler.codec.http.cookie.CookieHeaderNames;
|
||||||
import io.netty.util.internal.logging.InternalLogger;
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
@ -152,14 +154,10 @@ public final class CookieDecoder {
|
|||||||
} else if (CookieHeaderNames.PATH.equalsIgnoreCase(name)) {
|
} else if (CookieHeaderNames.PATH.equalsIgnoreCase(name)) {
|
||||||
path = value;
|
path = value;
|
||||||
} else if (CookieHeaderNames.EXPIRES.equalsIgnoreCase(name)) {
|
} else if (CookieHeaderNames.EXPIRES.equalsIgnoreCase(name)) {
|
||||||
try {
|
Date date = DateFormatter.parseHttpDate(value);
|
||||||
long maxAgeMillis =
|
if (date != null) {
|
||||||
HttpHeaderDateFormat.get().parse(value).getTime() -
|
long maxAgeMillis = date.getTime() - System.currentTimeMillis();
|
||||||
System.currentTimeMillis();
|
|
||||||
|
|
||||||
maxAge = maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0? 1 : 0);
|
maxAge = maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0? 1 : 0);
|
||||||
} catch (ParseException e) {
|
|
||||||
// Ignore.
|
|
||||||
}
|
}
|
||||||
} else if (CookieHeaderNames.MAX_AGE.equalsIgnoreCase(name)) {
|
} else if (CookieHeaderNames.MAX_AGE.equalsIgnoreCase(name)) {
|
||||||
maxAge = Integer.parseInt(value);
|
maxAge = Integer.parseInt(value);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.handler.codec.http;
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
import io.netty.handler.codec.CharSequenceValueConverter;
|
import io.netty.handler.codec.CharSequenceValueConverter;
|
||||||
|
import io.netty.handler.codec.DateFormatter;
|
||||||
import io.netty.handler.codec.DefaultHeaders;
|
import io.netty.handler.codec.DefaultHeaders;
|
||||||
import io.netty.handler.codec.DefaultHeaders.NameValidator;
|
import io.netty.handler.codec.DefaultHeaders.NameValidator;
|
||||||
import io.netty.handler.codec.DefaultHeadersImpl;
|
import io.netty.handler.codec.DefaultHeadersImpl;
|
||||||
@ -391,10 +392,10 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
|||||||
return (CharSequence) value;
|
return (CharSequence) value;
|
||||||
}
|
}
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
return HttpHeaderDateFormat.get().format((Date) value);
|
return DateFormatter.format((Date) value);
|
||||||
}
|
}
|
||||||
if (value instanceof Calendar) {
|
if (value instanceof Calendar) {
|
||||||
return HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
|
return DateFormatter.format(((Calendar) value).getTime());
|
||||||
}
|
}
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.handler.codec.http;
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
import io.netty.util.concurrent.FastThreadLocal;
|
import io.netty.util.concurrent.FastThreadLocal;
|
||||||
|
import io.netty.handler.codec.DateFormatter;
|
||||||
|
|
||||||
import java.text.ParsePosition;
|
import java.text.ParsePosition;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@ -32,7 +33,7 @@ import java.util.TimeZone;
|
|||||||
* <li>Sunday, 06-Nov-94 08:49:37 GMT: obsolete specification</li>
|
* <li>Sunday, 06-Nov-94 08:49:37 GMT: obsolete specification</li>
|
||||||
* <li>Sun Nov 6 08:49:37 1994: obsolete specification</li>
|
* <li>Sun Nov 6 08:49:37 1994: obsolete specification</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* @deprecated Use {@link HttpHeaderDateFormatter} instead
|
* @deprecated Use {@link DateFormatter} instead
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public final class HttpHeaderDateFormat extends SimpleDateFormat {
|
public final class HttpHeaderDateFormat extends SimpleDateFormat {
|
||||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
|
import io.netty.handler.codec.DateFormatter;
|
||||||
import io.netty.handler.codec.Headers;
|
import io.netty.handler.codec.Headers;
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
|
|
||||||
@ -839,7 +840,11 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw new ParseException("header not found: " + name, 0);
|
throw new ParseException("header not found: " + name, 0);
|
||||||
}
|
}
|
||||||
return HttpHeaderDateFormat.get().parse(value);
|
Date date = DateFormatter.parseHttpDate(value);
|
||||||
|
if (date == null) {
|
||||||
|
throw new ParseException("header can't be parsed into a Date: " + value, 0);
|
||||||
|
}
|
||||||
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -865,15 +870,8 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
public static Date getDateHeader(HttpMessage message, CharSequence name, Date defaultValue) {
|
public static Date getDateHeader(HttpMessage message, CharSequence name, Date defaultValue) {
|
||||||
final String value = getHeader(message, name);
|
final String value = getHeader(message, name);
|
||||||
if (value == null) {
|
Date date = DateFormatter.parseHttpDate(value);
|
||||||
return defaultValue;
|
return date != null ? date : defaultValue;
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return HttpHeaderDateFormat.get().parse(value);
|
|
||||||
} catch (ParseException ignored) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -897,7 +895,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
public static void setDateHeader(HttpMessage message, CharSequence name, Date value) {
|
public static void setDateHeader(HttpMessage message, CharSequence name, Date value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
message.headers().set(name, HttpHeaderDateFormat.get().format(value));
|
message.headers().set(name, DateFormatter.format(value));
|
||||||
} else {
|
} else {
|
||||||
message.headers().set(name, null);
|
message.headers().set(name, null);
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http.cookie;
|
package io.netty.handler.codec.http.cookie;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpHeaderDateFormatter;
|
import io.netty.handler.codec.DateFormatter;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
|||||||
if (maxAge != Long.MIN_VALUE) {
|
if (maxAge != Long.MIN_VALUE) {
|
||||||
return maxAge;
|
return maxAge;
|
||||||
} else if (isValueDefined(expiresStart, expiresEnd)) {
|
} else if (isValueDefined(expiresStart, expiresEnd)) {
|
||||||
Date expiresDate = HttpHeaderDateFormatter.parse(header, expiresStart, expiresEnd);
|
Date expiresDate = DateFormatter.parseHttpDate(header, expiresStart, expiresEnd);
|
||||||
if (expiresDate != null) {
|
if (expiresDate != null) {
|
||||||
long maxAgeMillis = expiresDate.getTime() - System.currentTimeMillis();
|
long maxAgeMillis = expiresDate.getTime() - System.currentTimeMillis();
|
||||||
return maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0 ? 1 : 0);
|
return maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0 ? 1 : 0);
|
||||||
|
@ -21,7 +21,7 @@ import static io.netty.handler.codec.http.cookie.CookieUtil.stringBuilder;
|
|||||||
import static io.netty.handler.codec.http.cookie.CookieUtil.stripTrailingSeparator;
|
import static io.netty.handler.codec.http.cookie.CookieUtil.stripTrailingSeparator;
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpHeaderDateFormatter;
|
import io.netty.handler.codec.DateFormatter;
|
||||||
import io.netty.handler.codec.http.HttpConstants;
|
import io.netty.handler.codec.http.HttpConstants;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ public final class ServerCookieEncoder extends CookieEncoder {
|
|||||||
Date expires = new Date(cookie.maxAge() * 1000 + System.currentTimeMillis());
|
Date expires = new Date(cookie.maxAge() * 1000 + System.currentTimeMillis());
|
||||||
buf.append(CookieHeaderNames.EXPIRES);
|
buf.append(CookieHeaderNames.EXPIRES);
|
||||||
buf.append((char) HttpConstants.EQUALS);
|
buf.append((char) HttpConstants.EQUALS);
|
||||||
HttpHeaderDateFormatter.append(expires, buf);
|
DateFormatter.append(expires, buf);
|
||||||
buf.append((char) HttpConstants.SEMICOLON);
|
buf.append((char) HttpConstants.SEMICOLON);
|
||||||
buf.append((char) HttpConstants.SP);
|
buf.append((char) HttpConstants.SP);
|
||||||
}
|
}
|
||||||
|
@ -1,114 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 testParseMidnight() {
|
|
||||||
assertEquals(new Date(784080000000L), parse("Sunday, 6 Nov 1994 00:00:00 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"));
|
|
||||||
//wrong number of digits in timestamp
|
|
||||||
assertNull(parse("Sunday, 6 Nov 1994 0:0:000 GMT"));
|
|
||||||
assertNull(parse("Sunday, 6 Nov 1994 0:000:0 GMT"));
|
|
||||||
assertNull(parse("Sunday, 6 Nov 1994 000:0:0 GMT"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFormat() {
|
|
||||||
assertEquals("Sun, 6 Nov 1994 08:49:37 GMT", format(DATE));
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http.cookie;
|
package io.netty.handler.codec.http.cookie;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpHeaderDateFormatter;
|
import io.netty.handler.codec.DateFormatter;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -30,26 +30,18 @@ import static org.junit.Assert.*;
|
|||||||
public class ClientCookieDecoderTest {
|
public class ClientCookieDecoderTest {
|
||||||
@Test
|
@Test
|
||||||
public void testDecodingSingleCookieV0() {
|
public void testDecodingSingleCookieV0() {
|
||||||
String cookieString = "myCookie=myValue;expires=XXX;path=/apathsomewhere;domain=.adomainsomewhere;secure;";
|
String cookieString = "myCookie=myValue;expires="
|
||||||
cookieString = cookieString.replace("XXX",
|
+ DateFormatter.format(new Date(System.currentTimeMillis() + 50000))
|
||||||
HttpHeaderDateFormatter.format(new Date(System.currentTimeMillis() + 50000)));
|
+ ";path=/apathsomewhere;domain=.adomainsomewhere;secure;";
|
||||||
|
|
||||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||||
assertNotNull(cookie);
|
assertNotNull(cookie);
|
||||||
assertEquals("myValue", cookie.value());
|
assertEquals("myValue", cookie.value());
|
||||||
assertEquals(".adomainsomewhere", cookie.domain());
|
assertEquals(".adomainsomewhere", cookie.domain());
|
||||||
|
assertNotEquals("maxAge should be defined when parsing cookie " + cookieString,
|
||||||
boolean fail = true;
|
Long.MIN_VALUE, cookie.maxAge());
|
||||||
for (int i = 40; i <= 60; i++) {
|
assertTrue("maxAge should be about 50ms when parsing cookie " + cookieString,
|
||||||
if (cookie.maxAge() == i) {
|
cookie.maxAge() >= 40 && cookie.maxAge() <= 60);
|
||||||
fail = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fail) {
|
|
||||||
fail("expected: 50, actual: " + cookie.maxAge());
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals("/apathsomewhere", cookie.path());
|
assertEquals("/apathsomewhere", cookie.path());
|
||||||
assertTrue(cookie.isSecure());
|
assertTrue(cookie.isSecure());
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,6 @@ package io.netty.handler.codec.http.cookie;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpHeaderDateFormat;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -29,9 +26,6 @@ public class ServerCookieDecoderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testDecodingSingleCookie() {
|
public void testDecodingSingleCookie() {
|
||||||
String cookieString = "myCookie=myValue";
|
String cookieString = "myCookie=myValue";
|
||||||
cookieString = cookieString.replace("XXX",
|
|
||||||
HttpHeaderDateFormat.get().format(new Date(System.currentTimeMillis() + 50000)));
|
|
||||||
|
|
||||||
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieString);
|
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieString);
|
||||||
assertEquals(1, cookies.size());
|
assertEquals(1, cookies.size());
|
||||||
Cookie cookie = cookies.iterator().next();
|
Cookie cookie = cookies.iterator().next();
|
||||||
|
@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals;
|
|||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import io.netty.handler.codec.http.HttpHeaderDateFormat;
|
import io.netty.handler.codec.DateFormatter;
|
||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -51,7 +51,7 @@ public class ServerCookieEncoderTest {
|
|||||||
|
|
||||||
Matcher matcher = Pattern.compile(result).matcher(encodedCookie);
|
Matcher matcher = Pattern.compile(result).matcher(encodedCookie);
|
||||||
assertTrue(matcher.find());
|
assertTrue(matcher.find());
|
||||||
Date expiresDate = HttpHeaderDateFormat.get().parse(matcher.group(1));
|
Date expiresDate = DateFormatter.parseHttpDate(matcher.group(1));
|
||||||
long diff = (expiresDate.getTime() - System.currentTimeMillis()) / 1000;
|
long diff = (expiresDate.getTime() - System.currentTimeMillis()) / 1000;
|
||||||
// 2 secs should be fine
|
// 2 secs should be fine
|
||||||
assertTrue(Math.abs(diff - maxAge) <= 2);
|
assertTrue(Math.abs(diff - maxAge) <= 2);
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec;
|
package io.netty.handler.codec;
|
||||||
|
|
||||||
import io.netty.handler.codec.DefaultHeaders.HeaderDateFormat;
|
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts to/from native types, general {@link Object}, and {@link CharSequence}s.
|
* Converts to/from native types, general {@link Object}, and {@link CharSequence}s.
|
||||||
@ -126,12 +126,12 @@ public class CharSequenceValueConverter implements ValueConverter<CharSequence>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long convertToTimeMillis(CharSequence value) {
|
public long convertToTimeMillis(CharSequence value) {
|
||||||
try {
|
Date date = DateFormatter.parseHttpDate(value);
|
||||||
return HeaderDateFormat.get().parse(value.toString());
|
if (date == null) {
|
||||||
} catch (ParseException e) {
|
PlatformDependent.throwException(new ParseException("header can't be parsed into a Date: " + value, 0));
|
||||||
PlatformDependent.throwException(e);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
return date.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http;
|
package io.netty.handler.codec;
|
||||||
|
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ import java.util.TimeZone;
|
|||||||
* @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/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.
|
* @see <a href="https://tools.ietf.org/html/rfc1123#page-55">RFC1123</a> for the encoding side.
|
||||||
*/
|
*/
|
||||||
public final class HttpHeaderDateFormatter {
|
public final class DateFormatter {
|
||||||
|
|
||||||
private static final BitSet DELIMITERS = new BitSet();
|
private static final BitSet DELIMITERS = new BitSet();
|
||||||
static {
|
static {
|
||||||
@ -68,11 +68,11 @@ public final class HttpHeaderDateFormatter {
|
|||||||
private static final String[] CALENDAR_MONTH_TO_SHORT_NAME =
|
private static final String[] CALENDAR_MONTH_TO_SHORT_NAME =
|
||||||
new String[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
new String[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||||
|
|
||||||
private static final FastThreadLocal<HttpHeaderDateFormatter> INSTANCES =
|
private static final FastThreadLocal<DateFormatter> INSTANCES =
|
||||||
new FastThreadLocal<HttpHeaderDateFormatter>() {
|
new FastThreadLocal<DateFormatter>() {
|
||||||
@Override
|
@Override
|
||||||
protected HttpHeaderDateFormatter initialValue() {
|
protected DateFormatter initialValue() {
|
||||||
return new HttpHeaderDateFormatter();
|
return new DateFormatter();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,8 +81,8 @@ public final class HttpHeaderDateFormatter {
|
|||||||
* @param txt text to parse
|
* @param txt text to parse
|
||||||
* @return a {@link Date}, or null if text couldn't be parsed
|
* @return a {@link Date}, or null if text couldn't be parsed
|
||||||
*/
|
*/
|
||||||
public static Date parse(CharSequence txt) {
|
public static Date parseHttpDate(CharSequence txt) {
|
||||||
return parse(txt, 0, txt.length());
|
return parseHttpDate(txt, 0, txt.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,7 +92,7 @@ public final class HttpHeaderDateFormatter {
|
|||||||
* @param end the end 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
|
* @return a {@link Date}, or null if text couldn't be parsed
|
||||||
*/
|
*/
|
||||||
public static Date parse(CharSequence txt, int start, int end) {
|
public static Date parseHttpDate(CharSequence txt, int start, int end) {
|
||||||
int length = end - start;
|
int length = end - start;
|
||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
return null;
|
return null;
|
||||||
@ -124,8 +124,8 @@ public final class HttpHeaderDateFormatter {
|
|||||||
return formatter().append0(checkNotNull(date, "date"), checkNotNull(sb, "sb"));
|
return formatter().append0(checkNotNull(date, "date"), checkNotNull(sb, "sb"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HttpHeaderDateFormatter formatter() {
|
private static DateFormatter formatter() {
|
||||||
HttpHeaderDateFormatter formatter = INSTANCES.get();
|
DateFormatter formatter = INSTANCES.get();
|
||||||
formatter.reset();
|
formatter.reset();
|
||||||
return formatter;
|
return formatter;
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ public final class HttpHeaderDateFormatter {
|
|||||||
private boolean yearFound;
|
private boolean yearFound;
|
||||||
private int year;
|
private int year;
|
||||||
|
|
||||||
private HttpHeaderDateFormatter() {
|
private DateFormatter() {
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ public final class HttpHeaderDateFormatter {
|
|||||||
month = -1;
|
month = -1;
|
||||||
yearFound = false;
|
yearFound = false;
|
||||||
year = -1;
|
year = -1;
|
||||||
cal.set(Calendar.MILLISECOND, 0);
|
cal.clear();
|
||||||
sb.setLength(0);
|
sb.setLength(0);
|
||||||
}
|
}
|
||||||
|
|
@ -15,24 +15,17 @@
|
|||||||
package io.netty.handler.codec;
|
package io.netty.handler.codec;
|
||||||
|
|
||||||
import io.netty.util.HashingStrategy;
|
import io.netty.util.HashingStrategy;
|
||||||
import io.netty.util.concurrent.FastThreadLocal;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TimeZone;
|
|
||||||
|
|
||||||
import static io.netty.util.HashingStrategy.JAVA_HASHER;
|
import static io.netty.util.HashingStrategy.JAVA_HASHER;
|
||||||
import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
|
import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
|
||||||
@ -960,77 +953,6 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
|
|||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This {@link DateFormat} decodes 3 formats of {@link Date}.
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with valid generation</li>
|
|
||||||
* <li>Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification</li>
|
|
||||||
* <li>Sun Nov 6 08:49:37 1994: obsolete specification</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public static final class HeaderDateFormat {
|
|
||||||
private static final FastThreadLocal<HeaderDateFormat> dateFormatThreadLocal =
|
|
||||||
new FastThreadLocal<HeaderDateFormat>() {
|
|
||||||
@Override
|
|
||||||
protected HeaderDateFormat initialValue() {
|
|
||||||
return new HeaderDateFormat();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static HeaderDateFormat get() {
|
|
||||||
return dateFormatThreadLocal.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard date format:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* Sun, 06 Nov 1994 08:49:37 GMT -> E, d MMM yyyy HH:mm:ss z
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
private final DateFormat dateFormat1 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First obsolete format:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* Sunday, 06-Nov-94 08:49:37 GMT -> E, d-MMM-y HH:mm:ss z
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
private final DateFormat dateFormat2 = new SimpleDateFormat("E, dd-MMM-yy HH:mm:ss z", Locale.ENGLISH);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Second obsolete format
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* Sun Nov 6 08:49:37 1994 -> EEE, MMM d HH:mm:ss yyyy
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
private final DateFormat dateFormat3 = new SimpleDateFormat("E MMM d HH:mm:ss yyyy", Locale.ENGLISH);
|
|
||||||
|
|
||||||
private HeaderDateFormat() {
|
|
||||||
TimeZone tz = TimeZone.getTimeZone("GMT");
|
|
||||||
dateFormat1.setTimeZone(tz);
|
|
||||||
dateFormat2.setTimeZone(tz);
|
|
||||||
dateFormat3.setTimeZone(tz);
|
|
||||||
}
|
|
||||||
|
|
||||||
long parse(String text) throws ParseException {
|
|
||||||
Date date = dateFormat1.parse(text);
|
|
||||||
if (date == null) {
|
|
||||||
date = dateFormat2.parse(text);
|
|
||||||
}
|
|
||||||
if (date == null) {
|
|
||||||
date = dateFormat3.parse(text);
|
|
||||||
}
|
|
||||||
if (date == null) {
|
|
||||||
throw new ParseException(text, 0);
|
|
||||||
}
|
|
||||||
return date.getTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class HeaderIterator implements Iterator<Map.Entry<K, V>> {
|
private final class HeaderIterator implements Iterator<Map.Entry<K, V>> {
|
||||||
private HeaderEntry<K, V> current = head;
|
private HeaderEntry<K, V> current = head;
|
||||||
|
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static io.netty.handler.codec.DateFormatter.*;
|
||||||
|
|
||||||
|
public class DateFormatterTest {
|
||||||
|
/**
|
||||||
|
* 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, parseHttpDate("Sun, 6 Nov 1994 08:49:37 GMT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseWithDoubleDigitDay() {
|
||||||
|
assertEquals(DATE, parseHttpDate("Sun, 06 Nov 1994 08:49:37 GMT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseWithDashSeparatorSingleDigitDay() {
|
||||||
|
assertEquals(DATE, parseHttpDate("Sunday, 06-Nov-94 08:49:37 GMT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseWithSingleDoubleDigitDay() {
|
||||||
|
assertEquals(DATE, parseHttpDate("Sunday, 6-Nov-94 08:49:37 GMT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseWithoutGMT() {
|
||||||
|
assertEquals(DATE, parseHttpDate("Sun Nov 6 08:49:37 1994"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseWithFunkyTimezone() {
|
||||||
|
assertEquals(DATE, parseHttpDate("Sun Nov 6 08:49:37 1994 -0000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseWithSingleDigitHourMinutesAndSecond() {
|
||||||
|
assertEquals(DATE, parseHttpDate("Sunday, 6-Nov-94 8:49:37 GMT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseWithSingleDigitTime() {
|
||||||
|
assertEquals(DATE, parseHttpDate("Sunday, 6 Nov 1994 8:49:37 GMT"));
|
||||||
|
|
||||||
|
Date _08_09_37 = new Date(TIMESTAMP - 40 * 60 * 1000);
|
||||||
|
assertEquals(_08_09_37, parseHttpDate("Sunday, 6 Nov 1994 8:9:37 GMT"));
|
||||||
|
assertEquals(_08_09_37, parseHttpDate("Sunday, 6 Nov 1994 8:09:37 GMT"));
|
||||||
|
|
||||||
|
Date _08_09_07 = new Date(TIMESTAMP - (40 * 60 + 30) * 1000);
|
||||||
|
assertEquals(_08_09_07, parseHttpDate("Sunday, 6 Nov 1994 8:9:7 GMT"));
|
||||||
|
assertEquals(_08_09_07, parseHttpDate("Sunday, 6 Nov 1994 8:9:07 GMT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseMidnight() {
|
||||||
|
assertEquals(new Date(784080000000L), parseHttpDate("Sunday, 6 Nov 1994 00:00:00 GMT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseInvalidInput() {
|
||||||
|
// missing field
|
||||||
|
assertNull(parseHttpDate("Sun, Nov 1994 08:49:37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 1994 08:49:37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 Nov 08:49:37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 Nov 1994 :49:37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 Nov 1994 49:37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 Nov 1994 08::37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 Nov 1994 08:37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 Nov 1994 08:49: GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 Nov 1994 08:49 GMT"));
|
||||||
|
//invalid value
|
||||||
|
assertNull(parseHttpDate("Sun, 6 FOO 1994 08:49:37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 36 Nov 1994 08:49:37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 Nov 1994 28:49:37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 Nov 1994 08:69:37 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sun, 6 Nov 1994 08:49:67 GMT"));
|
||||||
|
//wrong number of digits in timestamp
|
||||||
|
assertNull(parseHttpDate("Sunday, 6 Nov 1994 0:0:000 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sunday, 6 Nov 1994 0:000:0 GMT"));
|
||||||
|
assertNull(parseHttpDate("Sunday, 6 Nov 1994 000:0:0 GMT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFormat() {
|
||||||
|
assertEquals("Sun, 6 Nov 1994 08:49:37 GMT", format(DATE));
|
||||||
|
}
|
||||||
|
}
|
@ -13,10 +13,9 @@
|
|||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package io.netty.microbench.http;
|
package io.netty.handler.codec;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpHeaderDateFormat;
|
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.Benchmark;
|
||||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
|
||||||
@ -24,14 +23,14 @@ import java.util.Date;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
@OutputTimeUnit(TimeUnit.SECONDS)
|
||||||
public class HttpHeaderDateFormatterBenchmark {
|
public class DateFormatterBenchmark {
|
||||||
|
|
||||||
private static final String DATE_STRING = "Sun, 27 Nov 2016 19:18:46 GMT";
|
private static final String DATE_STRING = "Sun, 27 Nov 2016 19:18:46 GMT";
|
||||||
private static final Date DATE = new Date(784111777000L);
|
private static final Date DATE = new Date(784111777000L);
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
public Date parseHttpHeaderDateFormatter() {
|
public Date parseHttpHeaderDateFormatter() {
|
||||||
return HttpHeaderDateFormatter.parse(DATE_STRING);
|
return DateFormatter.parseHttpDate(DATE_STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@ -41,7 +40,7 @@ public class HttpHeaderDateFormatterBenchmark {
|
|||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
public String formatHttpHeaderDateFormatter() {
|
public String formatHttpHeaderDateFormatter() {
|
||||||
return HttpHeaderDateFormatter.format(DATE);
|
return DateFormatter.format(DATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
Loading…
Reference in New Issue
Block a user