1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 super.writeTo(outStream != null ? outStream : NullOutputStream.INSTANCE);
98 releaseConnection();
99 } catch (final IOException | RuntimeException ex) {
100 discardConnection();
101 throw ex;
102 } finally {
103 cleanup();
104 }
105 }
106
107 @Override
108 public boolean eofDetected(final InputStream wrapped) throws IOException {
109 try {
110
111
112 if (wrapped != null) {
113 wrapped.close();
114 }
115 releaseConnection();
116 } catch (final IOException | RuntimeException ex) {
117 discardConnection();
118 throw ex;
119 } finally {
120 cleanup();
121 }
122 return false;
123 }
124
125 @Override
126 public boolean streamClosed(final InputStream wrapped) throws IOException {
127 try {
128 final boolean open = execRuntime != null && execRuntime.isEndpointAcquired();
129
130
131 try {
132 if (wrapped != null) {
133 wrapped.close();
134 }
135 releaseConnection();
136 } catch (final SocketException ex) {
137 if (open) {
138 throw ex;
139 }
140 }
141 } catch (final IOException | RuntimeException ex) {
142 discardConnection();
143 throw ex;
144 } finally {
145 cleanup();
146 }
147 return false;
148 }
149
150 @Override
151 public boolean streamAbort(final InputStream wrapped) throws IOException {
152 cleanup();
153 return false;
154 }
155
156 @Override
157 public Supplier<List<? extends Header>> getTrailers() {
158 try {
159 final InputStream underlyingStream = super.getContent();
160 return () -> {
161 final Header[] footers;
162 if (underlyingStream instanceof ChunkedInputStream) {
163 final ChunkedInputStream chunkedInputStream = (ChunkedInputStream) underlyingStream;
164 footers = chunkedInputStream.getFooters();
165 } else {
166 footers = new Header[0];
167 }
168 return Arrays.asList(footers);
169 };
170 } catch (final IOException e) {
171 throw new IllegalStateException("Unable to retrieve input stream", e);
172 }
173 }
174
175 @Override
176 public void close() throws IOException {
177 try {
178
179
180 super.close();
181 releaseConnection();
182 } catch (final IOException | RuntimeException ex) {
183 discardConnection();
184 throw ex;
185 } finally {
186 cleanup();
187 }
188 }
189
190 private static final class NullOutputStream extends OutputStream {
191 private static final NullOutputStream INSTANCE = new NullOutputStream();
192
193 private NullOutputStream() {}
194
195 @Override
196 public void write(@SuppressWarnings("unused") final int byteValue) {
197
198 }
199
200 @Override
201 public void write(@SuppressWarnings("unused") final byte[] buffer) {
202
203 }
204
205 @Override
206 public void write(
207 @SuppressWarnings("unused") final byte[] buffer,
208 @SuppressWarnings("unused") final int off,
209 @SuppressWarnings("unused") final int len) {
210
211 }
212
213 @Override
214 public void flush() {
215
216 }
217
218 @Override
219 public void close() {
220
221 }
222
223 @Override
224 public String toString() {
225 return "NullOutputStream{}";
226 }
227 }
228 }