Reworked generation of frames (split into different generators) and

sketched server-side handling and linking with channel.
This commit is contained in:
Simone Bordet 2014-06-10 12:01:00 +02:00
parent 1d2e9da29c
commit ad034f4d54
32 changed files with 1314 additions and 333 deletions

View File

@ -18,9 +18,14 @@
package org.eclipse.jetty.http2;
import org.eclipse.jetty.http2.api.Session;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PingFrame;
@ -28,27 +33,38 @@ import org.eclipse.jetty.http2.frames.PriorityFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public abstract class HTTP2Session implements Session, Parser.Listener
public abstract class HTTP2Session implements ISession, Parser.Listener
{
private static final Logger LOG = Log.getLogger(HTTP2Session.class);
private final ConcurrentMap<Integer, IStream> streams = new ConcurrentHashMap<>();
private final Flusher flusher = new Flusher();
private final EndPoint endPoint;
private final Generator generator;
private final Listener listener;
public HTTP2Session(Session.Listener listener)
public HTTP2Session(EndPoint endPoint, Generator generator, Listener listener)
{
this.endPoint = endPoint;
this.generator = generator;
this.listener = listener;
}
@Override
public boolean onData(DataFrame frame)
{
return false;
IStream stream = streams.get(frame.getStreamId());
return stream.process(frame);
}
@Override
@ -126,6 +142,18 @@ public abstract class HTTP2Session implements Session, Parser.Listener
}
@Override
public void frame(Frame frame, Callback callback)
{
Generator.LeaseCallback lease = generator.generate(frame, callback);
flusher.flush(lease);
}
protected IStream putIfAbsent(IStream stream)
{
return streams.putIfAbsent(stream.getId(), stream);
}
protected Stream.Listener notifyNewStream(Stream stream, HeadersFrame frame)
{
try
@ -138,4 +166,55 @@ public abstract class HTTP2Session implements Session, Parser.Listener
return null;
}
}
private class Flusher extends IteratingCallback
{
private final ArrayQueue<Generator.LeaseCallback> queue = new ArrayQueue<>(ArrayQueue.DEFAULT_CAPACITY, ArrayQueue.DEFAULT_GROWTH);
private Generator.LeaseCallback active;
private void flush(Generator.LeaseCallback lease)
{
synchronized (queue)
{
queue.offer(lease);
}
iterate();
}
@Override
protected Action process() throws Exception
{
synchronized (queue)
{
active = queue.poll();
}
if (active == null)
{
return Action.IDLE;
}
List<ByteBuffer> byteBuffers = active.getByteBuffers();
endPoint.write(this, byteBuffers.toArray(new ByteBuffer[byteBuffers.size()]));
return Action.SCHEDULED;
}
@Override
public void succeeded()
{
active.succeeded();
super.succeeded();
}
@Override
public void failed(Throwable x)
{
active.failed(x);
super.failed(x);
}
@Override
protected void completed()
{
}
}
}

View File

