Skip to content

Commit 8b8cbb0

Browse files
committed
use registry provider for attributes
1 parent 4801530 commit 8b8cbb0

6 files changed

Lines changed: 348 additions & 116 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2026 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal.matcher;
18+
19+
/**
20+
* Interface for resolving attributes from {@link MatchContext}.
21+
*/
22+
public interface AttributeProvider {
23+
/**
24+
* Returns the names of the attributes this provider handles.
25+
*/
26+
Iterable<String> names();
27+
28+
/**
29+
* Resolves the attribute value from the context.
30+
*/
31+
Object get(MatchContext context);
32+
}
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/*
2+
* Copyright 2026 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal.matcher;
18+
19+
import com.google.common.collect.ImmutableSet;
20+
import io.grpc.Metadata;
21+
import java.util.Collections;
22+
import java.util.Map;
23+
import java.util.Set;
24+
import java.util.concurrent.ConcurrentHashMap;
25+
import javax.annotation.Nullable;
26+
27+
/**
28+
* Registry for {@link AttributeProvider}s.
29+
*/
30+
public final class AttributeProviderRegistry {
31+
private static final AttributeProviderRegistry DEFAULT_INSTANCE = new AttributeProviderRegistry();
32+
33+
private final Map<String, AttributeProvider> providers = new ConcurrentHashMap<>();
34+
35+
private AttributeProviderRegistry() {
36+
register(new HeadersProvider());
37+
register(new HostProvider());
38+
register(new IdProvider());
39+
register(new MethodProvider());
40+
register(new PathProvider());
41+
register(new QueryProvider());
42+
register(new SchemeProvider());
43+
register(new ProtocolProvider());
44+
register(new TimeProvider());
45+
register(new RefererProvider());
46+
register(new UserAgentProvider());
47+
}
48+
49+
public static AttributeProviderRegistry getDefaultRegistry() {
50+
return DEFAULT_INSTANCE;
51+
}
52+
53+
public void register(AttributeProvider provider) {
54+
for (String name : provider.names()) {
55+
providers.put(name, provider);
56+
}
57+
}
58+
59+
@Nullable
60+
public AttributeProvider get(String name) {
61+
return providers.get(name);
62+
}
63+
64+
public Set<String> getRegisteredNames() {
65+
return Collections.unmodifiableSet(providers.keySet());
66+
}
67+
68+
private static String orEmpty(@Nullable Object s) {
69+
return s == null ? "" : s.toString();
70+
}
71+
72+
private static String or(@Nullable Object s, String def) {
73+
return s == null ? def : s.toString();
74+
}
75+
76+
private static String getHeader(MatchContext context, String key) {
77+
Metadata metadata = context.getMetadata();
78+
Iterable<String> values = metadata.getAll(
79+
Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER));
80+
if (values == null) {
81+
return "";
82+
}
83+
return String.join(",", values);
84+
}
85+
86+
private static final class HeadersProvider implements AttributeProvider {
87+
private static final Set<String> NAMES = Collections.singleton("headers");
88+
89+
@Override
90+
public Iterable<String> names() {
91+
return NAMES;
92+
}
93+
94+
@Override
95+
public Object get(MatchContext context) {
96+
return new HeadersWrapper(context);
97+
}
98+
}
99+
100+
private static final class HostProvider implements AttributeProvider {
101+
private static final Set<String> NAMES = Collections.singleton("host");
102+
103+
@Override
104+
public Iterable<String> names() {
105+
return NAMES;
106+
}
107+
108+
@Override
109+
public Object get(MatchContext context) {
110+
return orEmpty(context.getRawAttribute("host"));
111+
}
112+
}
113+
114+
private static final class IdProvider implements AttributeProvider {
115+
private static final Set<String> NAMES = Collections.singleton("id");
116+
117+
@Override
118+
public Iterable<String> names() {
119+
return NAMES;
120+
}
121+
122+
@Override
123+
public Object get(MatchContext context) {
124+
return orEmpty(context.getRawAttribute("id"));
125+
}
126+
}
127+
128+
private static final class MethodProvider implements AttributeProvider {
129+
private static final Set<String> NAMES = Collections.singleton("method");
130+
131+
@Override
132+
public Iterable<String> names() {
133+
return NAMES;
134+
}
135+
136+
@Override
137+
public Object get(MatchContext context) {
138+
return or(context.getRawAttribute("method"), "POST");
139+
}
140+
}
141+
142+
private static final class PathProvider implements AttributeProvider {
143+
private static final Set<String> NAMES = ImmutableSet.of("path", "url_path");
144+
145+
@Override
146+
public Iterable<String> names() {
147+
return NAMES;
148+
}
149+
150+
@Override
151+
public Object get(MatchContext context) {
152+
return orEmpty(context.getRawAttribute("path"));
153+
}
154+
}
155+
156+
private static final class QueryProvider implements AttributeProvider {
157+
private static final Set<String> NAMES = Collections.singleton("query");
158+
159+
@Override
160+
public Iterable<String> names() {
161+
return NAMES;
162+
}
163+
164+
@Override
165+
public Object get(MatchContext context) {
166+
return "";
167+
}
168+
}
169+
170+
private static final class SchemeProvider implements AttributeProvider {
171+
private static final Set<String> NAMES = Collections.singleton("scheme");
172+
173+
@Override
174+
public Iterable<String> names() {
175+
return NAMES;
176+
}
177+
178+
@Override
179+
public Object get(MatchContext context) {
180+
return "";
181+
}
182+
}
183+
184+
private static final class ProtocolProvider implements AttributeProvider {
185+
private static final Set<String> NAMES = Collections.singleton("protocol");
186+
187+
@Override
188+
public Iterable<String> names() {
189+
return NAMES;
190+
}
191+
192+
@Override
193+
public Object get(MatchContext context) {
194+
return "";
195+
}
196+
}
197+
198+
private static final class TimeProvider implements AttributeProvider {
199+
private static final Set<String> NAMES = Collections.singleton("time");
200+
201+
@Override
202+
public Iterable<String> names() {
203+
return NAMES;
204+
}
205+
206+
@Override
207+
@Nullable
208+
public Object get(MatchContext context) {
209+
return null;
210+
}
211+
}
212+
213+
private static final class RefererProvider implements AttributeProvider {
214+
private static final Set<String> NAMES = Collections.singleton("referer");
215+
216+
@Override
217+
public Iterable<String> names() {
218+
return NAMES;
219+
}
220+
221+
@Override
222+
public Object get(MatchContext context) {
223+
return getHeader(context, "referer");
224+
}
225+
}
226+
227+
private static final class UserAgentProvider implements AttributeProvider {
228+
private static final Set<String> NAMES = Collections.singleton("useragent");
229+
230+
@Override
231+
public Iterable<String> names() {
232+
return NAMES;
233+
}
234+
235+
@Override
236+
public Object get(MatchContext context) {
237+
return getHeader(context, "user-agent");
238+
}
239+
}
240+
}

