All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.caucho.v5.http.protocol2.OutHeader Maven / Gradle / Ivy

There is a newer version: 1.0.1
Show newest version
/*
 * Copyright (c) 1998-2015 Caucho Technology -- all rights reserved
 *
 * This file is part of Baratine(TM)
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Baratine is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Baratine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Baratine; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.v5.http.protocol2;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;

import com.caucho.v5.io.WriteBuffer;
import com.caucho.v5.util.BitsUtil;


/**
 * HeaderOut is the compression for the writer.
 */
public class OutHeader extends HeaderCommon implements AutoCloseable
{
  private HashMap _tableKeyMap;
  private HashMap _tableEntryMap;
    
  private int _tableCapacity = 4096;
  
  private TableEntry []_entries;
  
  private TableEntry _key = new TableEntry();
  
  private final WriteBuffer _os;
  
  private byte []_buffer;
  private int _headerOffset;
  private int _offset;
  
  private int _streamId;
  private StateHeader _state;
  
  private long _sequence;
  private long _tailSequence;
  
  private long _seqReference = 1;
  
  private int _tableSize = 0;
  
  private int _priorityDependency;
  private int _priorityWeight;
  private boolean _isPriorityExclusive;
  
  private int _pad;
  
  public OutHeader(WriteBuffer os)
  {
    Objects.requireNonNull(os);
    
    _tableKeyMap = new HashMap<>();
    _tableKeyMap.putAll(getTableKeyStatic());
    
    _tableEntryMap = new HashMap<>();
    _tableEntryMap.putAll(getTableEntryStatic());
    
    _entries = new TableEntry[256];
    
    _os = os;
  }
  
  public void openHeaders(int streamId, FlagsHttp flags)
    throws IOException
  {
    openHeaders(streamId, 0, -1, -1, false, flags);
  }
    
  public void openHeaders(int streamId,
                          int pad,
                          int priorityDependency,
                          int priorityWeight,
                          boolean isPriorityExclusive,
                          FlagsHttp flags)
    throws IOException
  {
    _streamId = streamId;
    
    _pad = pad;
    _priorityDependency = priorityDependency;
    _priorityWeight = priorityWeight;
    _isPriorityExclusive = isPriorityExclusive;
    
    ++_seqReference;
    
    startChunk();
    
    switch (flags) {
    case END_STREAM:
      _state = StateHeader.HEADER_INIT_STREAM_END;
      break;
      
    case CONT_STREAM:
      _state = StateHeader.HEADER_INIT_STREAM_CONT;
      break;
      
    default:
      throw new IllegalStateException(String.valueOf(flags));
    }
  }
  
  private void startChunk()
    throws IOException
  {
    WriteBuffer os = _os;
    
    int offset = os.getBufferOffset();
    byte []buffer = os.getBuffer();
    int size = buffer.length;
    
    if (size - offset < 16) {
      os.flush();
      
      offset = os.getBufferOffset();
      buffer = os.getBuffer();
    }
    
    _headerOffset = offset;
    _buffer = buffer;
    
    offset += 8;
    
    if (_pad >= 256) {
      offset += 2;
    }
    else if (_pad > 0) {
      offset += 1;
    }
    
    if (_priorityWeight >= 0) {
      offset += 5;
    }
    
    _offset = offset;
  }
  
  private int flushChunk(int offset)
    throws IOException
  {
    StateHeader state = _state;
    
    writeHeaders(offset, state.flagsFlush());
    
    _os.flush();
    
    _state = state.onFlush();
    
    return 8;
  }
  
  public void closeHeaders()
    throws IOException
  {
    completeReferenceSet();
    
    writeHeaders(_offset, _state.flagsClose());
  }
  