@ -18,6 +18,10 @@
package org.eclipse.jetty.http2;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
@ -25,6 +29,15 @@ import org.eclipse.jetty.util.Callback;
public class HTTP2Stream implements IStream
{
private final AtomicReference<ConcurrentMap<String, Object>> attributes = new AtomicReference<>();
private final ISession session;
private Listener listener;
public HTTP2Stream(ISession session)
{
this.session = session;
}
@Override
public int getId()
{
@ -40,13 +53,45 @@ public class HTTP2Stream implements IStream
@Override
public void headers(HeadersFrame frame, Callback callback)
{
session.frame(frame, callback);
}
@Override
public void data(DataFrame frame, Callback callback)
{
session.frame(frame, callback);
}
@Override
public Object getAttribute(String key)
{
return attributes().get(key);
}
@Override
public void setAttribute(String key, Object value)
{
attributes().put(key, value);
}
@Override
public Object removeAttribute(String key)
{
return attributes().remove(key);
}
private ConcurrentMap<String, Object> attributes()
{
ConcurrentMap<String, Object> map = attributes.get();
if (map == null)
{
map = new ConcurrentHashMap<>();
if (!attributes.compareAndSet(null, map))
{
map = attributes.get();
}
}
return map;
}
@Override
@ -54,4 +99,30 @@ public class HTTP2Stream implements IStream
{
}
@Override
public boolean process(DataFrame frame)
{
return notifyData(frame);
}
protected boolean notifyData(DataFrame frame)
{
final Listener listener = this.listener;
listener.onData(this, frame, new Callback()
{
@Override
public void succeeded()
{
// TODO: notify flow control
}
@Override
public void failed(Throwable x)
{
// TODO: bail out
}
});
return true;
}
}

View File

@ -0,0 +1,28 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.util.Callback;
public interface ISession extends Session
{
public void frame(Frame frame, Callback callback);
}

View File

@ -19,8 +19,11 @@
package org.eclipse.jetty.http2;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
public interface IStream extends Stream
{
public void setListener(Listener listener);
public boolean process(DataFrame frame);
}

View File

@ -32,11 +32,17 @@ public interface Stream
public void data(DataFrame frame, Callback callback);
public Object getAttribute(String key);
public void setAttribute(String key, Object value);
public Object removeAttribute(String key);
// TODO: see SPDY's Stream
public interface Listener
{
public void onData(Stream stream, DataFrame frame);
public void onData(Stream stream, DataFrame frame, Callback callback);
public void onFailure(Stream stream, Throwable x);
@ -45,7 +51,7 @@ public interface Stream
public static class Adapter implements Listener
{
@Override
public void onData(Stream stream, DataFrame frame)
public void onData(Stream stream, DataFrame frame, Callback callback)
{
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.http2.frames;
public interface Flag
{
public static final int NONE = 0x00;
public static final int END_STREAM = 0x01;
public static final int ACK = END_STREAM;
public static final int END_SEGMENT = 0x02;

View File

@ -0,0 +1,120 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.Flag;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class DataGenerator extends FrameGenerator
{
public DataGenerator(HeaderGenerator headerGenerator)
{
super(headerGenerator);
}
@Override
public void generate(ByteBufferPool.Lease lease, Frame frame, Callback callback)
{
DataFrame dataFrame = (DataFrame)frame;
generateData(lease, dataFrame.getStreamId(), dataFrame.getData(), dataFrame.isEndStream(), false, null);
}
public void generateData(ByteBufferPool.Lease lease, int streamId, ByteBuffer data, boolean last, boolean compress, byte[] paddingBytes)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
int paddingLength = paddingBytes == null ? 0 : paddingBytes.length;
// Leave space for at least one byte of content.
if (paddingLength > Frame.MAX_LENGTH - 3)
throw new IllegalArgumentException("Invalid padding length: " + paddingLength);
if (compress)
throw new IllegalArgumentException("Data compression not supported");
int extraPaddingBytes = paddingLength > 0xFF ? 2 : paddingLength > 0 ? 1 : 0;
int dataLength = data.remaining();
// Can we fit just one frame ?
if (dataLength + extraPaddingBytes + paddingLength <= Frame.MAX_LENGTH)
{
generateData(lease, streamId, data, last, compress, extraPaddingBytes, paddingBytes);
}
else
{
int dataBytesPerFrame = Frame.MAX_LENGTH - extraPaddingBytes - paddingLength;
int frames = dataLength / dataBytesPerFrame;
if (frames * dataBytesPerFrame != dataLength)
{
++frames;
}
int limit = data.limit();
for (int i = 1; i <= frames; ++i)
{
data.limit(Math.min(dataBytesPerFrame * i, limit));
ByteBuffer slice = data.slice();
data.position(data.limit());
generateData(lease, streamId, slice, i == frames && last, compress, extraPaddingBytes, paddingBytes);
}
}
}
private void generateData(ByteBufferPool.Lease lease, int streamId, ByteBuffer data, boolean last, boolean compress, int extraPaddingBytes, byte[] paddingBytes)
{
int paddingLength = paddingBytes == null ? 0 : paddingBytes.length;
int length = extraPaddingBytes + data.remaining() + paddingLength;
int flags = Flag.NONE;
if (last)
flags |= Flag.END_STREAM;
if (extraPaddingBytes > 0)
flags |= Flag.PADDING_LOW;
if (extraPaddingBytes > 1)
flags |= Flag.PADDING_HIGH;
if (compress)
flags |= Flag.COMPRESS;
ByteBuffer header = generateHeader(lease, FrameType.DATA, Frame.HEADER_LENGTH + extraPaddingBytes, length, flags, streamId);
if (extraPaddingBytes == 2)
{
header.putShort((short)paddingLength);
}
else if (extraPaddingBytes == 1)
{
header.put((byte)paddingLength);
}
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
lease.append(data, false);
if (paddingBytes != null)
{
lease.append(ByteBuffer.wrap(paddingBytes), false);
}
}
}

View File

@ -0,0 +1,48 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.Callback;
public abstract class FrameGenerator
{
private final HeaderGenerator headerGenerator;
protected FrameGenerator(HeaderGenerator headerGenerator)
{
this.headerGenerator = headerGenerator;
}
public abstract void generate(ByteBufferPool.Lease lease, Frame frame, Callback callback);
protected ByteBuffer generateHeader(ByteBufferPool.Lease lease, FrameType frameType, int length, int flags, int streamId)
{
return generateHeader(lease, frameType, Frame.HEADER_LENGTH + length, length, flags, streamId);
}
protected ByteBuffer generateHeader(ByteBufferPool.Lease lease, FrameType frameType, int capacity, int length, int flags, int streamId)
{
return headerGenerator.generate(lease, frameType, capacity, length, flags, streamId);
}
}

View File

@ -18,284 +18,69 @@
package org.eclipse.jetty.http2.generator;
import java.nio.ByteBuffer;
import java.util.Map;
import org.eclipse.jetty.http2.frames.Flag;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.hpack.HpackEncoder;
import org.eclipse.jetty.http2.hpack.MetaData;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class Generator
{
private final ByteBufferPool byteBufferPool;
private final HpackEncoder encoder;
private final FrameGenerator[] generators;
public Generator(ByteBufferPool byteBufferPool)
{
this.byteBufferPool = byteBufferPool;
this.encoder = new HpackEncoder();
HeaderGenerator headerGenerator = new HeaderGenerator();
HpackEncoder encoder = new HpackEncoder();
this.generators = new FrameGenerator[FrameType.values().length];
this.generators[FrameType.DATA.getType()] = new DataGenerator(headerGenerator);
this.generators[FrameType.HEADERS.getType()] = new HeadersGenerator(headerGenerator, encoder);
this.generators[FrameType.PRIORITY.getType()] = new PriorityGenerator(headerGenerator);
this.generators[FrameType.RST_STREAM.getType()] = new ResetGenerator(headerGenerator);
this.generators[FrameType.SETTINGS.getType()] = new SettingsGenerator(headerGenerator);
this.generators[FrameType.PUSH_PROMISE.getType()] = null; // TODO
this.generators[FrameType.PING.getType()] = new PingGenerator(headerGenerator);
this.generators[FrameType.GO_AWAY.getType()] = new GoAwayGenerator(headerGenerator);
this.generators[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateGenerator(headerGenerator);
this.generators[FrameType.CONTINUATION.getType()] = null; // TODO
this.generators[FrameType.ALTSVC.getType()] = null; // TODO
this.generators[FrameType.BLOCKED.getType()] = null; // TODO
}
public ByteBufferPool.Lease generateData(int streamId, ByteBuffer data, boolean last, boolean compress, byte[] paddingBytes)
public LeaseCallback generate(Frame frame, Callback callback)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
int paddingLength = paddingBytes == null ? 0 : paddingBytes.length;
// Leave space for at least one byte of content.
if (paddingLength > Frame.MAX_LENGTH - 3)
throw new IllegalArgumentException("Invalid padding length: " + paddingLength);
int extraPaddingBytes = paddingLength > 0xFF ? 2 : paddingLength > 0 ? 1 : 0;
// TODO: here we should compress the data, and then reason on the data length !
int dataLength = data.remaining();
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
// Can we fit just one frame ?
if (dataLength + extraPaddingBytes + paddingLength <= Frame.MAX_LENGTH)
{
generateData(lease, streamId, data, last, compress, extraPaddingBytes, paddingBytes);
}
else
{
int dataBytesPerFrame = Frame.MAX_LENGTH - extraPaddingBytes - paddingLength;
int frames = dataLength / dataBytesPerFrame;
if (frames * dataBytesPerFrame != dataLength)
{
++frames;
}
int limit = data.limit();
for (int i = 1; i <= frames; ++i)
{
data.limit(Math.min(dataBytesPerFrame * i, limit));
ByteBuffer slice = data.slice();
data.position(data.limit());
generateData(lease, streamId, slice, i == frames && last, compress, extraPaddingBytes, paddingBytes);
}
}
LeaseCallback lease = new LeaseCallback(byteBufferPool, callback);
generators[frame.getType().getType()].generate(lease, frame, callback);
return lease;
}
public ByteBufferPool.Lease generateHeaders(int streamId, MetaData metaData, boolean contentFollows, byte[] paddingBytes)
public static class LeaseCallback extends ByteBufferPool.Lease implements Callback
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
int paddingLength = paddingBytes == null ? 0 : paddingBytes.length;
// Leave space for at least one byte of content.
if (paddingLength > Frame.MAX_LENGTH - 3)
throw new IllegalArgumentException("Invalid padding length: " + paddingLength);
private final Callback callback;
int extraPaddingBytes = paddingLength > 0xFF ? 2 : paddingLength > 0 ? 1 : 0;
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
encoder.encode(metaData, lease);
long hpackLength = lease.getTotalLength();
long length = extraPaddingBytes + hpackLength + paddingLength;
if (length > Frame.MAX_LENGTH)
throw new IllegalArgumentException("Invalid headers, too big");
int flags = Flag.END_HEADERS;
if (!contentFollows)
flags |= Flag.END_STREAM;
if (extraPaddingBytes > 0)
flags |= Flag.PADDING_LOW;
if (extraPaddingBytes > 1)
flags |= Flag.PADDING_HIGH;
ByteBuffer header = generateHeader(FrameType.HEADERS, Frame.HEADER_LENGTH + extraPaddingBytes, (int)length, flags, streamId);
if (extraPaddingBytes == 2)
header.putShort((short)paddingLength);
else if (extraPaddingBytes == 1)
header.put((byte)paddingLength);
BufferUtil.flipToFlush(header, 0);
lease.prepend(header, true);
if (paddingBytes != null)
public LeaseCallback(ByteBufferPool byteBufferPool, Callback callback)
{
lease.append(ByteBuffer.wrap(paddingBytes), false);
super(byteBufferPool);
this.callback = callback;
}
return lease;
}
public ByteBufferPool.Lease generatePriority(int streamId, int dependentStreamId, int weight, boolean exclusive)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
if (dependentStreamId < 0)
throw new IllegalArgumentException("Invalid dependent stream id: " + dependentStreamId);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
ByteBuffer header = generateHeader(FrameType.PRIORITY, 5, 0, dependentStreamId);
if (exclusive)
streamId |= 0x80_00_00_00;
header.putInt(streamId);
header.put((byte)weight);
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
return lease;
}
public ByteBufferPool.Lease generateReset(int streamId, int error)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
ByteBuffer header = generateHeader(FrameType.RST_STREAM, 4, 0, streamId);
header.putInt(error);
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
return lease;
}
public ByteBufferPool.Lease generateSettings(Map<Integer, Integer> settings, boolean reply)
{
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
ByteBuffer header = generateHeader(FrameType.SETTINGS, 5 * settings.size(), reply ? 0x01 : 0x00, 0);
for (Map.Entry<Integer, Integer> entry : settings.entrySet())
@Override
public void succeeded()
{
header.put(entry.getKey().byteValue());
header.putInt(entry.getValue());
recycle();
callback.succeeded();
}
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
return lease;
}
public ByteBufferPool.Lease generatePing(byte[] payload, boolean reply)
{
if (payload.length != 8)
throw new IllegalArgumentException("Invalid payload length: " + payload.length);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
ByteBuffer header = generateHeader(FrameType.PING, 8, reply ? 0x01 : 0x00, 0);
header.put(payload);
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
return lease;
}
public ByteBufferPool.Lease generateGoAway(int lastStreamId, int error, byte[] payload)
{
if (lastStreamId < 0)
throw new IllegalArgumentException("Invalid last stream id: " + lastStreamId);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
int length = 4 + 4 + (payload != null ? payload.length : 0);
ByteBuffer header = generateHeader(FrameType.GO_AWAY, length, 0, 0);
header.putInt(lastStreamId);
header.putInt(error);
if (payload != null)
@Override
public void failed(Throwable x)
{
header.put(payload);
recycle();
callback.failed(x);
}
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
return lease;
}
public ByteBufferPool.Lease generateWindowUpdate(int streamId, int windowUpdate)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
if (windowUpdate < 0)
throw new IllegalArgumentException("Invalid window update: " + windowUpdate);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
ByteBuffer header = generateHeader(FrameType.WINDOW_UPDATE, 4, 0, streamId);
header.putInt(windowUpdate);
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
return lease;
}
private void generateData(ByteBufferPool.Lease lease, int streamId, ByteBuffer data, boolean last, boolean compress, int extraPaddingBytes, byte[] paddingBytes)
{
int paddingLength = paddingBytes == null ? 0 : paddingBytes.length;
int length = extraPaddingBytes + data.remaining() + paddingLength;
int flags = 0;
if (last)
flags |= Flag.END_STREAM;
if (extraPaddingBytes > 0)
flags |= Flag.PADDING_LOW;
if (extraPaddingBytes > 1)
flags |= Flag.PADDING_HIGH;
if (compress)
flags |= Flag.COMPRESS;
ByteBuffer header = generateHeader(FrameType.DATA, Frame.HEADER_LENGTH + extraPaddingBytes, length, flags, streamId);
if (extraPaddingBytes == 2)
header.putShort((short)paddingLength);
else if (extraPaddingBytes == 1)
header.put((byte)paddingLength);
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
lease.append(data, false);
if (paddingBytes != null)
{
lease.append(ByteBuffer.wrap(paddingBytes), false);
}
}
private ByteBuffer generateHeader(FrameType frameType, int length, int flags, int streamId)
{
return generateHeader(frameType, Frame.HEADER_LENGTH + length, length, flags, streamId);
}
private ByteBuffer generateHeader(FrameType frameType, int capacity, int length, int flags, int streamId)
{
ByteBuffer header = byteBufferPool.acquire(capacity, true);
BufferUtil.clearToFill(header);
header.putShort((short)length);
header.put((byte)frameType.getType());
header.put((byte)flags);
header.putInt(streamId);
return header;
}
}

