test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/HeaderTableTest.java
branchhttp-client-branch
changeset 56092 fd85b2bf2b0d
parent 56089 42208b2f224e
child 56369 24a8fafec3ff
equal deleted inserted replaced
56091:aedd6133e7a0 56092:fd85b2bf2b0d
       
     1 /*
       
     2  * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 package jdk.internal.net.http.hpack;
       
    24 
       
    25 import org.testng.annotations.Test;
       
    26 import jdk.internal.net.http.hpack.HeaderTable.HeaderField;
       
    27 
       
    28 import java.nio.charset.StandardCharsets;
       
    29 import java.util.Collections;
       
    30 import java.util.HashMap;
       
    31 import java.util.Locale;
       
    32 import java.util.Map;
       
    33 import java.util.Random;
       
    34 import java.util.regex.Matcher;
       
    35 import java.util.regex.Pattern;
       
    36 
       
    37 import static java.lang.String.format;
       
    38 import static org.testng.Assert.assertEquals;
       
    39 import static jdk.internal.net.http.hpack.TestHelper.assertExceptionMessageContains;
       
    40 import static jdk.internal.net.http.hpack.TestHelper.assertThrows;
       
    41 import static jdk.internal.net.http.hpack.TestHelper.assertVoidThrows;
       
    42 import static jdk.internal.net.http.hpack.TestHelper.newRandom;
       
    43 
       
    44 public class HeaderTableTest {
       
    45 
       
    46     //
       
    47     // https://tools.ietf.org/html/rfc7541#appendix-A
       
    48     //
       
    49     // @formatter:off
       
    50     private static final String SPEC =
       
    51        "          | 1     | :authority                  |               |\n" +
       
    52        "          | 2     | :method                     | GET           |\n" +
       
    53        "          | 3     | :method                     | POST          |\n" +
       
    54        "          | 4     | :path                       | /             |\n" +
       
    55        "          | 5     | :path                       | /index.html   |\n" +
       
    56        "          | 6     | :scheme                     | http          |\n" +
       
    57        "          | 7     | :scheme                     | https         |\n" +
       
    58        "          | 8     | :status                     | 200           |\n" +
       
    59        "          | 9     | :status                     | 204           |\n" +
       
    60        "          | 10    | :status                     | 206           |\n" +
       
    61        "          | 11    | :status                     | 304           |\n" +
       
    62        "          | 12    | :status                     | 400           |\n" +
       
    63        "          | 13    | :status                     | 404           |\n" +
       
    64        "          | 14    | :status                     | 500           |\n" +
       
    65        "          | 15    | accept-charset              |               |\n" +
       
    66        "          | 16    | accept-encoding             | gzip, deflate |\n" +
       
    67        "          | 17    | accept-language             |               |\n" +
       
    68        "          | 18    | accept-ranges               |               |\n" +
       
    69        "          | 19    | accept                      |               |\n" +
       
    70        "          | 20    | access-control-allow-origin |               |\n" +
       
    71        "          | 21    | age                         |               |\n" +
       
    72        "          | 22    | allow                       |               |\n" +
       
    73        "          | 23    | authorization               |               |\n" +
       
    74        "          | 24    | cache-control               |               |\n" +
       
    75        "          | 25    | content-disposition         |               |\n" +
       
    76        "          | 26    | content-encoding            |               |\n" +
       
    77        "          | 27    | content-language            |               |\n" +
       
    78        "          | 28    | content-length              |               |\n" +
       
    79        "          | 29    | content-location            |               |\n" +
       
    80        "          | 30    | content-range               |               |\n" +
       
    81        "          | 31    | content-type                |               |\n" +
       
    82        "          | 32    | cookie                      |               |\n" +
       
    83        "          | 33    | date                        |               |\n" +
       
    84        "          | 34    | etag                        |               |\n" +
       
    85        "          | 35    | expect                      |               |\n" +
       
    86        "          | 36    | expires                     |               |\n" +
       
    87        "          | 37    | from                        |               |\n" +
       
    88        "          | 38    | host                        |               |\n" +
       
    89        "          | 39    | if-match                    |               |\n" +
       
    90        "          | 40    | if-modified-since           |               |\n" +
       
    91        "          | 41    | if-none-match               |               |\n" +
       
    92        "          | 42    | if-range                    |               |\n" +
       
    93        "          | 43    | if-unmodified-since         |               |\n" +
       
    94        "          | 44    | last-modified               |               |\n" +
       
    95        "          | 45    | link                        |               |\n" +
       
    96        "          | 46    | location                    |               |\n" +
       
    97        "          | 47    | max-forwards                |               |\n" +
       
    98        "          | 48    | proxy-authenticate          |               |\n" +
       
    99        "          | 49    | proxy-authorization         |               |\n" +
       
   100        "          | 50    | range                       |               |\n" +
       
   101        "          | 51    | referer                     |               |\n" +
       
   102        "          | 52    | refresh                     |               |\n" +
       
   103        "          | 53    | retry-after                 |               |\n" +
       
   104        "          | 54    | server                      |               |\n" +
       
   105        "          | 55    | set-cookie                  |               |\n" +
       
   106        "          | 56    | strict-transport-security   |               |\n" +
       
   107        "          | 57    | transfer-encoding           |               |\n" +
       
   108        "          | 58    | user-agent                  |               |\n" +
       
   109        "          | 59    | vary                        |               |\n" +
       
   110        "          | 60    | via                         |               |\n" +
       
   111        "          | 61    | www-authenticate            |               |\n";
       
   112     // @formatter:on
       
   113 
       
   114     private static final int STATIC_TABLE_LENGTH = createStaticEntries().size();
       
   115     private final Random rnd = newRandom();
       
   116 
       
   117     @Test
       
   118     public void staticData() {
       
   119         HeaderTable table = new HeaderTable(0, HPACK.getLogger());
       
   120         Map<Integer, HeaderField> staticHeaderFields = createStaticEntries();
       
   121 
       
   122         Map<String, Integer> minimalIndexes = new HashMap<>();
       
   123 
       
   124         for (Map.Entry<Integer, HeaderField> e : staticHeaderFields.entrySet()) {
       
   125             Integer idx = e.getKey();
       
   126             String hName = e.getValue().name;
       
   127             Integer midx = minimalIndexes.get(hName);
       
   128             if (midx == null) {
       
   129                 minimalIndexes.put(hName, idx);
       
   130             } else {
       
   131                 minimalIndexes.put(hName, Math.min(idx, midx));
       
   132             }
       
   133         }
       
   134 
       
   135         staticHeaderFields.entrySet().forEach(
       
   136                 e -> {
       
   137                     // lookup
       
   138                     HeaderField actualHeaderField = table.get(e.getKey());
       
   139                     HeaderField expectedHeaderField = e.getValue();
       
   140                     assertEquals(actualHeaderField, expectedHeaderField);
       
   141 
       
   142                     // reverse lookup (name, value)
       
   143                     String hName = expectedHeaderField.name;
       
   144                     String hValue = expectedHeaderField.value;
       
   145                     int expectedIndex = e.getKey();
       
   146                     int actualIndex = table.indexOf(hName, hValue);
       
   147 
       
   148                     assertEquals(actualIndex, expectedIndex);
       
   149 
       
   150                     // reverse lookup (name)
       
   151                     int expectedMinimalIndex = minimalIndexes.get(hName);
       
   152                     int actualMinimalIndex = table.indexOf(hName, "blah-blah");
       
   153 
       
   154                     assertEquals(-actualMinimalIndex, expectedMinimalIndex);
       
   155                 }
       
   156         );
       
   157     }
       
   158 
       
   159     @Test
       
   160     public void constructorSetsMaxSize() {
       
   161         int size = rnd.nextInt(64);
       
   162         HeaderTable t = new HeaderTable(size, HPACK.getLogger());
       
   163         assertEquals(t.size(), 0);
       
   164         assertEquals(t.maxSize(), size);
       
   165     }
       
   166 
       
   167     @Test
       
   168     public void negativeMaximumSize() {
       
   169         int maxSize = -(rnd.nextInt(100) + 1); // [-100, -1]
       
   170         IllegalArgumentException e =
       
   171                 assertVoidThrows(IllegalArgumentException.class,
       
   172                         () -> new HeaderTable(0, HPACK.getLogger()).setMaxSize(maxSize));
       
   173         assertExceptionMessageContains(e, "maxSize");
       
   174     }
       
   175 
       
   176     @Test
       
   177     public void zeroMaximumSize() {
       
   178         HeaderTable table = new HeaderTable(0, HPACK.getLogger());
       
   179         table.setMaxSize(0);
       
   180         assertEquals(table.maxSize(), 0);
       
   181     }
       
   182 
       
   183     @Test
       
   184     public void negativeIndex() {
       
   185         int idx = -(rnd.nextInt(256) + 1); // [-256, -1]
       
   186         IndexOutOfBoundsException e =
       
   187                 assertVoidThrows(IndexOutOfBoundsException.class,
       
   188                         () -> new HeaderTable(0, HPACK.getLogger()).get(idx));
       
   189         assertExceptionMessageContains(e, "index");
       
   190     }
       
   191 
       
   192     @Test
       
   193     public void zeroIndex() {
       
   194         IndexOutOfBoundsException e =
       
   195                 assertThrows(IndexOutOfBoundsException.class,
       
   196                         () -> new HeaderTable(0, HPACK.getLogger()).get(0));
       
   197         assertExceptionMessageContains(e, "index");
       
   198     }
       
   199 
       
   200     @Test
       
   201     public void length() {
       
   202         HeaderTable table = new HeaderTable(0, HPACK.getLogger());
       
   203         assertEquals(table.length(), STATIC_TABLE_LENGTH);
       
   204     }
       
   205 
       
   206     @Test
       
   207     public void indexOutsideStaticRange() {
       
   208         HeaderTable table = new HeaderTable(0, HPACK.getLogger());
       
   209         int idx = table.length() + (rnd.nextInt(256) + 1);
       
   210         IndexOutOfBoundsException e =
       
   211                 assertThrows(IndexOutOfBoundsException.class,
       
   212                         () -> table.get(idx));
       
   213         assertExceptionMessageContains(e, "index");
       
   214     }
       
   215 
       
   216     @Test
       
   217     public void entryPutAfterStaticArea() {
       
   218         HeaderTable table = new HeaderTable(256, HPACK.getLogger());
       
   219         int idx = table.length() + 1;
       
   220         assertThrows(IndexOutOfBoundsException.class, () -> table.get(idx));
       
   221 
       
   222         byte[] bytes = new byte[32];
       
   223         rnd.nextBytes(bytes);
       
   224         String name = new String(bytes, StandardCharsets.ISO_8859_1);
       
   225         String value = "custom-value";
       
   226 
       
   227         table.put(name, value);
       
   228         HeaderField f = table.get(idx);
       
   229         assertEquals(name, f.name);
       
   230         assertEquals(value, f.value);
       
   231     }
       
   232 
       
   233     @Test
       
   234     public void staticTableHasZeroSize() {
       
   235         HeaderTable table = new HeaderTable(0, HPACK.getLogger());
       
   236         assertEquals(0, table.size());
       
   237     }
       
   238 
       
   239     @Test
       
   240     public void lowerIndexPriority() {
       
   241         HeaderTable table = new HeaderTable(256, HPACK.getLogger());
       
   242         int oldLength = table.length();
       
   243         table.put("bender", "rodriguez");
       
   244         table.put("bender", "rodriguez");
       
   245         table.put("bender", "rodriguez");
       
   246 
       
   247         assertEquals(table.length(), oldLength + 3); // more like an assumption
       
   248         int i = table.indexOf("bender", "rodriguez");
       
   249         assertEquals(oldLength + 1, i);
       
   250     }
       
   251 
       
   252     @Test
       
   253     public void lowerIndexPriority2() {
       
   254         HeaderTable table = new HeaderTable(256, HPACK.getLogger());
       
   255         int oldLength = table.length();
       
   256         int idx = rnd.nextInt(oldLength) + 1;
       
   257         HeaderField f = table.get(idx);
       
   258         table.put(f.name, f.value);
       
   259         assertEquals(table.length(), oldLength + 1);
       
   260         int i = table.indexOf(f.name, f.value);
       
   261         assertEquals(idx, i);
       
   262     }
       
   263 
       
   264     // TODO: negative indexes check
       
   265     // TODO: ensure full table clearance when adding huge header field
       
   266     // TODO: ensure eviction deletes minimum needed entries, not more
       
   267 
       
   268     @Test
       
   269     public void fifo() {
       
   270         // Let's add a series of header fields
       
   271         int NUM_HEADERS = 32;
       
   272         HeaderTable t = new HeaderTable((32 + 4) * NUM_HEADERS, HPACK.getLogger());
       
   273         //                                ^   ^
       
   274         //                   entry overhead   symbols per entry (max 2x2 digits)
       
   275         for (int i = 1; i <= NUM_HEADERS; i++) {
       
   276             String s = String.valueOf(i);
       
   277             t.put(s, s);
       
   278         }
       
   279         // They MUST appear in a FIFO order:
       
   280         //   newer entries are at lower indexes
       
   281         //   older entries are at higher indexes
       
   282         for (int j = 1; j <= NUM_HEADERS; j++) {
       
   283             HeaderField f = t.get(STATIC_TABLE_LENGTH + j);
       
   284             int actualName = Integer.parseInt(f.name);
       
   285             int expectedName = NUM_HEADERS - j + 1;
       
   286             assertEquals(expectedName, actualName);
       
   287         }
       
   288         // Entries MUST be evicted in the order they were added:
       
   289         //   the newer the entry the later it is evicted
       
   290         for (int k = 1; k <= NUM_HEADERS; k++) {
       
   291             HeaderField f = t.evictEntry();
       
   292             assertEquals(String.valueOf(k), f.name);
       
   293         }
       
   294     }
       
   295 
       
   296     @Test
       
   297     public void indexOf() {
       
   298         // Let's put a series of header fields
       
   299         int NUM_HEADERS = 32;
       
   300         HeaderTable t = new HeaderTable((32 + 4) * NUM_HEADERS, HPACK.getLogger());
       
   301         //                                ^   ^
       
   302         //                   entry overhead   symbols per entry (max 2x2 digits)
       
   303         for (int i = 1; i <= NUM_HEADERS; i++) {
       
   304             String s = String.valueOf(i);
       
   305             t.put(s, s);
       
   306         }
       
   307         // and verify indexOf (reverse lookup) returns correct indexes for
       
   308         // full lookup
       
   309         for (int j = 1; j <= NUM_HEADERS; j++) {
       
   310             String s = String.valueOf(j);
       
   311             int actualIndex = t.indexOf(s, s);
       
   312             int expectedIndex = STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1;
       
   313             assertEquals(expectedIndex, actualIndex);
       
   314         }
       
   315         // as well as for just a name lookup
       
   316         for (int j = 1; j <= NUM_HEADERS; j++) {
       
   317             String s = String.valueOf(j);
       
   318             int actualIndex = t.indexOf(s, "blah");
       
   319             int expectedIndex = -(STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1);
       
   320             assertEquals(expectedIndex, actualIndex);
       
   321         }
       
   322         // lookup for non-existent name returns 0
       
   323         assertEquals(0, t.indexOf("chupacabra", "1"));
       
   324     }
       
   325 
       
   326     @Test
       
   327     public void testToString() {
       
   328         testToString0();
       
   329     }
       
   330 
       
   331     @Test
       
   332     public void testToStringDifferentLocale() {
       
   333         Locale locale = Locale.getDefault();
       
   334         Locale.setDefault(Locale.FRENCH);
       
   335         try {
       
   336             String s = format("%.1f", 3.1);
       
   337             assertEquals("3,1", s); // assumption of the test, otherwise the test is useless
       
   338             testToString0();
       
   339         } finally {
       
   340             Locale.setDefault(locale);
       
   341         }
       
   342     }
       
   343 
       
   344     private void testToString0() {
       
   345         HeaderTable table = new HeaderTable(0, HPACK.getLogger());
       
   346         {
       
   347             int maxSize = 2048;
       
   348             table.setMaxSize(maxSize);
       
   349             String expected = format(
       
   350                     "dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
       
   351                     0, STATIC_TABLE_LENGTH, 0, maxSize, 0.0);
       
   352             assertEquals(expected, table.toString());
       
   353         }
       
   354 
       
   355         {
       
   356             String name = "custom-name";
       
   357             String value = "custom-value";
       
   358             int size = 512;
       
   359 
       
   360             table.setMaxSize(size);
       
   361             table.put(name, value);
       
   362             String s = table.toString();
       
   363 
       
   364             int used = name.length() + value.length() + 32;
       
   365             double ratio = used * 100.0 / size;
       
   366 
       
   367             String expected = format(
       
   368                     "dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
       
   369                     1, STATIC_TABLE_LENGTH + 1, used, size, ratio);
       
   370             assertEquals(expected, s);
       
   371         }
       
   372 
       
   373         {
       
   374             table.setMaxSize(78);
       
   375             table.put(":method", "");
       
   376             table.put(":status", "");
       
   377             String s = table.toString();
       
   378             String expected =
       
   379                     format("dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
       
   380                            2, STATIC_TABLE_LENGTH + 2, 78, 78, 100.0);
       
   381             assertEquals(expected, s);
       
   382         }
       
   383     }
       
   384 
       
   385     @Test
       
   386     public void stateString() {
       
   387         HeaderTable table = new HeaderTable(256, HPACK.getLogger());
       
   388         table.put("custom-key", "custom-header");
       
   389         // @formatter:off
       
   390         assertEquals("[  1] (s =  55) custom-key: custom-header\n" +
       
   391                      "      Table size:  55", table.getStateString());
       
   392         // @formatter:on
       
   393     }
       
   394 
       
   395     private static Map<Integer, HeaderField> createStaticEntries() {
       
   396         Pattern line = Pattern.compile(
       
   397                 "\\|\\s*(?<index>\\d+?)\\s*\\|\\s*(?<name>.+?)\\s*\\|\\s*(?<value>.*?)\\s*\\|");
       
   398         Matcher m = line.matcher(SPEC);
       
   399         Map<Integer, HeaderField> result = new HashMap<>();
       
   400         while (m.find()) {
       
   401             int index = Integer.parseInt(m.group("index"));
       
   402             String name = m.group("name");
       
   403             String value = m.group("value");
       
   404             HeaderField f = new HeaderField(name, value);
       
   405             result.put(index, f);
       
   406         }
       
   407         return Collections.unmodifiableMap(result); // lol
       
   408     }
       
   409 }