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