View Javadoc

    1   /*
    2    * Copyright 2012 The Netty Project
    3    *
    4    * The Netty Project licenses this file to you under the Apache License,
    5    * version 2.0 (the "License"); you may not use this file except in compliance
    6    * with the License. You may obtain a copy of the License at:
    7    *
    8    *   http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    13   * License for the specific language governing permissions and limitations
    14   * under the License.
    15   */
    16  package org.jboss.netty.handler.codec.spdy;
    17
    18  import java.util.LinkedList;
    19  import java.util.List;
    20  import java.util.Map;
    21  import java.util.Set;
    22  import java.util.TreeSet;
    23
    24  import org.jboss.netty.handler.codec.http.HttpMethod;
    25  import org.jboss.netty.handler.codec.http.HttpResponseStatus;
    26  import org.jboss.netty.handler.codec.http.HttpVersion;
    27
    28  /**
    29   * Provides the constants for the standard SPDY HTTP header names and commonly
    30   * used utility methods that access a {@link SpdyHeaderBlock}.
    31   * @apiviz.stereotype static
    32   */
    33  public class SpdyHeaders {
    34
    35      /**
    36       * SPDY HTTP header names
    37       * @apiviz.stereotype static
    38       */
    39      public static final class HttpNames {
    40          /**
    41           * {@code ":host"}
    42           */
    43          public static final String HOST = ":host";
    44          /**
    45           * {@code ":method"}
    46           */
    47          public static final String METHOD = ":method";
    48          /**
    49           * {@code ":path"}
    50           */
    51          public static final String PATH = ":path";
    52          /**
    53           * {@code ":scheme"}
    54           */
    55          public static final String SCHEME = ":scheme";
    56          /**
    57           * {@code ":status"}
    58           */
    59          public static final String STATUS = ":status";
    60          /**
    61           * {@code ":version"}
    62           */
    63          public static final String VERSION = ":version";
    64
    65          private HttpNames() {
    66              super();
    67          }
    68      }
    69
    70      /**
    71       * SPDY/2 HTTP header names
    72       * @apiviz.stereotype static
    73       */
    74      public static final class Spdy2HttpNames {
    75          /**
    76           * {@code "method"}
    77           */
    78          public static final String METHOD = "method";
    79          /**
    80           * {@code "scheme"}
    81           */
    82          public static final String SCHEME = "scheme";
    83          /**
    84           * {@code "status"}
    85           */
    86          public static final String STATUS = "status";
    87          /**
    88           * {@code "url"}
    89           */
    90          public static final String URL = "url";
    91          /**
    92           * {@code "version"}
    93           */
    94          public static final String VERSION = "version";
    95
    96          private Spdy2HttpNames() {
    97              super();
    98          }
    99      }
    100
    101
    102     /**
    103      * Returns the header value with the specified header name.  If there are
    104      * more than one header value for the specified header name, the first
    105      * value is returned.
    106      *
    107      * @return the header value or {@code null} if there is no such header
    108      */
    109     public static String getHeader(SpdyHeaderBlock block, String name) {
    110         return block.getHeader(name);
    111     }
    112
    113     /**
    114      * Returns the header value with the specified header name.  If there are
    115      * more than one header value for the specified header name, the first
    116      * value is returned.
    117      *
    118      * @return the header value or the {@code defaultValue} if there is no such
    119      *         header
    120      */
    121     public static String getHeader(SpdyHeaderBlock block, String name, String defaultValue) {
    122         String value = block.getHeader(name);
    123         if (value == null) {
    124             return defaultValue;
    125         }
    126         return value;
    127     }
    128
    129     /**
    130      * Sets a new header with the specified name and value.  If there is an
    131      * existing header with the same name, the existing header is removed.
    132      */
    133     public static void setHeader(SpdyHeaderBlock block, String name, Object value) {
    134         block.setHeader(name, value);
    135     }
    136
    137     /**
    138      * Sets a new header with the specified name and values.  If there is an
    139      * existing header with the same name, the existing header is removed.
    140      */
    141     public static void setHeader(SpdyHeaderBlock block, String name, Iterable<?> values) {
    142         block.setHeader(name, values);
    143     }
    144
    145     /**
    146      * Adds a new header with the specified name and value.
    147      */
    148     public static void addHeader(SpdyHeaderBlock block, String name, Object value) {
    149         block.addHeader(name, value);
    150     }
    151
    152     /**
    153      * Removes the SPDY host header.
    154      */
    155     public static void removeHost(SpdyHeaderBlock block) {
    156         block.removeHeader(HttpNames.HOST);
    157     }
    158
    159     /**
    160      * Returns the SPDY host header.
    161      */
    162     public static String getHost(SpdyHeaderBlock block) {
    163         return block.getHeader(HttpNames.HOST);
    164     }
    165
    166     /**
    167      * Set the SPDY host header.
    168      */
    169     public static void setHost(SpdyHeaderBlock block, String host) {
    170         block.setHeader(HttpNames.HOST, host);
    171     }
    172
    173     /**
    174      * Removes the HTTP method header.
    175      */
    176     @Deprecated
    177     public static void removeMethod(SpdyHeaderBlock block) {
    178         removeMethod(2, block);
    179     }
    180
    181     /**
    182      * Removes the HTTP method header.
    183      */
    184     public static void removeMethod(int spdyVersion, SpdyHeaderBlock block) {
    185         if (spdyVersion < 3) {
    186             block.removeHeader(Spdy2HttpNames.METHOD);
    187         } else {
    188             block.removeHeader(HttpNames.METHOD);
    189         }
    190     }
    191
    192     /**
    193      * Returns the {@link HttpMethod} represented by the HTTP method header.
    194      */
    195     @Deprecated
    196     public static HttpMethod getMethod(SpdyHeaderBlock block) {
    197         return getMethod(2, block);
    198     }
    199
    200     /**
    201      * Returns the {@link HttpMethod} represented by the HTTP method header.
    202      */
    203     public static HttpMethod getMethod(int spdyVersion, SpdyHeaderBlock block) {
    204         try {
    205             if (spdyVersion < 3) {
    206                 return HttpMethod.valueOf(block.getHeader(Spdy2HttpNames.METHOD));
    207             } else {
    208                 return HttpMethod.valueOf(block.getHeader(HttpNames.METHOD));
    209             }
    210         } catch (Exception e) {
    211             return null;
    212         }
    213     }
    214
    215     /**
    216      * Sets the HTTP method header.
    217      */
    218     @Deprecated
    219     public static void setMethod(SpdyHeaderBlock block, HttpMethod method) {
    220         setMethod(2, block, method);
    221     }
    222
    223     /**
    224      * Sets the HTTP method header.
    225      */
    226     public static void setMethod(int spdyVersion, SpdyHeaderBlock block, HttpMethod method) {
    227         if (spdyVersion < 3) {
    228             block.setHeader(Spdy2HttpNames.METHOD, method.getName());
    229         } else {
    230             block.setHeader(HttpNames.METHOD, method.getName());
    231         }
    232     }
    233
    234     /**
    235      * Removes the URL scheme header.
    236      */
    237     @Deprecated
    238     public static void removeScheme(SpdyHeaderBlock block) {
    239         removeMethod(2, block);
    240     }
    241
    242     /**
    243      * Removes the URL scheme header.
    244      */
    245     public static void removeScheme(int spdyVersion, SpdyHeaderBlock block) {
    246         if (spdyVersion < 2) {
    247             block.removeHeader(Spdy2HttpNames.SCHEME);
    248         } else {
    249             block.removeHeader(HttpNames.SCHEME);
    250         }
    251     }
    252
    253     /**
    254      * Returns the value of the URL scheme header.
    255      */
    256     @Deprecated
    257     public static String getScheme(SpdyHeaderBlock block) {
    258         return getScheme(2, block);
    259     }
    260
    261     /**
    262      * Returns the value of the URL scheme header.
    263      */
    264     public static String getScheme(int spdyVersion, SpdyHeaderBlock block) {
    265         if (spdyVersion < 3) {
    266             return block.getHeader(Spdy2HttpNames.SCHEME);
    267         } else {
    268             return block.getHeader(HttpNames.SCHEME);
    269         }
    270     }
    271
    272     /**
    273      * Sets the URL scheme header.
    274      */
    275     @Deprecated
    276     public static void setScheme(SpdyHeaderBlock block, String scheme) {
    277         setScheme(2, block, scheme);
    278     }
    279
    280     /**
    281      * Sets the URL scheme header.
    282      */
    283     public static void setScheme(int spdyVersion, SpdyHeaderBlock block, String scheme) {
    284         if (spdyVersion < 3) {
    285             block.setHeader(Spdy2HttpNames.SCHEME, scheme);
    286         } else {
    287             block.setHeader(HttpNames.SCHEME, scheme);
    288         }
    289     }
    290
    291     /**
    292      * Removes the HTTP response status header.
    293      */
    294     @Deprecated
    295     public static void removeStatus(SpdyHeaderBlock block) {
    296         removeMethod(2, block);
    297     }
    298
    299     /**
    300      * Removes the HTTP response status header.
    301      */
    302     public static void removeStatus(int spdyVersion, SpdyHeaderBlock block) {
    303         if (spdyVersion < 3) {
    304             block.removeHeader(Spdy2HttpNames.STATUS);
    305         } else {
    306             block.removeHeader(HttpNames.STATUS);
    307         }
    308     }
    309
    310     /**
    311      * Returns the {@link HttpResponseStatus} represented by the HTTP response status header.
    312      */
    313     @Deprecated
    314     public static HttpResponseStatus getStatus(SpdyHeaderBlock block) {
    315         return getStatus(2, block);
    316     }
    317
    318     /**
    319      * Returns the {@link HttpResponseStatus} represented by the HTTP response status header.
    320      */
    321     public static HttpResponseStatus getStatus(int spdyVersion, SpdyHeaderBlock block) {
    322         try {
    323             String status;
    324             if (spdyVersion < 3) {
    325                 status = block.getHeader(Spdy2HttpNames.STATUS);
    326             } else {
    327                 status = block.getHeader(HttpNames.STATUS);
    328             }
    329             int space = status.indexOf(' ');
    330             if (space == -1) {
    331                 return HttpResponseStatus.valueOf(Integer.parseInt(status));
    332             } else {
    333                 int code = Integer.parseInt(status.substring(0, space));
    334                 String reasonPhrase = status.substring(space + 1);
    335                 HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code);
    336                 if (responseStatus.getReasonPhrase().equals(reasonPhrase)) {
    337                     return responseStatus;
    338                 } else {
    339                     return new HttpResponseStatus(code, reasonPhrase);
    340                 }
    341             }
    342         } catch (Exception e) {
    343             return null;
    344         }
    345     }
    346
    347     /**
    348      * Sets the HTTP response status header.
    349      */
    350     @Deprecated
    351     public static void setStatus(SpdyHeaderBlock block, HttpResponseStatus status) {
    352         setStatus(2, block, status);
    353     }
    354
    355     /**
    356      * Sets the HTTP response status header.
    357      */
    358     public static void setStatus(int spdyVersion, SpdyHeaderBlock block, HttpResponseStatus status) {
    359         if (spdyVersion < 3) {
    360             block.setHeader(Spdy2HttpNames.STATUS, status.toString());
    361         } else {
    362             block.setHeader(HttpNames.STATUS, status.toString());
    363         }
    364     }
    365
    366     /**
    367      * Removes the URL path header.
    368      */
    369     @Deprecated
    370     public static void removeUrl(SpdyHeaderBlock block) {
    371         removeUrl(2, block);
    372     }
    373
    374     /**
    375      * Removes the URL path header.
    376      */
    377     public static void removeUrl(int spdyVersion, SpdyHeaderBlock block) {
    378         if (spdyVersion < 3) {
    379             block.removeHeader(Spdy2HttpNames.URL);
    380         } else {
    381             block.removeHeader(HttpNames.PATH);
    382         }
    383     }
    384
    385     /**
    386      * Returns the value of the URL path header.
    387      */
    388     @Deprecated
    389     public static String getUrl(SpdyHeaderBlock block) {
    390         return getUrl(2, block);
    391     }
    392
    393     /**
    394      * Returns the value of the URL path header.
    395      */
    396     public static String getUrl(int spdyVersion, SpdyHeaderBlock block) {
    397         if (spdyVersion < 3) {
    398             return block.getHeader(Spdy2HttpNames.URL);
    399         } else {
    400             return block.getHeader(HttpNames.PATH);
    401         }
    402     }
    403
    404     /**
    405      * Sets the URL path header.
    406      */
    407     @Deprecated
    408     public static void setUrl(SpdyHeaderBlock block, String path) {
    409         setUrl(2, block, path);
    410     }
    411
    412     /**
    413      * Sets the URL path header.
    414      */
    415     public static void setUrl(int spdyVersion, SpdyHeaderBlock block, String path) {
    416         if (spdyVersion < 3) {
    417             block.setHeader(Spdy2HttpNames.URL, path);
    418         } else {
    419             block.setHeader(HttpNames.PATH, path);
    420         }
    421     }
    422
    423     /**
    424      * Removes the HTTP version header.
    425      */
    426     @Deprecated
    427     public static void removeVersion(SpdyHeaderBlock block) {
    428         removeVersion(2, block);
    429     }
    430
    431     /**
    432      * Removes the HTTP version header.
    433      */
    434     public static void removeVersion(int spdyVersion, SpdyHeaderBlock block) {
    435         if (spdyVersion < 3) {
    436             block.removeHeader(Spdy2HttpNames.VERSION);
    437         } else {
    438             block.removeHeader(HttpNames.VERSION);
    439         }
    440     }
    441
    442     /**
    443      * Returns the {@link HttpVersion} represented by the HTTP version header.
    444      */
    445     @Deprecated
    446     public static HttpVersion getVersion(SpdyHeaderBlock block) {
    447         return getVersion(2, block);
    448     }
    449
    450     /**
    451      * Returns the {@link HttpVersion} represented by the HTTP version header.
    452      */
    453     public static HttpVersion getVersion(int spdyVersion, SpdyHeaderBlock block) {
    454         try {
    455             if (spdyVersion < 3) {
    456                 return HttpVersion.valueOf(block.getHeader(Spdy2HttpNames.VERSION));
    457             } else {
    458                 return HttpVersion.valueOf(block.getHeader(HttpNames.VERSION));
    459             }
    460         } catch (Exception e) {
    461             return null;
    462         }
    463     }
    464
    465     /**
    466      * Sets the HTTP version header.
    467      */
    468     @Deprecated
    469     public static void setVersion(SpdyHeaderBlock block, HttpVersion httpVersion) {
    470         setVersion(2, block, httpVersion);
    471     }
    472
    473     /**
    474      * Sets the HTTP version header.
    475      */
    476     public static void setVersion(int spdyVersion, SpdyHeaderBlock block, HttpVersion httpVersion) {
    477         if (spdyVersion < 3) {
    478             block.setHeader(Spdy2HttpNames.VERSION, httpVersion.getText());
    479         } else {
    480             block.setHeader(HttpNames.VERSION, httpVersion.getText());
    481         }
    482     }
    483
    484
    485     private static final int BUCKET_SIZE = 17;
    486
    487     private static int hash(String name) {
    488         int h = 0;
    489         for (int i = name.length() - 1; i >= 0; i --) {
    490             char c = name.charAt(i);
    491             if (c >= 'A' && c <= 'Z') {
    492                 c += 32;
    493             }
    494             h = 31 * h + c;
    495         }
    496
    497         if (h > 0) {
    498             return h;
    499         } else if (h == Integer.MIN_VALUE) {
    500             return Integer.MAX_VALUE;
    501         } else {
    502             return -h;
    503         }
    504     }
    505
    506     private static boolean eq(String name1, String name2) {
    507         int nameLen = name1.length();
    508         if (nameLen != name2.length()) {
    509             return false;
    510         }
    511
    512         for (int i = nameLen - 1; i >= 0; i --) {
    513             char c1 = name1.charAt(i);
    514             char c2 = name2.charAt(i);
    515             if (c1 != c2) {
    516                 if (c1 >= 'A' && c1 <= 'Z') {
    517                     c1 += 32;
    518                 }
    519                 if (c2 >= 'A' && c2 <= 'Z') {
    520                     c2 += 32;
    521                 }
    522                 if (c1 != c2) {
    523                     return false;
    524                 }
    525             }
    526         }
    527         return true;
    528     }
    529
    530     private static int index(int hash) {
    531         return hash % BUCKET_SIZE;
    532     }
    533
    534     private final Entry[] entries = new Entry[BUCKET_SIZE];
    535     private final Entry head = new Entry(-1, null, null);
    536
    537     SpdyHeaders() {
    538         head.before = head.after = head;
    539     }
    540
    541     void addHeader(final String name, final Object value) {
    542         String lowerCaseName = name.toLowerCase();
    543         SpdyCodecUtil.validateHeaderName(lowerCaseName);
    544         String strVal = toString(value);
    545         SpdyCodecUtil.validateHeaderValue(strVal);
    546         int h = hash(lowerCaseName);
    547         int i = index(h);
    548         addHeader0(h, i, lowerCaseName, strVal);
    549     }
    550
    551     private void addHeader0(int h, int i, final String name, final String value) {
    552         // Update the hash table.
    553         Entry e = entries[i];
    554         Entry newEntry;
    555         entries[i] = newEntry = new Entry(h, name, value);
    556         newEntry.next = e;
    557
    558         // Update the linked list.
    559         newEntry.addBefore(head);
    560     }
    561
    562     void removeHeader(final String name) {
    563         if (name == null) {
    564             throw new NullPointerException("name");
    565         }
    566         String lowerCaseName = name.toLowerCase();
    567         int h = hash(lowerCaseName);
    568         int i = index(h);
    569         removeHeader0(h, i, lowerCaseName);
    570     }
    571
    572     private void removeHeader0(int h, int i, String name) {
    573         Entry e = entries[i];
    574         if (e == null) {
    575             return;
    576         }
    577
    578         for (;;) {
    579             if (e.hash == h && eq(name, e.key)) {
    580                 e.remove();
    581                 Entry next = e.next;
    582                 if (next != null) {
    583                     entries[i] = next;
    584                     e = next;
    585                 } else {
    586                     entries[i] = null;
    587                     return;
    588                 }
    589             } else {
    590                 break;
    591             }
    592         }
    593
    594         for (;;) {
    595             Entry next = e.next;
    596             if (next == null) {
    597                 break;
    598             }
    599             if (next.hash == h && eq(name, next.key)) {
    600                 e.next = next.next;
    601                 next.remove();
    602             } else {
    603                 e = next;
    604             }
    605         }
    606     }
    607
    608     void setHeader(final String name, final Object value) {
    609         String lowerCaseName = name.toLowerCase();
    610         SpdyCodecUtil.validateHeaderName(lowerCaseName);
    611         String strVal = toString(value);
    612         SpdyCodecUtil.validateHeaderValue(strVal);
    613         int h = hash(lowerCaseName);
    614         int i = index(h);
    615         removeHeader0(h, i, lowerCaseName);
    616         addHeader0(h, i, lowerCaseName, strVal);
    617     }
    618
    619     void setHeader(final String name, final Iterable<?> values) {
    620         if (values == null) {
    621             throw new NullPointerException("values");
    622         }
    623
    624         String lowerCaseName = name.toLowerCase();
    625         SpdyCodecUtil.validateHeaderName(lowerCaseName);
    626
    627         int h = hash(lowerCaseName);
    628         int i = index(h);
    629
    630         removeHeader0(h, i, lowerCaseName);
    631         for (Object v: values) {
    632             if (v == null) {
    633                 break;
    634             }
    635             String strVal = toString(v);
    636             SpdyCodecUtil.validateHeaderValue(strVal);
    637             addHeader0(h, i, lowerCaseName, strVal);
    638         }
    639     }
    640
    641     void clearHeaders() {
    642         for (int i = 0; i < entries.length; i ++) {
    643             entries[i] = null;
    644         }
    645         head.before = head.after = head;
    646     }
    647
    648     String getHeader(final String name) {
    649         if (name == null) {
    650             throw new NullPointerException("name");
    651         }
    652
    653         int h = hash(name);
    654         int i = index(h);
    655         Entry e = entries[i];
    656         while (e != null) {
    657             if (e.hash == h && eq(name, e.key)) {
    658                 return e.value;
    659             }
    660
    661             e = e.next;
    662         }
    663         return null;
    664     }
    665
    666     List<String> getHeaders(final String name) {
    667         if (name == null) {
    668             throw new NullPointerException("name");
    669         }
    670
    671         LinkedList<String> values = new LinkedList<String>();
    672
    673         int h = hash(name);
    674         int i = index(h);
    675         Entry e = entries[i];
    676         while (e != null) {
    677             if (e.hash == h && eq(name, e.key)) {
    678                 values.addFirst(e.value);
    679             }
    680             e = e.next;
    681         }
    682         return values;
    683     }
    684
    685     List<Map.Entry<String, String>> getHeaders() {
    686         List<Map.Entry<String, String>> all =
    687             new LinkedList<Map.Entry<String, String>>();
    688
    689         Entry e = head.after;
    690         while (e != head) {
    691             all.add(e);
    692             e = e.after;
    693         }
    694         return all;
    695     }
    696
    697     boolean containsHeader(String name) {
    698         return getHeader(name) != null;
    699     }
    700
    701     Set<String> getHeaderNames() {
    702         Set<String> names = new TreeSet<String>();
    703
    704         Entry e = head.after;
    705         while (e != head) {
    706             names.add(e.key);
    707             e = e.after;
    708         }
    709         return names;
    710     }
    711
    712     private static String toString(Object value) {
    713         if (value == null) {
    714             return null;
    715         }
    716         return value.toString();
    717     }
    718
    719     private static final class Entry implements Map.Entry<String, String> {
    720         final int hash;
    721         final String key;
    722         String value;
    723         Entry next;
    724         Entry before, after;
    725
    726         Entry(int hash, String key, String value) {
    727             this.hash = hash;
    728             this.key = key;
    729             this.value = value;
    730         }
    731
    732         void remove() {
    733             before.after = after;
    734             after.before = before;
    735         }
    736
    737         void addBefore(Entry e) {
    738             after  = e;
    739             before = e.before;
    740             before.after = this;
    741             after.before = this;
    742         }
    743
    744         public String getKey() {
    745             return key;
    746         }
    747
    748         public String getValue() {
    749             return value;
    750         }
    751
    752         public String setValue(String value) {
    753             if (value == null) {
    754                 throw new NullPointerException("value");
    755             }
    756             SpdyCodecUtil.validateHeaderValue(value);
    757             String oldValue = this.value;
    758             this.value = value;
    759             return oldValue;
    760         }
    761
    762         @Override
    763         public String toString() {
    764             return key + "=" + value;
    765         }
    766     }
    767 }