44
44
import java .io .ByteArrayInputStream ;
45
45
import java .io .ByteArrayOutputStream ;
46
46
import java .io .Closeable ;
47
- import java .io .FilterInputStream ;
48
47
import java .io .IOException ;
49
48
import java .io .InputStream ;
50
49
import java .io .OutputStream ;
64
63
import javax .ws .rs .core .HttpHeaders ;
65
64
import javax .ws .rs .core .MultivaluedMap ;
66
65
import javax .ws .rs .core .Response ;
67
-
66
+ import javax . ws . rs . core . Response . StatusType ;
68
67
import javax .net .ssl .HostnameVerifier ;
69
68
import javax .net .ssl .SSLContext ;
70
69
import javax .net .ssl .SSLSocketFactory ;
100
99
import org .apache .http .config .ConnectionConfig ;
101
100
import org .apache .http .config .Registry ;
102
101
import org .apache .http .config .RegistryBuilder ;
102
+ import org .apache .http .conn .ConnectionReleaseTrigger ;
103
103
import org .apache .http .conn .HttpClientConnectionManager ;
104
104
import org .apache .http .conn .ManagedHttpClientConnection ;
105
105
import org .apache .http .conn .routing .HttpRoute ;
@@ -430,8 +430,8 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
430
430
final HttpUriRequest request = getUriHttpRequest (clientRequest );
431
431
final Map <String , String > clientHeadersSnapshot = writeOutBoundHeaders (clientRequest .getHeaders (), request );
432
432
433
+ CloseableHttpResponse response = null ;
433
434
try {
434
- final CloseableHttpResponse response ;
435
435
final HttpClientContext context = HttpClientContext .create ();
436
436
if (preemptiveBasicAuth ) {
437
437
final AuthCache authCache = new BasicAuthCache ();
@@ -442,11 +442,14 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
442
442
response = client .execute (getHost (request ), request , context );
443
443
HeaderUtils .checkHeaderChanges (clientHeadersSnapshot , clientRequest .getHeaders (), this .getClass ().getName ());
444
444
445
+ final HttpEntity entity = response .getEntity ();
446
+ final InputStream entityContent = entity != null ? entity .getContent () : null ;
447
+
445
448
final Response .StatusType status = response .getStatusLine ().getReasonPhrase () == null
446
449
? Statuses .from (response .getStatusLine ().getStatusCode ())
447
450
: Statuses .from (response .getStatusLine ().getStatusCode (), response .getStatusLine ().getReasonPhrase ());
448
451
449
- final ClientResponse responseContext = new ClientResponse (status , clientRequest );
452
+ final ClientResponse responseContext = new ApacheClientResponse (status , clientRequest , response , entityContent );
450
453
final List <URI > redirectLocations = context .getRedirectLocations ();
451
454
if (redirectLocations != null && !redirectLocations .isEmpty ()) {
452
455
responseContext .setResolvedRequestUri (redirectLocations .get (redirectLocations .size () - 1 ));
@@ -464,8 +467,6 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
464
467
headers .put (headerName , list );
465
468
}
466
469
467
- final HttpEntity entity = response .getEntity ();
468
-
469
470
if (entity != null ) {
470
471
if (headers .get (HttpHeaders .CONTENT_LENGTH ) == null ) {
471
472
headers .add (HttpHeaders .CONTENT_LENGTH , String .valueOf (entity .getContentLength ()));
@@ -477,15 +478,18 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
477
478
}
478
479
}
479
480
480
- try {
481
- responseContext .setEntityStream (new HttpClientResponseInputStream (getInputStream (response )));
482
- } catch (final IOException e ) {
483
- LOGGER .log (Level .SEVERE , null , e );
484
- }
481
+ responseContext .setEntityStream (bufferedStream (entityContent ));
482
+
483
+ // prevent response-close on correct return
484
+ response = null ;
485
485
486
486
return responseContext ;
487
487
} catch (final Exception e ) {
488
488
throw new ProcessingException (e );
489
+ } finally {
490
+ if (response != null ) {
491
+ ReaderWriter .safelyClose (response );
492
+ }
489
493
}
490
494
}
491
495
@@ -617,40 +621,60 @@ private static Map<String, String> writeOutBoundHeaders(final MultivaluedMap<Str
617
621
return stringHeaders ;
618
622
}
619
623
620
- private static final class HttpClientResponseInputStream extends FilterInputStream {
624
+ /**
625
+ * Overrides Response-close() to release the connection without consuming it.
626
+ *
627
+ * From <a href="http://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/fundamentals.html#d5e145">Apache HttpClient
628
+ * documentation</a>:
629
+ *
630
+ * <q>The difference between closing the content stream and closing the response is that the former will attempt to keep
631
+ * the underlying connection alive by consuming the entity content while the latter immediately shuts down and discards
632
+ * the connection.</q>
633
+ *
634
+ * JAX-RS spec is silent whether closing the content stream consumes the response or closes the connection. This
635
+ * ApacheConnector follows apache-behaviour.
636
+ */
637
+ private static final class ApacheClientResponse extends ClientResponse {
638
+
639
+ private final CloseableHttpResponse httpResponse ;
621
640
622
- HttpClientResponseInputStream (final InputStream inputStream ) throws IOException {
623
- super (inputStream );
641
+ private final InputStream entityContent ;
642
+
643
+ public ApacheClientResponse (StatusType status , ClientRequest requestContext , CloseableHttpResponse httpResponse ,
644
+ InputStream entityContent ) {
645
+ super (status , requestContext );
646
+ this .httpResponse = httpResponse ;
647
+ this .entityContent = entityContent ;
624
648
}
625
649
626
650
@ Override
627
- public void close () throws IOException {
628
- super .close ();
651
+ public void close () {
652
+ try {
653
+ if (entityContent instanceof ConnectionReleaseTrigger ) {
654
+ // necessary to prevent an exception during stream-close in apache httpclient 4.5.1+
655
+ ((ConnectionReleaseTrigger ) entityContent ).abortConnection ();
656
+ }
657
+ httpResponse .close ();
658
+ } catch (IOException e ) {
659
+ // Cannot happen according to ConnectionHolder#releaseConnection
660
+ throw new ProcessingException (e );
661
+ } finally {
662
+ super .close ();
663
+ }
629
664
}
630
665
}
631
666
632
- private static InputStream getInputStream (final CloseableHttpResponse response ) throws IOException {
667
+ private static InputStream bufferedStream (final InputStream entityContent ) {
633
668
634
669
final InputStream inputStream ;
635
670
636
- if (response . getEntity () == null ) {
671
+ if (entityContent == null ) {
637
672
inputStream = new ByteArrayInputStream (new byte [0 ]);
638
673
} else {
639
- final InputStream i = response .getEntity ().getContent ();
640
- if (i .markSupported ()) {
641
- inputStream = i ;
642
- } else {
643
- inputStream = new BufferedInputStream (i , ReaderWriter .BUFFER_SIZE );
644
- }
674
+ inputStream = new BufferedInputStream (entityContent , ReaderWriter .BUFFER_SIZE );
645
675
}
646
676
647
- return new FilterInputStream (inputStream ) {
648
- @ Override
649
- public void close () throws IOException {
650
- response .close ();
651
- super .close ();
652
- }
653
- };
677
+ return inputStream ;
654
678
}
655
679
656
680
private static class ConnectionFactory extends ManagedHttpClientConnectionFactory {
0 commit comments