HTTP/2 revert Http2FrameWriter throws API change

Motivation:
2fd42cfc6b fixed a bug related to encoding headers but it also introduced a throws statement onto the Http2FrameWriter methods which write headers. This throws statement makes the API more verbose and is not necessary because we can communicate the failure in the ChannelFuture that is returned by these methods.

Modifications:
- Remove throws from all Http2FrameWriter methods.

Result:
Http2FrameWriter APIs do not propagate checked exceptions.
This commit is contained in:
Scott Mitchell 2017-01-26 09:03:42 -08:00
parent 81735c2535
commit e13da218e9
14 changed files with 73 additions and 73 deletions

View File

@ -41,14 +41,14 @@ public class DecoratingHttp2FrameWriter implements Http2FrameWriter {
@Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
boolean endStream, ChannelPromise promise) throws Http2Exception {
boolean endStream, ChannelPromise promise) {
return delegate.writeHeaders(ctx, streamId, headers, padding, endStream, promise);
}
@Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding,
boolean endStream, ChannelPromise promise) throws Http2Exception {
boolean endStream, ChannelPromise promise) {
return delegate
.writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream, promise);
}
@ -82,8 +82,7 @@ public class DecoratingHttp2FrameWriter implements Http2FrameWriter {
@Override
public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding, ChannelPromise promise)
throws Http2Exception {
Http2Headers headers, int padding, ChannelPromise promise) {
return delegate.writePushPromise(ctx, streamId, promisedStreamId, headers, padding, promise);
}

View File

