Skip to content

Commit 8e754d0

Browse files
committed
Add URI template support to ProxyExchange for observability
1 parent 9c38aad commit 8e754d0

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

spring-cloud-gateway-proxyexchange-webmvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.LinkedHashSet;
3333
import java.util.List;
3434
import java.util.Locale;
35+
import java.util.Map;
3536
import java.util.Objects;
3637
import java.util.Set;
3738
import java.util.Vector;
@@ -51,6 +52,7 @@
5152
import org.springframework.core.Conventions;
5253
import org.springframework.core.MethodParameter;
5354
import org.springframework.core.ParameterizedTypeReference;
55+
import org.springframework.http.HttpEntity;
5456
import org.springframework.http.HttpHeaders;
5557
import org.springframework.http.RequestEntity;
5658
import org.springframework.http.RequestEntity.BodyBuilder;
@@ -142,6 +144,10 @@ public class ProxyExchange<T> {
142144

143145
private @Nullable URI uri;
144146

147+
private @Nullable String uriTemplate;
148+
149+
private @Nullable Map<String, ?> uriVariables;
150+
145151
private RestTemplate rest;
146152

147153
private @Nullable Object body;
@@ -231,6 +237,8 @@ public ProxyExchange<T> excluded(String... names) {
231237
*/
232238
public ProxyExchange<T> uri(URI uri) {
233239
this.uri = uri;
240+
this.uriTemplate = null;
241+
this.uriVariables = null;
234242
return this;
235243
}
236244

@@ -248,6 +256,21 @@ public ProxyExchange<T> uri(String uri) {
248256
}
249257
}
250258

259+
/**
260+
* Sets the uri for the backend call using a URI template with variables. When a
261+
* template is provided, the downstream {@link RestTemplate} call preserves the
262+
* template pattern for observability (e.g. Micrometer URI tags).
263+
* @param uriTemplate the URI template (e.g. {@code "http://service/foos/{id}"})
264+
* @param uriVariables the variables to expand in the template
265+
* @return this for convenience
266+
*/
267+
public ProxyExchange<T> uri(String uriTemplate, Map<String, ?> uriVariables) {
268+
this.uriTemplate = uriTemplate;
269+
this.uriVariables = uriVariables;
270+
this.uri = rest.getUriTemplateHandler().expand(uriTemplate, uriVariables);
271+
return this;
272+
}
273+
251274
public @Nullable String path() {
252275
return (String) this.webRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE,
253276
WebRequest.SCOPE_REQUEST);
@@ -383,6 +406,11 @@ private ResponseEntity<T> exchange(RequestEntity<?> requestEntity) {
383406
if (type instanceof TypeVariable || type instanceof WildcardType) {
384407
type = Object.class;
385408
}
409+
if (this.uriTemplate != null && this.uriVariables != null) {
410+
return rest.exchange(this.uriTemplate, Objects.requireNonNull(requestEntity.getMethod()),
411+
new HttpEntity<>(requestEntity.getBody(), requestEntity.getHeaders()),
412+
ParameterizedTypeReference.forType(type), this.uriVariables);
413+
}
386414
return rest.exchange(requestEntity, ParameterizedTypeReference.forType(type));
387415
}
388416

spring-cloud-gateway-proxyexchange-webmvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Map;
2424

2525
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
26+
import io.micrometer.core.instrument.MeterRegistry;
2627
import org.junit.jupiter.api.BeforeEach;
2728
import org.junit.jupiter.api.Disabled;
2829
import org.junit.jupiter.api.Test;
@@ -70,6 +71,9 @@ public class ProductionConfigurationTests {
7071
@Autowired
7172
private TestApplication application;
7273

74+
@Autowired
75+
private MeterRegistry meterRegistry;
76+
7377
@LocalServerPort
7478
private int port;
7579

@@ -84,6 +88,17 @@ public void get() {
8488
assertThat(rest.getForObject("/proxy/0", Foo.class).getName()).isEqualTo("bye");
8589
}
8690

91+
@Test
92+
public void getWithUriTemplate() {
93+
assertThat(rest.getForObject("/proxy/template/0", Foo.class).getName()).isEqualTo("bye");
94+
}
95+
96+
@Test
97+
public void getWithUriTemplatePreservesUriTagForObservability() {
98+
rest.getForObject("/proxy/template/0", Foo.class);
99+
assertThat(meterRegistry.find("http.client.requests").tag("uri", "/foos/{id}").timer()).isNotNull();
100+
}
101+
87102
@Test
88103
public void path() {
89104
assertThat(rest.getForObject("/proxy/path/1", Foo.class).getName()).isEqualTo("foo");
@@ -356,6 +371,11 @@ public ResponseEntity<?> proxyFoos(@PathVariable Integer id, ProxyExchange<?> pr
356371
return proxy.uri(home.toString() + "/foos/" + id).get();
357372
}
358373

374+
@GetMapping("/proxy/template/{id}")
375+
public ResponseEntity<?> proxyWithTemplate(@PathVariable Integer id, ProxyExchange<?> proxy) {
376+
return proxy.uri(home.toString() + "/foos/{id}", Map.of("id", String.valueOf(id))).get();
377+
}
378+
359379
@GetMapping("/proxy/path/**")
360380
public ResponseEntity<?> proxyPath(ProxyExchange<?> proxy, UriComponentsBuilder uri) {
361381
String path = proxy.path("/proxy/path/");

0 commit comments

Comments
 (0)