5353import io .servicetalk .grpc .netty .CompatProto .Compat .ServiceFactory ;
5454import io .servicetalk .grpc .netty .CompatProto .RequestContainer .CompatRequest ;
5555import io .servicetalk .grpc .netty .CompatProto .ResponseContainer .CompatResponse ;
56+ import io .servicetalk .http .api .FilterableStreamingHttpClient ;
57+ import io .servicetalk .http .api .HttpExecutionStrategies ;
58+ import io .servicetalk .http .api .HttpExecutionStrategy ;
5659import io .servicetalk .http .api .HttpResponse ;
5760import io .servicetalk .http .api .HttpResponseStatus ;
5861import io .servicetalk .http .api .HttpServerBuilder ;
5962import io .servicetalk .http .api .HttpServerContext ;
6063import io .servicetalk .http .api .HttpServiceContext ;
6164import io .servicetalk .http .api .SingleAddressHttpClientBuilder ;
6265import io .servicetalk .http .api .StreamingHttpClient ;
66+ import io .servicetalk .http .api .StreamingHttpClientFilter ;
67+ import io .servicetalk .http .api .StreamingHttpClientFilterFactory ;
6368import io .servicetalk .http .api .StreamingHttpRequest ;
69+ import io .servicetalk .http .api .StreamingHttpRequester ;
6470import io .servicetalk .http .api .StreamingHttpResponse ;
6571import io .servicetalk .http .api .StreamingHttpResponseFactory ;
6672import io .servicetalk .http .api .StreamingHttpService ;
6773import io .servicetalk .http .api .StreamingHttpServiceFilter ;
6874import io .servicetalk .http .netty .HttpClients ;
6975import io .servicetalk .http .netty .HttpServers ;
76+ import io .servicetalk .http .utils .BeforeFinallyHttpOperator ;
7077import io .servicetalk .test .resources .DefaultTestCerts ;
7178import io .servicetalk .transport .api .ClientSslConfigBuilder ;
7279import io .servicetalk .transport .api .ServerContext ;
9198import io .grpc .stub .StreamObserver ;
9299import io .netty .handler .ssl .SslContext ;
93100import io .netty .handler .ssl .util .InsecureTrustManagerFactory ;
101+ import org .junit .jupiter .api .AfterEach ;
94102import org .junit .jupiter .api .function .ThrowingSupplier ;
95103import org .junit .jupiter .params .ParameterizedTest ;
96104import org .junit .jupiter .params .provider .Arguments ;
@@ -200,6 +208,15 @@ public SocketAddress listenAddress() {
200208 private static final String CUSTOM_ERROR_MESSAGE = "custom error message" ;
201209 private static final DeliberateException SERVER_PROCESSED_TOKEN = new DeliberateException ();
202210 private static final Duration DEFAULT_DEADLINE = ofMillis (100 );
211+ private static final boolean [] TRUE_FALSE = {true , false };
212+ private static final String [] COMPRESSION = {"gzip" , "identity" , null };
213+
214+ private final ResponseLeakValidator responseLeakValidator = new ResponseLeakValidator ();
215+
216+ @ AfterEach
217+ void finalChecks () {
218+ responseLeakValidator .assertNoPendingRequests ();
219+ }
203220
204221 private enum ErrorMode {
205222 NONE ,
@@ -211,9 +228,6 @@ private enum ErrorMode {
211228 STATUS_IN_RESPONSE
212229 }
213230
214- private static final boolean [] TRUE_FALSE = {true , false };
215- private static final String [] COMPRESSION = {"gzip" , "identity" , null };
216-
217231 private static Collection <Arguments > sslStreamingAndCompressionParams () {
218232 List <Arguments > args = new ArrayList <>();
219233 for (boolean ssl : TRUE_FALSE ) {
@@ -1217,16 +1231,21 @@ private static CompatResponse computeResponse(final int value) {
12171231 .build ();
12181232 }
12191233
1220- private static CompatClient serviceTalkClient (final SocketAddress serverAddress , final boolean ssl ,
1221- @ Nullable final String compression ,
1222- @ Nullable final Duration timeout ) {
1234+ private CompatClient serviceTalkClient (final SocketAddress serverAddress , final boolean ssl ,
1235+ @ Nullable final String compression ,
1236+ @ Nullable final Duration timeout ) {
12231237 final GrpcClientBuilder <InetSocketAddress , InetSocketAddress > builder =
12241238 GrpcClients .forResolvedAddress ((InetSocketAddress ) serverAddress );
1225- if (ssl ) {
1226- builder .initializeHttp (b -> b .sslConfig (new ClientSslConfigBuilder (
1227- DefaultTestCerts ::loadServerCAPem ).peerHost (serverPemHostname ()).build ()));
1228- }
1229- if (null != timeout ) {
1239+
1240+ builder .initializeHttp (b -> {
1241+ if (ssl ) {
1242+ b .sslConfig (new ClientSslConfigBuilder (
1243+ DefaultTestCerts ::loadServerCAPem ).peerHost (serverPemHostname ()).build ());
1244+ }
1245+ b .appendClientFilter (responseLeakValidator );
1246+ });
1247+
1248+ if (timeout != null ) {
12301249 builder .defaultTimeout (timeout );
12311250 }
12321251 return builder .build (new Compat .ClientFactory ()
@@ -1854,4 +1873,34 @@ private CompatResponse response(final int value) throws Exception {
18541873 return computeResponse (value );
18551874 }
18561875 }
1876+
1877+ private static final class ResponseLeakValidator implements StreamingHttpClientFilterFactory {
1878+
1879+ private final AtomicInteger pendingRequests = new AtomicInteger ();
1880+
1881+ @ Override
1882+ public StreamingHttpClientFilter create (FilterableStreamingHttpClient client ) {
1883+ return new StreamingHttpClientFilter (client ) {
1884+ @ Override
1885+ protected Single <StreamingHttpResponse > request (StreamingHttpRequester delegate ,
1886+ StreamingHttpRequest request ) {
1887+ return Single .defer (() -> {
1888+ pendingRequests .incrementAndGet ();
1889+ return delegate .request (request )
1890+ .liftSync (new BeforeFinallyHttpOperator (pendingRequests ::decrementAndGet ))
1891+ .shareContextOnSubscribe ();
1892+ });
1893+ }
1894+ };
1895+ }
1896+
1897+ @ Override
1898+ public HttpExecutionStrategy requiredOffloads () {
1899+ return HttpExecutionStrategies .offloadNone ();
1900+ }
1901+
1902+ void assertNoPendingRequests () {
1903+ assertThat ("Detected pending requests, possible response leak" , pendingRequests .get (), is (0 ));
1904+ }
1905+ }
18571906}
0 commit comments