Search in sources :

Example 6 with Entry

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();
}
Also used : PushPromiseFrame(org.eclipse.jetty.http2.frames.PushPromiseFrame) EndPoint(org.eclipse.jetty.io.EndPoint)

Example 7 with Entry

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();
}
Also used : BadMessageException(org.eclipse.jetty.http.BadMessageException) Entry(org.eclipse.jetty.http2.hpack.HpackContext.Entry) HttpHeader(org.eclipse.jetty.http.HttpHeader) HttpField(org.eclipse.jetty.http.HttpField)

Example 8 with Entry

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));
    }
}
Also used : StaticEntry(org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry) Entry(org.eclipse.jetty.http2.hpack.HpackContext.Entry) HttpHeader(org.eclipse.jetty.http.HttpHeader) HttpField(org.eclipse.jetty.http.HttpField) PreEncodedHttpField(org.eclipse.jetty.http.PreEncodedHttpField) PreEncodedHttpField(org.eclipse.jetty.http.PreEncodedHttpField)

Example 9 with Entry

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--;
    }
}
Also used : Entry(org.eclipse.jetty.http2.hpack.HpackContext.Entry) HttpField(org.eclipse.jetty.http.HttpField) Test(org.junit.Test)

Example 10 with Entry

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,"));
}
Also used : Entry(org.eclipse.jetty.http2.hpack.HpackContext.Entry) HttpField(org.eclipse.jetty.http.HttpField) Test(org.junit.Test)

Aggregations

Entry (org.eclipse.jetty.http2.hpack.HpackContext.Entry)9 HttpField (org.eclipse.jetty.http.HttpField)7 Test (org.junit.Test)7 EndPoint (org.eclipse.jetty.io.EndPoint)3 HttpHeader (org.eclipse.jetty.http.HttpHeader)2 ByteBuffer (java.nio.ByteBuffer)1 Map (java.util.Map)1 ConcurrentHashMap (java.util.concurrent.ConcurrentHashMap)1 ConcurrentMap (java.util.concurrent.ConcurrentMap)1 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)1 BadMessageException (org.eclipse.jetty.http.BadMessageException)1 PreEncodedHttpField (org.eclipse.jetty.http.PreEncodedHttpField)1 HeadersFrame (org.eclipse.jetty.http2.frames.HeadersFrame)1 PriorityFrame (org.eclipse.jetty.http2.frames.PriorityFrame)1 PushPromiseFrame (org.eclipse.jetty.http2.frames.PushPromiseFrame)1 SettingsFrame (org.eclipse.jetty.http2.frames.SettingsFrame)1 StaticEntry (org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry)1