  private void writeHeaders(int offset, int flags)
    throws IOException
  {
    int headerOffset = _headerOffset;
    byte []buffer = _buffer;
    
    StateHeader state = _state;
    
    int startOffset = headerOffset + 8;
    
    int len = offset - startOffset;
    int fillLen = 0;
    
    if (_pad > 0) {
      fillLen = _pad - len % _pad;
      
      if (fillLen == _pad) {
        fillLen = 0;
      }
      
      len += fillLen;
      
      if (_pad >= 256) {
        flags |= Http2Constants.PAD_HIGH|Http2Constants.PAD_LOW;
        
        buffer[startOffset + 0] = (byte) (fillLen / 256);
        buffer[startOffset + 1] = (byte) fillLen;
        
        startOffset += 2;
      }
      else {
        flags |= Http2Constants.PAD_LOW;
        
        buffer[startOffset + 0] = (byte) fillLen;
        
        startOffset += 1;
      }
    }
    
    if (_priorityWeight >= 0) {
      flags |= Http2Constants.PRIORITY;
      
      BitsUtil.writeInt(buffer, startOffset, _priorityDependency);
      buffer[startOffset + 4] = (byte) _priorityWeight;
      
      startOffset += 5;
      
      _priorityDependency = -1;
      _priorityWeight = -1;
    }
    
    buffer[headerOffset + 0] = (byte) (len >> 8);
    buffer[headerOffset + 1] = (byte) (len);
    buffer[headerOffset + 2] = (byte) state.opcode();
    buffer[headerOffset + 3] = (byte) flags;
    
    BitsUtil.writeInt(buffer, headerOffset + 4, _streamId);
    
    WriteBuffer os = _os;
    
    if (fillLen > 0) {
      Arrays.fill(buffer, offset, offset + fillLen, (byte) 0);
    }
    os.setBufferOffset(offset + fillLen);
    
    _headerOffset = 0;
  }

  public void header(String key, String value)
    throws IOException
  {
    // literal header field with incremental indexing
    // i.e. add to index table
    
    TableEntry entryKey = _key;
    
    entryKey.update(0, key, value);
    
    TableEntry entry = _tableEntryMap.get(entryKey);
    
    if (entry != null) {
      int index = getIndex(entry);
      
      if (entry.getReference() < _seqReference) {
        writeKeyValue(0x80, index);

        if (entry.isStatic()) {
          // static has a copy added
          entry = new TableEntry(entry);
          _tableEntryMap.put(entry, entry);
          addEntry(entry);
          
          return;
        }
      }
      
      if (entry.getReference() <= _seqReference) {
        if (! entry.isStatic()) {
          // XXX: Need to add
          entry.setReference(_seqReference + 1);
        }
        
      }
      
      return;
    }
    
    writeKey(0x40, 6, key);

    writeString(value);
    
    _key = new TableEntry();
    
    addEntry(entryKey);
  }
  
  private void addEntry(TableEntry entry)
  {
    long seq = _sequence++;
    entry.setSequence(seq);
    entry.setReference(_seqReference + 1);
    
    _tableEntryMap.put(entry, entry);
    
    if (entry.getNext() == null) {
      _tableKeyMap.put(entry.getKey(), entry);
    }
    
    _entries[(int) (seq % _entries.length)] = entry;
    
    _tableSize += entry.getSize();
    
    updateTableSize();
  }
  
  public void headerUnique(String key, String value)
    throws IOException
  {
    // literal header field without incremental indexing
    // i.e. don't add to index table
    
    TableEntry entryKey = _key;
    
    entryKey.update(0, key, value);
    
    TableEntry entry = _tableEntryMap.get(entryKey);
    
    if (entry != null) {
      int index = getIndex(entry);
      
      writeKeyValue(0x80, index);
      
      return;
    }
    
    writeKey(0x00, 4, key);
    
    writeString(value);
  }
  
  public void headerNever(String key, String value)
    throws IOException
  {
    // literal header field without incremental indexing
    // i.e. don't add to index table
    writeKey(0x10, 4, key);
    
    writeString(value);
  }
  
  public void clearReferenceSet()
    throws IOException
  {
    write(0x30);
    
    for (long i = _tailSequence; i < _sequence; i++) {
      TableEntry entry = _entries[(int) (i % _entries.length)];
      entry.setReference(0);
    }
  }
  
  public void setTableSize(int size)
    throws IOException
  {
    if (size < 0) {
      throw new IllegalArgumentException();
    }
    _tableCapacity = size;
    
    writeInt(0x20, 4, size);
    
    updateTableSize();
  }
  
  private void updateTableSize()
  {
    while (_tableCapacity < _tableSize) {
      removeTableEntry();
    }
  }
  
  private void removeTableEntry()
  {
    long i = _tailSequence++;
    
    TableEntry entry = _entries[(int) (i % _entries.length)];
    entry.setReference(0);
    
    _tableEntryMap.remove(entry);
    
    TableEntry next = entry.getNext();
    
    if (next != null) {
      // restore static
      _tableEntryMap.put(next, next);
    }
    
    _tableSize -= entry.getSize();
  }
  
  private void writeKeyValue(int opcode, long seq)
    throws IOException
  {
    writeInt(opcode, 7, seq);
  }
  