View File

@ -0,0 +1,64 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.Flag;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class GoAwayGenerator extends FrameGenerator
{
public GoAwayGenerator(HeaderGenerator headerGenerator)
{
super(headerGenerator);
}
@Override
public void generate(ByteBufferPool.Lease lease, Frame frame, Callback callback)
{
GoAwayFrame goAwayFrame = (GoAwayFrame)frame;
generateGoAway(lease, goAwayFrame.getLastStreamId(), goAwayFrame.getError(), null);
}
public void generateGoAway(ByteBufferPool.Lease lease, int lastStreamId, int error, byte[] payload)
{
if (lastStreamId < 0)
throw new IllegalArgumentException("Invalid last stream id: " + lastStreamId);
int length = 4 + 4 + (payload != null ? payload.length : 0);
ByteBuffer header = generateHeader(lease, FrameType.GO_AWAY, length, Flag.NONE, 0);
header.putInt(lastStreamId);
header.putInt(error);
if (payload != null)
{
header.put(payload);
}
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
}
}

View File

@ -0,0 +1,37 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.io.ByteBufferPool;
public class HeaderGenerator
{
public ByteBuffer generate(ByteBufferPool.Lease lease, FrameType frameType, int capacity, int length, int flags, int streamId)
{
ByteBuffer header = lease.acquire(capacity, true);
header.putShort((short)length);
header.put((byte)frameType.getType());
header.put((byte)flags);
header.putInt(streamId);
return header;
}
}

