Skip to content

Commit 60b5bbe

Browse files
committed
Introduce request attributes in RestClient
This commit introduces request attributes in the RestClient and underlying infrastructure (i.e. HttpRequest). Closes gh-32027
1 parent c36e270 commit 60b5bbe

File tree

21 files changed

+385
-9
lines changed

21 files changed

+385
-9
lines changed

framework-docs/modules/ROOT/pages/integration/rest-clients.adoc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,8 @@ method parameters:
10071007
is supported for non-String values.
10081008

10091009
| `@RequestAttribute`
1010-
| Provide an `Object` to add as a request attribute. Only supported by `WebClient`.
1010+
| Provide an `Object` to add as a request attribute. Only supported by `RestClient`
1011+
and `WebClient`.
10111012

10121013
| `@RequestBody`
10131014
| Provide the body of the request either as an Object to be serialized, or a

spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java

+15
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.io.IOException;
2020
import java.net.URI;
21+
import java.util.Map;
22+
import java.util.concurrent.ConcurrentHashMap;
2123

2224
import org.springframework.http.HttpMethod;
2325
import org.springframework.http.client.ClientHttpRequest;
@@ -46,6 +48,9 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie
4648

4749
private boolean executed = false;
4850

51+
@Nullable
52+
Map<String, Object> attributes;
53+
4954

5055
/**
5156
* Create a {@code MockClientHttpRequest} with {@link HttpMethod#GET GET} as
@@ -115,6 +120,16 @@ public boolean isExecuted() {
115120
return this.executed;
116121
}
117122

123+
@Override
124+
public Map<String, Object> getAttributes() {
125+
Map<String, Object> attributes = this.attributes;
126+
if (attributes == null) {
127+
attributes = new ConcurrentHashMap<>();
128+
this.attributes = attributes;
129+
}
130+
return attributes;
131+
}
132+
118133
/**
119134
* Set the {@link #isExecuted() executed} flag to {@code true} and return the
120135
* configured {@link #setResponse(ClientHttpResponse) response}.

spring-web/src/main/java/org/springframework/http/HttpRequest.java

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.http;
1818

1919
import java.net.URI;
20+
import java.util.Map;
2021

2122
/**
2223
* Represents an HTTP request message, consisting of a
@@ -41,4 +42,10 @@ public interface HttpRequest extends HttpMessage {
4142
*/
4243
URI getURI();
4344

45+
/**
46+
* Return a mutable map of request attributes for this request.
47+
* @since 6.2
48+
*/
49+
Map<String, Object> getAttributes();
50+
4451
}

spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java

+15
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.io.IOException;
2020
import java.io.OutputStream;
21+
import java.util.LinkedHashMap;
22+
import java.util.Map;
2123

2224
import org.springframework.http.HttpHeaders;
2325
import org.springframework.lang.Nullable;
@@ -39,6 +41,9 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
3941
@Nullable
4042
private HttpHeaders readOnlyHeaders;
4143

44+
@Nullable
45+
private Map<String, Object> attributes;
46+
4247

4348
@Override
4449
public final HttpHeaders getHeaders() {
@@ -60,6 +65,16 @@ public final OutputStream getBody() throws IOException {
6065
return getBodyInternal(this.headers);
6166
}
6267

68+
@Override
69+
public Map<String, Object> getAttributes() {
70+
Map<String, Object> attributes = this.attributes;
71+
if (attributes == null) {
72+
attributes = new LinkedHashMap<>();
73+
this.attributes = attributes;
74+
}
75+
return attributes;
76+
}
77+
6378
@Override
6479
public final ClientHttpResponse execute() throws IOException {
6580
assertNotExecuted();

spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOExc
9191
HttpMethod method = request.getMethod();
9292
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
9393
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
94+
request.getAttributes().forEach((key, value) -> delegate.getAttributes().put(key, value));
9495
if (body.length > 0) {
9596
if (delegate instanceof StreamingHttpOutputMessage streamingOutputMessage) {
9697
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {

spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java

+9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.http.client.support;
1818

1919
import java.net.URI;
20+
import java.util.Map;
2021

2122
import org.springframework.http.HttpHeaders;
2223
import org.springframework.http.HttpMethod;
@@ -70,6 +71,14 @@ public URI getURI() {
7071
return this.request.getURI();
7172
}
7273

74+
/**
75+
* Return the attributes of the wrapped request.
76+
*/
77+
@Override
78+
public Map<String, Object> getAttributes() {
79+
return this.request.getAttributes();
80+
}
81+
7382
/**
7483
* Return the headers of the wrapped request.
7584
*/

spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java

+166
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,16 @@
2929
import java.nio.charset.Charset;
3030
import java.nio.charset.StandardCharsets;
3131
import java.security.Principal;
32+
import java.util.AbstractCollection;
33+
import java.util.AbstractMap;
34+
import java.util.AbstractSet;
3235
import java.util.Arrays;
36+
import java.util.Collection;
3337
import java.util.Enumeration;
3438
import java.util.Iterator;
3539
import java.util.List;
3640
import java.util.Map;
41+
import java.util.Set;
3742

3843
import jakarta.servlet.http.HttpServletRequest;
3944

@@ -67,6 +72,10 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
6772
@Nullable
6873
private HttpHeaders headers;
6974

75+
@Nullable
76+
private Map<String, Object> attributes;
77+
78+
7079
@Nullable
7180
private ServerHttpAsyncRequestControl asyncRequestControl;
7281

@@ -207,6 +216,16 @@ public InetSocketAddress getRemoteAddress() {
207216
return new InetSocketAddress(this.servletRequest.getRemoteHost(), this.servletRequest.getRemotePort());
208217
}
209218

219+
@Override
220+
public Map<String, Object> getAttributes() {
221+
Map<String, Object> attributes = this.attributes;
222+
if (attributes == null) {
223+
attributes = new AttributesMap();
224+
this.attributes = attributes;
225+
}
226+
return attributes;
227+
}
228+
210229
@Override
211230
public InputStream getBody() throws IOException {
212231
if (isFormPost(this.servletRequest) && this.servletRequest.getQueryString() == null) {
@@ -276,4 +295,151 @@ private InputStream getBodyFromServletRequestParameters(HttpServletRequest reque
276295
return new ByteArrayInputStream(bytes);
277296
}
278297

298+
299+
private final class AttributesMap extends AbstractMap<String, Object> {
300+
301+
@Nullable
302+
private transient Set<String> keySet;
303+
304+
@Nullable
305+
private transient Collection<Object> values;
306+
307+
@Nullable
308+
private transient Set<Entry<String, Object>> entrySet;
309+
310+
311+
@Override
312+
public int size() {
313+
int size = 0;
314+
for (Enumeration<?> names = servletRequest.getAttributeNames(); names.hasMoreElements(); names.nextElement()) {
315+
size++;
316+
}
317+
return size;
318+
}
319+
320+
@Override
321+
@Nullable
322+
public Object get(Object key) {
323+
if (key instanceof String name) {
324+
return servletRequest.getAttribute(name);
325+
}
326+
else {
327+
return null;
328+
}
329+
}
330+
331+
@Override
332+
@Nullable
333+
public Object put(String key, Object value) {
334+
Object old = get(key);
335+
servletRequest.setAttribute(key, value);
336+
return old;
337+
}
338+
339+
@Override
340+
@Nullable
341+
public Object remove(Object key) {
342+
if (key instanceof String name) {
343+
Object old = get(key);
344+
servletRequest.removeAttribute(name);
345+
return old;
346+
}
347+
else {
348+
return null;
349+
}
350+
}
351+
352+
@Override
353+
public void clear() {
354+
for (Enumeration<String> names = servletRequest.getAttributeNames(); names.hasMoreElements(); ) {
355+
String name = names.nextElement();
356+
servletRequest.removeAttribute(name);
357+
}
358+
}
359+
360+
@Override
361+
public Set<String> keySet() {
362+
Set<String> keySet = this.keySet;
363+
if (keySet == null) {
364+
keySet = new AbstractSet<>() {
365+
@Override
366+
public Iterator<String> iterator() {
367+
return servletRequest.getAttributeNames().asIterator();
368+
}
369+
370+
@Override
371+
public int size() {
372+
return AttributesMap.this.size();
373+
}
374+
};
375+
this.keySet = keySet;
376+
}
377+
return keySet;
378+
}
379+
380+
@Override
381+
public Collection<Object> values() {
382+
Collection<Object> values = this.values;
383+
if (values == null) {
384+
values = new AbstractCollection<>() {
385+
@Override
386+
public Iterator<Object> iterator() {
387+
Enumeration<String> e = servletRequest.getAttributeNames();
388+
return new Iterator<>() {
389+
@Override
390+
public boolean hasNext() {
391+
return e.hasMoreElements();
392+
}
393+
394+
@Override
395+
public Object next() {
396+
String name = e.nextElement();
397+
return servletRequest.getAttribute(name);
398+
}
399+
};
400+
}
401+
402+
@Override
403+
public int size() {
404+
return AttributesMap.this.size();
405+
}
406+
};
407+
this.values = values;
408+
}
409+
return values;
410+
}
411+
412+
@Override
413+
public Set<Entry<String, Object>> entrySet() {
414+
Set<Entry<String, Object>> entrySet = this.entrySet;
415+
if (entrySet == null) {
416+
entrySet = new AbstractSet<>() {
417+
@Override
418+
public Iterator<Entry<String, Object>> iterator() {
419+
Enumeration<String> e = servletRequest.getAttributeNames();
420+
return new Iterator<>() {
421+
@Override
422+
public boolean hasNext() {
423+
return e.hasMoreElements();
424+
}
425+
426+
@Override
427+
public Entry<String, Object> next() {
428+
String name = e.nextElement();
429+
Object value = servletRequest.getAttribute(name);
430+
return new SimpleImmutableEntry<>(name, value);
431+
}
432+
};
433+
}
434+
435+
@Override
436+
public int size() {
437+
return AttributesMap.this.size();
438+
}
439+
};
440+
this.entrySet = entrySet;
441+
}
442+
return entrySet;
443+
}
444+
}
279445
}

spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java

+24
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import java.net.URI;
2020
import java.net.URLDecoder;
2121
import java.nio.charset.StandardCharsets;
22+
import java.util.Collections;
23+
import java.util.Map;
24+
import java.util.function.Supplier;
2225
import java.util.regex.Matcher;
2326
import java.util.regex.Pattern;
2427

@@ -68,6 +71,9 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
6871
@Nullable
6972
private String logPrefix;
7073

74+
@Nullable
75+
private Supplier<Map<String, Object>> attributesSupplier;
76+
7177

7278
/**
7379
* Constructor with the method, URI and headers for the request.
@@ -122,6 +128,16 @@ public URI getURI() {
122128
return this.uri;
123129
}
124130

131+
@Override
132+
public Map<String, Object> getAttributes() {
133+
if (this.attributesSupplier != null) {
134+
return this.attributesSupplier.get();
135+
}
136+
else {
137+
return Collections.emptyMap();
138+
}
139+
}
140+
125141
@Override
126142
public RequestPath getPath() {
127143
return this.path;
@@ -230,4 +246,12 @@ protected String initLogPrefix() {
230246
return getId();
231247
}
232248

249+
/**
250+
* Set the attribute supplier.
251+
* <p><strong>Note:</strong> This is exposed mainly for internal framework
252+
* use.
253+
*/
254+
public void setAttributesSupplier(Supplier<Map<String, Object>> attributesSupplier) {
255+
this.attributesSupplier = attributesSupplier;
256+
}
233257
}

spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.net.InetSocketAddress;
2020
import java.net.URI;
21+
import java.util.Map;
2122

2223
import reactor.core.publisher.Flux;
2324

@@ -70,6 +71,11 @@ public URI getURI() {
7071
return getDelegate().getURI();
7172
}
7273

74+
@Override
75+
public Map<String, Object> getAttributes() {
76+
return getDelegate().getAttributes();
77+
}
78+
7379
@Override
7480
public RequestPath getPath() {
7581
return getDelegate().getPath();

0 commit comments

Comments
 (0)