From f755e584638e20a4ae62466dd4b7a14954650348 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 21 Nov 2016 21:29:44 +0100 Subject: [PATCH] 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 --- .../handler/codec/http/CookieDecoder.java | 14 +-- .../codec/http/DefaultHttpHeaders.java | 5 +- .../codec/http/HttpHeaderDateFormat.java | 3 +- .../netty/handler/codec/http/HttpHeaders.java | 20 ++- .../http/cookie/ClientCookieDecoder.java | 4 +- .../http/cookie/ServerCookieEncoder.java | 4 +- .../http/HttpHeaderDateFormatterTest.java | 114 ------------------ .../http/cookie/ClientCookieDecoderTest.java | 24 ++-- .../http/cookie/ServerCookieDecoderTest.java | 6 - .../http/cookie/ServerCookieEncoderTest.java | 4 +- .../codec/CharSequenceValueConverter.java | 10 +- .../io/netty/handler/codec/DateFormatter.java | 26 ++-- .../netty/handler/codec/DefaultHeaders.java | 78 ------------ .../handler/codec/DateFormatterTest.java | 114 ++++++++++++++++++ .../codec/DateFormatterBenchmark.java} | 9 +- 15 files changed, 170 insertions(+), 265 deletions(-) delete mode 100644 codec-http/src/test/java/io/netty/handler/codec/http/HttpHeaderDateFormatterTest.java rename codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormatter.java => codec/src/main/java/io/netty/handler/codec/DateFormatter.java (95%) create mode 100644 codec/src/test/java/io/netty/handler/codec/DateFormatterTest.java rename microbench/src/main/java/io/netty/{microbench/http/HttpHeaderDateFormatterBenchmark.java => handler/codec/DateFormatterBenchmark.java} (85%) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/CookieDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/CookieDecoder.java index 137266bbea..040e12d710 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/CookieDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/CookieDecoder.java @@ -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.firstInvalidCookieValueOctet; 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.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; -import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Set; import java.util.TreeSet; @@ -152,14 +154,10 @@ public final class CookieDecoder { } else if (CookieHeaderNames.PATH.equalsIgnoreCase(name)) { path = value; } else if (CookieHeaderNames.EXPIRES.equalsIgnoreCase(name)) { - try { - long maxAgeMillis = - HttpHeaderDateFormat.get().parse(value).getTime() - - System.currentTimeMillis(); - + Date date = DateFormatter.parseHttpDate(value); + if (date != null) { + long maxAgeMillis = date.getTime() - System.currentTimeMillis(); maxAge = maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0? 1 : 0); - } catch (ParseException e) { - // Ignore. } } else if (CookieHeaderNames.MAX_AGE.equalsIgnoreCase(name)) { maxAge = Integer.parseInt(value); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java index 8e9518b3ef..05650d66c5 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java @@ -16,6 +16,7 @@ package io.netty.handler.codec.http; import io.netty.handler.codec.CharSequenceValueConverter; +import io.netty.handler.codec.DateFormatter; import io.netty.handler.codec.DefaultHeaders; import io.netty.handler.codec.DefaultHeaders.NameValidator; import io.netty.handler.codec.DefaultHeadersImpl; @@ -391,10 +392,10 @@ public class DefaultHttpHeaders extends HttpHeaders { return (CharSequence) value; } if (value instanceof Date) { - return HttpHeaderDateFormat.get().format((Date) value); + return DateFormatter.format((Date) value); } if (value instanceof Calendar) { - return HttpHeaderDateFormat.get().format(((Calendar) value).getTime()); + return DateFormatter.format(((Calendar) value).getTime()); } return value.toString(); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormat.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormat.java index bf717cf021..45de26df61 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormat.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormat.java @@ -16,6 +16,7 @@ package io.netty.handler.codec.http; import io.netty.util.concurrent.FastThreadLocal; +import io.netty.handler.codec.DateFormatter; import java.text.ParsePosition; import java.text.SimpleDateFormat; @@ -32,7 +33,7 @@ import java.util.TimeZone; *
  • Sunday, 06-Nov-94 08:49:37 GMT: obsolete specification
  • *
  • Sun Nov 6 08:49:37 1994: obsolete specification
  • * - * @deprecated Use {@link HttpHeaderDateFormatter} instead + * @deprecated Use {@link DateFormatter} instead */ @Deprecated public final class HttpHeaderDateFormat extends SimpleDateFormat { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java index 587a20d943..5d27cb1887 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java @@ -17,6 +17,7 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; +import io.netty.handler.codec.DateFormatter; import io.netty.handler.codec.Headers; import io.netty.util.AsciiString; @@ -839,7 +840,11 @@ public abstract class HttpHeaders implements Iterable> if (value == null) { 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> @Deprecated public static Date getDateHeader(HttpMessage message, CharSequence name, Date defaultValue) { final String value = getHeader(message, name); - if (value == null) { - return defaultValue; - } - - try { - return HttpHeaderDateFormat.get().parse(value); - } catch (ParseException ignored) { - return defaultValue; - } + Date date = DateFormatter.parseHttpDate(value); + return date != null ? date : defaultValue; } /** @@ -897,7 +895,7 @@ public abstract class HttpHeaders implements Iterable> @Deprecated public static void setDateHeader(HttpMessage message, CharSequence name, Date value) { if (value != null) { - message.headers().set(name, HttpHeaderDateFormat.get().format(value)); + message.headers().set(name, DateFormatter.format(value)); } else { message.headers().set(name, null); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieDecoder.java index b76bb25698..cbfe20d40f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieDecoder.java @@ -15,7 +15,7 @@ */ package io.netty.handler.codec.http.cookie; -import io.netty.handler.codec.http.HttpHeaderDateFormatter; +import io.netty.handler.codec.DateFormatter; import java.util.Date; @@ -168,7 +168,7 @@ public final class ClientCookieDecoder extends CookieDecoder { if (maxAge != Long.MIN_VALUE) { return maxAge; } else if (isValueDefined(expiresStart, expiresEnd)) { - Date expiresDate = HttpHeaderDateFormatter.parse(header, expiresStart, expiresEnd); + Date expiresDate = DateFormatter.parseHttpDate(header, expiresStart, expiresEnd); if (expiresDate != null) { long maxAgeMillis = expiresDate.getTime() - System.currentTimeMillis(); return maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0 ? 1 : 0); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java index 2e96d8217b..44cc6c7201 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java @@ -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.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.HttpRequest; @@ -106,7 +106,7 @@ public final class ServerCookieEncoder extends CookieEncoder { Date expires = new Date(cookie.maxAge() * 1000 + System.currentTimeMillis()); buf.append(CookieHeaderNames.EXPIRES); buf.append((char) HttpConstants.EQUALS); - HttpHeaderDateFormatter.append(expires, buf); + DateFormatter.append(expires, buf); buf.append((char) HttpConstants.SEMICOLON); buf.append((char) HttpConstants.SP); } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeaderDateFormatterTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeaderDateFormatterTest.java deleted file mode 100644 index 24f8b4343e..0000000000 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeaderDateFormatterTest.java +++ /dev/null @@ -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 - * examples in RFC documentation - */ - 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)); - } -} diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieDecoderTest.java index 5df87de9d0..60b85899c6 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieDecoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieDecoderTest.java @@ -15,7 +15,7 @@ */ 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 java.util.ArrayList; @@ -30,26 +30,18 @@ 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", - HttpHeaderDateFormatter.format(new Date(System.currentTimeMillis() + 50000))); + String cookieString = "myCookie=myValue;expires=" + + DateFormatter.format(new Date(System.currentTimeMillis() + 50000)) + + ";path=/apathsomewhere;domain=.adomainsomewhere;secure;"; Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString); assertNotNull(cookie); assertEquals("myValue", cookie.value()); assertEquals(".adomainsomewhere", cookie.domain()); - - boolean fail = true; - for (int i = 40; i <= 60; i++) { - if (cookie.maxAge() == i) { - fail = false; - break; - } - } - if (fail) { - fail("expected: 50, actual: " + cookie.maxAge()); - } - + assertNotEquals("maxAge should be defined when parsing cookie " + cookieString, + Long.MIN_VALUE, cookie.maxAge()); + assertTrue("maxAge should be about 50ms when parsing cookie " + cookieString, + cookie.maxAge() >= 40 && cookie.maxAge() <= 60); assertEquals("/apathsomewhere", cookie.path()); assertTrue(cookie.isSecure()); } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieDecoderTest.java index fe9df5ec30..b157bc3c73 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieDecoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieDecoderTest.java @@ -17,9 +17,6 @@ package io.netty.handler.codec.http.cookie; import org.junit.Test; -import io.netty.handler.codec.http.HttpHeaderDateFormat; - -import java.util.Date; import java.util.Iterator; import java.util.Set; @@ -29,9 +26,6 @@ public class ServerCookieDecoderTest { @Test public void testDecodingSingleCookie() { String cookieString = "myCookie=myValue"; - cookieString = cookieString.replace("XXX", - HttpHeaderDateFormat.get().format(new Date(System.currentTimeMillis() + 50000))); - Set cookies = ServerCookieDecoder.STRICT.decode(cookieString); assertEquals(1, cookies.size()); Cookie cookie = cookies.iterator().next(); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java index d171679b6c..723a7a17a9 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java @@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; 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.util.ArrayList; @@ -51,7 +51,7 @@ public class ServerCookieEncoderTest { Matcher matcher = Pattern.compile(result).matcher(encodedCookie); 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; // 2 secs should be fine assertTrue(Math.abs(diff - maxAge) <= 2); diff --git a/codec/src/main/java/io/netty/handler/codec/CharSequenceValueConverter.java b/codec/src/main/java/io/netty/handler/codec/CharSequenceValueConverter.java index dfdc856c6c..fa833d2c31 100644 --- a/codec/src/main/java/io/netty/handler/codec/CharSequenceValueConverter.java +++ b/codec/src/main/java/io/netty/handler/codec/CharSequenceValueConverter.java @@ -14,11 +14,11 @@ */ package io.netty.handler.codec; -import io.netty.handler.codec.DefaultHeaders.HeaderDateFormat; import io.netty.util.AsciiString; import io.netty.util.internal.PlatformDependent; import java.text.ParseException; +import java.util.Date; /** * Converts to/from native types, general {@link Object}, and {@link CharSequence}s. @@ -126,12 +126,12 @@ public class CharSequenceValueConverter implements ValueConverter @Override public long convertToTimeMillis(CharSequence value) { - try { - return HeaderDateFormat.get().parse(value.toString()); - } catch (ParseException e) { - PlatformDependent.throwException(e); + Date date = DateFormatter.parseHttpDate(value); + if (date == null) { + PlatformDependent.throwException(new ParseException("header can't be parsed into a Date: " + value, 0)); return 0; } + return date.getTime(); } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormatter.java b/codec/src/main/java/io/netty/handler/codec/DateFormatter.java similarity index 95% rename from codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormatter.java rename to codec/src/main/java/io/netty/handler/codec/DateFormatter.java index 729c568cba..0c7e7797dd 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormatter.java +++ b/codec/src/main/java/io/netty/handler/codec/DateFormatter.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.netty.handler.codec.http; +package io.netty.handler.codec; import static io.netty.util.internal.ObjectUtil.checkNotNull; @@ -43,7 +43,7 @@ import java.util.TimeZone; * @see RFC6265 for the parsing side * @see RFC1123 for the encoding side. */ -public final class HttpHeaderDateFormatter { +public final class DateFormatter { private static final BitSet DELIMITERS = new BitSet(); static { @@ -68,11 +68,11 @@ public final class HttpHeaderDateFormatter { 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 INSTANCES = - new FastThreadLocal() { + private static final FastThreadLocal INSTANCES = + new FastThreadLocal() { @Override - protected HttpHeaderDateFormatter initialValue() { - return new HttpHeaderDateFormatter(); + protected DateFormatter initialValue() { + return new DateFormatter(); } }; @@ -81,8 +81,8 @@ public final class HttpHeaderDateFormatter { * @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()); + public static Date parseHttpDate(CharSequence txt) { + return parseHttpDate(txt, 0, txt.length()); } /** @@ -92,7 +92,7 @@ public final class HttpHeaderDateFormatter { * @param end the end index inside txt * @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; if (length == 0) { return null; @@ -124,8 +124,8 @@ public final class HttpHeaderDateFormatter { return formatter().append0(checkNotNull(date, "date"), checkNotNull(sb, "sb")); } - private static HttpHeaderDateFormatter formatter() { - HttpHeaderDateFormatter formatter = INSTANCES.get(); + private static DateFormatter formatter() { + DateFormatter formatter = INSTANCES.get(); formatter.reset(); return formatter; } @@ -156,7 +156,7 @@ public final class HttpHeaderDateFormatter { private boolean yearFound; private int year; - private HttpHeaderDateFormatter() { + private DateFormatter() { reset(); } @@ -171,7 +171,7 @@ public final class HttpHeaderDateFormatter { month = -1; yearFound = false; year = -1; - cal.set(Calendar.MILLISECOND, 0); + cal.clear(); sb.setLength(0); } diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java index 5625aec980..a5701f295f 100644 --- a/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java @@ -15,24 +15,17 @@ package io.netty.handler.codec; 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.Collections; -import java.util.Date; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; -import java.util.TimeZone; import static io.netty.util.HashingStrategy.JAVA_HASHER; import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo; @@ -960,77 +953,6 @@ public class DefaultHeaders> implements Headers return (T) this; } - /** - * This {@link DateFormat} decodes 3 formats of {@link Date}. - * - *
      - *
    • Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with valid generation
    • - *
    • Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification
    • - *
    • Sun Nov 6 08:49:37 1994: obsolete specification
    • - *
    - */ - public static final class HeaderDateFormat { - private static final FastThreadLocal dateFormatThreadLocal = - new FastThreadLocal() { - @Override - protected HeaderDateFormat initialValue() { - return new HeaderDateFormat(); - } - }; - - static HeaderDateFormat get() { - return dateFormatThreadLocal.get(); - } - - /** - * Standard date format: - * - *
    -         * Sun, 06 Nov 1994 08:49:37 GMT -> E, d MMM yyyy HH:mm:ss z
    -         * 
    - */ - private final DateFormat dateFormat1 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH); - - /** - * First obsolete format: - * - *
    -         * Sunday, 06-Nov-94 08:49:37 GMT -> E, d-MMM-y HH:mm:ss z
    -         * 
    - */ - private final DateFormat dateFormat2 = new SimpleDateFormat("E, dd-MMM-yy HH:mm:ss z", Locale.ENGLISH); - - /** - * Second obsolete format - * - *
    -         * Sun Nov 6 08:49:37 1994 -> EEE, MMM d HH:mm:ss yyyy
    -         * 
    - */ - 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> { private HeaderEntry current = head; diff --git a/codec/src/test/java/io/netty/handler/codec/DateFormatterTest.java b/codec/src/test/java/io/netty/handler/codec/DateFormatterTest.java new file mode 100644 index 0000000000..99d1d3864a --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/DateFormatterTest.java @@ -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 + * examples in RFC documentation + */ + 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)); + } +} diff --git a/microbench/src/main/java/io/netty/microbench/http/HttpHeaderDateFormatterBenchmark.java b/microbench/src/main/java/io/netty/handler/codec/DateFormatterBenchmark.java similarity index 85% rename from microbench/src/main/java/io/netty/microbench/http/HttpHeaderDateFormatterBenchmark.java rename to microbench/src/main/java/io/netty/handler/codec/DateFormatterBenchmark.java index 2c05869a5e..5d2a11adc5 100644 --- a/microbench/src/main/java/io/netty/microbench/http/HttpHeaderDateFormatterBenchmark.java +++ b/microbench/src/main/java/io/netty/handler/codec/DateFormatterBenchmark.java @@ -13,10 +13,9 @@ * License for the specific language governing permissions and limitations * 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.HttpHeaderDateFormatter; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.OutputTimeUnit; @@ -24,14 +23,14 @@ import java.util.Date; import java.util.concurrent.TimeUnit; @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 Date DATE = new Date(784111777000L); @Benchmark public Date parseHttpHeaderDateFormatter() { - return HttpHeaderDateFormatter.parse(DATE_STRING); + return DateFormatter.parseHttpDate(DATE_STRING); } @Benchmark @@ -41,7 +40,7 @@ public class HttpHeaderDateFormatterBenchmark { @Benchmark public String formatHttpHeaderDateFormatter() { - return HttpHeaderDateFormatter.format(DATE); + return DateFormatter.format(DATE); } @Benchmark