View File

@ -0,0 +1,92 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.Flag;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.hpack.HpackEncoder;
import org.eclipse.jetty.http2.hpack.MetaData;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class HeadersGenerator extends FrameGenerator
{
private final HpackEncoder encoder;
public HeadersGenerator(HeaderGenerator headerGenerator, HpackEncoder encoder)
{
super(headerGenerator);
this.encoder = encoder;
}
@Override
public void generate(ByteBufferPool.Lease lease, Frame frame, Callback callback)
{
HeadersFrame headersFrame = (HeadersFrame)frame;
generate(lease, headersFrame.getStreamId(), headersFrame.getMetaData(), !headersFrame.isEndStream(), null);
}
private void generate(ByteBufferPool.Lease lease, int streamId, MetaData metaData, boolean contentFollows, byte[] paddingBytes)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
int paddingLength = paddingBytes == null ? 0 : paddingBytes.length;
// Leave space for at least one byte of content.
if (paddingLength > Frame.MAX_LENGTH - 3)
throw new IllegalArgumentException("Invalid padding length: " + paddingLength);
int extraPaddingBytes = paddingLength > 0xFF ? 2 : paddingLength > 0 ? 1 : 0;
encoder.encode(metaData, lease);
long hpackLength = lease.getTotalLength();
long length = extraPaddingBytes + hpackLength + paddingLength;
if (length > Frame.MAX_LENGTH)
throw new IllegalArgumentException("Invalid headers, too big");
int flags = Flag.END_HEADERS;
if (!contentFollows)
flags |= Flag.END_STREAM;
if (extraPaddingBytes > 0)
flags |= Flag.PADDING_LOW;
if (extraPaddingBytes > 1)
flags |= Flag.PADDING_HIGH;
ByteBuffer header = generateHeader(lease, FrameType.HEADERS, Frame.HEADER_LENGTH + extraPaddingBytes, (int)length, flags, streamId);
if (extraPaddingBytes == 2)
header.putShort((short)paddingLength);
else if (extraPaddingBytes == 1)
header.put((byte)paddingLength);
BufferUtil.flipToFlush(header, 0);
lease.prepend(header, true);
if (paddingBytes != null)
{
lease.append(ByteBuffer.wrap(paddingBytes), false);
}
}
}

View File

@ -0,0 +1,57 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.Flag;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.frames.PingFrame;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class PingGenerator extends FrameGenerator
{
public PingGenerator(HeaderGenerator headerGenerator)
{
super(headerGenerator);
}
@Override
public void generate(ByteBufferPool.Lease lease, Frame frame, Callback callback)
{
PingFrame pingFrame = (PingFrame)frame;
generatePing(lease, pingFrame.getPayload(), pingFrame.isReply());
}
public void generatePing(ByteBufferPool.Lease lease, byte[] payload, boolean reply)
{
if (payload.length != 8)
throw new IllegalArgumentException("Invalid payload length: " + payload.length);
ByteBuffer header = generateHeader(lease, FrameType.PING, 8, reply ? Flag.ACK : Flag.NONE, 0);
header.put(payload);
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
}
}

View File

@ -0,0 +1,64 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.Flag;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.frames.PriorityFrame;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class PriorityGenerator extends FrameGenerator
{
public PriorityGenerator(HeaderGenerator headerGenerator)
{
super(headerGenerator);
}
@Override
public void generate(ByteBufferPool.Lease lease, Frame frame, Callback callback)
{
PriorityFrame priorityFrame = (PriorityFrame)frame;
generatePriority(lease, priorityFrame.getStreamId(), priorityFrame.getDependentStreamId(), priorityFrame.getWeight(), priorityFrame.isExclusive());
}
public void generatePriority(ByteBufferPool.Lease lease, int streamId, int dependentStreamId, int weight, boolean exclusive)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
if (dependentStreamId < 0)
throw new IllegalArgumentException("Invalid dependent stream id: " + dependentStreamId);
ByteBuffer header = generateHeader(lease, FrameType.PRIORITY, 5, Flag.NONE, dependentStreamId);
if (exclusive)
streamId |= 0x80_00_00_00;
header.putInt(streamId);
header.put((byte)weight);
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
}
}

View File

