use of org.eclipse.jetty.http2.hpack.HpackContext.Entry in project jetty.project by eclipse.
the class HTTP2Session method push.
@Override
public void push(IStream stream, Promise<Stream> promise, PushPromiseFrame frame, Stream.Listener listener) {
// Synchronization is necessary to atomically create
// the stream id and enqueue the frame to be sent.
boolean queued;
synchronized (this) {
int streamId = streamIds.getAndAdd(2);
frame = new PushPromiseFrame(frame.getStreamId(), streamId, frame.getMetaData());
final IStream pushStream = createLocalStream(streamId, promise);
if (pushStream == null)
return;
pushStream.setListener(listener);
ControlEntry entry = new ControlEntry(frame, pushStream, new PromiseCallback<>(promise, pushStream));
queued = flusher.append(entry);
}
// Iterate outside the synchronized block.
if (queued)
flusher.iterate();
}
use of org.eclipse.jetty.http2.hpack.HpackContext.Entry in project jetty.project by eclipse.
the class HpackDecoder method decode.
public MetaData decode(ByteBuffer buffer) {
if (LOG.isDebugEnabled())
LOG.debug(String.format("CtxTbl[%x] decoding %d octets", _context.hashCode(), buffer.remaining()));
// If the buffer is big, don't even think about decoding it
if (buffer.remaining() > _builder.getMaxSize())
throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431, "Header frame size " + buffer.remaining() + ">" + _builder.getMaxSize());
while (buffer.hasRemaining()) {
if (LOG.isDebugEnabled() && buffer.hasArray()) {
int l = Math.min(buffer.remaining(), 32);
LOG.debug("decode {}{}", TypeUtil.toHexString(buffer.array(), buffer.arrayOffset() + buffer.position(), l), l < buffer.remaining() ? "..." : "");
}
byte b = buffer.get();
if (b < 0) {
// 7.1 indexed if the high bit is set
int index = NBitInteger.decode(buffer, 7);
Entry entry = _context.get(index);
if (entry == null) {
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Unknown index " + index);
} else if (entry.isStatic()) {
if (LOG.isDebugEnabled())
LOG.debug("decode IdxStatic {}", entry);
// emit field
_builder.emit(entry.getHttpField());
// TODO copy and add to reference set if there is room
// _context.add(entry.getHttpField());
} else {
if (LOG.isDebugEnabled())
LOG.debug("decode Idx {}", entry);
// emit
_builder.emit(entry.getHttpField());
}
} else {
// look at the first nibble in detail
byte f = (byte) ((b & 0xF0) >> 4);
String name;
HttpHeader header;
String value;
boolean indexed;
int name_index;
switch(f) {
// 7.3
case 2:
case // 7.3
3:
// change table size
int size = NBitInteger.decode(buffer, 5);
if (LOG.isDebugEnabled())
LOG.debug("decode resize=" + size);
if (size > _localMaxDynamicTableSize)
throw new IllegalArgumentException();
_context.resize(size);
continue;
// 7.2.2
case 0:
case // 7.2.3
1:
indexed = false;
name_index = NBitInteger.decode(buffer, 4);
break;
// 7.2.1
case 4:
// 7.2.1
case 5:
// 7.2.1
case 6:
case // 7.2.1
7:
indexed = true;
name_index = NBitInteger.decode(buffer, 6);
break;
default:
throw new IllegalStateException();
}
boolean huffmanName = false;
// decode the name
if (name_index > 0) {
Entry name_entry = _context.get(name_index);
name = name_entry.getHttpField().getName();
header = name_entry.getHttpField().getHeader();
} else {
huffmanName = (buffer.get() & 0x80) == 0x80;
int length = NBitInteger.decode(buffer, 7);
_builder.checkSize(length, huffmanName);
if (huffmanName)
name = Huffman.decode(buffer, length);
else
name = toASCIIString(buffer, length);
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
if (c >= 'A' && c <= 'Z') {
throw new BadMessageException(400, "Uppercase header name");
}
}
header = HttpHeader.CACHE.get(name);
}
// decode the value
boolean huffmanValue = (buffer.get() & 0x80) == 0x80;
int length = NBitInteger.decode(buffer, 7);
_builder.checkSize(length, huffmanValue);
if (huffmanValue)
value = Huffman.decode(buffer, length);
else
value = toASCIIString(buffer, length);
// Make the new field
HttpField field;
if (header == null) {
// just make a normal field and bypass header name lookup
field = new HttpField(null, name, value);
} else {
// and/or of a type that may be looked up multiple times.
switch(header) {
case C_STATUS:
if (indexed)
field = new HttpField.IntValueHttpField(header, name, value);
else
field = new HttpField(header, name, value);
break;
case C_AUTHORITY:
field = new AuthorityHttpField(value);
break;
case CONTENT_LENGTH:
if ("0".equals(value))
field = CONTENT_LENGTH_0;
else
field = new HttpField.LongValueHttpField(header, name, value);
break;
default:
field = new HttpField(header, name, value);
break;
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("decoded '{}' by {}/{}/{}", field, name_index > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"), huffmanValue ? "HuffVal" : "LitVal", indexed ? "Idx" : "");
}
// emit the field
_builder.emit(field);
// if indexed
if (indexed) {
// add to dynamic table
if (_context.add(field) == null)
throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431, "Indexed field value too large");
}
}
}
return _builder.build();
}
use of org.eclipse.jetty.http2.hpack.HpackContext.Entry in project jetty.project by eclipse.
the class HpackEncoder method encode.
public void encode(ByteBuffer buffer, HttpField field) {
if (field.getValue() == null)
field = new HttpField(field.getHeader(), field.getName(), "");
int field_size = field.getName().length() + field.getValue().length();
_headerListSize += field_size + 32;
final int p = _debug ? buffer.position() : -1;
String encoding = null;
// Is there an entry for the field?
Entry entry = _context.get(field);
if (entry != null) {
// Known field entry, so encode it as indexed
if (entry.isStatic()) {
buffer.put(((StaticEntry) entry).getEncodedField());
if (_debug)
encoding = "IdxFieldS1";
} else {
int index = _context.index(entry);
buffer.put((byte) 0x80);
NBitInteger.encode(buffer, 7, index);
if (_debug)
encoding = "IdxField" + (entry.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(7, index));
}
} else {
// Unknown field entry, so we will have to send literally.
final boolean indexed;
// But do we know it's name?
HttpHeader header = field.getHeader();
// Select encoding strategy
if (header == null) {
// Select encoding strategy for unknown header names
Entry name = _context.get(field.getName());
if (field instanceof PreEncodedHttpField) {
int i = buffer.position();
((PreEncodedHttpField) field).putTo(buffer, HttpVersion.HTTP_2);
byte b = buffer.get(i);
indexed = b < 0 || b >= 0x40;
if (_debug)
encoding = indexed ? "PreEncodedIdx" : "PreEncoded";
} else // has the custom header name been seen before?
if (name == null) {
// unknown name and value, so let's index this just in case it is
// the first time we have seen a custom name or a custom field.
// unless the name is changing, this is worthwhile
indexed = true;
encodeName(buffer, (byte) 0x40, 6, field.getName(), null);
encodeValue(buffer, true, field.getValue());
if (_debug)
encoding = "LitHuffNHuffVIdx";
} else {
// known custom name, but unknown value.
// This is probably a custom field with changing value, so don't index.
indexed = false;
encodeName(buffer, (byte) 0x00, 4, field.getName(), null);
encodeValue(buffer, true, field.getValue());
if (_debug)
encoding = "LitHuffNHuffV!Idx";
}
} else {
// Select encoding strategy for known header names
Entry name = _context.get(header);
if (field instanceof PreEncodedHttpField) {
// Preencoded field
int i = buffer.position();
((PreEncodedHttpField) field).putTo(buffer, HttpVersion.HTTP_2);
byte b = buffer.get(i);
indexed = b < 0 || b >= 0x40;
if (_debug)
encoding = indexed ? "PreEncodedIdx" : "PreEncoded";
} else if (__DO_NOT_INDEX.contains(header)) {
// Non indexed field
indexed = false;
boolean never_index = __NEVER_INDEX.contains(header);
boolean huffman = !__DO_NOT_HUFFMAN.contains(header);
encodeName(buffer, never_index ? (byte) 0x10 : (byte) 0x00, 4, header.asString(), name);
encodeValue(buffer, huffman, field.getValue());
if (_debug)
encoding = "Lit" + ((name == null) ? "HuffN" : ("IdxN" + (name.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(4, _context.index(name))))) + (huffman ? "HuffV" : "LitV") + (indexed ? "Idx" : (never_index ? "!!Idx" : "!Idx"));
} else if (field_size >= _context.getMaxDynamicTableSize() || header == HttpHeader.CONTENT_LENGTH && field.getValue().length() > 2) {
// Non indexed if field too large or a content length for 3 digits or more
indexed = false;
encodeName(buffer, (byte) 0x00, 4, header.asString(), name);
encodeValue(buffer, true, field.getValue());
if (_debug)
encoding = "LitIdxNS" + (1 + NBitInteger.octectsNeeded(4, _context.index(name))) + "HuffV!Idx";
} else {
// indexed
indexed = true;
boolean huffman = !__DO_NOT_HUFFMAN.contains(header);
encodeName(buffer, (byte) 0x40, 6, header.asString(), name);
encodeValue(buffer, huffman, field.getValue());
if (_debug)
encoding = ((name == null) ? "LitHuffN" : ("LitIdxN" + (name.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(6, _context.index(name))))) + (huffman ? "HuffVIdx" : "LitVIdx");
}
}
// table and reference set.
if (indexed)
if (_context.add(field) == null)
throw new IllegalStateException();
}
if (_debug) {
int e = buffer.position();
if (LOG.isDebugEnabled())
LOG.debug("encode {}:'{}' to '{}'", encoding, field, TypeUtil.toHexString(buffer.array(), buffer.arrayOffset() + p, e - p));
}
}
use of org.eclipse.jetty.http2.hpack.HpackContext.Entry in project jetty.project by eclipse.
the class HpackContextTest method testIndexes.
@Test
public void testIndexes() {
// Only enough space for 5 entries
HpackContext ctx = new HpackContext(38 * 5);
HttpField methodPost = new HttpField(":method", "POST");
HttpField[] field = { new HttpField("fo0", "b0r"), new HttpField("fo1", "b1r"), new HttpField("fo2", "b2r"), new HttpField("fo3", "b3r"), new HttpField("fo4", "b4r"), new HttpField("fo5", "b5r"), new HttpField("fo6", "b6r"), new HttpField("fo7", "b7r"), new HttpField("fo8", "b8r"), new HttpField("fo9", "b9r"), new HttpField("foA", "bAr") };
Entry[] entry = new Entry[100];
// Lookup the index of a static field
assertEquals(0, ctx.size());
assertEquals(":authority", ctx.get(1).getHttpField().getName());
assertEquals(3, ctx.index(ctx.get(methodPost)));
assertEquals(methodPost, ctx.get(3).getHttpField());
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
assertEquals(null, ctx.get(62));
// Add a single entry
entry[0] = ctx.add(field[0]);
// Check new entry is 62
assertEquals(1, ctx.size());
assertEquals(62, ctx.index(entry[0]));
assertEquals(entry[0], ctx.get(62));
// and statics still OK
assertEquals(":authority", ctx.get(1).getHttpField().getName());
assertEquals(3, ctx.index(ctx.get(methodPost)));
assertEquals(methodPost, ctx.get(3).getHttpField());
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
assertEquals(null, ctx.get(62 + ctx.size()));
// Add 4 more entries
for (int i = 1; i <= 4; i++) entry[i] = ctx.add(field[i]);
// Check newest entry is at 62 oldest at 66
assertEquals(5, ctx.size());
int index = 66;
for (int i = 0; i <= 4; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// and statics still OK
assertEquals(":authority", ctx.get(1).getHttpField().getName());
assertEquals(3, ctx.index(ctx.get(methodPost)));
assertEquals(methodPost, ctx.get(3).getHttpField());
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
assertEquals(null, ctx.get(62 + ctx.size()));
// add 1 more entry and this should cause an eviction!
entry[5] = ctx.add(field[5]);
// Check newest entry is at 1 oldest at 5
index = 66;
for (int i = 1; i <= 5; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// check entry 0 evicted
assertNull(ctx.get(field[0]));
assertEquals(0, ctx.index(entry[0]));
// and statics still OK
assertEquals(":authority", ctx.get(1).getHttpField().getName());
assertEquals(3, ctx.index(ctx.get(methodPost)));
assertEquals(methodPost, ctx.get(3).getHttpField());
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
assertEquals(null, ctx.get(62 + ctx.size()));
// Add 4 more entries
for (int i = 6; i <= 9; i++) entry[i] = ctx.add(field[i]);
// Check newest entry is at 1 oldest at 5
index = 66;
for (int i = 5; i <= 9; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
// check entry 0-4 evicted
for (int i = 0; i <= 4; i++) {
assertNull(ctx.get(field[i]));
assertEquals(0, ctx.index(entry[i]));
}
// Add new entries enough so that array queue will wrap
for (int i = 10; i <= 52; i++) entry[i] = ctx.add(new HttpField("n" + i, "v" + i));
index = 66;
for (int i = 48; i <= 52; i++) {
assertEquals(index, ctx.index(entry[i]));
assertEquals(entry[i], ctx.get(index));
index--;
}
}
use of org.eclipse.jetty.http2.hpack.HpackContext.Entry in project jetty.project by eclipse.
the class HpackContextTest method testJustRight.
@Test
public void testJustRight() {
HpackContext ctx = new HpackContext(38);
HttpField field = new HttpField("foo", "bar");
Entry entry = ctx.add(field);
Assert.assertNotNull(entry);
Assert.assertThat(entry.toString(), Matchers.startsWith("{D,0,foo: bar,"));
}
Aggregations