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.InterruptedIOException;
32
33 import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
34 import org.apache.hc.client5.http.HttpRoute;
35 import org.apache.hc.client5.http.UserTokenHandler;
36 import org.apache.hc.client5.http.classic.ExecChain;
37 import org.apache.hc.client5.http.classic.ExecChainHandler;
38 import org.apache.hc.client5.http.classic.ExecRuntime;
39 import org.apache.hc.client5.http.impl.ConnectionShutdownException;
40 import org.apache.hc.client5.http.impl.ProtocolSwitchStrategy;
41 import org.apache.hc.client5.http.io.HttpClientConnectionManager;
42 import org.apache.hc.client5.http.protocol.HttpClientContext;
43 import org.apache.hc.core5.annotation.Contract;
44 import org.apache.hc.core5.annotation.Internal;
45 import org.apache.hc.core5.annotation.ThreadingBehavior;
46 import org.apache.hc.core5.http.ClassicHttpRequest;
47 import org.apache.hc.core5.http.ClassicHttpResponse;
48 import org.apache.hc.core5.http.ConnectionReuseStrategy;
49 import org.apache.hc.core5.http.HttpEntity;
50 import org.apache.hc.core5.http.HttpException;
51 import org.apache.hc.core5.http.HttpStatus;
52 import org.apache.hc.core5.http.ProtocolException;
53 import org.apache.hc.core5.http.ProtocolVersion;
54 import org.apache.hc.core5.http.protocol.HttpProcessor;
55 import org.apache.hc.core5.io.CloseMode;
56 import org.apache.hc.core5.util.Args;
57 import org.apache.hc.core5.util.TimeValue;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61
62
63
64
65
66
67
68 @Contract(threading = ThreadingBehavior.STATELESS)
69 @Internal
70 public final class MainClientExec implements ExecChainHandler {
71
72 private static final Logger LOG = LoggerFactory.getLogger(MainClientExec.class);
73
74 private final HttpClientConnectionManager connectionManager;
75 private final HttpProcessor httpProcessor;
76 private final ConnectionReuseStrategy reuseStrategy;
77 private final ConnectionKeepAliveStrategy keepAliveStrategy;
78 private final UserTokenHandler userTokenHandler;
79 private final ProtocolSwitchStrategy protocolSwitchStrategy;
80
81
82
83
84 public MainClientExec(
85 final HttpClientConnectionManager connectionManager,
86 final HttpProcessor httpProcessor,
87 final ConnectionReuseStrategy reuseStrategy,
88 final ConnectionKeepAliveStrategy keepAliveStrategy,
89 final UserTokenHandler userTokenHandler) {
90 this.connectionManager = Args.notNull(connectionManager, "Connection manager");
91 this.httpProcessor = Args.notNull(httpProcessor, "HTTP protocol processor");
92 this.reuseStrategy = Args.notNull(reuseStrategy, "Connection reuse strategy");
93 this.keepAliveStrategy = Args.notNull(keepAliveStrategy, "Connection keep alive strategy");
94 this.userTokenHandler = Args.notNull(userTokenHandler, "User token handler");
95 this.protocolSwitchStrategy = new ProtocolSwitchStrategy();
96 }
97
98 @Override
99 public ClassicHttpResponse execute(
100 final ClassicHttpRequest request,
101 final ExecChain.Scope scope,
102 final ExecChain chain) throws IOException, HttpException {
103 Args.notNull(request, "HTTP request");
104 Args.notNull(scope, "Scope");
105 final String exchangeId = scope.exchangeId;
106 final HttpRoute route = scope.route;
107 final HttpClientContext context = scope.clientContext;
108 final ExecRuntime execRuntime = scope.execRuntime;
109
110 if (LOG.isDebugEnabled()) {
111 LOG.debug("{} executing {} {}", exchangeId, request.getMethod(), request.getRequestUri());
112 }
113 try {
114
115 context.setRoute(route);
116 context.setRequest(request);
117
118 httpProcessor.process(request, request.getEntity(), context);
119
120 final ClassicHttpResponse response = execRuntime.execute(
121 exchangeId,
122 request,
123 (r, connection, c) -> {
124 if (r.getCode() == HttpStatus.SC_SWITCHING_PROTOCOLS) {
125 final ProtocolVersion upgradeProtocol = protocolSwitchStrategy.switchProtocol(r);
126 if (upgradeProtocol == null || !upgradeProtocol.getProtocol().equals("TLS")) {
127 throw new ProtocolException("Failure switching protocols");
128 }
129 if (LOG.isDebugEnabled()) {
130 LOG.debug("Switching to {}", upgradeProtocol);
131 }
132 try {
133 execRuntime.upgradeTls(context);
134 } catch (final IOException ex) {
135 throw new HttpException("Failure upgrading to TLS", ex);
136 }
137 LOG.debug("Successfully switched to {}", upgradeProtocol);
138 }
139 },
140 context);
141
142 context.setResponse(response);
143 httpProcessor.process(response, response.getEntity(), context);
144
145 Object userToken = context.getUserToken();
146 if (userToken == null) {
147 userToken = userTokenHandler.getUserToken(route, request, context);
148 context.setUserToken(userToken);
149 }
150
151
152 if (reuseStrategy.keepAlive(request, response, context)) {
153
154 final TimeValue duration = keepAliveStrategy.getKeepAliveDuration(response, context);
155 if (LOG.isDebugEnabled()) {
156 final String s;
157 if (duration != null) {
158 s = "for " + duration;
159 } else {
160 s = "indefinitely";
161 }
162 LOG.debug("{} connection can be kept alive {}", exchangeId, s);
163 }
164 execRuntime.markConnectionReusable(userToken, duration);
165 } else {
166 execRuntime.markConnectionNonReusable();
167 }
168
169 final HttpEntity entity = response.getEntity();
170 if (entity == null || !entity.isStreaming()) {
171
172 execRuntime.releaseEndpoint();
173 return new CloseableHttpResponse(response);
174 }
175 return new CloseableHttpResponse(response, execRuntime);
176 } catch (final ConnectionShutdownException ex) {
177 final InterruptedIOException ioex = new InterruptedIOException(
178 "Connection has been shut down");
179 ioex.initCause(ex);
180 execRuntime.discardEndpoint();
181 throw ioex;
182 } catch (final HttpException | RuntimeException | IOException ex) {
183 execRuntime.discardEndpoint();
184 throw ex;
185 } catch (final Error error) {
186 connectionManager.close(CloseMode.IMMEDIATE);
187 throw error;
188 }
189
190 }
191
192 }