@ -0,0 +1,57 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.Flag;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class ResetGenerator extends FrameGenerator
{
public ResetGenerator(HeaderGenerator headerGenerator)
{
super(headerGenerator);
}
@Override
public void generate(ByteBufferPool.Lease lease, Frame frame, Callback callback)
{
ResetFrame resetFrame = (ResetFrame)frame;
generateReset(lease, resetFrame.getStreamId(), resetFrame.getError());
}
public void generateReset(ByteBufferPool.Lease lease, int streamId, int error)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
ByteBuffer header = generateHeader(lease, FrameType.RST_STREAM, 4, Flag.NONE, streamId);
header.putInt(error);
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
}
}

View File

@ -0,0 +1,59 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.generator;
import java.nio.ByteBuffer;
import java.util.Map;
import org.eclipse.jetty.http2.frames.Flag;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class SettingsGenerator extends FrameGenerator
{
public SettingsGenerator(HeaderGenerator headerGenerator)
{
super(headerGenerator);
}
@Override
public void generate(ByteBufferPool.Lease lease, Frame frame, Callback callback)
{
SettingsFrame settingsFrame = (SettingsFrame)frame;
generateSettings(lease, settingsFrame.getSettings(), settingsFrame.isReply());
}
public void generateSettings(ByteBufferPool.Lease lease, Map<Integer, Integer> settings, boolean reply)
{
ByteBuffer header = generateHeader(lease, FrameType.SETTINGS, 5 * settings.size(), reply ? Flag.ACK : Flag.NONE, 0);
for (Map.Entry<Integer, Integer> entry : settings.entrySet())
{
header.put(entry.getKey().byteValue());
header.putInt(entry.getValue());
}
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
}
}

View File

@ -0,0 +1,59 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.Flag;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class WindowUpdateGenerator extends FrameGenerator
{
public WindowUpdateGenerator(HeaderGenerator headerGenerator)
{
super(headerGenerator);
}
@Override
public void generate(ByteBufferPool.Lease lease, Frame frame, Callback callback)
{
WindowUpdateFrame windowUpdateFrame = (WindowUpdateFrame)frame;
generateWindowUpdate(lease, windowUpdateFrame.getStreamId(), windowUpdateFrame.getWindowDelta());
}
public void generateWindowUpdate(ByteBufferPool.Lease lease, int streamId, int windowUpdate)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
if (windowUpdate < 0)
throw new IllegalArgumentException("Invalid window update: " + windowUpdate);
ByteBuffer header = generateHeader(lease, FrameType.WINDOW_UPDATE, 4, Flag.NONE, streamId);
header.putInt(windowUpdate);
BufferUtil.flipToFlush(header, 0);
lease.append(header, true);
}
}

View File

