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