in servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/HttpObjectEncoder.java [124:219]
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
if (msg instanceof HttpMetaData) {
if (state == CONTENT_LEN_CHUNKED) {
// The user didn't write any trailers, so just send the last chunk.
encodeAndWriteTrailers(ctx, EmptyHttpHeaders.INSTANCE, promise);
} else if (state > 0) {
tryTooLittleContent(ctx, msg, promise);
return;
} else if (state == -1) {
unknownContentLengthNewRequest(ctx);
}
T metaData = castMetaData(msg);
closeHandler.protocolPayloadBeginOutbound(ctx);
if (shouldClose(metaData)) {
closeHandler.protocolClosingOutbound(ctx);
}
// We prefer a direct allocation here because it is expected the resulted encoded Buffer will be written
// to a socket. In order to do the write to the socket the memory typically needs to be allocated in direct
// memory and will be copied to direct memory if not. Using a direct buffer will avoid the copy.
ByteBuf byteBuf = ctx.alloc().directBuffer((int) headersEncodedSizeAccumulator);
try {
Buffer stBuf = newBufferFrom(byteBuf);
// Encode the message.
encodeInitialLine(stBuf, metaData);
if (isContentAlwaysEmpty(metaData)) {
state = CONTENT_LEN_EMPTY;
closeHandler.protocolPayloadEndOutbound(ctx, promise);
} else if (isTransferEncodingChunked(metaData.headers())) {
state = CONTENT_LEN_CHUNKED;
} else {
state = getContentLength(metaData);
assert state > CONTENT_LEN_LARGEST_VALUE;
if (state == 0) {
state = CONTENT_LEN_CONSUMED;
closeHandler.protocolPayloadEndOutbound(ctx, promise);
}
}
sanitizeHeadersBeforeEncode(metaData);
encodeHeaders(metaData.headers(), byteBuf, stBuf);
writeShortBE(byteBuf, CRLF_SHORT);
headersEncodedSizeAccumulator = HEADERS_WEIGHT_NEW * padSizeForAccumulation(byteBuf.readableBytes()) +
HEADERS_WEIGHT_HISTORICAL * headersEncodedSizeAccumulator;
} catch (Throwable e) {
// Encoding of meta-data can fail or cause expansion of the initial ByteBuf capacity that can fail
byteBuf.release();
tryIoException(ctx, e, promise);
return;
}
ctx.write(byteBuf, promise);
} else if (msg instanceof Buffer) {
final Buffer stBuffer = (Buffer) msg;
final int readableBytes = stBuffer.readableBytes();
if (readableBytes <= 0) {
ctx.write(EMPTY_BUFFER, promise);
} else if (state == CONTENT_LEN_CHUNKED) {
PromiseCombiner promiseCombiner = new PromiseCombiner(ctx.executor());
encodeChunkedContent(ctx, stBuffer, stBuffer.readableBytes(), promiseCombiner);
promiseCombiner.finish(promise);
} else if (state <= CONTENT_LEN_LARGEST_VALUE || state >= 0 && (state -= readableBytes) < 0) {
// state may be <0 if there is no content-length or transfer-encoding, so let this pass through, but if
// state would go negative (or already zeroed) then fail.
tryTooMuchContent(ctx, readableBytes, promise);
} else {
if (state == 0) {
state = CONTENT_LEN_CONSUMED;
closeHandler.protocolPayloadEndOutbound(ctx, promise);
}
ctx.write(encodeAndRetain(stBuffer), promise);
}
} else if (msg instanceof HttpHeaders) {
final boolean isChunked = state == CONTENT_LEN_CHUNKED;
state = CONTENT_LEN_INIT;
final HttpHeaders trailers = (HttpHeaders) msg;
if (isChunked) {
closeHandler.protocolPayloadEndOutbound(ctx, promise);
encodeAndWriteTrailers(ctx, trailers, promise);
} else if (!trailers.isEmpty()) {
tryFailNonEmptyTrailers(ctx, trailers, promise);
} else if (state > 0) {
tryTooLittleContent(ctx, promise);
} else {
// Allow trailers to be written as a marker indicating the request is done.
if (state != CONTENT_LEN_CONSUMED) {
closeHandler.protocolPayloadEndOutbound(ctx, promise);
}
state = CONTENT_LEN_INIT;
ctx.write(EMPTY_BUFFER, promise);
}
}
}