  private void writeKey(int opcode, int bits, String key)
    throws IOException
  {
    TableEntry entry = _tableKeyMap.get(key);
    
    if (entry != null
        && (entry.isStatic() || _tailSequence <= entry.getSequence())) {
      int index = getIndex(entry);
      
      writeInt(opcode, bits, index);
    }
    else {
      write((byte) opcode);
      
      writeString(key);
    }
  }
  
  private int getIndex(TableEntry entry)
  {
    if (entry.isStatic()) {
      return (int) ((_sequence - _tailSequence) + entry.getSequence());
    }
    else {
      return (int) (_sequence - entry.getSequence());
    }
  }
  
  private void writeString(String value)
    throws IOException
  {
    int strlen = value.length();
    
    int offset = _offset;
    byte []buffer = _buffer;
    int bufferLength = buffer.length;
    
    if (bufferLength < offset + 4 + strlen) {
      offset = flushChunk(offset);
      buffer = _buffer;
    }
    
    _offset = writeStringImpl(buffer, offset, value, strlen);
  }
  
  protected int writeStringImpl(byte []buffer, int offset, 
                                String value, int strlen)
    throws IOException
  {
    int bufferLength = buffer.length;
    
    buffer[offset++] = (byte) strlen;
    
    int i = 0;
    
    while (true) {
      int end = Math.min(offset + strlen - i, bufferLength);
      
      while (offset < end) {
        buffer[offset++] = (byte) value.charAt(i++);
      }
      
      if (offset < bufferLength) {
        return offset;
      }
      
      offset = flushChunk(offset);
      buffer = _buffer;
    }
  }
  
  private void writeInt(int opcode, int bits, long value)
    throws IOException
  {
    int mask = (1 << bits) - 1;
    
    if ((mask & value) == value) {
      int d = (int) (opcode + value);
        
      write(d);
    }
    else {
      write((int) (opcode + mask));
      
      value -= mask;
      while (value >= 0x80) {
        write((int) (0x80 | (value & 0x7f)));
        
        value >>= 7;
      }
      
      write((int) value);
    }
  }
  
  private void write(int v)
    throws IOException
  {
    int offset = ensureCapacity(1);
    byte []buffer = _buffer;
    
    buffer[offset++] = (byte) v;
    
    _offset = offset;
  }
  
  private int ensureCapacity(int len)
    throws IOException
  {
    int offset = _offset;
    byte []buffer = _buffer;
    
    if (offset + len <= buffer.length) {
      return offset;
    }
    else {
      offset = flushChunk(offset);
      
      _offset = offset;
      
      return offset;
    }
  }
  
  public void flush()
  {
  }

  /**
   * References that were not used need to be deleted.
   */
  private void completeReferenceSet()
    throws IOException
  {
    long reference = _seqReference;
    
    for (long i = _tailSequence; i < _sequence; i++) {
      TableEntry entry = _entries[(int) (i % _entries.length)];
      
      if (entry.getReference() == reference) {
        int index = getIndex(entry);
        write(0x80 + index);
      }
    }
  }
  
  @Override
  public void close()
    throws IOException
  {
    //_os.write(_buffer, 0, _index);
  }
  
  static enum StateHeader {
    HEADER_INIT_STREAM_END {
      @Override
      int opcode()
      { 
        return Http2Constants.FRAME_HEADERS; 
      }
      
      @Override
      int flagsFlush()
      {
        return Http2Constants.END_STREAM;
      }
      
      @Override
      int flagsClose()
      { 
        return Http2Constants.END_STREAM|Http2Constants.END_HEADERS;
      }
    },
    
    HEADER_INIT_STREAM_CONT {
      @Override
      int opcode()
      { 
        return Http2Constants.FRAME_HEADERS; 
      }
      
      @Override
      int flagsFlush()
      {
        return 0;
      }
      
      @Override
      int flagsClose()
      { 
        return Http2Constants.END_HEADERS;
      }
    },
    
    HEADER_CONT {
      @Override
      int opcode()
      { 
        return Http2Constants.FRAME_CONT; 
      }
      
      @Override
      int flagsFlush()
      {
        return 0;
      }
      
      @Override
      int flagsClose()
      { 
        return Http2Constants.END_HEADERS;
      }
    };
    
    int opcode()
    {
      throw new IllegalStateException(toString());
    }
    
    int flagsFlush()
    {
      throw new IllegalStateException(toString());
    }
    
    int flagsClose()
    {
      throw new IllegalStateException(toString());
    }
    
    StateHeader onFlush()
    {
      return HEADER_CONT;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy