Skip to content

Commit b3724eb

Browse files
committed
Fix guard against multiple subscriptions
This commit changes the guard against multiple subscriptions, as the previously used doOnSubscribe hook could not function as guard in certain scenarios. See gh-32727 Closes gh-32728
1 parent 2e74a4d commit b3724eb

File tree

1 file changed

+40
-10
lines changed

1 file changed

+40
-10
lines changed

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

+40-10
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616

1717
package org.springframework.http.client.reactive;
1818

19+
import java.util.Objects;
1920
import java.util.concurrent.atomic.AtomicBoolean;
2021

22+
import org.reactivestreams.Publisher;
23+
import org.reactivestreams.Subscriber;
24+
import org.reactivestreams.Subscription;
2125
import reactor.core.publisher.Flux;
2226

2327
import org.springframework.core.io.buffer.DataBuffer;
@@ -54,16 +58,7 @@ protected AbstractClientHttpResponse(int statusCode, HttpHeaders headers,
5458
this.statusCode = statusCode;
5559
this.headers = headers;
5660
this.cookies = cookies;
57-
this.body = singleSubscription(body);
58-
}
59-
60-
private static Flux<DataBuffer> singleSubscription(Flux<DataBuffer> body) {
61-
AtomicBoolean subscribed = new AtomicBoolean();
62-
return body.doOnSubscribe(s -> {
63-
if (!subscribed.compareAndSet(false, true)) {
64-
throw new IllegalStateException("The client response body can only be consumed once");
65-
}
66-
});
61+
this.body = Flux.from(new SingleSubscriberPublisher<>(body));
6762
}
6863

6964

@@ -91,4 +86,39 @@ public MultiValueMap<String, ResponseCookie> getCookies() {
9186
public Flux<DataBuffer> getBody() {
9287
return this.body;
9388
}
89+
90+
91+
private static final class SingleSubscriberPublisher<T> implements Publisher<T> {
92+
93+
private static final Subscription NO_OP_SUBSCRIPTION = new Subscription() {
94+
@Override
95+
public void request(long l) {
96+
}
97+
98+
@Override
99+
public void cancel() {
100+
}
101+
};
102+
103+
private final Publisher<T> delegate;
104+
105+
private final AtomicBoolean subscribed = new AtomicBoolean();
106+
107+
108+
public SingleSubscriberPublisher(Publisher<T> delegate) {
109+
this.delegate = delegate;
110+
}
111+
112+
@Override
113+
public void subscribe(Subscriber<? super T> subscriber) {
114+
Objects.requireNonNull(subscriber, "Subscriber must not be null");
115+
if (this.subscribed.compareAndSet(false, true)) {
116+
this.delegate.subscribe(subscriber);
117+
}
118+
else {
119+
subscriber.onSubscribe(NO_OP_SUBSCRIPTION);
120+
subscriber.onError(new IllegalStateException("The client response body can only be consumed once"));
121+
}
122+
}
123+
}
94124
}

0 commit comments

Comments
 (0)