View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.client5.http.impl.classic;
29  
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.net.SocketException;
34  import java.util.Arrays;
35  import java.util.List;
36  
37  import org.apache.hc.client5.http.classic.ExecRuntime;
38  import org.apache.hc.core5.function.Supplier;
39  import org.apache.hc.core5.http.ClassicHttpResponse;
40  import org.apache.hc.core5.http.Header;
41  import org.apache.hc.core5.http.HttpEntity;
42  import org.apache.hc.core5.http.impl.io.ChunkedInputStream;
43  import org.apache.hc.core5.http.io.EofSensorInputStream;
44  import org.apache.hc.core5.http.io.EofSensorWatcher;
45  import org.apache.hc.core5.http.io.entity.HttpEntityWrapper;
46  
47  class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher {
48  
49      private final ExecRuntime execRuntime;
50  
51      public static void enhance(final ClassicHttpResponse response, final ExecRuntime execRuntime) {
52          final HttpEntity entity = response.getEntity();
53          if (entity != null && entity.isStreaming() && execRuntime != null) {
54              response.setEntity(new ResponseEntityProxy(entity, execRuntime));
55          }
56      }
57  
58      ResponseEntityProxy(final HttpEntity entity, final ExecRuntime execRuntime) {
59          super(entity);
60          this.execRuntime = execRuntime;
61      }
62  
63      private void cleanup() throws IOException {
64          if (this.execRuntime != null) {
65              if (this.execRuntime.isEndpointConnected()) {
66                  this.execRuntime.disconnectEndpoint();
67              }
68              this.execRuntime.discardEndpoint();
69          }
70      }
71  
72      private void discardConnection() {
73          if (this.execRuntime != null) {
74              this.execRuntime.discardEndpoint();
75          }
76      }
77  
78      public void releaseConnection() {
79          if (this.execRuntime != null) {
80              this.execRuntime.releaseEndpoint();
81          }
82      }
83  
84      @Override
85      public boolean isRepeatable() {
86          return false;
87      }
88  
89      @Override
90      public InputStream getContent() throws IOException {
91          return new EofSensorInputStream(super.getContent(), this);
92      }
93  
94      @Override
95      public void writeTo(final OutputStream outStream) throws IOException {
96          try {
97              if (outStream != null) {
98                  super.writeTo(outStream);
99              }
100             releaseConnection();
101         } catch (final IOException | RuntimeException ex) {
102             discardConnection();
103             throw ex;
104         } finally {
105             cleanup();
106         }
107     }
108 
109     @Override
110     public boolean eofDetected(final InputStream wrapped) throws IOException {
111         try {
112             // there may be some cleanup required, such as
113             // reading trailers after the response body:
114             if (wrapped != null) {
115                 wrapped.close();
116             }
117             releaseConnection();
118         } catch (final IOException | RuntimeException ex) {
119             discardConnection();
120             throw ex;
121         } finally {
122             cleanup();
123         }
124         return false;
125     }
126 
127     @Override
128     public boolean streamClosed(final InputStream wrapped) throws IOException {
129         try {
130             final boolean open = execRuntime != null && execRuntime.isEndpointAcquired();
131             // this assumes that closing the stream will
132             // consume the remainder of the response body:
133             try {
134                 if (wrapped != null) {
135                     wrapped.close();
136                 }
137                 releaseConnection();
138             } catch (final SocketException ex) {
139                 if (open) {
140                     throw ex;
141                 }
142             }
143         } catch (final IOException | RuntimeException ex) {
144             discardConnection();
145             throw ex;
146         } finally {
147             cleanup();
148         }
149         return false;
150     }
151 
152     @Override
153     public boolean streamAbort(final InputStream wrapped) throws IOException {
154         cleanup();
155         return false;
156     }
157 
158     @Override
159     public Supplier<List<? extends Header>> getTrailers() {
160             try {
161                 final InputStream underlyingStream = super.getContent();
162                 return new Supplier<List<? extends Header>>() {
163                     @Override
164                     public List<? extends Header> get() {
165                         final Header[] footers;
166                         if (underlyingStream instanceof ChunkedInputStream) {
167                             final ChunkedInputStream chunkedInputStream = (ChunkedInputStream) underlyingStream;
168                             footers = chunkedInputStream.getFooters();
169                         } else {
170                             footers = new Header[0];
171                         }
172                         return Arrays.asList(footers);
173                     }
174                 };
175             } catch (final IOException e) {
176                 throw new IllegalStateException("Unable to retrieve input stream", e);
177             }
178     }
179 
180 }