下面列出了怎么用 io.netty.handler.codec.http2.DefaultHttp2Headers 的API类实例代码及写法,或者点击链接到github查看源代码。
private void respondWithHttpError(
ChannelHandlerContext ctx, int streamId, int code, Status.Code statusCode, String msg) {
Metadata metadata = new Metadata();
metadata.put(InternalStatus.CODE_KEY, statusCode.toStatus());
metadata.put(InternalStatus.MESSAGE_KEY, msg);
byte[][] serialized = InternalMetadata.serialize(metadata);
Http2Headers headers = new DefaultHttp2Headers(true, serialized.length / 2)
.status("" + code)
.set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
for (int i = 0; i < serialized.length; i += 2) {
headers.add(new AsciiString(serialized[i], false), new AsciiString(serialized[i + 1], false));
}
encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise());
ByteBuf msgBuf = ByteBufUtil.writeUtf8(ctx.alloc(), msg);
encoder().writeData(ctx, streamId, msgBuf, 0, true, ctx.newPromise());
}
@Test
public void writeMessageShouldSendResponse() throws Exception {
ListMultimap<CharSequence, CharSequence> expectedHeaders =
ImmutableListMultimap.copyOf(new DefaultHttp2Headers()
.status(Utils.STATUS_OK)
.set(Utils.CONTENT_TYPE_HEADER, Utils.CONTENT_TYPE_GRPC));
stream.writeHeaders(new Metadata());
ArgumentCaptor<SendResponseHeadersCommand> sendHeadersCap =
ArgumentCaptor.forClass(SendResponseHeadersCommand.class);
verify(writeQueue).enqueue(sendHeadersCap.capture(), eq(true));
SendResponseHeadersCommand sendHeaders = sendHeadersCap.getValue();
assertThat(sendHeaders.stream()).isSameAs(stream.transportState());
assertThat(ImmutableListMultimap.copyOf(sendHeaders.headers()))
.containsExactlyEntriesIn(expectedHeaders);
assertThat(sendHeaders.endOfStream()).isFalse();
byte[] msg = smallMessage();
stream.writeMessage(new ByteArrayInputStream(msg));
stream.flush();
verify(writeQueue).enqueue(
eq(new SendGrpcFrameCommand(stream.transportState(), messageFrame(MESSAGE), false)),
eq(true));
}
@Test
public void inboundShouldForwardToStream() throws Exception {
createStream();
// Read a headers frame first.
Http2Headers headers = new DefaultHttp2Headers().status(STATUS_OK)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.set(as("magic"), as("value"));
ByteBuf headersFrame = headersFrame(3, headers);
channelRead(headersFrame);
ArgumentCaptor<Metadata> captor = ArgumentCaptor.forClass(Metadata.class);
verify(streamListener).headersRead(captor.capture());
assertEquals("value",
captor.getValue().get(Metadata.Key.of("magic", Metadata.ASCII_STRING_MARSHALLER)));
streamTransportState.requestMessagesFromDeframerForTesting(1);
// Create a data frame and then trigger the handler to read it.
ByteBuf frame = grpcDataFrame(3, false, contentAsArray());
channelRead(frame);
InputStream message = streamListenerMessageQueue.poll();
assertArrayEquals(ByteBufUtil.getBytes(content()), ByteStreams.toByteArray(message));
message.close();
assertNull("no additional message expected", streamListenerMessageQueue.poll());
}
@Test
public void closeWithErrorBeforeClientHalfCloseShouldSucceed() throws Exception {
ListMultimap<CharSequence, CharSequence> expectedHeaders =
ImmutableListMultimap.copyOf(new DefaultHttp2Headers()
.status(new AsciiString("200"))
.set(new AsciiString("content-type"), new AsciiString("application/grpc"))
.set(new AsciiString("grpc-status"), new AsciiString("1")));
// Error is sent on wire and ends the stream
stream().close(Status.CANCELLED, trailers);
ArgumentCaptor<SendResponseHeadersCommand> sendHeadersCap =
ArgumentCaptor.forClass(SendResponseHeadersCommand.class);
verify(writeQueue).enqueue(sendHeadersCap.capture(), eq(true));
SendResponseHeadersCommand sendHeaders = sendHeadersCap.getValue();
assertThat(sendHeaders.stream()).isSameAs(stream.transportState());
assertThat(ImmutableListMultimap.copyOf(sendHeaders.headers()))
.containsExactlyEntriesIn(expectedHeaders);
assertThat(sendHeaders.endOfStream()).isTrue();
verifyZeroInteractions(serverListener);
// Sending complete. Listener gets closed()
stream().transportState().complete();
verify(serverListener).closed(Status.OK);
assertNull("no message expected", listenerMessageQueue.poll());
}
@Test
public void headersSupportExtensionContentType() throws Exception {
manualSetUp();
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, new AsciiString("application/grpc+json", UTF_8))
.set(TE_HEADER, TE_TRAILERS)
.path(new AsciiString("/foo/bar"));
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
ArgumentCaptor<NettyServerStream> streamCaptor =
ArgumentCaptor.forClass(NettyServerStream.class);
ArgumentCaptor<String> methodCaptor = ArgumentCaptor.forClass(String.class);
verify(transportListener).streamCreated(streamCaptor.capture(), methodCaptor.capture(),
any(Metadata.class));
stream = streamCaptor.getValue();
}
@Test
public void emptyFramerShouldSendNoPayload() {
ListMultimap<CharSequence, CharSequence> expectedHeaders =
ImmutableListMultimap.copyOf(new DefaultHttp2Headers()
.status(new AsciiString("200"))
.set(new AsciiString("content-type"), new AsciiString("application/grpc"))
.set(new AsciiString("grpc-status"), new AsciiString("0")));
ArgumentCaptor<SendResponseHeadersCommand> cmdCap =
ArgumentCaptor.forClass(SendResponseHeadersCommand.class);
stream().close(Status.OK, new Metadata());
verify(writeQueue).enqueue(cmdCap.capture(), eq(true));
SendResponseHeadersCommand cmd = cmdCap.getValue();
assertThat(cmd.stream()).isSameAs(stream.transportState());
assertThat(ImmutableListMultimap.copyOf(cmd.headers()))
.containsExactlyEntriesIn(expectedHeaders);
assertThat(cmd.endOfStream()).isTrue();
}
@Test
public void decode_responseHeaders() throws Http2Exception {
Http2HeadersDecoder decoder = new GrpcHttp2ClientHeadersDecoder(DEFAULT_MAX_HEADER_LIST_SIZE);
Http2HeadersEncoder encoder =
new DefaultHttp2HeadersEncoder(NEVER_SENSITIVE);
Http2Headers headers = new DefaultHttp2Headers(false);
headers.add(of(":status"), of("200")).add(of("custom"), of("header"));
encodedHeaders = Unpooled.buffer();
encoder.encodeHeaders(1 /* randomly chosen */, headers, encodedHeaders);
Http2Headers decodedHeaders = decoder.decodeHeaders(3 /* randomly chosen */, encodedHeaders);
assertEquals(headers.get(of(":status")), decodedHeaders.get(of(":status")));
assertEquals(headers.get(of("custom")), decodedHeaders.get(of("custom")));
assertEquals(headers.size(), decodedHeaders.size());
String toString = decodedHeaders.toString();
assertContainsKeyAndValue(toString, ":status", decodedHeaders.get(of(":status")));
assertContainsKeyAndValue(toString, "custom", decodedHeaders.get(of("custom")));
}
@Test
public void headersWithInvalidContentTypeShouldFail() throws Exception {
manualSetUp();
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, new AsciiString("application/bad", UTF_8))
.set(TE_HEADER, TE_TRAILERS)
.path(new AsciiString("/foo/bar"));
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
Http2Headers responseHeaders = new DefaultHttp2Headers()
.set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.INTERNAL.value()))
.set(InternalStatus.MESSAGE_KEY.name(), "Content-Type 'application/bad' is not supported")
.status("" + 415)
.set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
verifyWrite().writeHeaders(eq(ctx()), eq(STREAM_ID), eq(responseHeaders), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class));
}
@Test
public void headersWithInvalidMethodShouldFail() throws Exception {
manualSetUp();
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_FAKE_METHOD)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.path(new AsciiString("/foo/bar"));
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
Http2Headers responseHeaders = new DefaultHttp2Headers()
.set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.INTERNAL.value()))
.set(InternalStatus.MESSAGE_KEY.name(), "Method 'FAKE' is not supported")
.status("" + 405)
.set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
verifyWrite().writeHeaders(eq(ctx()), eq(STREAM_ID), eq(responseHeaders), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class));
}
@Test
public void headersWithMissingPathShouldFail() throws Exception {
manualSetUp();
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC);
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
Http2Headers responseHeaders = new DefaultHttp2Headers()
.set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.UNIMPLEMENTED.value()))
.set(InternalStatus.MESSAGE_KEY.name(), "Expected path but is missing")
.status("" + 404)
.set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
verifyWrite().writeHeaders(eq(ctx()), eq(STREAM_ID), eq(responseHeaders), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class));
}
@Test
public void headersWithInvalidPathShouldFail() throws Exception {
manualSetUp();
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.path(new AsciiString("foo/bar"));
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
Http2Headers responseHeaders = new DefaultHttp2Headers()
.set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.UNIMPLEMENTED.value()))
.set(InternalStatus.MESSAGE_KEY.name(), "Expected path to start with /: foo/bar")
.status("" + 404)
.set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
verifyWrite().writeHeaders(eq(ctx()), eq(STREAM_ID), eq(responseHeaders), eq(0),
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class));
}
@Test
public void headersSupportExtensionContentType() throws Exception {
manualSetUp();
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, new AsciiString("application/grpc+json", UTF_8))
.set(TE_HEADER, TE_TRAILERS)
.path(new AsciiString("/foo/bar"));
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
ArgumentCaptor<NettyServerStream> streamCaptor =
ArgumentCaptor.forClass(NettyServerStream.class);
ArgumentCaptor<String> methodCaptor = ArgumentCaptor.forClass(String.class);
verify(transportListener).streamCreated(streamCaptor.capture(), methodCaptor.capture(),
any(Metadata.class));
stream = streamCaptor.getValue();
}
private void createStream() throws Exception {
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.set(TE_HEADER, TE_TRAILERS)
.path(new AsciiString("/foo/bar"));
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
ArgumentCaptor<NettyServerStream> streamCaptor =
ArgumentCaptor.forClass(NettyServerStream.class);
ArgumentCaptor<String> methodCaptor = ArgumentCaptor.forClass(String.class);
verify(transportListener).streamCreated(streamCaptor.capture(), methodCaptor.capture(),
any(Metadata.class));
stream = streamCaptor.getValue();
}
@Test
public void decode_responseHeaders() throws Http2Exception {
Http2HeadersDecoder decoder = new GrpcHttp2ClientHeadersDecoder(DEFAULT_MAX_HEADER_LIST_SIZE);
Http2HeadersEncoder encoder =
new DefaultHttp2HeadersEncoder(NEVER_SENSITIVE);
Http2Headers headers = new DefaultHttp2Headers(false);
headers.add(of(":status"), of("200")).add(of("custom"), of("header"));
encodedHeaders = Unpooled.buffer();
encoder.encodeHeaders(1 /* randomly chosen */, headers, encodedHeaders);
Http2Headers decodedHeaders = decoder.decodeHeaders(3 /* randomly chosen */, encodedHeaders);
assertEquals(headers.get(of(":status")), decodedHeaders.get(of(":status")));
assertEquals(headers.get(of("custom")), decodedHeaders.get(of("custom")));
assertEquals(headers.size(), decodedHeaders.size());
String toString = decodedHeaders.toString();
assertContainsKeyAndValue(toString, ":status", decodedHeaders.get(of(":status")));
assertContainsKeyAndValue(toString, "custom", decodedHeaders.get(of("custom")));
}
@Test
@SuppressWarnings("UndefinedEquals") // AsciiString.equals
public void convertServerHeaders_sanitizes() {
Metadata metaData = new Metadata();
// Intentionally being explicit here rather than relying on any pre-defined lists of headers,
// since the goal of this test is to validate the correctness of such lists in the first place.
metaData.put(GrpcUtil.CONTENT_TYPE_KEY, "to-be-removed");
metaData.put(GrpcUtil.TE_HEADER, "to-be-removed");
metaData.put(GrpcUtil.USER_AGENT_KEY, "to-be-removed");
metaData.put(userKey, userValue);
Http2Headers output = Utils.convertServerHeaders(metaData);
DefaultHttp2Headers headers = new DefaultHttp2Headers();
for (Map.Entry<CharSequence, CharSequence> entry : output) {
headers.add(entry.getKey(), entry.getValue());
}
// 2 reserved headers, 1 user header
assertEquals(2 + 1, headers.size());
assertEquals(Utils.CONTENT_TYPE_GRPC, headers.get(GrpcUtil.CONTENT_TYPE_KEY.name()));
}
private void createStream() throws Exception {
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.set(TE_HEADER, TE_TRAILERS)
.path(new AsciiString("/foo/bar"));
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
ArgumentCaptor<NettyServerStream> streamCaptor =
ArgumentCaptor.forClass(NettyServerStream.class);
ArgumentCaptor<String> methodCaptor = ArgumentCaptor.forClass(String.class);
verify(transportListener).streamCreated(streamCaptor.capture(), methodCaptor.capture(),
any(Metadata.class));
stream = streamCaptor.getValue();
}
private void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame headers) {
if (headers.isEndStream()) {
ctx.write(new DefaultHttp2HeadersFrame(headers.headers(), true));
} else {
Http2Headers outHeaders = new DefaultHttp2Headers();
if (headers.headers().contains(EXPECT, CONTINUE)) {
if (headers.headers().contains(EXPECT_FAIL_HEADER)) {
outHeaders.status(
io.netty.handler.codec.http.HttpResponseStatus.EXPECTATION_FAILED.codeAsText());
ctx.write(new DefaultHttp2HeadersFrame(outHeaders, true));
return;
} else {
outHeaders.status(io.netty.handler.codec.http.HttpResponseStatus.CONTINUE.codeAsText());
}
} else {
outHeaders.status(io.netty.handler.codec.http.HttpResponseStatus.OK.codeAsText());
}
CharSequence contentType = headers.headers().get(CONTENT_TYPE);
if (contentType != null) {
outHeaders.add(CONTENT_TYPE, contentType);
}
outHeaders.add(HttpHeaderNames.COOKIE, headers.headers().getAll(HttpHeaderNames.COOKIE));
ctx.write(new DefaultHttp2HeadersFrame(outHeaders));
}
}
@Test
@SuppressWarnings("UndefinedEquals") // AsciiString.equals
public void convertServerHeaders_sanitizes() {
Metadata metaData = new Metadata();
// Intentionally being explicit here rather than relying on any pre-defined lists of headers,
// since the goal of this test is to validate the correctness of such lists in the first place.
metaData.put(GrpcUtil.CONTENT_TYPE_KEY, "to-be-removed");
metaData.put(GrpcUtil.TE_HEADER, "to-be-removed");
metaData.put(GrpcUtil.USER_AGENT_KEY, "to-be-removed");
metaData.put(userKey, userValue);
Http2Headers output = Utils.convertServerHeaders(metaData);
DefaultHttp2Headers headers = new DefaultHttp2Headers();
for (Map.Entry<CharSequence, CharSequence> entry : output) {
headers.add(entry.getKey(), entry.getValue());
}
// 2 reserved headers, 1 user header
assertEquals(2 + 1, headers.size());
assertEquals(Utils.CONTENT_TYPE_GRPC, headers.get(GrpcUtil.CONTENT_TYPE_KEY.name()));
}
private void sendHttp2Response0(HttpResponseStatus status, boolean error, ByteBuf data) {
Http2Headers headers = new DefaultHttp2Headers().status(status.codeAsText());
if (request.getSerializeType() > 0) {
String serialization = SerializerFactory.getAliasByCode(request.getSerializeType());
headers.set(RemotingConstants.HEAD_SERIALIZE_TYPE, serialization);
} else {
headers.set(CONTENT_TYPE, "text/plain; charset=" + RpcConstants.DEFAULT_CHARSET.displayName());
}
if (error) {
headers.set(RemotingConstants.HEAD_RESPONSE_ERROR, "true");
}
if (data != null) {
encoder.writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise());
encoder.writeData(ctx, streamId, data, 0, true, ctx.newPromise());
} else {
encoder.writeHeaders(ctx, streamId, headers, 0, true, ctx.newPromise());
}
}
/**
* Writes the given response body as "text/plain" to the given stream. Marks the response status
* metric. Closes the stream after writing the response.
*/
private void writeResponse(
ChannelHandlerContext ctx, int streamId, HttpResponseStatus status, ByteBuf body) {
Preconditions.checkArgument(body != null, "body must not be null");
markResponseStatus(ctx, status);
Http2Headers headers = new DefaultHttp2Headers(true);
// TODO(jkinkead): This should honor accept headers; we shouldn't send text/plain if the client
// doesn't want it.
headers.set(CONTENT_TYPE, "text/plain");
headers.setInt(CONTENT_LENGTH, body.readableBytes());
headers.status(status.codeAsText());
writeResponse(ctx, streamId, headers, Optional.of(body));
}
/** Test that trailer-part headers are handled correctly. */
@Test
void testOnHeadersRead_trailerPart() {
testHandler = new Http2Handler(mockEncoder, MAX_PAYLOAD, NO_CORS);
// Fake the initial request + handler.
Http2Headers initialHeaders = new DefaultHttp2Headers().method("GET").path(OK_PATH);
XrpcRequest fakeRequest = new XrpcRequest(initialHeaders, null, null, channel);
testHandler.requests.put(STREAM_ID, fakeRequest);
testHandler.handlers.put(STREAM_ID, OK_HANDLER);
headers.add("some-header", "some-value");
testHandler.onHeadersRead(mockContext, STREAM_ID, headers, 1, true);
// Expect an OK response, but DON'T expect a request count.
assertEquals(0L, requestMeter.getCount());
verifyResponse(HttpResponseStatus.OK, ImmutableMap.of(), Optional.empty(), STREAM_ID);
// Assert that the request's headers were updated.
assertEquals("some-value", fakeRequest.h2Headers().get("some-header"));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (!(msg instanceof Http2Frame)) {
ctx.fireChannelRead(msg);
return;
}
Http2Frame frame = (Http2Frame) msg;
receivedFrames.add(frame);
if (frame instanceof Http2DataFrame) {
Http2DataFrame dataFrame = (Http2DataFrame) frame;
if (dataFrame.isEndStream()) {
Http2HeadersFrame respHeaders = new DefaultHttp2HeadersFrame(
new DefaultHttp2Headers().status("204"), true)
.stream(dataFrame.stream());
ctx.writeAndFlush(respHeaders);
}
}
ReferenceCountUtil.release(frame);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Http2Frame frame) {
if (frame instanceof Http2DataFrame) {
// Not respond if this is channel 1
if (channelIds[0].equals(ctx.channel().parent().id().asShortText()) && failOnFirstChannel) {
ctx.channel().parent().close();
} else {
DefaultHttp2DataFrame dataFrame = new DefaultHttp2DataFrame(false);
try {
LOGGER.info(() -> "return empty data " + ctx.channel() + " frame " + frame.getClass());
Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText());
ctx.write(dataFrame);
ctx.write(new DefaultHttp2HeadersFrame(headers, true));
ctx.flush();
} finally {
dataFrame.release();
}
}
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Http2Frame frame) {
if (frame instanceof Http2DataFrame) {
// Not respond if this is channel 1
if (channelIds[0].equals(ctx.channel().parent().id().asShortText()) && notRespondOnFirstChannel) {
LOGGER.info(() -> "This is the first request, not responding" + ctx.channel());
} else {
DefaultHttp2DataFrame dataFrame = new DefaultHttp2DataFrame(false);
try {
LOGGER.info(() -> "return empty data " + ctx.channel() + " frame " + frame.getClass());
Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText());
ctx.write(dataFrame);
ctx.write(new DefaultHttp2HeadersFrame(headers, true));
ctx.flush();
} finally {
dataFrame.release();
}
}
}
}
@Test
public void headersWithInvalidContentTypeShouldFail() throws Exception {
manualSetUp();
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, new AsciiString("application/bad", UTF_8))
.set(TE_HEADER, TE_TRAILERS)
.path(new AsciiString("/foo/bar"));
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
Http2Headers responseHeaders = new DefaultHttp2Headers()
.set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.INTERNAL.value()))
.set(InternalStatus.MESSAGE_KEY.name(), "Content-Type 'application/bad' is not supported")
.status("" + 415)
.set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
verifyWrite()
.writeHeaders(
eq(ctx()),
eq(STREAM_ID),
eq(responseHeaders),
eq(0),
eq(false),
any(ChannelPromise.class));
}
private void writeErrorResponse(ChannelHandlerContext ctx, int streamId, HttpResponseStatus status,
@Nullable ByteBuf content) throws Http2Exception {
final ByteBuf data =
content != null ? content
: Unpooled.wrappedBuffer(status.toString().getBytes(StandardCharsets.UTF_8));
writer.writeHeaders(
ctx, streamId,
new DefaultHttp2Headers(false)
.status(status.codeAsText())
.set(HttpHeaderNames.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString())
.setInt(HttpHeaderNames.CONTENT_LENGTH, data.readableBytes()),
0, false, ctx.voidPromise());
writer.writeData(ctx, streamId, data, 0, true, ctx.voidPromise());
final Http2Stream stream = writer.connection().stream(streamId);
if (stream != null && writer.flowController().hasFlowControlled(stream)) {
// Ensure to flush the error response if it's flow-controlled so that it is sent
// before an RST_STREAM frame.
writer.flowController().writePendingBytes();
}
}
/**
* Converts the specified Armeria HTTP/2 response headers into Netty HTTP/2 headers.
*
* @param inputHeaders the HTTP/2 response headers to convert.
*/
public static Http2Headers toNettyHttp2ServerTrailer(HttpHeaders inputHeaders) {
final Http2Headers outputHeaders = new DefaultHttp2Headers(false, inputHeaders.size());
for (Entry<AsciiString, String> entry : inputHeaders) {
final AsciiString name = entry.getKey();
final String value = entry.getValue();
if (HTTP_TO_HTTP2_HEADER_BLACKLIST.contains(name)) {
continue;
}
if (ADDITIONAL_RESPONSE_HEADER_BLACKLIST.contains(name)) {
continue;
}
if (isTrailerBlacklisted(name)) {
continue;
}
outputHeaders.add(name, value);
}
return outputHeaders;
}
@Test
public void headersWithMissingPathShouldFail() throws Exception {
manualSetUp();
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC);
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
channelRead(headersFrame);
Http2Headers responseHeaders = new DefaultHttp2Headers()
.set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.UNIMPLEMENTED.value()))
.set(InternalStatus.MESSAGE_KEY.name(), "Expected path but is missing")
.status("" + 404)
.set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
verifyWrite()
.writeHeaders(
eq(ctx()),
eq(STREAM_ID),
eq(responseHeaders),
eq(0),
eq(false),
any(ChannelPromise.class));
}
@Test
void toArmeriaRequestHeaders() {
final Http2Headers in = new DefaultHttp2Headers().set("a", "b");
final InetSocketAddress socketAddress = new InetSocketAddress(36462);
final Channel channel = mock(Channel.class);
when(channel.localAddress()).thenReturn(socketAddress);
final ChannelHandlerContext ctx = mock(ChannelHandlerContext.class);
when(ctx.channel()).thenReturn(channel);
in.set(HttpHeaderNames.METHOD, "GET")
.set(HttpHeaderNames.PATH, "/");
// Request headers without pseudo headers.
final RequestHeaders headers =
ArmeriaHttpUtil.toArmeriaRequestHeaders(ctx, in, false, "https", serverConfig());
assertThat(headers.scheme()).isEqualTo("https");
assertThat(headers.authority()).isEqualTo("foo:36462");
}
@Test
public void testFullRequest() throws Exception {
outputReceived = new CountDownLatch(1);
Http2Headers headers = new DefaultHttp2Headers().method("GET").path("/");
Http2Request requestIn = Http2Request.build(1, headers, true);
channel.writeInbound(requestIn);
channel.runPendingTasks(); // blocks
Uninterruptibles.awaitUninterruptibly(outputReceived);
Request requestOut = requests.remove(0);
assertNotNull(requestOut);
assertTrue(requestOut instanceof FullRequest);
assertEquals("h2", requestOut.version());
assertEquals(HttpMethod.GET, requestOut.method());
assertEquals("/", requestOut.path());
assertFalse(requestOut.hasBody());
assertNotNull(requestOut.body());
assertEquals(0, requestOut.body().readableBytes());
assertEquals(1, requestOut.streamId());
}