View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.spdy.generator;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.nio.ByteBuffer;
23  import java.nio.charset.Charset;
24  import java.util.Locale;
25  
26  import org.eclipse.jetty.spdy.CompressionDictionary;
27  import org.eclipse.jetty.spdy.CompressionFactory;
28  import org.eclipse.jetty.spdy.api.Headers;
29  import org.eclipse.jetty.spdy.api.SPDY;
30  
31  public class HeadersBlockGenerator
32  {
33      private final CompressionFactory.Compressor compressor;
34      private boolean needsDictionary = true;
35  
36      public HeadersBlockGenerator(CompressionFactory.Compressor compressor)
37      {
38          this.compressor = compressor;
39      }
40  
41      public ByteBuffer generate(short version, Headers headers)
42      {
43          // TODO: ByteArrayOutputStream is quite inefficient, but grows on demand; optimize using ByteBuffer ?
44          Charset iso1 = Charset.forName("ISO-8859-1");
45          ByteArrayOutputStream buffer = new ByteArrayOutputStream(headers.size() * 64);
46          writeCount(version, buffer, headers.size());
47          for (Headers.Header header : headers)
48          {
49              String name = header.name().toLowerCase(Locale.ENGLISH);
50              byte[] nameBytes = name.getBytes(iso1);
51              writeNameLength(version, buffer, nameBytes.length);
52              buffer.write(nameBytes, 0, nameBytes.length);
53  
54              // Most common path first
55              String value = header.value();
56              byte[] valueBytes = value.getBytes(iso1);
57              if (header.hasMultipleValues())
58              {
59                  String[] values = header.values();
60                  for (int i = 1; i < values.length; ++i)
61                  {
62                      byte[] moreValueBytes = values[i].getBytes(iso1);
63                      byte[] newValueBytes = new byte[valueBytes.length + 1 + moreValueBytes.length];
64                      System.arraycopy(valueBytes, 0, newValueBytes, 0, valueBytes.length);
65                      newValueBytes[valueBytes.length] = 0;
66                      System.arraycopy(moreValueBytes, 0, newValueBytes, valueBytes.length + 1, moreValueBytes.length);
67                      valueBytes = newValueBytes;
68                  }
69              }
70  
71              writeValueLength(version, buffer, valueBytes.length);
72              buffer.write(valueBytes, 0, valueBytes.length);
73          }
74  
75          return compress(version, buffer.toByteArray());
76      }
77  
78      private ByteBuffer compress(short version, byte[] bytes)
79      {
80          ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length);
81  
82          // The headers compression context is per-session, so we need to synchronize
83          synchronized (compressor)
84          {
85              if (needsDictionary)
86              {
87                  compressor.setDictionary(CompressionDictionary.get(version));
88                  needsDictionary = false;
89              }
90  
91              compressor.setInput(bytes);
92  
93              // Compressed bytes may be bigger than input bytes, so we need to loop and accumulate them
94              // Beware that the minimum amount of bytes generated by the compressor is few bytes, so we
95              // need to use an output buffer that is big enough to exit the compress loop
96              buffer.reset();
97              int compressed;
98              byte[] output = new byte[Math.max(256, bytes.length)];
99              while (true)
100             {
101                 // SPDY uses the SYNC_FLUSH mode
102                 compressed = compressor.compress(output);
103                 buffer.write(output, 0, compressed);
104                 if (compressed < output.length)
105                     break;
106             }
107         }
108 
109         return ByteBuffer.wrap(buffer.toByteArray());
110     }
111 
112     private void writeCount(short version, ByteArrayOutputStream buffer, int value)
113     {
114         switch (version)
115         {
116             case SPDY.V2:
117             {
118                 buffer.write((value & 0xFF_00) >>> 8);
119                 buffer.write(value & 0x00_FF);
120                 break;
121             }
122             case SPDY.V3:
123             {
124                 buffer.write((value & 0xFF_00_00_00) >>> 24);
125                 buffer.write((value & 0x00_FF_00_00) >>> 16);
126                 buffer.write((value & 0x00_00_FF_00) >>> 8);
127                 buffer.write(value & 0x00_00_00_FF);
128                 break;
129             }
130             default:
131             {
132                 // Here the version is trusted to be correct; if it's not
133                 // then it's a bug rather than an application error
134                 throw new IllegalStateException();
135             }
136         }
137     }
138 
139     private void writeNameLength(short version, ByteArrayOutputStream buffer, int length)
140     {
141         writeCount(version, buffer, length);
142     }
143 
144     private void writeValueLength(short version, ByteArrayOutputStream buffer, int length)
145     {
146         writeCount(version, buffer, length);
147     }
148 }