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