17
17
package org .springframework .http .client .reactive ;
18
18
19
19
import java .util .Collection ;
20
- import java .util .concurrent .atomic .AtomicBoolean ;
20
+ import java .util .concurrent .atomic .AtomicInteger ;
21
+ import java .util .function .BiFunction ;
21
22
22
23
import io .netty .buffer .ByteBufAllocator ;
23
24
import reactor .core .publisher .Flux ;
25
+ import reactor .netty .Connection ;
24
26
import reactor .netty .NettyInbound ;
25
27
import reactor .netty .http .client .HttpClientResponse ;
26
28
29
31
import org .springframework .http .HttpHeaders ;
30
32
import org .springframework .http .HttpStatus ;
31
33
import org .springframework .http .ResponseCookie ;
34
+ import org .springframework .lang .Nullable ;
35
+ import org .springframework .util .Assert ;
32
36
import org .springframework .util .CollectionUtils ;
33
37
import org .springframework .util .LinkedMultiValueMap ;
34
38
import org .springframework .util .MultiValueMap ;
@@ -48,31 +52,53 @@ class ReactorClientHttpResponse implements ClientHttpResponse {
48
52
49
53
private final NettyInbound inbound ;
50
54
51
- private final AtomicBoolean rejectSubscribers = new AtomicBoolean ();
55
+ @ Nullable
56
+ private final Connection connection ;
52
57
58
+ // 0 - not subscribed, 1 - subscribed, 2 - cancelled
59
+ private final AtomicInteger state = new AtomicInteger (0 );
53
60
61
+
62
+ /**
63
+ * Constructor that matches the inputs from
64
+ * {@link reactor.netty.http.client.HttpClient.ResponseReceiver#responseConnection(BiFunction)}.
65
+ * @since 5.3
66
+ */
67
+ public ReactorClientHttpResponse (HttpClientResponse response , Connection connection ) {
68
+ this .response = response ;
69
+ this .inbound = connection .inbound ();
70
+ this .bufferFactory = new NettyDataBufferFactory (connection .outbound ().alloc ());
71
+ this .connection = connection ;
72
+ }
73
+
74
+ /**
75
+ * Constructor with inputs extracted from a {@link Connection}.
76
+ * @deprecated as of 5.2.8
77
+ */
78
+ @ Deprecated
54
79
public ReactorClientHttpResponse (HttpClientResponse response , NettyInbound inbound , ByteBufAllocator alloc ) {
55
80
this .response = response ;
56
81
this .inbound = inbound ;
57
82
this .bufferFactory = new NettyDataBufferFactory (alloc );
83
+ this .connection = null ;
58
84
}
59
85
60
86
61
87
@ Override
62
88
public Flux <DataBuffer > getBody () {
63
89
return this .inbound .receive ()
64
90
.doOnSubscribe (s -> {
65
- if (this .rejectSubscribers .get ()) {
66
- throw new IllegalStateException ("The client response body can only be consumed once." );
91
+ if (!this .state .compareAndSet (0 , 1 )) {
92
+ // https://github.com/reactor/reactor-netty/issues/503
93
+ // FluxReceive rejects multiple subscribers, but not after a cancel().
94
+ // Subsequent subscribers after cancel() will not be rejected, but will hang instead.
95
+ // So we need to reject once in cancelled state.
96
+ if (this .state .get () == 2 ) {
97
+ throw new IllegalStateException ("The client response body can only be consumed once." );
98
+ }
67
99
}
68
100
})
69
- .doOnCancel (() ->
70
- // https://github.com/reactor/reactor-netty/issues/503
71
- // FluxReceive rejects multiple subscribers, but not after a cancel().
72
- // Subsequent subscribers after cancel() will not be rejected, but will hang instead.
73
- // So we need to intercept and reject them in that case.
74
- this .rejectSubscribers .set (true )
75
- )
101
+ .doOnCancel (() -> this .state .compareAndSet (1 , 2 ))
76
102
.map (byteBuf -> {
77
103
byteBuf .retain ();
78
104
return this .bufferFactory .wrap (byteBuf );
@@ -111,6 +137,21 @@ public MultiValueMap<String, ResponseCookie> getCookies() {
111
137
return CollectionUtils .unmodifiableMultiValueMap (result );
112
138
}
113
139
140
+ /**
141
+ * For use by {@link ReactorClientHttpConnector}.
142
+ */
143
+ boolean bodyNotSubscribed () {
144
+ return this .state .get () == 0 ;
145
+ }
146
+
147
+ /**
148
+ * For use by {@link ReactorClientHttpConnector}.
149
+ */
150
+ Connection getConnection () {
151
+ Assert .notNull (this .connection , "Constructor with connection wasn't used" );
152
+ return this .connection ;
153
+ }
154
+
114
155
@ Override
115
156
public String toString () {
116
157
return "ReactorClientHttpResponse{" +
0 commit comments