@ -23,7 +23,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.generator.DataGenerator;
import org.eclipse.jetty.http2.generator.HeaderGenerator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
@ -132,7 +133,7 @@ public class DataGenerateParseTest
private List<DataFrame> testGenerateParse(int paddingLength, ByteBuffer... data)
{
Generator generator = new Generator(byteBufferPool);
DataGenerator generator = new DataGenerator(new HeaderGenerator());
// Iterate a few times to be sure generator and parser are properly reset.
final List<DataFrame> frames = new ArrayList<>();
@ -141,7 +142,7 @@ public class DataGenerateParseTest
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
for (int j = 1; j <= data.length; ++j)
{
lease = lease.merge(generator.generateData(13, data[j - 1].slice(), j == data.length, false, new byte[paddingLength]));
generator.generateData(lease, 13, data[j - 1].slice(), j == data.length, false, new byte[paddingLength]);
}
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
@ -167,9 +168,10 @@ public class DataGenerateParseTest
@Test
public void testGenerateParseOneByteAtATime()
{
Generator generator = new Generator(byteBufferPool);
DataGenerator generator = new DataGenerator(new HeaderGenerator());
ByteBufferPool.Lease lease = generator.generateData(13, ByteBuffer.wrap(largeContent).slice(), true, false, new byte[1024]);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generateData(lease, 13, ByteBuffer.wrap(largeContent).slice(), true, false, new byte[1024]);
final List<DataFrame> frames = new ArrayList<>();
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()

View File

@ -23,7 +23,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.generator.GoAwayGenerator;
import org.eclipse.jetty.http2.generator.HeaderGenerator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
@ -37,7 +38,7 @@ public class GoAwayGenerateParseTest
@Test
public void testGenerateParse() throws Exception
{
Generator generator = new Generator(byteBufferPool);
GoAwayGenerator generator = new GoAwayGenerator(new HeaderGenerator());
int lastStreamId = 13;
int error = 17;
@ -46,7 +47,8 @@ public class GoAwayGenerateParseTest
final List<GoAwayFrame> frames = new ArrayList<>();
for (int i = 0; i < 2; ++i)
{
ByteBufferPool.Lease lease = generator.generateGoAway(lastStreamId, error, null);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generateGoAway(lease, lastStreamId, error, null);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override
@ -77,7 +79,7 @@ public class GoAwayGenerateParseTest
@Test
public void testGenerateParseOneByteAtATime() throws Exception
{
Generator generator = new Generator(byteBufferPool);
GoAwayGenerator generator = new GoAwayGenerator(new HeaderGenerator());
int lastStreamId = 13;
int error = 17;
@ -85,7 +87,8 @@ public class GoAwayGenerateParseTest
new Random().nextBytes(payload);
final List<GoAwayFrame> frames = new ArrayList<>();
ByteBufferPool.Lease lease = generator.generateGoAway(lastStreamId, error, payload);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generateGoAway(lease, lastStreamId, error, payload);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override

View File

@ -23,7 +23,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.generator.HeaderGenerator;
import org.eclipse.jetty.http2.generator.PingGenerator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
@ -37,7 +38,7 @@ public class PingGenerateParseTest
@Test
public void testGenerateParse() throws Exception
{
Generator generator = new Generator(byteBufferPool);
PingGenerator generator = new PingGenerator(new HeaderGenerator());
byte[] payload = new byte[8];
new Random().nextBytes(payload);
@ -46,7 +47,8 @@ public class PingGenerateParseTest
final List<PingFrame> frames = new ArrayList<>();
for (int i = 0; i < 2; ++i)
{
ByteBufferPool.Lease lease = generator.generatePing(payload, true);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generatePing(lease, payload, true);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override
@ -76,13 +78,14 @@ public class PingGenerateParseTest
@Test
public void testGenerateParseOneByteAtATime() throws Exception
{
Generator generator = new Generator(byteBufferPool);
PingGenerator generator = new PingGenerator(new HeaderGenerator());
byte[] payload = new byte[8];
new Random().nextBytes(payload);
final List<PingFrame> frames = new ArrayList<>();
ByteBufferPool.Lease lease = generator.generatePing(payload, true);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generatePing(lease, payload, true);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override

View File

@ -22,7 +22,8 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.generator.HeaderGenerator;
import org.eclipse.jetty.http2.generator.PriorityGenerator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
@ -36,7 +37,7 @@ public class PriorityGenerateParseTest
@Test
public void testGenerateParse() throws Exception
{
Generator generator = new Generator(byteBufferPool);
PriorityGenerator generator = new PriorityGenerator(new HeaderGenerator());
int streamId = 13;
int dependentStreamId = 17;
@ -47,7 +48,8 @@ public class PriorityGenerateParseTest
final List<PriorityFrame> frames = new ArrayList<>();
for (int i = 0; i < 2; ++i)
{
ByteBufferPool.Lease lease = generator.generatePriority(streamId, dependentStreamId, weight, exclusive);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generatePriority(lease, streamId, dependentStreamId, weight, exclusive);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override
@ -79,7 +81,7 @@ public class PriorityGenerateParseTest
@Test
public void testGenerateParseOneByteAtATime() throws Exception
{
Generator generator = new Generator(byteBufferPool);
PriorityGenerator generator = new PriorityGenerator(new HeaderGenerator());
int streamId = 13;
int dependentStreamId = 17;
@ -87,7 +89,8 @@ public class PriorityGenerateParseTest
boolean exclusive = true;
final List<PriorityFrame> frames = new ArrayList<>();
ByteBufferPool.Lease lease = generator.generatePriority(streamId, dependentStreamId, weight, exclusive);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generatePriority(lease, streamId, dependentStreamId, weight, exclusive);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override

View File

@ -22,7 +22,8 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.generator.HeaderGenerator;
import org.eclipse.jetty.http2.generator.ResetGenerator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
@ -36,7 +37,7 @@ public class ResetGenerateParseTest
@Test
public void testGenerateParse() throws Exception
{
Generator generator = new Generator(byteBufferPool);
ResetGenerator generator = new ResetGenerator(new HeaderGenerator());
int streamId = 13;
int error = 17;
@ -45,7 +46,8 @@ public class ResetGenerateParseTest
final List<ResetFrame> frames = new ArrayList<>();
for (int i = 0; i < 2; ++i)
{
ByteBufferPool.Lease lease = generator.generateReset(streamId, error);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generateReset(lease, streamId, error);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override
@ -75,13 +77,14 @@ public class ResetGenerateParseTest
@Test
public void testGenerateParseOneByteAtATime() throws Exception
{
Generator generator = new Generator(byteBufferPool);
ResetGenerator generator = new ResetGenerator(new HeaderGenerator());
int streamId = 13;
int error = 17;
final List<ResetFrame> frames = new ArrayList<>();
ByteBufferPool.Lease lease = generator.generateReset(streamId, error);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generateReset(lease, streamId, error);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override

View File

@ -26,7 +26,8 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.generator.HeaderGenerator;
import org.eclipse.jetty.http2.generator.SettingsGenerator;
import org.eclipse.jetty.http2.parser.ErrorCode;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
@ -69,13 +70,14 @@ public class SettingsGenerateParseTest
private List<SettingsFrame> testGenerateParse(Map<Integer, Integer> settings)
{
Generator generator = new Generator(byteBufferPool);
SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator());
// Iterate a few times to be sure generator and parser are properly reset.
final List<SettingsFrame> frames = new ArrayList<>();
for (int i = 0; i < 2; ++i)
{
ByteBufferPool.Lease lease = generator.generateSettings(settings, true);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generateSettings(lease, settings, true);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override
@ -102,10 +104,11 @@ public class SettingsGenerateParseTest
@Test
public void testGenerateParseInvalidSettings() throws Exception
{
Generator generator = new Generator(byteBufferPool);
SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator());
Map<Integer, Integer> settings1 = new HashMap<>();
settings1.put(13, 17);
ByteBufferPool.Lease lease = generator.generateSettings(settings1, true);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generateSettings(lease, settings1, true);
// Modify the length of the frame to make it invalid
ByteBuffer bytes = lease.getByteBuffers().get(0);
bytes.putShort(0, (short)(bytes.getShort(0) - 1));
@ -134,7 +137,7 @@ public class SettingsGenerateParseTest
@Test
public void testGenerateParseOneByteAtATime() throws Exception
{
Generator generator = new Generator(byteBufferPool);
SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator());
Map<Integer, Integer> settings1 = new HashMap<>();
int key = 13;
@ -142,7 +145,8 @@ public class SettingsGenerateParseTest
settings1.put(key, value);
final List<SettingsFrame> frames = new ArrayList<>();
ByteBufferPool.Lease lease = generator.generateSettings(settings1, true);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generateSettings(lease, settings1, true);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override

View File

@ -22,7 +22,8 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.generator.HeaderGenerator;
import org.eclipse.jetty.http2.generator.WindowUpdateGenerator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
@ -36,7 +37,7 @@ public class WindowUpdateGenerateParseTest
@Test
public void testGenerateParse() throws Exception
{
Generator generator = new Generator(byteBufferPool);
WindowUpdateGenerator generator = new WindowUpdateGenerator(new HeaderGenerator());
int streamId = 13;
int windowUpdate = 17;
@ -45,7 +46,8 @@ public class WindowUpdateGenerateParseTest
final List<WindowUpdateFrame> frames = new ArrayList<>();
for (int i = 0; i < 2; ++i)
{
ByteBufferPool.Lease lease = generator.generateWindowUpdate(streamId, windowUpdate);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generateWindowUpdate(lease, streamId, windowUpdate);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override
@ -75,13 +77,14 @@ public class WindowUpdateGenerateParseTest
@Test
public void testGenerateParseOneByteAtATime() throws Exception
{
Generator generator = new Generator(byteBufferPool);
WindowUpdateGenerator generator = new WindowUpdateGenerator(new HeaderGenerator());
int streamId = 13;
int windowUpdate = 17;
final List<WindowUpdateFrame> frames = new ArrayList<>();
ByteBufferPool.Lease lease = generator.generateWindowUpdate(streamId, windowUpdate);
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
generator.generateWindowUpdate(lease, streamId, windowUpdate);
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override

View File

@ -54,6 +54,17 @@
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,62 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.server;
import java.nio.ByteBuffer;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.Callback;
public class ByteBufferCallback implements Callback
{
private final ByteBufferPool byteBufferPool;
private final ByteBuffer buffer;
private final Callback callback;
public ByteBufferCallback(ByteBufferPool byteBufferPool, ByteBuffer buffer, Callback callback)
{
this.byteBufferPool = byteBufferPool;
this.buffer = buffer;
this.callback = callback;
}
public ByteBuffer getByteBuffer()
{
return buffer;
}
@Override
public void succeeded()
{
recycle();
callback.succeeded();
}
@Override
public void failed(Throwable x)
{
recycle();
callback.failed(x);
}
private void recycle()
{
byteBufferPool.release(buffer);
}
}

View File

@ -25,18 +25,22 @@ import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class HTTP2ServerConnectionFactory extends AbstractConnectionFactory
{
private static final Logger LOG = Log.getLogger(HTTP2ServerConnectionFactory.class);
private static final String CHANNEL_ATTRIBUTE = HttpChannelOverHTTP2.class.getName();
private final HttpConfiguration httpConfiguration;
public HTTP2ServerConnectionFactory(HttpConfiguration httpConfiguration)
@ -50,7 +54,8 @@ public class HTTP2ServerConnectionFactory extends AbstractConnectionFactory
{
Session.Listener listener = new HTTPServerSessionListener(connector, httpConfiguration, endPoint);
HTTP2Session session = new HTTP2ServerSession(listener);
Generator generator = new Generator(connector.getByteBufferPool());
HTTP2Session session = new HTTP2ServerSession(endPoint, generator, listener);
Parser parser = new Parser(connector.getByteBufferPool(), session);
HTTP2Connection connection = new HTTP2Connection(connector.getByteBufferPool(), connector.getExecutor(),
@ -67,7 +72,6 @@ public class HTTP2ServerConnectionFactory extends AbstractConnectionFactory
public HTTPServerSessionListener(Connector connector, HttpConfiguration httpConfiguration, EndPoint endPoint)
{
this.connector = connector;
this.httpConfiguration = httpConfiguration;
this.endPoint = endPoint;
@ -81,29 +85,24 @@ public class HTTP2ServerConnectionFactory extends AbstractConnectionFactory
HttpTransportOverHTTP2 transport = new HttpTransportOverHTTP2();
HttpInputOverHTTP2 input = new HttpInputOverHTTP2();
HttpChannelOverHTTP2 channel = new HttpChannelOverHTTP2(connector, httpConfiguration, endPoint, transport, input);
// TODO: link channel to stream.
stream.setAttribute(CHANNEL_ATTRIBUTE, channel);
// if (frame.getMetaData().isEmpty())
// {
// TODO: abort.
// return null;
// }
channel.requestStart(frame);
channel.requestHeaders(frame);
return frame.isEndStream() ? null : this;
}
@Override
public void onData(Stream stream, DataFrame frame)
public void onData(Stream stream, DataFrame frame, Callback callback)
{
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(CHANNEL_ATTRIBUTE);
channel.requestContent(frame, callback);
}
@Override
public void onFailure(Stream stream, Throwable x)
{
// TODO
}
}
}

View File

@ -18,22 +18,19 @@
package org.eclipse.jetty.http2.server;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.HTTP2Stream;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.io.EndPoint;
public class HTTP2ServerSession extends HTTP2Session
{
private final ConcurrentMap<Integer, IStream> streams = new ConcurrentHashMap<>();
public HTTP2ServerSession(Listener listener)
public HTTP2ServerSession(EndPoint endPoint, Generator generator, Listener listener)
{
super(listener);
super(endPoint, generator, listener);
}
@Override
@ -41,9 +38,10 @@ public class HTTP2ServerSession extends HTTP2Session
{
// TODO: handle max concurrent streams
// TODO: handle duplicate streams
// TODO: handle empty headers
IStream stream = new HTTP2Stream();
IStream existing = streams.putIfAbsent(stream.getId(), stream);
IStream stream = new HTTP2Stream(this);
IStream existing = putIfAbsent(stream);
if (existing == null)
{
Stream.Listener listener = notifyNewStream(stream, frame);

View File

@ -19,11 +19,16 @@
package org.eclipse.jetty.http2.server;
import java.nio.ByteBuffer;
import java.util.List;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.hpack.MetaData;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpChannel;
@ -31,23 +36,83 @@ import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpInput;
import org.eclipse.jetty.server.HttpTransport;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class HttpChannelOverHTTP2 extends HttpChannel<ByteBuffer>
public class HttpChannelOverHTTP2 extends HttpChannel<ByteBufferCallback>
{
public HttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
public HttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBufferCallback> input)
{
super(connector, configuration, endPoint, transport, input);
}
public void requestStart(HeadersFrame frame)
public void requestHeaders(HeadersFrame frame)
{
// TODO: extract method, etc.
String method = null;
HttpURI uri = new HttpURI("/foo/bar");
HttpVersion version = null;
MetaData metaData = frame.getMetaData();
if (!metaData.isRequest())
{
badMessage(400, null);
return;
}
MetaData.Request requestMetaData = (MetaData.Request)metaData;
String method = requestMetaData.getMethod();
HttpURI uri = new HttpURI(requestMetaData.getPath());
HttpVersion version = HttpVersion.HTTP_2_0;
startRequest(method, uri, version);
// TODO: See SPDY's
HttpScheme scheme = requestMetaData.getScheme();
if (scheme != null)
{
getRequest().setScheme(scheme.asString());
}
parsedHostHeader(requestMetaData.getHost(), requestMetaData.getPort());
List<HttpField> fields = requestMetaData.getFields();
for (int i = 0; i < fields.size(); ++i)
{
HttpField field = fields.get(i);
parsedHeader(field);
}
headerComplete();
if (frame.isEndStream())
{
messageComplete();
}
// TODO: pending refactoring of HttpChannel API.
// Here we "cheat", knowing that headerComplete() will always return true
// and that content() and messageComplete() will always return false.
// This is the only place where we process the channel.
execute(this);
}
public void requestContent(DataFrame frame, Callback callback)
{
// We must copy the data since we do not know when its bytes will be consumed.
ByteBufferPool byteBufferPool = getByteBufferPool();
ByteBuffer original = frame.getData();
final ByteBuffer copy = byteBufferPool.acquire(original.remaining(), original.isDirect());
BufferUtil.clearToFill(copy);
copy.put(original).flip();
// TODO: pending refactoring of HttpChannel API (see above).
content(new ByteBufferCallback(byteBufferPool, copy, callback));
if (frame.isEndStream())
{
messageComplete();
}
}
@Override
public boolean messageComplete()
{
super.messageComplete();
return false;
}
}

View File

@ -22,27 +22,35 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.server.QueuedHttpInput;
public class HttpInputOverHTTP2 extends QueuedHttpInput<ByteBuffer>
public class HttpInputOverHTTP2 extends QueuedHttpInput<ByteBufferCallback>
{
@Override
protected void onContentConsumed(ByteBuffer item)
protected int remaining(ByteBufferCallback item)
{
return item.getByteBuffer().remaining();
}
@Override
protected int remaining(ByteBuffer item)
protected int get(ByteBufferCallback item, byte[] buffer, int offset, int length)
{
return 0;
ByteBuffer byteBuffer = item.getByteBuffer();
length = Math.min(byteBuffer.remaining(), length);
byteBuffer.get(buffer, offset, length);
return length;
}
@Override
protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
protected void consume(ByteBufferCallback item, int length)
{
return 0;
ByteBuffer byteBuffer = item.getByteBuffer();
byteBuffer.position(byteBuffer.position() + length);
if (!byteBuffer.hasRemaining())
onContentConsumed(item);
}
@Override
protected void consume(ByteBuffer item, int length)
protected void onContentConsumed(ByteBufferCallback item)
{
item.succeeded();
}
}

View File

@ -19,21 +19,80 @@
package org.eclipse.jetty.http2.server;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.hpack.MetaData;
import org.eclipse.jetty.server.HttpTransport;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class HttpTransportOverHTTP2 implements HttpTransport
{
private final AtomicBoolean commit = new AtomicBoolean();
private final IStream stream = null;
private final HeadersFrame request = null;
@Override
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
{
MetaData.Request metaData = (MetaData.Request)request.getMetaData();
boolean isHeadRequest = HttpMethod.HEAD.is(metaData.getMethod());
boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
// TODO: the idea here is this:
// CallbackLease lease = new CallbackLease(callback);
// commit(lease, ...)
// stream.header(lease, frame)
// session.frame(lease, frame)
// generator.generate(lease, frame)
// generateHeader(lease, frame);
// bodyGenerator[frame.getType()].generateBody(lease, frame);
// stream.content(lease, frame)
// ...
// flush(lease)
//
// Problem is that in this way I need to aggregate multiple callbacks for the same lease.
// So it'd need another abstraction that is a Lease+Callback
if (commit.compareAndSet(false, true))
{
commit(info, !hasContent, !hasContent ? callback : new Callback.Adapter()
{
@Override
public void failed(Throwable x)
{
// TODO
}
});
}
else
{
// TODO
}
if (hasContent)
{
send(content, lastContent, callback);
}
}
private void commit(HttpGenerator.ResponseInfo info, boolean endStream, Callback callback)
{
MetaData metaData = new MetaData.Response(info.getStatus(), info.getHttpFields());
HeadersFrame frame = new HeadersFrame(stream.getId(), metaData, null, endStream);
stream.headers(frame, callback);
}
@Override
public void send(ByteBuffer content, boolean lastContent, Callback callback)
{
DataFrame frame = new DataFrame(stream.getId(), content, lastContent);
stream.data(frame, callback);
}
@Override

View File

@ -0,0 +1,128 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.hpack.MetaData;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.Callback;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
public class HTTP2ServerTest
{
private Server server;
private ServerConnector connector;
private String path;
private ByteBufferPool byteBufferPool;
private Generator generator;
private void startServer(HttpServlet servlet) throws Exception
{
server = new Server();
connector = new ServerConnector(server, new HTTP2ServerConnectionFactory(new HttpConfiguration()));
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(server, "/");
path = "/test";
context.addServlet(new ServletHolder(servlet), path);
byteBufferPool = new MappedByteBufferPool();
generator = new Generator(byteBufferPool);
server.start();
}
@After
public void dispose() throws Exception
{
server.stop();
}
@Test
public void testRequestResponseNoContent() throws Exception
{
startServer(new HttpServlet()
{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
}
});
String host = "localhost";
int port = connector.getLocalPort();
HttpFields fields = new HttpFields();
MetaData.Request metaData = new MetaData.Request(HttpScheme.HTTP, HttpMethod.GET.asString(),
host + ":" + port, host, port, path, fields);
HeadersFrame request = new HeadersFrame(1, metaData, null, true);
Generator.LeaseCallback lease = generator.generate(request, Callback.Adapter.INSTANCE);
try (SocketChannel client = SocketChannel.open(new InetSocketAddress(host, port)))
{
for (ByteBuffer buffer : lease.getByteBuffers())
{
client.write(buffer);
}
ByteBuffer buffer = ByteBuffer.allocate(2048);
client.read(buffer);
buffer.flip();
final AtomicReference<HeadersFrame> frameRef = new AtomicReference<>();
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
{
@Override
public boolean onHeaders(HeadersFrame frame)
{
frameRef.set(frame);
return false;
}
});
parser.parse(buffer);
HeadersFrame response = frameRef.get();
Assert.assertNotNull(response);
}
}
}