4444import java .io .ByteArrayInputStream ;
4545import java .io .ByteArrayOutputStream ;
4646import java .io .Closeable ;
47- import java .io .FilterInputStream ;
4847import java .io .IOException ;
4948import java .io .InputStream ;
5049import java .io .OutputStream ;
6463import javax .ws .rs .core .HttpHeaders ;
6564import javax .ws .rs .core .MultivaluedMap ;
6665import javax .ws .rs .core .Response ;
67-
66+ import javax . ws . rs . core . Response . StatusType ;
6867import javax .net .ssl .HostnameVerifier ;
6968import javax .net .ssl .SSLContext ;
7069import javax .net .ssl .SSLSocketFactory ;
10099import org .apache .http .config .ConnectionConfig ;
101100import org .apache .http .config .Registry ;
102101import org .apache .http .config .RegistryBuilder ;
102+ import org .apache .http .conn .ConnectionReleaseTrigger ;
103103import org .apache .http .conn .HttpClientConnectionManager ;
104104import org .apache .http .conn .ManagedHttpClientConnection ;
105105import org .apache .http .conn .routing .HttpRoute ;
@@ -430,8 +430,8 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
430430 final HttpUriRequest request = getUriHttpRequest (clientRequest );
431431 final Map <String , String > clientHeadersSnapshot = writeOutBoundHeaders (clientRequest .getHeaders (), request );
432432
433+ CloseableHttpResponse response = null ;
433434 try {
434- final CloseableHttpResponse response ;
435435 final HttpClientContext context = HttpClientContext .create ();
436436 if (preemptiveBasicAuth ) {
437437 final AuthCache authCache = new BasicAuthCache ();
@@ -442,11 +442,14 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
442442 response = client .execute (getHost (request ), request , context );
443443 HeaderUtils .checkHeaderChanges (clientHeadersSnapshot , clientRequest .getHeaders (), this .getClass ().getName ());
444444
445+ final HttpEntity entity = response .getEntity ();
446+ final InputStream entityContent = entity != null ? entity .getContent () : null ;
447+
445448 final Response .StatusType status = response .getStatusLine ().getReasonPhrase () == null
446449 ? Statuses .from (response .getStatusLine ().getStatusCode ())
447450 : Statuses .from (response .getStatusLine ().getStatusCode (), response .getStatusLine ().getReasonPhrase ());
448451
449- final ClientResponse responseContext = new ClientResponse (status , clientRequest );
452+ final ClientResponse responseContext = new ApacheClientResponse (status , clientRequest , response , entityContent );
450453 final List <URI > redirectLocations = context .getRedirectLocations ();
451454 if (redirectLocations != null && !redirectLocations .isEmpty ()) {
452455 responseContext .setResolvedRequestUri (redirectLocations .get (redirectLocations .size () - 1 ));
@@ -464,8 +467,6 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
464467 headers .put (headerName , list );
465468 }
466469
467- final HttpEntity entity = response .getEntity ();
468-
469470 if (entity != null ) {
470471 if (headers .get (HttpHeaders .CONTENT_LENGTH ) == null ) {
471472 headers .add (HttpHeaders .CONTENT_LENGTH , String .valueOf (entity .getContentLength ()));
@@ -477,15 +478,18 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
477478 }
478479 }
479480
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 ;
485485
486486 return responseContext ;
487487 } catch (final Exception e ) {
488488 throw new ProcessingException (e );
489+ } finally {
490+ if (response != null ) {
491+ ReaderWriter .safelyClose (response );
492+ }
489493 }
490494 }
491495
@@ -617,40 +621,60 @@ private static Map<String, String> writeOutBoundHeaders(final MultivaluedMap<Str
617621 return stringHeaders ;
618622 }
619623
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 ;
621640
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 ;
624648 }
625649
626650 @ 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+ }
629664 }
630665 }
631666
632- private static InputStream getInputStream (final CloseableHttpResponse response ) throws IOException {
667+ private static InputStream bufferedStream (final InputStream entityContent ) {
633668
634669 final InputStream inputStream ;
635670
636- if (response . getEntity () == null ) {
671+ if (entityContent == null ) {
637672 inputStream = new ByteArrayInputStream (new byte [0 ]);
638673 } 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 );
645675 }
646676
647- return new FilterInputStream (inputStream ) {
648- @ Override
649- public void close () throws IOException {
650- response .close ();
651- super .close ();
652- }
653- };
677+ return inputStream ;
654678 }
655679
656680 private static class ConnectionFactory extends ManagedHttpClientConnectionFactory {
0 commit comments