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