use of io.servicetalk.http.api.HttpMetaData in project servicetalk by apple.
the class AbstractH2DuplexHandlerTest method noContentLength.
private void noContentLength(Variant variant, boolean endStream) {
variant.writeOutbound(channel);
Http2Headers headers = variant.setHeaders(new DefaultHttp2Headers());
channel.writeInbound(headersFrame(headers, endStream));
HttpMetaData metaData = channel.readInbound();
if (endStream) {
assertThat(metaData.headers().contains(CONTENT_LENGTH), is(true));
} else {
assertThat(isTransferEncodingChunked(metaData.headers()), is(true));
}
}
use of io.servicetalk.http.api.HttpMetaData in project servicetalk by apple.
the class HttpObjectEncoder method write.
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
if (msg instanceof HttpMetaData) {
T metaData = castMetaData(msg);
final boolean realResponse = !isInterim(metaData);
if (!realResponse && messageSent) {
// Discard an "interim message" if it arrives after a "real message" has been sent.
return;
}
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);
}
onMetaData(ctx, metaData);
if (realResponse) {
// Notify the CloseHandler only about "real" messages. We don't expose "interim messages", like 1xx
// responses to the user, and handle them internally.
messageSent = true;
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(ctx, stBuf, metaData);
if (isContentAlwaysEmpty(metaData)) {
state = CONTENT_LEN_EMPTY;
if (realResponse) {
signalProtocolPayloadEndOutbound(ctx, promise);
}
} else if (isTransferEncodingChunked(metaData.headers())) {
state = CONTENT_LEN_CHUNKED;
} else {
state = getContentLength(metaData);
assert state > CONTENT_LEN_LARGEST_VALUE;
if (state == 0) {
contentLenConsumed(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;
}
if (realResponse) {
ctx.write(byteBuf, promise);
} else {
// All "interim messages" have to be flushed right away.
ctx.writeAndFlush(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) {
contentLenConsumed(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) {
signalProtocolPayloadEndOutbound(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) {
signalProtocolPayloadEndOutbound(ctx, promise);
}
state = CONTENT_LEN_INIT;
ctx.write(EMPTY_BUFFER, promise);
}
}
}
use of io.servicetalk.http.api.HttpMetaData in project servicetalk by apple.
the class HttpObjectDecoderTest method smuggleTransferEncoding.
void smuggleTransferEncoding(boolean smuggleBeforeTransferEncoding, boolean crlf) {
EmbeddedChannel channel = channel(crlf);
String br = br(crlf);
assertThrows(DecoderException.class, () -> writeMsg(startLineForContent() + br + "Host: servicetalk.io" + br + // [1] https://tools.ietf.org/html/rfc7230#section-3.3.3
(smuggleBeforeTransferEncoding ? "Smuggled: " + startLine() + br + br + TRANSFER_ENCODING + ":" + CHUNKED + br : TRANSFER_ENCODING + ":" + CHUNKED + br + "Smuggled: " + startLine() + br + br) + "Connection: keep-alive" + br + br, channel));
HttpMetaData metaData = assertStartLineForContent(channel);
assertSingleHeaderValue(metaData.headers(), HOST, "servicetalk.io");
assertSingleHeaderValue(metaData.headers(), "Smuggled", startLine());
}
use of io.servicetalk.http.api.HttpMetaData in project servicetalk by apple.
the class HttpObjectDecoderTest method smuggleZeroContentLength.
private void smuggleZeroContentLength(boolean smuggleBeforeContentLength, boolean crlf) {
EmbeddedChannel channel = channel(crlf);
String br = br(crlf);
DecoderException e = assertThrows(DecoderException.class, () -> writeMsg(startLine() + br + "Host: servicetalk.io" + br + // [1] https://tools.ietf.org/html/rfc7230#section-3.3.3
(smuggleBeforeContentLength ? "Smuggled: " + startLine() + br + br + "Content-Length: 0" + br : "Content-Length: 0" + br + "Smuggled: " + startLine() + br + br) + "Connection: keep-alive" + br + br, channel));
assertThat(e.getMessage(), startsWith("Invalid start-line"));
HttpMetaData metaData = assertStartLine(channel);
assertSingleHeaderValue(metaData.headers(), HOST, "servicetalk.io");
assertSingleHeaderValue(metaData.headers(), "Smuggled", startLine());
assertEmptyTrailers(channel);
}
use of io.servicetalk.http.api.HttpMetaData in project servicetalk by apple.
the class HttpObjectDecoderTest method validateWithContent.
final HttpMetaData validateWithContent(int expectedContentLength, boolean containsTrailers, EmbeddedChannel channel) {
HttpMetaData metaData = assertStartLineForContent(channel);
assertStandardHeaders(metaData.headers());
if (expectedContentLength > 0) {
assertSingleHeaderValue(metaData.headers(), CONTENT_LENGTH, String.valueOf(expectedContentLength));
HttpHeaders trailers = assertPayloadSize(expectedContentLength, channel);
assertThat("Trailers are not empty", trailers, nullValue());
} else if (expectedContentLength == 0) {
if (containsTrailers) {
assertSingleHeaderValue(metaData.headers(), TRANSFER_ENCODING, CHUNKED);
HttpHeaders trailers = channel.readInbound();
assertSingleHeaderValue(trailers, "TrailerStatus", "good");
} else {
assertSingleHeaderValue(metaData.headers(), CONTENT_LENGTH, "0");
assertEmptyTrailers(channel);
}
} else {
assertThat("No 'transfer-encoding: chunked' header", isTransferEncodingChunked(metaData.headers()), is(true));
HttpHeaders trailers = assertPayloadSize(-expectedContentLength, channel);
if (containsTrailers) {
assertThat(trailers, not(nullValue()));
assertSingleHeaderValue(trailers, "TrailerStatus", "good");
} else if (trailers != null) {
assertThat("Trailers are not empty", trailers.isEmpty(), is(true));
}
}
assertFalse(channel.finishAndReleaseAll());
return metaData;
}
Aggregations