下面列出了io.grpc.InternalStatus#com.linecorp.armeria.common.HttpHeadersBuilder 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
static HttpHeaders statusToTrailers(
ServiceRequestContext ctx, Status status, Metadata metadata, boolean headersSent) {
final HttpHeadersBuilder trailers = GrpcTrailersUtil.statusToTrailers(
status.getCode().value(), status.getDescription(), headersSent);
MetadataUtil.fillHeaders(metadata, trailers);
if (ctx.config().verboseResponses() && status.getCause() != null) {
final ThrowableProto proto = GrpcStatus.serializeThrowable(status.getCause());
trailers.add(GrpcHeaderNames.ARMERIA_GRPC_THROWABLEPROTO_BIN,
Base64.getEncoder().encodeToString(proto.toByteArray()));
}
final HttpHeaders additionalTrailers = ctx.additionalResponseTrailers();
ctx.mutateAdditionalResponseTrailers(HttpHeadersBuilder::clear);
trailers.add(additionalTrailers);
return trailers.build();
}
/**
* Converts the given gRPC status code, and optionally an error message, to headers. The headers will be
* either trailers-only or normal trailers based on {@code headersSent}, whether leading headers have
* already been sent to the client.
*/
public static HttpHeadersBuilder statusToTrailers(int code, @Nullable String message, boolean headersSent) {
final HttpHeadersBuilder trailers;
if (headersSent) {
// Normal trailers.
trailers = HttpHeaders.builder();
} else {
// Trailers only response
trailers = ResponseHeaders.builder()
.endOfStream(true)
.add(HttpHeaderNames.STATUS, HttpStatus.OK.codeAsText())
.add(HttpHeaderNames.CONTENT_TYPE, "application/grpc+proto");
}
trailers.add(GrpcHeaderNames.GRPC_STATUS, Integer.toString(code));
if (message != null) {
trailers.add(GrpcHeaderNames.GRPC_MESSAGE, StatusMessageEscaper.escape(message));
}
return trailers;
}
public static HttpHeaders mergeTrailers(HttpHeaders headers, HttpHeaders additionalTrailers) {
if (additionalTrailers.isEmpty()) {
return headers;
}
if (headers.isEmpty()) {
return additionalTrailers;
}
final HttpHeadersBuilder builder = headers.toBuilder();
for (AsciiString name : additionalTrailers.names()) {
if (!ADDITIONAL_RESPONSE_HEADER_BLACKLIST.contains(name) &&
!isTrailerBlacklisted(name)) {
builder.remove(name);
additionalTrailers.forEachValue(name, value -> builder.add(name, value));
}
}
return builder.build();
}
/**
* Filter the {@link HttpHeaderNames#TE} header according to the
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.2">special rules in the HTTP/2 RFC</a>.
*
* @param entry the entry whose name is {@link HttpHeaderNames#TE}.
* @param out the resulting HTTP/2 headers.
*/
private static void toHttp2HeadersFilterTE(Entry<CharSequence, CharSequence> entry,
HttpHeadersBuilder out) {
if (AsciiString.indexOf(entry.getValue(), ',', 0) == -1) {
if (AsciiString.contentEqualsIgnoreCase(AsciiString.trim(entry.getValue()),
HttpHeaderValues.TRAILERS)) {
out.add(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS.toString());
}
} else {
final List<CharSequence> teValues = StringUtil.unescapeCsvFields(entry.getValue());
for (CharSequence teValue : teValues) {
if (AsciiString.contentEqualsIgnoreCase(AsciiString.trim(teValue),
HttpHeaderValues.TRAILERS)) {
out.add(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS.toString());
break;
}
}
}
}
protected void beforeMethod(final URI uri, final HttpMethod httpMethod, final String path) {
final ContextCarrier contextCarrier = new ContextCarrier();
final String remotePeer = uri.getHost() + ":" + uri.getPort();
final AbstractSpan exitSpan = ContextManager.createExitSpan(path, contextCarrier, remotePeer);
exitSpan.setComponent(ComponentsDefine.ARMERIA);
exitSpan.setLayer(SpanLayer.HTTP);
Tags.HTTP.METHOD.set(exitSpan, httpMethod.name());
ContextManager.getRuntimeContext().put(KEY_SAFE_CLOSEABLE, Clients.withHttpHeaders(headers -> {
HttpHeadersBuilder builder = headers.toBuilder();
for (CarrierItem item = contextCarrier.items(); item.hasNext(); ) {
item = item.next();
builder.add(AsciiString.of(item.getHeadKey()), item.getHeadValue());
}
return builder.build();
}));
}
@Provides
@Singleton
static Function<HttpService, LoggingService> loggingService(
LoggingConfig config,
@RequestHeaderSanitizer Set<Consumer<HttpHeadersBuilder>> requestHeaderSanitizers,
@ResponseHeaderSanitizer Set<Consumer<HttpHeadersBuilder>> responseHeaderSanitizers) {
LoggingServiceBuilder builder = LoggingService.builder();
configureLoggingDecorator(builder, config, requestHeaderSanitizers, responseHeaderSanitizers);
if (config.getLogAllServerRequests()) {
builder.requestLogLevel(LogLevel.INFO);
builder.successfulResponseLogLevel(LogLevel.INFO);
}
return builder::build;
}
@Provides
@Singleton
static Function<HttpClient, LoggingClient> loggingClient(
LoggingConfig config,
@RequestHeaderSanitizer Set<Consumer<HttpHeadersBuilder>> requestHeaderSanitizers,
@ResponseHeaderSanitizer Set<Consumer<HttpHeadersBuilder>> responseHeaderSanitizers) {
LoggingClientBuilder builder = LoggingClient.builder();
configureLoggingDecorator(builder, config, requestHeaderSanitizers, responseHeaderSanitizers);
if (config.getLogAllClientRequests()) {
builder.requestLogLevel(LogLevel.INFO);
builder.successfulResponseLogLevel(LogLevel.INFO);
}
return builder::build;
}
private static void configureLoggingDecorator(
LoggingDecoratorBuilder builder,
LoggingConfig config,
@RequestHeaderSanitizer Set<Consumer<HttpHeadersBuilder>> requestHeaderSanitizers,
@ResponseHeaderSanitizer Set<Consumer<HttpHeadersBuilder>> responseHeaderSanitizers) {
if (!config.getBlacklistedRequestHeaders().isEmpty() || !requestHeaderSanitizers.isEmpty()) {
builder.requestHeadersSanitizer(
new HeaderSanitizer(requestHeaderSanitizers, config.getBlacklistedRequestHeaders()));
}
if (!config.getBlacklistedResponseHeaders().isEmpty() || !responseHeaderSanitizers.isEmpty()) {
builder.responseHeadersSanitizer(
new HeaderSanitizer(responseHeaderSanitizers, config.getBlacklistedResponseHeaders()));
}
}
@Override
public HttpHeaders apply(HttpHeaders entries) {
HttpHeadersBuilder builder = entries.toBuilder();
blacklistedHeaders.forEach(builder::remove);
customSanitizers.forEach(c -> c.accept(builder));
return builder.build();
}
@Nullable
private static HttpHeaders parseGrpcWebTrailers(ByteBuf buf) {
final HttpHeadersBuilder trailers = HttpHeaders.builder();
while (buf.readableBytes() > 0) {
int start = buf.forEachByte(ByteProcessor.FIND_NON_LINEAR_WHITESPACE);
if (start == -1) {
return null;
}
int endExclusive;
if (buf.getByte(start) == ':') {
// We need to skip the pseudoheader colon when searching for the separator.
buf.skipBytes(1);
endExclusive = buf.forEachByte(FIND_COLON);
buf.readerIndex(start);
} else {
endExclusive = buf.forEachByte(FIND_COLON);
}
if (endExclusive == -1) {
return null;
}
final CharSequence name = buf.readCharSequence(endExclusive - start, StandardCharsets.UTF_8);
buf.readerIndex(endExclusive + 1);
start = buf.forEachByte(ByteProcessor.FIND_NON_LINEAR_WHITESPACE);
buf.readerIndex(start);
endExclusive = buf.forEachByte(ByteProcessor.FIND_CRLF);
final CharSequence value = buf.readCharSequence(endExclusive - start, StandardCharsets.UTF_8);
trailers.add(name, value.toString());
start = buf.forEachByte(ByteProcessor.FIND_NON_CRLF);
if (start != -1) {
buf.readerIndex(start);
} else {
// Nothing but CRLF remaining, we're done.
buf.skipBytes(buf.readableBytes());
}
}
return trailers.build();
}
/**
* Copies the headers in the gRPC {@link Metadata} to the Armeria {@link HttpHeadersBuilder}. Headers will
* be added, without replacing any currently present in the {@link HttpHeaders}.
*/
public static void fillHeaders(Metadata metadata, HttpHeadersBuilder builder) {
if (InternalMetadata.headerCount(metadata) == 0) {
return;
}
final byte[][] serializedMetadata = InternalMetadata.serialize(metadata);
assert serializedMetadata.length % 2 == 0;
for (int i = 0; i < serializedMetadata.length; i += 2) {
final AsciiString name = new AsciiString(serializedMetadata[i], false);
if (STRIPPED_HEADERS.contains(name)) {
continue;
}
final byte[] valueBytes = serializedMetadata[i + 1];
final String value;
if (isBinary(name)) {
value = BASE64_ENCODING_OMIT_PADDING.encode(valueBytes);
} else if (isGrpcAscii(valueBytes)) {
value = new String(valueBytes, StandardCharsets.US_ASCII);
} else {
logger.warn("Metadata name=" + name + ", value=" + Arrays.toString(valueBytes) +
" contains invalid ASCII characters, skipping.");
continue;
}
builder.add(name, value);
}
}
@Test
void fillHeadersTest() {
final HttpHeadersBuilder trailers =
ResponseHeaders.builder()
.endOfStream(true)
.add(HttpHeaderNames.STATUS, HttpStatus.OK.codeAsText())
.add(HttpHeaderNames.CONTENT_TYPE, "application/grpc+proto")
.add(GrpcHeaderNames.GRPC_STATUS, "3")
.add(GrpcHeaderNames.GRPC_MESSAGE, "test_grpc_message");
final Metadata metadata = new Metadata();
// be copied into HttpHeaderBuilder trailers
metadata.put(TEST_ASCII_KEY, "metadata_test_string");
metadata.put(TEST_BIN_KEY, "metadata_test_string".getBytes());
// must not be copied into HttpHeaderBuilder trailers
metadata.put(STATUS_KEY, "200");
metadata.put(InternalStatus.CODE_KEY, Status.OK);
metadata.put(InternalStatus.MESSAGE_KEY, "grpc_message_must_not_be_copied");
metadata.put(THROWABLE_PROTO_METADATA_KEY, THROWABLE_PROTO);
MetadataUtil.fillHeaders(metadata, trailers);
assertThat(trailers.getAll(TEST_ASCII_KEY.originalName())).containsExactly("metadata_test_string");
assertThat(Base64.getDecoder().decode(trailers.get(TEST_BIN_KEY.originalName())))
.containsExactly("metadata_test_string".getBytes());
assertThat(trailers.getAll(HttpHeaderNames.STATUS)).containsExactly(HttpStatus.OK.codeAsText());
assertThat(trailers.getAll(HttpHeaderNames.CONTENT_TYPE)).containsExactly("application/grpc+proto");
assertThat(trailers.getAll(GrpcHeaderNames.GRPC_STATUS)).containsExactly("3");
assertThat(trailers.getAll(GrpcHeaderNames.GRPC_MESSAGE)).containsOnly("test_grpc_message");
assertThat(trailers.getAll(GrpcHeaderNames.ARMERIA_GRPC_THROWABLEPROTO_BIN)).isEmpty();
}
@Override
protected final HttpResponse doPost(ServiceRequestContext ctx, PooledHttpRequest req) {
final CompletableFuture<HttpResponse> responseFuture =
req.aggregateWithPooledObjects(ctx.contextAwareEventLoop(), ctx.alloc())
.thenCompose(msg -> deframeMessage(msg.content(), ctx.alloc()))
.thenCompose(this::handleMessage)
.thenApply(responseMessage -> {
final ArmeriaMessageFramer framer = new ArmeriaMessageFramer(
ctx.alloc(), Integer.MAX_VALUE);
final HttpData framed = framer.writePayload(responseMessage);
return HttpResponse.of(
RESPONSE_HEADERS,
framed,
GrpcTrailersUtil.statusToTrailers(StatusCodes.OK, null, true).build());
})
.exceptionally(t -> {
final HttpHeadersBuilder trailers;
if (t instanceof ArmeriaStatusException) {
final ArmeriaStatusException statusException = (ArmeriaStatusException) t;
trailers = GrpcTrailersUtil.statusToTrailers(
statusException.getCode(), statusException.getMessage(), false);
} else {
trailers = GrpcTrailersUtil.statusToTrailers(
StatusCodes.INTERNAL, t.getMessage(), false);
}
return HttpResponse.of(trailers.build());
});
return HttpResponse.from(responseFuture);
}
@Override
public void mutateAdditionalRequestHeaders(Consumer<HttpHeadersBuilder> mutator) {
requireNonNull(mutator, "mutator");
for (;;) {
final HttpHeaders oldValue = additionalRequestHeaders;
final HttpHeadersBuilder builder = oldValue.toBuilder();
mutator.accept(builder);
final HttpHeaders newValue = builder.build();
if (additionalRequestHeadersUpdater.compareAndSet(this, oldValue, newValue)) {
return;
}
}
}
private void mutateAdditionalResponseHeaders(
AtomicReferenceFieldUpdater<DefaultServiceRequestContext, HttpHeaders> atomicUpdater,
Consumer<HttpHeadersBuilder> mutator) {
for (;;) {
final HttpHeaders oldValue = atomicUpdater.get(this);
final HttpHeadersBuilder builder = oldValue.toBuilder();
mutator.accept(builder);
final HttpHeaders newValue = builder.build();
if (atomicUpdater.compareAndSet(this, oldValue, newValue)) {
return;
}
}
}
/**
* Generates immutable HTTP response headers that should be added to a CORS preflight response.
*
* @return {@link HttpHeaders} the HTTP response headers to be added.
*/
public HttpHeaders generatePreflightResponseHeaders() {
final HttpHeadersBuilder headers = HttpHeaders.builder();
preflightResponseHeaders.forEach((key, value) -> {
final Object val = getValue(value);
if (val instanceof Iterable) {
headers.addObject(key, (Iterable<?>) val);
} else {
headers.addObject(key, val);
}
});
return headers.build();
}
private static <T extends Annotation> void setAdditionalHeader(HttpHeadersBuilder headers,
AnnotatedElement element,
String clsAlias,
String elementAlias,
String level,
Class<T> annotation,
Function<T, String> nameGetter,
Function<T, String[]> valueGetter) {
requireNonNull(headers, "headers");
requireNonNull(element, "element");
requireNonNull(level, "level");
final Set<String> addedHeaderSets = new HashSet<>();
AnnotationUtil.findAll(element, annotation).forEach(header -> {
final String name = nameGetter.apply(header);
final String[] value = valueGetter.apply(header);
if (addedHeaderSets.contains(name)) {
logger.warn("The additional {} named '{}' at '{}' is set at the same {} level already;" +
"ignoring.",
clsAlias, name, elementAlias, level);
return;
}
headers.set(HttpHeaderNames.of(name), value);
addedHeaderSets.add(name);
});
}
/**
* Converts the specified Netty HTTP/2 into Armeria HTTP/2 headers.
*/
public static HttpHeaders toArmeria(Http2Headers headers, boolean request, boolean endOfStream) {
final HttpHeadersBuilder builder;
if (request) {
builder = headers.contains(HttpHeaderNames.METHOD) ? RequestHeaders.builder()
: HttpHeaders.builder();
} else {
builder = headers.contains(HttpHeaderNames.STATUS) ? ResponseHeaders.builder()
: HttpHeaders.builder();
}
toArmeria(builder, headers, endOfStream);
return builder.build();
}
/**
* Converts the specified Netty HTTP/1 headers into Armeria HTTP/2 headers.
*/
public static HttpHeaders toArmeria(io.netty.handler.codec.http.HttpHeaders inHeaders) {
if (inHeaders.isEmpty()) {
return HttpHeaders.of();
}
final HttpHeadersBuilder out = HttpHeaders.builder();
out.sizeHint(inHeaders.size());
toArmeria(inHeaders, out);
return out.build();
}
@Test
void inboundCookiesMustBeMergedForHttp1() {
final io.netty.handler.codec.http.HttpHeaders in = new DefaultHttpHeaders();
in.add(HttpHeaderNames.COOKIE, "a=b; c=d");
in.add(HttpHeaderNames.COOKIE, "e=f;g=h");
in.add(HttpHeaderNames.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8);
in.add(HttpHeaderNames.COOKIE, "i=j");
in.add(HttpHeaderNames.COOKIE, "k=l;");
final HttpHeadersBuilder out = HttpHeaders.builder();
toArmeria(in, out);
assertThat(out.getAll(HttpHeaderNames.COOKIE))
.containsExactly("a=b; c=d; e=f; g=h; i=j; k=l");
}
@Test
void stripTEHeaders() {
final io.netty.handler.codec.http.HttpHeaders in = new DefaultHttpHeaders();
in.add(HttpHeaderNames.TE, HttpHeaderValues.GZIP);
final HttpHeadersBuilder out = HttpHeaders.builder();
toArmeria(in, out);
assertThat(out).isEmpty();
}
@Test
void stripTEHeadersExcludingTrailers() {
final io.netty.handler.codec.http.HttpHeaders in = new DefaultHttpHeaders();
in.add(HttpHeaderNames.TE, HttpHeaderValues.GZIP);
in.add(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS);
final HttpHeadersBuilder out = HttpHeaders.builder();
toArmeria(in, out);
assertThat(out.get(HttpHeaderNames.TE)).isEqualTo(HttpHeaderValues.TRAILERS.toString());
}
@Test
void stripTEHeadersCsvSeparatedExcludingTrailers() {
final io.netty.handler.codec.http.HttpHeaders in = new DefaultHttpHeaders();
in.add(HttpHeaderNames.TE, HttpHeaderValues.GZIP + "," + HttpHeaderValues.TRAILERS);
final HttpHeadersBuilder out = HttpHeaders.builder();
toArmeria(in, out);
assertThat(out.get(HttpHeaderNames.TE)).isEqualTo(HttpHeaderValues.TRAILERS.toString());
}
@Test
void stripTEHeadersCsvSeparatedAccountsForValueSimilarToTrailers() {
final io.netty.handler.codec.http.HttpHeaders in = new DefaultHttpHeaders();
in.add(HttpHeaderNames.TE, HttpHeaderValues.GZIP + "," + HttpHeaderValues.TRAILERS + "foo");
final HttpHeadersBuilder out = HttpHeaders.builder();
toArmeria(in, out);
assertThat(out.contains(HttpHeaderNames.TE)).isFalse();
}
@Test
void stripTEHeadersAccountsForValueSimilarToTrailers() {
final io.netty.handler.codec.http.HttpHeaders in = new DefaultHttpHeaders();
in.add(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS + "foo");
final HttpHeadersBuilder out = HttpHeaders.builder();
toArmeria(in, out);
assertThat(out.contains(HttpHeaderNames.TE)).isFalse();
}
@Test
void stripTEHeadersAccountsForOWS() {
final io.netty.handler.codec.http.HttpHeaders in = new DefaultHttpHeaders();
in.add(HttpHeaderNames.TE, " " + HttpHeaderValues.TRAILERS + ' ');
final HttpHeadersBuilder out = HttpHeaders.builder();
toArmeria(in, out);
assertThat(out.get(HttpHeaderNames.TE)).isEqualTo(HttpHeaderValues.TRAILERS.toString());
}
@Test
void stripConnectionHeadersAndNominees() {
final io.netty.handler.codec.http.HttpHeaders in = new DefaultHttpHeaders();
in.add(HttpHeaderNames.CONNECTION, "foo");
in.add("foo", "bar");
final HttpHeadersBuilder out = HttpHeaders.builder();
toArmeria(in, out);
assertThat(out).isEmpty();
}
@Test
void stripConnectionNomineesWithCsv() {
final io.netty.handler.codec.http.HttpHeaders in = new DefaultHttpHeaders();
in.add(HttpHeaderNames.CONNECTION, "foo, bar");
in.add("foo", "baz");
in.add("bar", "qux");
in.add("hello", "world");
final HttpHeadersBuilder out = HttpHeaders.builder();
toArmeria(in, out);
assertThat(out).hasSize(1);
assertThat(out.get(HttpHeaderNames.of("hello"))).isEqualTo("world");
}
@Multibinds
abstract @RequestHeaderSanitizer Set<Consumer<HttpHeadersBuilder>> requestHeaderSanitizers();
@Multibinds
abstract @ResponseHeaderSanitizer Set<Consumer<HttpHeadersBuilder>> responseHeaderSanitizers();