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 }