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.core5.http.impl.io;
29
30 import java.io.IOException;
31
32 import org.apache.hc.core5.annotation.Contract;
33 import org.apache.hc.core5.annotation.ThreadingBehavior;
34 import org.apache.hc.core5.http.ClassicHttpRequest;
35 import org.apache.hc.core5.http.ClassicHttpResponse;
36 import org.apache.hc.core5.http.ConnectionReuseStrategy;
37 import org.apache.hc.core5.http.Header;
38 import org.apache.hc.core5.http.HeaderElements;
39 import org.apache.hc.core5.http.HttpEntity;
40 import org.apache.hc.core5.http.HttpException;
41 import org.apache.hc.core5.http.HttpHeaders;
42 import org.apache.hc.core5.http.HttpStatus;
43 import org.apache.hc.core5.http.HttpVersion;
44 import org.apache.hc.core5.http.ProtocolException;
45 import org.apache.hc.core5.http.ProtocolVersion;
46 import org.apache.hc.core5.http.UnsupportedHttpVersionException;
47 import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
48 import org.apache.hc.core5.http.impl.Http1StreamListener;
49 import org.apache.hc.core5.http.io.HttpClientConnection;
50 import org.apache.hc.core5.http.io.HttpResponseInformationCallback;
51 import org.apache.hc.core5.http.message.MessageSupport;
52 import org.apache.hc.core5.http.message.StatusLine;
53 import org.apache.hc.core5.http.protocol.HttpContext;
54 import org.apache.hc.core5.http.protocol.HttpCoreContext;
55 import org.apache.hc.core5.http.protocol.HttpProcessor;
56 import org.apache.hc.core5.io.Closer;
57 import org.apache.hc.core5.util.Args;
58 import org.apache.hc.core5.util.Timeout;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 @Contract(threading = ThreadingBehavior.IMMUTABLE)
74 public class HttpRequestExecutor {
75
76 public static final Timeout DEFAULT_WAIT_FOR_CONTINUE = Timeout.ofSeconds(3);
77
78 private final Timeout waitForContinue;
79 private final ConnectionReuseStrategy connReuseStrategy;
80 private final Http1StreamListener streamListener;
81
82
83
84
85
86
87 public HttpRequestExecutor(
88 final Timeout waitForContinue,
89 final ConnectionReuseStrategy connReuseStrategy,
90 final Http1StreamListener streamListener) {
91 super();
92 this.waitForContinue = Args.positive(waitForContinue, "Wait for continue time");
93 this.connReuseStrategy = connReuseStrategy != null ? connReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE;
94 this.streamListener = streamListener;
95 }
96
97 public HttpRequestExecutor(final ConnectionReuseStrategy connReuseStrategy) {
98 this(DEFAULT_WAIT_FOR_CONTINUE, connReuseStrategy, null);
99 }
100
101 public HttpRequestExecutor() {
102 this(DEFAULT_WAIT_FOR_CONTINUE, null, null);
103 }
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119 public ClassicHttpResponse execute(
120 final ClassicHttpRequest request,
121 final HttpClientConnection conn,
122 final HttpResponseInformationCallback informationCallback,
123 final HttpContext context) throws IOException, HttpException {
124 Args.notNull(request, "HTTP request");
125 Args.notNull(conn, "Client connection");
126 Args.notNull(context, "HTTP context");
127 try {
128 context.setAttribute(HttpCoreContext.SSL_SESSION, conn.getSSLSession());
129 context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, conn.getEndpointDetails());
130
131 conn.sendRequestHeader(request);
132 if (streamListener != null) {
133 streamListener.onRequestHead(conn, request);
134 }
135 boolean expectContinue = false;
136 final HttpEntity entity = request.getEntity();
137 if (entity != null) {
138 final Header expect = request.getFirstHeader(HttpHeaders.EXPECT);
139 expectContinue = expect != null && HeaderElements.CONTINUE.equalsIgnoreCase(expect.getValue());
140 if (!expectContinue) {
141 conn.sendRequestEntity(request);
142 }
143 }
144 conn.flush();
145 ClassicHttpResponse response = null;
146 while (response == null) {
147 if (expectContinue) {
148 if (conn.isDataAvailable(this.waitForContinue)) {
149 response = conn.receiveResponseHeader();
150 if (streamListener != null) {
151 streamListener.onResponseHead(conn, response);
152 }
153 final int status = response.getCode();
154 if (status == HttpStatus.SC_CONTINUE) {
155
156 response = null;
157 conn.sendRequestEntity(request);
158 } else if (status < HttpStatus.SC_SUCCESS) {
159 if (informationCallback != null) {
160 informationCallback.execute(response, conn, context);
161 }
162 response = null;
163 continue;
164 } else if (status >= HttpStatus.SC_CLIENT_ERROR){
165 conn.terminateRequest(request);
166 } else {
167 conn.sendRequestEntity(request);
168 }
169 } else {
170 conn.sendRequestEntity(request);
171 }
172 conn.flush();
173 expectContinue = false;
174 } else {
175 response = conn.receiveResponseHeader();
176 if (streamListener != null) {
177 streamListener.onResponseHead(conn, response);
178 }
179 final int status = response.getCode();
180 if (status < HttpStatus.SC_INFORMATIONAL) {
181 throw new ProtocolException("Invalid response: " + new StatusLine(response));
182 }
183 if (status < HttpStatus.SC_SUCCESS) {
184 if (informationCallback != null && status != HttpStatus.SC_CONTINUE) {
185 informationCallback.execute(response, conn, context);
186 }
187 response = null;
188 }
189 }
190 }
191 if (MessageSupport.canResponseHaveBody(request.getMethod(), response)) {
192 conn.receiveResponseEntity(response);
193 }
194 return response;
195
196 } catch (final HttpException | IOException | RuntimeException ex) {
197 Closer.closeQuietly(conn);
198 throw ex;
199 }
200 }
201
202
203
204
205
206
207
208
209
210
211
212
213
214 public ClassicHttpResponse execute(
215 final ClassicHttpRequest request,
216 final HttpClientConnection conn,
217 final HttpContext context) throws IOException, HttpException {
218 return execute(request, conn, null, context);
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232
233 public void preProcess(
234 final ClassicHttpRequest request,
235 final HttpProcessor processor,
236 final HttpContext context) throws HttpException, IOException {
237 Args.notNull(request, "HTTP request");
238 Args.notNull(processor, "HTTP processor");
239 Args.notNull(context, "HTTP context");
240 final ProtocolVersion transportVersion = request.getVersion();
241 if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) {
242 throw new UnsupportedHttpVersionException(transportVersion);
243 }
244 context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1);
245 context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
246 processor.process(request, request.getEntity(), context);
247 }
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266 public void postProcess(
267 final ClassicHttpResponse response,
268 final HttpProcessor processor,
269 final HttpContext context) throws HttpException, IOException {
270 Args.notNull(response, "HTTP response");
271 Args.notNull(processor, "HTTP processor");
272 Args.notNull(context, "HTTP context");
273 final ProtocolVersion transportVersion = response.getVersion();
274 context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1);
275 context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
276 processor.process(response, response.getEntity(), context);
277 }
278
279
280
281
282
283
284
285
286
287
288
289 public boolean keepAlive(
290 final ClassicHttpRequest request,
291 final ClassicHttpResponse response,
292 final HttpClientConnection connection,
293 final HttpContext context) throws IOException {
294 Args.notNull(connection, "HTTP connection");
295 Args.notNull(request, "HTTP request");
296 Args.notNull(response, "HTTP response");
297 Args.notNull(context, "HTTP context");
298 final boolean keepAlive = connection.isConsistent() && connReuseStrategy.keepAlive(request, response, context);
299 if (streamListener != null) {
300 streamListener.onExchangeComplete(connection, keepAlive);
301 }
302 return keepAlive;
303 }
304
305
306
307
308
309
310 public static Builder builder() {
311 return new Builder();
312 }
313
314
315
316
317
318
319 public static final class Builder {
320
321 private Timeout waitForContinue;
322 private ConnectionReuseStrategy connReuseStrategy;
323 private Http1StreamListener streamListener;
324
325 private Builder() {}
326
327 public Builder withWaitForContinue(final Timeout waitForContinue) {
328 this.waitForContinue = waitForContinue;
329 return this;
330 }
331
332 public Builder withConnectionReuseStrategy(final ConnectionReuseStrategy connReuseStrategy) {
333 this.connReuseStrategy = connReuseStrategy;
334 return this;
335 }
336
337 public Builder withHttp1StreamListener(final Http1StreamListener streamListener) {
338 this.streamListener = streamListener;
339 return this;
340 }
341
342
343
344
345
346 public HttpRequestExecutor build() {
347 return new HttpRequestExecutor(
348 waitForContinue,
349 connReuseStrategy,
350 streamListener);
351 }
352 }
353
354 }