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 context) throws IOException, HttpException {
140 Args.notNull(request, "HTTP request");
141 Args.notNull(conn, "Client connection");
142 Args.notNull(context, "HTTP context");
143 try {
144 context.setAttribute(HttpCoreContext.SSL_SESSION, conn.getSSLSession());
145 context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, conn.getEndpointDetails());
146
147 conn.sendRequestHeader(request);
148 if (streamListener != null) {
149 streamListener.onRequestHead(conn, request);
150 }
151 boolean expectContinue = false;
152 final HttpEntity entity = request.getEntity();
153 if (entity != null) {
154 final Header expect = request.getFirstHeader(HttpHeaders.EXPECT);
155 expectContinue = expect != null && HeaderElements.CONTINUE.equalsIgnoreCase(expect.getValue());
156 if (!expectContinue) {
157 conn.sendRequestEntity(request);
158 }
159 }
160 conn.flush();
161 ClassicHttpResponse response = null;
162 while (response == null) {
163 if (expectContinue) {
164 final Timeout timeout = http1Config.getWaitForContinueTimeout() != null ? http1Config.getWaitForContinueTimeout() : DEFAULT_WAIT_FOR_CONTINUE;
165 if (conn.isDataAvailable(timeout)) {
166 response = conn.receiveResponseHeader();
167 if (streamListener != null) {
168 streamListener.onResponseHead(conn, response);
169 }
170 final int status = response.getCode();
171 if (status == HttpStatus.SC_CONTINUE) {
172
173 response = null;
174 conn.sendRequestEntity(request);
175 } else if (status < HttpStatus.SC_SUCCESS) {
176 if (informationCallback != null) {
177 informationCallback.execute(response, conn, context);
178 }
179 response = null;
180 continue;
181 } else if (status >= HttpStatus.SC_CLIENT_ERROR){
182 conn.terminateRequest(request);
183 } else {
184 conn.sendRequestEntity(request);
185 }
186 } else {
187 conn.sendRequestEntity(request);
188 }
189 conn.flush();
190 expectContinue = false;
191 } else {
192 response = conn.receiveResponseHeader();
193 if (streamListener != null) {
194 streamListener.onResponseHead(conn, response);
195 }
196 final int status = response.getCode();
197 if (status < HttpStatus.SC_INFORMATIONAL) {
198 throw new ProtocolException("Invalid response: " + new StatusLine(response));
199 }
200 if (status < HttpStatus.SC_SUCCESS) {
201 if (informationCallback != null && status != HttpStatus.SC_CONTINUE) {
202 informationCallback.execute(response, conn, context);
203 }
204 response = null;
205 }
206 }
207 }
208 if (MessageSupport.canResponseHaveBody(request.getMethod(), response)) {
209 conn.receiveResponseEntity(response);
210 }
211 return response;
212
213 } catch (final HttpException | IOException | RuntimeException ex) {
214 Closer.closeQuietly(conn);
215 throw ex;
216 }
217 }
218
219
220
221
222
223
224
225
226
227
228
229
230
231 public ClassicHttpResponse execute(
232 final ClassicHttpRequest request,
233 final HttpClientConnection conn,
234 final HttpContext context) throws IOException, HttpException {
235 return execute(request, conn, null, context);
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249
250 public void preProcess(
251 final ClassicHttpRequest request,
252 final HttpProcessor processor,
253 final HttpContext context) throws HttpException, IOException {
254 Args.notNull(request, "HTTP request");
255 Args.notNull(processor, "HTTP processor");
256 Args.notNull(context, "HTTP context");
257 final ProtocolVersion transportVersion = request.getVersion();
258 if (transportVersion != null && !transportVersion.lessEquals(http1Config.getVersion())) {
259 throw new UnsupportedHttpVersionException(transportVersion);
260 }
261 context.setProtocolVersion(transportVersion != null ? transportVersion : http1Config.getVersion());
262 context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
263 processor.process(request, request.getEntity(), context);
264 }
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283 public void postProcess(
284 final ClassicHttpResponse response,
285 final HttpProcessor processor,
286 final HttpContext context) throws HttpException, IOException {
287 Args.notNull(response, "HTTP response");
288 Args.notNull(processor, "HTTP processor");
289 Args.notNull(context, "HTTP context");
290 final ProtocolVersion transportVersion = response.getVersion();
291 if (transportVersion != null) {
292 if (transportVersion.greaterEquals(HttpVersion.HTTP_2)) {
293 throw new UnsupportedHttpVersionException(transportVersion);
294 }
295 context.setProtocolVersion(transportVersion);
296 }
297 context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
298 processor.process(response, response.getEntity(), context);
299 }
300
301
302
303
304
305
306
307
308
309
310
311 public boolean keepAlive(
312 final ClassicHttpRequest request,
313 final ClassicHttpResponse response,
314 final HttpClientConnection connection,
315 final HttpContext context) throws IOException {
316 Args.notNull(connection, "HTTP connection");
317 Args.notNull(request, "HTTP request");
318 Args.notNull(response, "HTTP response");
319 Args.notNull(context, "HTTP context");
320 final boolean keepAlive = connection.isConsistent() && connReuseStrategy.keepAlive(request, response, context);
321 if (streamListener != null) {
322 streamListener.onExchangeComplete(connection, keepAlive);
323 }
324 return keepAlive;
325 }
326
327
328
329
330
331
332 public static Builder builder() {
333 return new Builder();
334 }
335
336
337
338
339
340
341 public static final class Builder {
342
343 private Timeout waitForContinue;
344 private ConnectionReuseStrategy connReuseStrategy;
345 private Http1StreamListener streamListener;
346
347 private Builder() {}
348
349 public Builder withWaitForContinue(final Timeout waitForContinue) {
350 this.waitForContinue = waitForContinue;
351 return this;
352 }
353
354 public Builder withConnectionReuseStrategy(final ConnectionReuseStrategy connReuseStrategy) {
355 this.connReuseStrategy = connReuseStrategy;
356 return this;
357 }
358
359 public Builder withHttp1StreamListener(final Http1StreamListener streamListener) {
360 this.streamListener = streamListener;
361 return this;
362 }
363
364
365
366
367
368 public HttpRequestExecutor build() {
369 return new HttpRequestExecutor(
370 waitForContinue,
371 connReuseStrategy,
372 streamListener);
373 }
374 }
375
376 }