| // |
| // ======================================================================== |
| // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.spdy.generator; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.nio.ByteBuffer; |
| import java.nio.charset.Charset; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Locale; |
| |
| import org.eclipse.jetty.spdy.CompressionDictionary; |
| import org.eclipse.jetty.spdy.CompressionFactory; |
| import org.eclipse.jetty.spdy.api.SPDY; |
| import org.eclipse.jetty.util.Fields; |
| |
| 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, Fields headers) |
| { |
| // TODO: ByteArrayOutputStream is quite inefficient, but grows on demand; optimize using ByteBuffer ? |
| final Charset iso1 = StandardCharsets.ISO_8859_1; |
| ByteArrayOutputStream buffer = new ByteArrayOutputStream(headers.getSize() * 64); |
| writeCount(version, buffer, headers.getSize()); |
| for (Fields.Field header : headers) |
| { |
| String name = header.getName().toLowerCase(Locale.ENGLISH); |
| byte[] nameBytes = name.getBytes(iso1); |
| writeNameLength(version, buffer, nameBytes.length); |
| buffer.write(nameBytes, 0, nameBytes.length); |
| |
| // Most common path first |
| String value = header.getValue(); |
| byte[] valueBytes = value.getBytes(iso1); |
| if (header.hasMultipleValues()) |
| { |
| List<String> values = header.getValues(); |
| for (int i = 1; i < values.size(); ++i) |
| { |
| byte[] moreValueBytes = values.get(i).getBytes(iso1); |
| byte[] newValueBytes = Arrays.copyOf(valueBytes,valueBytes.length + 1 + moreValueBytes.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); |
| } |
| } |