@ -187,16 +187,22 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
}
ChannelFuture future = frameWriter.writeHeaders(ctx, streamId, headers, streamDependency,
weight, exclusive, padding, endOfStream, promise);
// Synchronously set the headersSent flag to ensure that we do not subsequently write
// other headers containing pseudo-header fields.
stream.headersSent();
// Writing headers may fail during the encode state if they violate HPACK limits.
Throwable failureCause = future.cause();
if (failureCause == null) {
// Synchronously set the headersSent flag to ensure that we do not subsequently write
// other headers containing pseudo-header fields.
stream.headersSent();
} else {
lifecycleManager.onError(ctx, failureCause);
}
return future;
} else {
// Pass headers to the flow-controller so it can maintain their sequence relative to DATA frames.
flowController.addFlowControlled(stream,
new FlowControlledHeaders(stream, headers, streamDependency, weight, exclusive, padding,
endOfStream, promise));
endOfStream, promise));
return promise;
}
} catch (Throwable t) {
@ -276,7 +282,13 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
ChannelFuture future = frameWriter.writePushPromise(ctx, streamId, promisedStreamId, headers, padding,
promise);
stream.pushPromiseSent();
// Writing headers may fail during the encode state if they violate HPACK limits.
Throwable failureCause = future.cause();
if (failureCause == null) {
stream.pushPromiseSent();
} else {
lifecycleManager.onError(ctx, failureCause);
}
return future;
} catch (Throwable t) {
lifecycleManager.onError(ctx, t);
@ -450,15 +462,21 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder {
}
@Override
public void write(ChannelHandlerContext ctx, int allowedBytes) throws Http2Exception {
public void write(ChannelHandlerContext ctx, int allowedBytes) {
if (promise.isVoid()) {
promise = ctx.newPromise();
}
promise.addListener(this);
frameWriter.writeHeaders(ctx, stream.id(), headers, streamDependency, weight, exclusive,
padding, endOfStream, promise);
stream.headersSent();
ChannelFuture f = frameWriter.writeHeaders(ctx, stream.id(), headers, streamDependency, weight, exclusive,
padding, endOfStream, promise);
// Writing headers may fail during the encode state if they violate HPACK limits.
Throwable failureCause = f.cause();
if (failureCause == null) {
stream.headersSent();
} else {
lifecycleManager.onError(ctx, failureCause);
}
}
@Override

View File

@ -188,7 +188,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int padding, boolean endStream, ChannelPromise promise) throws Http2Exception {
Http2Headers headers, int padding, boolean endStream, ChannelPromise promise) {
return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
false, 0, (short) 0, false, promise);
}
@ -196,7 +196,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int streamDependency, short weight, boolean exclusive,
int padding, boolean endStream, ChannelPromise promise) throws Http2Exception {
int padding, boolean endStream, ChannelPromise promise) {
return writeHeadersInternal(ctx, streamId, headers, padding, endStream,
true, streamDependency, weight, exclusive, promise);
}
@ -293,7 +293,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
@Override
public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId,
int promisedStreamId, Http2Headers headers, int padding, ChannelPromise promise) throws Http2Exception {
int promisedStreamId, Http2Headers headers, int padding, ChannelPromise promise) {
ByteBuf headerBlock = null;
SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
@ -337,8 +337,6 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
}
} catch (Http2Exception e) {
promiseAggregator.setFailure(e);
promiseAggregator.doneAllocatingPromises();
throw e;
} catch (Throwable t) {
promiseAggregator.setFailure(t);
promiseAggregator.doneAllocatingPromises();
@ -420,8 +418,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
private ChannelFuture writeHeadersInternal(ChannelHandlerContext ctx,
int streamId, Http2Headers headers, int padding, boolean endStream,
boolean hasPriority, int streamDependency, short weight, boolean exclusive, ChannelPromise promise)
throws Http2Exception {
boolean hasPriority, int streamDependency, short weight, boolean exclusive, ChannelPromise promise) {
ByteBuf headerBlock = null;
SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
@ -475,8 +472,6 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
}
} catch (Http2Exception e) {
promiseAggregator.setFailure(e);
promiseAggregator.doneAllocatingPromises();
throw e;
} catch (Throwable t) {
promiseAggregator.setFailure(t);
promiseAggregator.doneAllocatingPromises();

View File

@ -633,8 +633,6 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
if (stream != null && !stream.isHeadersSent()) {
try {
handleServerHeaderDecodeSizeError(ctx, stream);
} catch (Http2Exception e) {
onError(ctx, e);
} catch (Throwable cause2) {
onError(ctx, connectionError(INTERNAL_ERROR, cause2, "Error DecodeSizeError"));
}
@ -656,8 +654,7 @@ public class Http2ConnectionHandler extends ByteToMessageDecoder implements Http
* @param stream the Http2Stream on which the header was received
* @throws Http2Exception if an exception occurs while processing the decode error.
*/
protected void handleServerHeaderDecodeSizeError(ChannelHandlerContext ctx, Http2Stream stream)
throws Http2Exception {
protected void handleServerHeaderDecodeSizeError(ChannelHandlerContext ctx, Http2Stream stream) {
encoder().writeHeaders(ctx, stream.id(), HEADERS_TOO_LARGE_HEADERS, 0, true, ctx.newPromise());
}

View File

@ -209,7 +209,7 @@ public class Http2FrameCodec extends ChannelDuplexHandler {
* streams.
*/
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Http2Exception {
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
try {
if (msg instanceof Http2WindowUpdateFrame) {
Http2WindowUpdateFrame frame = (Http2WindowUpdateFrame) msg;
@ -252,7 +252,7 @@ public class Http2FrameCodec extends ChannelDuplexHandler {
http2HandlerCtx, lastStreamId, frame.errorCode(), frame.content().retain(), promise);
}
private void writeStreamFrame(Http2StreamFrame frame, ChannelPromise promise) throws Http2Exception {
private void writeStreamFrame(Http2StreamFrame frame, ChannelPromise promise) {
if (frame instanceof Http2DataFrame) {
Http2DataFrame dataFrame = (Http2DataFrame) frame;
http2Handler.encoder().writeData(http2HandlerCtx, frame.streamId(), dataFrame.content().retain(),
@ -267,7 +267,7 @@ public class Http2FrameCodec extends ChannelDuplexHandler {
}
}
private void writeHeadersFrame(Http2HeadersFrame headersFrame, ChannelPromise promise) throws Http2Exception {
private void writeHeadersFrame(Http2HeadersFrame headersFrame, ChannelPromise promise) {
int streamId = headersFrame.streamId();
if (!isStreamIdValid(streamId)) {
final Endpoint<Http2LocalFlowController> localEndpoint = http2Handler.connection().local();

View File

@ -56,7 +56,6 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* @param endStream indicates if this is the last frame to be sent for the stream.
* @param promise the promise for the write.
* @return the future for the write.
* @throws Http2Exception if an exception occurs while encoding headers.
* <a href="https://tools.ietf.org/html/rfc7540#section-10.5.1">Section 10.5.1</a> states the following:
* <pre>
* The header block MUST be processed to ensure a consistent connection state, unless the connection is closed.
@ -66,7 +65,7 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* If this call has <strong>NOT</strong> modified the HPACK header state you are free to throw a stream error.
*/
ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int padding, boolean endStream, ChannelPromise promise) throws Http2Exception;
int padding, boolean endStream, ChannelPromise promise);
/**
* Writes a HEADERS frame with priority specified to the remote endpoint.
@ -83,7 +82,6 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* @param endStream indicates if this is the last frame to be sent for the stream.
* @param promise the promise for the write.
* @return the future for the write.
* @throws Http2Exception if an exception occurs while encoding headers.
* <a href="https://tools.ietf.org/html/rfc7540#section-10.5.1">Section 10.5.1</a> states the following:
* <pre>
* The header block MUST be processed to ensure a consistent connection state, unless the connection is closed.
@ -93,8 +91,8 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* If this call has <strong>NOT</strong> modified the HPACK header state you are free to throw a stream error.
*/
ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream,
ChannelPromise promise) throws Http2Exception;
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream,
ChannelPromise promise);
/**
* Writes a PRIORITY frame to the remote endpoint.
@ -167,7 +165,6 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* 256 (inclusive).
* @param promise the promise for the write.
* @return the future for the write.
* @throws Http2Exception if an exception occurs while encoding headers.
* <a href="https://tools.ietf.org/html/rfc7540#section-10.5.1">Section 10.5.1</a> states the following:
* <pre>
* The header block MUST be processed to ensure a consistent connection state, unless the connection is closed.
@ -177,7 +174,7 @@ public interface Http2FrameWriter extends Http2DataWriter, Closeable {
* If this call has <strong>NOT</strong> modified the HPACK header state you are free to throw a stream error.
*/
ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding, ChannelPromise promise) throws Http2Exception;
Http2Headers headers, int padding, ChannelPromise promise);
/**
* Writes a GO_AWAY frame to the remote endpoint.

View File

@ -46,7 +46,7 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
@Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int padding, boolean endStream, ChannelPromise promise) throws Http2Exception {
Http2Headers headers, int padding, boolean endStream, ChannelPromise promise) {
logger.logHeaders(OUTBOUND, ctx, streamId, headers, padding, endStream);
return writer.writeHeaders(ctx, streamId, headers, padding, endStream, promise);
}
@ -54,7 +54,7 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
@Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int streamDependency, short weight, boolean exclusive,
int padding, boolean endStream, ChannelPromise promise) throws Http2Exception {
int padding, boolean endStream, ChannelPromise promise) {
logger.logHeaders(OUTBOUND, ctx, streamId, headers, streamDependency, weight, exclusive,
padding, endStream);
return writer.writeHeaders(ctx, streamId, headers, streamDependency, weight,
@ -101,7 +101,7 @@ public class Http2OutboundFrameLogger implements Http2FrameWriter {
@Override
public ChannelFuture writePushPromise(ChannelHandlerContext ctx, int streamId,
int promisedStreamId, Http2Headers headers, int padding, ChannelPromise promise) throws Http2Exception {
int promisedStreamId, Http2Headers headers, int padding, ChannelPromise promise) {
logger.logPushPromise(OUTBOUND, ctx, streamId, promisedStreamId, headers, padding);
return writer.writePushPromise(ctx, streamId, promisedStreamId, headers, padding, promise);
}

View File

@ -130,9 +130,8 @@ public interface Http2RemoteFlowController extends Http2FlowController {
*
* @param ctx The context to use for writing.
* @param allowedBytes an upper bound on the number of bytes the payload can write at this time.
* @throws Http2Exception if an error occurs during the encode or write.
*/
void write(ChannelHandlerContext ctx, int allowedBytes) throws Http2Exception;
void write(ChannelHandlerContext ctx, int allowedBytes);
/**
* Merge the contents of the {@code next} message into this message so they can be written out as one unit.

View File

@ -124,7 +124,7 @@ public class HttpToHttp2ConnectionHandler extends Http2ConnectionHandler {
private static void writeHeaders(ChannelHandlerContext ctx, Http2ConnectionEncoder encoder, int streamId,
HttpHeaders headers, Http2Headers http2Headers, boolean endStream,
SimpleChannelPromiseAggregator promiseAggregator) throws Http2Exception {
SimpleChannelPromiseAggregator promiseAggregator) {
int dependencyId = headers.getInt(
HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 0);
short weight = headers.getShort(

View File

@ -137,7 +137,7 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
@Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int padding, boolean endStream, ChannelPromise promise) throws Http2Exception {
int padding, boolean endStream, ChannelPromise promise) {
return writeHeaders(ctx, streamId, headers, 0, Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT,
false, padding, endStream, promise);
}
@ -145,7 +145,7 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
@Override
public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive,
int padding, boolean endOfStream, ChannelPromise promise) throws Http2Exception {
int padding, boolean endOfStream, ChannelPromise promise) {
if (closed) {
return promise.setFailure(new Http2ChannelClosedException());
}
@ -281,7 +281,7 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
this.streamId = streamId;
}
void sendFrames() throws Http2Exception {
void sendFrames() {
for (Frame frame : frames) {
frame.send(ctx, streamId);
}
@ -312,7 +312,7 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
}
}
abstract void send(ChannelHandlerContext ctx, int streamId) throws Http2Exception;
abstract void send(ChannelHandlerContext ctx, int streamId);
}
private final class HeadersFrame extends Frame {
@ -335,9 +335,8 @@ public class StreamBufferingEncoder extends DecoratingHttp2ConnectionEncoder {
}
@Override
void send(ChannelHandlerContext ctx, int streamId) throws Http2Exception {
writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, padding,
endOfStream, promise);
void send(ChannelHandlerContext ctx, int streamId) {
writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream, promise);
}
}

View File

@ -565,7 +565,7 @@ public class DefaultHttp2ConnectionEncoderTest {
}
@Test
public void canWriteHeaderFrameAfterGoAwayReceived() throws Http2Exception {
public void canWriteHeaderFrameAfterGoAwayReceived() {
writeAllFlowControlledFrames();
goAwayReceived(STREAM_ID);
ChannelPromise promise = newPromise();

View File

@ -29,6 +29,7 @@ import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyShort;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@ -246,7 +247,7 @@ public class StreamBufferingEncoderTest {
failCount++;
}
}
assertEquals(4, failCount);
assertEquals(9, failCount);
assertEquals(0, encoder.numBufferedStreams());
}
@ -255,6 +256,11 @@ public class StreamBufferingEncoderTest {
encoder.writeSettingsAck(ctx, newPromise());
setMaxConcurrentStreams(1);
when(writer.writeHeaders(any(ChannelHandlerContext.class), anyInt(), any(Http2Headers.class), anyInt(),
anyBoolean(), any(ChannelPromise.class))).thenAnswer(successAnswer());
when(writer.writeHeaders(any(ChannelHandlerContext.class), anyInt(), any(Http2Headers.class), anyInt(),
anyShort(), anyBoolean(), anyInt(), anyBoolean(), any(ChannelPromise.class))).thenAnswer(successAnswer());
ChannelFuture f1 = encoderWriteHeaders(3, newPromise());
assertEquals(0, encoder.numBufferedStreams());
ChannelFuture f2 = encoderWriteHeaders(5, newPromise());
@ -487,9 +493,9 @@ public class StreamBufferingEncoderTest {
}
private ChannelFuture encoderWriteHeaders(int streamId, ChannelPromise promise) {
encoder.writeHeaders(ctx, streamId, new DefaultHttp2Headers(), 0, DEFAULT_PRIORITY_WEIGHT,
false, 0, false, promise);
try {
encoder.writeHeaders(ctx, streamId, new DefaultHttp2Headers(), 0, DEFAULT_PRIORITY_WEIGHT,
false, 0, false, promise);
encoder.flowController().writePendingBytes();
return promise;
} catch (Http2Exception e) {
@ -498,13 +504,9 @@ public class StreamBufferingEncoderTest {
}
private void writeVerifyWriteHeaders(VerificationMode mode, int streamId) {
try {
verify(writer, mode).writeHeaders(eq(ctx), eq(streamId), any(Http2Headers.class), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0),
eq(false), any(ChannelPromise.class));
} catch (Http2Exception e) {
throw new RuntimeException(e);
}
verify(writer, mode).writeHeaders(eq(ctx), eq(streamId), any(Http2Headers.class), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0),
eq(false), any(ChannelPromise.class));
}
private Answer<ChannelFuture> successAnswer() {

View File

@ -74,7 +74,7 @@ public final class HelloWorldHttp2Handler extends Http2ConnectionHandler impleme
/**
* Sends a "Hello World" DATA frame to the client.
*/
private void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) throws Http2Exception {
private void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) {
// Send a frame for the response status
Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText());
encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise());
@ -87,8 +87,7 @@ public final class HelloWorldHttp2Handler extends Http2ConnectionHandler impleme
}
@Override
public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)
throws Http2Exception {
public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) {
int processed = data.readableBytes() + padding;
if (endOfStream) {
sendResponse(ctx, streamId, data.retain());
@ -98,7 +97,7 @@ public final class HelloWorldHttp2Handler extends Http2ConnectionHandler impleme
@Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int padding, boolean endOfStream) throws Http2Exception {
Http2Headers headers, int padding, boolean endOfStream) {
if (endOfStream) {
ByteBuf content = ctx.alloc().buffer();
content.writeBytes(RESPONSE_BYTES.duplicate());
@ -109,7 +108,7 @@ public final class HelloWorldHttp2Handler extends Http2ConnectionHandler impleme
@Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception {
short weight, boolean exclusive, int padding, boolean endOfStream) {
onHeadersRead(ctx, streamId, headers, padding, endOfStream);
}

View File

@ -14,13 +14,12 @@
*/
package io.netty.microbench.http2;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http2.Http2Exception;
import io.netty.handler.codec.http2.Http2RemoteFlowController;
import io.netty.handler.codec.http2.Http2Stream;
import io.netty.util.internal.PlatformDependent;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
public final class NoopHttp2RemoteFlowController implements Http2RemoteFlowController {
public static final NoopHttp2RemoteFlowController INSTANCE = new NoopHttp2RemoteFlowController();
@ -63,11 +62,7 @@ public final class NoopHttp2RemoteFlowController implements Http2RemoteFlowContr
public void addFlowControlled(Http2Stream stream, FlowControlled payload) {
// Don't check size beforehand because Headers payload returns 0 all the time.
do {
try {
payload.write(ctx, MAX_INITIAL_WINDOW_SIZE);
} catch (Http2Exception e) {
PlatformDependent.throwException(e);
}
payload.write(ctx, MAX_INITIAL_WINDOW_SIZE);
} while (payload.size() > 0);
}