/*
* Copyright (c) 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.jetty.spdy.generator;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import org.eclipse.jetty.spdy.CompressionDictionary;
import org.eclipse.jetty.spdy.CompressionFactory;
import org.eclipse.jetty.spdy.api.Headers;
import org.eclipse.jetty.spdy.api.SPDY;
public class HeadersBlockGenerator
{
private final CompressionFactory.Compressor compressor;
private boolean needsDictionary = true;
public HeadersBlockGenerator(CompressionFactory.Compressor compressor)
{
this.compressor = compressor;
}
public ByteBuffer generate(short version, Headers headers)
{
// TODO: ByteArrayOutputStream is quite inefficient, but grows on demand; optimize using ByteBuffer ?
Charset iso1 = Charset.forName("ISO-8859-1");
ByteArrayOutputStream buffer = new ByteArrayOutputStream(headers.size() * 64);
writeCount(version, buffer, headers.size());
for (Headers.Header header : headers)
{
String name = header.name();
byte[] nameBytes = name.getBytes(iso1);
writeNameLength(version, buffer, nameBytes.length);
buffer.write(nameBytes, 0, nameBytes.length);
// Most common path first
String value = header.value();
byte[] valueBytes = value.getBytes(iso1);
if (header.hasMultipleValues())
{
String[] values = header.values();
for (int i = 1; i < values.length; ++i)
{
byte[] moreValueBytes = values[i].getBytes(iso1);
byte[] newValueBytes = new byte[valueBytes.length + 1 + moreValueBytes.length];
System.arraycopy(valueBytes, 0, newValueBytes, 0, valueBytes.length);
newValueBytes[valueBytes.length] = 0;
System.arraycopy(moreValueBytes, 0, newValueBytes, valueBytes.length + 1, moreValueBytes.length);
valueBytes = newValueBytes;
}
}
writeValueLength(version, buffer, valueBytes.length);
buffer.write(valueBytes, 0, valueBytes.length);
}
return compress(version, buffer.toByteArray());
}
private ByteBuffer compress(short version, byte[] bytes)
{
ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length);
// The headers compression context is per-session, so we need to synchronize
synchronized (compressor)
{
if (needsDictionary)
{
compressor.setDictionary(CompressionDictionary.get(version));
needsDictionary = false;
}
compressor.setInput(bytes);
// Compressed bytes may be bigger than input bytes, so we need to loop and accumulate them
// Beware that the minimum amount of bytes generated by the compressor is few bytes, so we
// need to use an output buffer that is big enough to exit the compress loop
buffer.reset();
int compressed;
byte[] output = new byte[Math.max(256, bytes.length)];
while (true)
{
// SPDY uses the SYNC_FLUSH mode
compressed = compressor.compress(output);
buffer.write(output, 0, compressed);
if (compressed < output.length)
break;
}
}
return ByteBuffer.wrap(buffer.toByteArray());
}
private void writeCount(short version, ByteArrayOutputStream buffer, int value)
{
switch (version)
{
case SPDY.V2:
{
buffer.write((value & 0xFF_00) >>> 8);
buffer.write(value & 0x00_FF);
break;
}
case SPDY.V3:
{
buffer.write((value & 0xFF_00_00_00) >>> 24);
buffer.write((value & 0x00_FF_00_00) >>> 16);
buffer.write((value & 0x00_00_FF_00) >>> 8);
buffer.write(value & 0x00_00_00_FF);
break;
}
default:
{
// Here the version is trusted to be correct; if it's not
// then it's a bug rather than an application error
throw new IllegalStateException();
}
}
}
private void writeNameLength(short version, ByteArrayOutputStream buffer, int length)
{
writeCount(version, buffer, length);
}
private void writeValueLength(short version, ByteArrayOutputStream buffer, int length)
{
writeCount(version, buffer, length);
}
}