Skip to content

Commit 3bfe7bd

Browse files
sauravzgkannanjgithub
authored andcommitted
xds: Add header mutations library (grpc#12494)
This commit introduces a library for handling header mutations as specified by the xDS protocol. This library provides the core functionality for modifying request and response headers based on a set of rules. The main components of this library are: - `HeaderMutator`: Applies header mutations to `Metadata` objects. - `HeaderMutationFilter`: Filters header mutations based on a set of configurable rules, such as disallowing mutations of system headers. - `HeaderMutations`: A value class that represents the set of mutations to be applied to request and response headers. - `HeaderMutationDisallowedException`: An exception that is thrown when a disallowed header mutation is attempted. This commit also includes comprehensive unit tests for the new library.
1 parent 646b8ed commit 3bfe7bd

4 files changed

Lines changed: 204 additions & 189 deletions

File tree

xds/src/main/java/io/grpc/xds/internal/headermutations/HeaderMutationFilter.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,14 @@ private <T> ImmutableList<T> filterCollection(Collection<T> items,
6262
throws HeaderMutationDisallowedException {
6363
ImmutableList.Builder<T> allowed = ImmutableList.builder();
6464
for (T item : items) {
65-
if (isIgnoredPredicate.test(item)) {
66-
continue;
67-
}
68-
if (isAllowedPredicate.test(item)) {
65+
boolean isIgnored = isIgnoredPredicate.test(item);
66+
boolean isAllowed = isAllowedPredicate.test(item);
67+
68+
// TODO(sauravzg): The specification is ambiguous regarding whether system headers
69+
// should be silently ignored or trigger an error when disallowIsError is enabled.
70+
// We default to triggering errors matching Envoy's implementation.
71+
// Ref: https://github.com/grpc/proposal/pull/481#discussion_r3124453674
72+
if (!isIgnored && isAllowed) {
6973
allowed.add(item);
7074
} else if (disallowIsError()) {
7175
throw new HeaderMutationDisallowedException("Header mutation disallowed");
@@ -97,8 +101,9 @@ private boolean isHeaderMutationAllowed(String headerName,
97101
&& rules.disallowExpression().get().matcher(headerName).matches()) {
98102
return false;
99103
}
100-
if (rules.allowExpression().isPresent()) {
101-
return rules.allowExpression().get().matcher(headerName).matches();
104+
if (rules.allowExpression().isPresent()
105+
&& rules.allowExpression().get().matcher(headerName).matches()) {
106+
return true;
102107
}
103108
return !rules.disallowAll();
104109
}

xds/src/main/java/io/grpc/xds/internal/headermutations/HeaderMutator.java

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import java.util.logging.Logger;
2424

2525
/**
26-
* The HeaderMutator class is an implementation of the HeaderMutator interface. It provides methods
27-
* to apply header mutations to a given set of headers based on a given set of rules.
26+
* The HeaderMutator provides methods to apply header mutations to a given set of headers based on a
27+
* given set of rules.
2828
*/
2929
public class HeaderMutator {
3030

@@ -71,23 +71,29 @@ private void updateHeader(final HeaderValueOption option, Metadata mutableHeader
7171

7272
if (header.key().endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
7373
if (header.rawValue().isPresent()) {
74-
updateHeader(action, Metadata.Key.of(header.key(), Metadata.BINARY_BYTE_MARSHALLER),
75-
header.rawValue().get().toByteArray(), mutableHeaders, keepEmptyValue);
74+
byte[] value = header.rawValue().get().toByteArray();
75+
if (value.length > 0 || keepEmptyValue) {
76+
updateHeader(action, Metadata.Key.of(header.key(), Metadata.BINARY_BYTE_MARSHALLER),
77+
value, mutableHeaders);
78+
}
7679
} else {
7780
logger.fine("Missing binary rawValue for header: " + header.key());
7881
}
7982
} else {
8083
if (header.value().isPresent()) {
81-
updateHeader(action, Metadata.Key.of(header.key(), Metadata.ASCII_STRING_MARSHALLER),
82-
header.value().get(), mutableHeaders, keepEmptyValue);
84+
String value = header.value().get();
85+
if (!value.isEmpty() || keepEmptyValue) {
86+
updateHeader(action, Metadata.Key.of(header.key(), Metadata.ASCII_STRING_MARSHALLER),
87+
value, mutableHeaders);
88+
}
8389
} else {
8490
logger.fine("Missing value for header: " + header.key());
8591
}
8692
}
8793
}
8894

8995
private <T> void updateHeader(final HeaderAppendAction action, final Metadata.Key<T> key,
90-
final T value, Metadata mutableHeaders, boolean keepEmptyValue) {
96+
final T value, Metadata mutableHeaders) {
9197
switch (action) {
9298
case APPEND_IF_EXISTS_OR_ADD:
9399
mutableHeaders.put(key, value);
@@ -112,33 +118,6 @@ private <T> void updateHeader(final HeaderAppendAction action, final Metadata.Ke
112118
// Should be unreachable unless there's a proto schema mismatch.
113119
logger.fine("Unknown HeaderAppendAction: " + action);
114120
}
115-
116-
if (!keepEmptyValue) {
117-
checkAndRemoveEmpty(key, mutableHeaders);
118-
}
119-
}
120-
121-
private <T> void checkAndRemoveEmpty(Metadata.Key<T> key, Metadata headers) {
122-
Iterable<T> values = headers.getAll(key);
123-
if (values == null) {
124-
return;
125-
}
126-
boolean allEmpty = true;
127-
for (T val : values) {
128-
if (val instanceof String) {
129-
if (!((String) val).isEmpty()) {
130-
allEmpty = false;
131-
break;
132-
}
133-
} else if (val instanceof byte[]) {
134-
if (((byte[]) val).length > 0) {
135-
allEmpty = false;
136-
break;
137-
}
138-
}
139-
}
140-
if (allEmpty) {
141-
headers.discardAll(key);
142-
}
143121
}
144122
}
123+

0 commit comments

Comments
 (0)