xds/src/main/java/io/grpc/xds/internal/matcher/GrpcCelEnvironment.java

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import com.google.common.collect.ImmutableSet;
2020
import dev.cel.runtime.CelVariableResolver;
21-
import io.grpc.Metadata;
2221
import java.util.AbstractMap;
2322
import java.util.Optional;
2423
import java.util.Set;
@@ -44,48 +43,10 @@ public Optional<Object> find(String name) {
4443

4544
@Nullable
4645
private Object getRequestField(String requestField) {
47-
switch (requestField) {
48-
case "headers": return new HeadersWrapper(context);
49-
case "host": return orEmpty(context.getHost());
50-
case "id": return orEmpty(context.getId());
51-
case "method": return or(context.getMethod(), "POST");
52-
case "path":
53-
case "url_path":
54-
return orEmpty(context.getPath());
55-
case "query": return "";
56-
case "scheme": return "";
57-
case "protocol": return "";
58-
case "time": return null;
59-
case "referer": return getHeader("referer");
60-
case "useragent": return getHeader("user-agent");
61-
62-
default:
63-
return null;
64-
}
65-
}
66-
67-
private String getHeader(String key) {
68-
Metadata metadata = context.getMetadata();
69-
Iterable<String> values = metadata.getAll(
70-
Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER));
71-
if (values == null) {
72-
return "";
73-
}
74-
return String.join(",", values);
75-
}
76-
77-
private static String orEmpty(@Nullable String s) {
78-
return s == null ? "" : s;
79-
}
80-
81-
private static String or(@Nullable String s, String def) {
82-
return s == null ? def : s;
46+
return context.getAttribute(requestField);
8347
}
8448

8549
private static final class LazyRequestMap extends AbstractMap<String, Object> {
86-
private static final Set<String> KNOWN_KEYS = ImmutableSet.of(
87-
"headers", "host", "id", "method", "path", "url_path", "query", "scheme", "protocol",
88-
"referer", "useragent", "time");
8950
private final GrpcCelEnvironment resolver;
9051

9152
LazyRequestMap(GrpcCelEnvironment resolver) {
@@ -95,34 +56,37 @@ private static final class LazyRequestMap extends AbstractMap<String, Object> {
9556
@Override
9657
public Object get(Object key) {
9758
if (key instanceof String) {
98-
Object val = resolver.getRequestField((String) key);
99-
if (val == null) {
100-
return null;
101-
}
102-
return val;
59+
return resolver.getRequestField((String) key);
10360
}
10461
return null;
10562
}
10663

10764
@Override
10865
public boolean containsKey(Object key) {
109-
boolean contains = key instanceof String && KNOWN_KEYS.contains(key);
110-
return contains;
66+
if (!(key instanceof String)) {
67+
return false;
68+
}
69+
String name = (String) key;
70+
return AttributeProviderRegistry.getDefaultRegistry().get(name) != null
71+
|| resolver.context.getRawAttribute(name) != null;
11172
}
11273

11374
@Override
11475
public Set<String> keySet() {
115-
return KNOWN_KEYS;
76+
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
77+
builder.addAll(AttributeProviderRegistry.getDefaultRegistry().getRegisteredNames());
78+
builder.addAll(resolver.context.getRawAttributes().keySet());
79+
return builder.build();
11680
}
11781

11882
@Override
11983
public int size() {
120-
return KNOWN_KEYS.size();
84+
return keySet().size();
12185
}
12286

12387
@Override
12488
public boolean isEmpty() {
125-
return false;
89+
return keySet().isEmpty();
12690
}
12791

12892
@Override

xds/src/main/java/io/grpc/xds/internal/matcher/HeadersWrapper.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ public String get(Object key) {
4848
return null;
4949
}
5050
switch (headerName) {
51-
case ":method": return context.getMethod();
52-
case ":authority": return context.getHost();
53-
case "host": return context.getHost();
54-
case ":path": return context.getPath();
51+
case ":method": return (String) context.getAttribute("method");
52+
case ":authority": return (String) context.getAttribute("host");
53+
case "host": return (String) context.getAttribute("host");
54+
case ":path": return (String) context.getAttribute("path");
5555
default: return getHeader(headerName);
5656
}
5757
}

0 commit comments

Comments
 (0)