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