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.nio.charset.StandardCharsets;
25  import java.util.List;
26  import java.util.Locale;
27  
28  import org.eclipse.jetty.spdy.CompressionDictionary;
29  import org.eclipse.jetty.spdy.CompressionFactory;
30  import org.eclipse.jetty.spdy.api.SPDY;
31  import org.eclipse.jetty.util.Fields;
32  
33  public class HeadersBlockGenerator
34  {
35      private final CompressionFactory.Compressor compressor;
36      private boolean needsDictionary = true;
37  
38      public HeadersBlockGenerator(CompressionFactory.Compressor compressor)
39      {
40          this.compressor = compressor;
41      }
42  
43      public ByteBuffer generate(short version, Fields headers)
44      {
45          // TODO: ByteArrayOutputStream is quite inefficient, but grows on demand; optimize using ByteBuffer ?
46          final Charset iso1 = StandardCharsets.ISO_8859_1;
47          ByteArrayOutputStream buffer = new ByteArrayOutputStream(headers.getSize() * 64);
48          writeCount(version, buffer, headers.getSize());
49          for (Fields.Field header : headers)
50          {
51              String name = header.getName().toLowerCase(Locale.ENGLISH);
52              byte[] nameBytes = name.getBytes(iso1);
53              writeNameLength(version, buffer, nameBytes.length);
54              buffer.write(nameBytes, 0, nameBytes.length);
55  
56              // Most common path first
57              String value = header.getValue();
58              byte[] valueBytes = value.getBytes(iso1);
59              if (header.hasMultipleValues())
60              {
61                  List<String> values = header.getValues();
62                  for (int i = 1; i < values.size(); ++i)
63                  {
64                      byte[] moreValueBytes = values.get(i).getBytes(iso1);
65                      byte[] newValueBytes = new byte[valueBytes.length + 1 + moreValueBytes.length];
66                      System.arraycopy(valueBytes, 0, newValueBytes, 0, valueBytes.length);
67                      newValueBytes[valueBytes.length] = 0;
68                      System.arraycopy(moreValueBytes, 0, newValueBytes, valueBytes.length + 1, moreValueBytes.length);
69                      valueBytes = newValueBytes;
70                  }
71              }
72  
73              writeValueLength(version, buffer, valueBytes.length);
74              buffer.write(valueBytes, 0, valueBytes.length);
75          }
76  
77          return compress(version, buffer.toByteArray());
78      }
79  
80      private ByteBuffer compress(short version, byte[] bytes)
81      {
82          ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length);
83  
84          // The headers compression context is per-session, so we need to synchronize
85          synchronized (compressor)
86          {
87              if (needsDictionary)
88              {
89                  compressor.setDictionary(CompressionDictionary.get(version));
90                  needsDictionary = false;
91              }
92  
93              compressor.setInput(bytes);
94  
95              // Compressed bytes may be bigger than input bytes, so we need to loop and accumulate them
96              // Beware that the minimum amount of bytes generated by the compressor is few bytes, so we
97              // need to use an output buffer that is big enough to exit the compress loop
98              buffer.reset();
99              int compressed;
100             byte[] output = new byte[Math.max(256, bytes.length)];
101             while (true)
102             {
103                 // SPDY uses the SYNC_FLUSH mode
104                 compressed = compressor.compress(output);
105                 buffer.write(output, 0, compressed);
106                 if (compressed < output.length)
107                     break;
108             }
109         }
110 
111         return ByteBuffer.wrap(buffer.toByteArray());
112     }
113 
114     private void writeCount(short version, ByteArrayOutputStream buffer, int value)
115     {
116         switch (version)
117         {
118             case SPDY.V2:
119             {
120                 buffer.write((value & 0xFF_00) >>> 8);
121                 buffer.write(value & 0x00_FF);
122                 break;
123             }
124             case SPDY.V3:
125             {
126                 buffer.write((value & 0xFF_00_00_00) >>> 24);
127                 buffer.write((value & 0x00_FF_00_00) >>> 16);
128                 buffer.write((value & 0x00_00_FF_00) >>> 8);
129                 buffer.write(value & 0x00_00_00_FF);
130                 break;
131             }
132             default:
133             {
134                 // Here the version is trusted to be correct; if it's not
135                 // then it's a bug rather than an application error
136                 throw new IllegalStateException();
137             }
138         }
139     }
140 
141     private void writeNameLength(short version, ByteArrayOutputStream buffer, int length)
142     {
143         writeCount(version, buffer, length);
144     }
145 
146     private void writeValueLength(short version, ByteArrayOutputStream buffer, int length)
147     {
148         writeCount(version, buffer, length);
149     }
150 }