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 package org.apache.hc.client5.http.impl.async;
28
29 import java.io.IOException;
30 import java.util.Iterator;
31 import java.util.concurrent.atomic.AtomicBoolean;
32
33 import org.apache.hc.client5.http.AuthenticationStrategy;
34 import org.apache.hc.client5.http.HttpRoute;
35 import org.apache.hc.client5.http.SchemePortResolver;
36 import org.apache.hc.client5.http.async.AsyncExecCallback;
37 import org.apache.hc.client5.http.async.AsyncExecChain;
38 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
39 import org.apache.hc.client5.http.async.AsyncExecRuntime;
40 import org.apache.hc.client5.http.auth.AuthExchange;
41 import org.apache.hc.client5.http.auth.AuthenticationException;
42 import org.apache.hc.client5.http.auth.ChallengeType;
43 import org.apache.hc.client5.http.auth.MalformedChallengeException;
44 import org.apache.hc.client5.http.config.RequestConfig;
45 import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
46 import org.apache.hc.client5.http.impl.RequestSupport;
47 import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
48 import org.apache.hc.client5.http.impl.auth.AuthenticationHandler;
49 import org.apache.hc.client5.http.protocol.HttpClientContext;
50 import org.apache.hc.core5.annotation.Contract;
51 import org.apache.hc.core5.annotation.Internal;
52 import org.apache.hc.core5.annotation.ThreadingBehavior;
53 import org.apache.hc.core5.http.EntityDetails;
54 import org.apache.hc.core5.http.Header;
55 import org.apache.hc.core5.http.HttpException;
56 import org.apache.hc.core5.http.HttpHeaders;
57 import org.apache.hc.core5.http.HttpHost;
58 import org.apache.hc.core5.http.HttpRequest;
59 import org.apache.hc.core5.http.HttpResponse;
60 import org.apache.hc.core5.http.Method;
61 import org.apache.hc.core5.http.ProtocolException;
62 import org.apache.hc.core5.http.nio.AsyncDataConsumer;
63 import org.apache.hc.core5.http.nio.AsyncEntityProducer;
64 import org.apache.hc.core5.http.support.BasicRequestBuilder;
65 import org.apache.hc.core5.net.URIAuthority;
66 import org.apache.hc.core5.util.Args;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70
71
72
73
74
75
76
77
78
79
80
81 @Contract(threading = ThreadingBehavior.STATELESS)
82 @Internal
83 public final class AsyncProtocolExec implements AsyncExecChainHandler {
84
85 private static final Logger LOG = LoggerFactory.getLogger(AsyncProtocolExec.class);
86
87 private final AuthenticationStrategy targetAuthStrategy;
88 private final AuthenticationStrategy proxyAuthStrategy;
89 private final AuthenticationHandler authenticator;
90 private final SchemePortResolver schemePortResolver;
91 private final AuthCacheKeeper authCacheKeeper;
92
93 AsyncProtocolExec(
94 final AuthenticationStrategy targetAuthStrategy,
95 final AuthenticationStrategy proxyAuthStrategy,
96 final SchemePortResolver schemePortResolver,
97 final boolean authCachingDisabled) {
98 this.targetAuthStrategy = Args.notNull(targetAuthStrategy, "Target authentication strategy");
99 this.proxyAuthStrategy = Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
100 this.authenticator = new AuthenticationHandler();
101 this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
102 this.authCacheKeeper = authCachingDisabled ? null : new AuthCacheKeeper(this.schemePortResolver);
103 }
104
105 @Override
106 public void execute(
107 final HttpRequest userRequest,
108 final AsyncEntityProducer entityProducer,
109 final AsyncExecChain.Scope scope,
110 final AsyncExecChain chain,
111 final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
112
113 if (Method.CONNECT.isSame(userRequest.getMethod())) {
114 throw new ProtocolException("Direct execution of CONNECT is not allowed");
115 }
116
117 final HttpRoute route = scope.route;
118 final HttpHost routeTarget = route.getTargetHost();
119 final HttpHost proxy = route.getProxyHost();
120 final HttpClientContext clientContext = scope.clientContext;
121
122 final HttpRequest request;
123 if (proxy != null && !route.isTunnelled()) {
124 final BasicRequestBuilder requestBuilder = BasicRequestBuilder.copy(userRequest);
125 if (requestBuilder.getAuthority() == null) {
126 requestBuilder.setAuthority(new URIAuthority(routeTarget));
127 }
128 requestBuilder.setAbsoluteRequestUri(true);
129 request = requestBuilder.build();
130 } else {
131 request = userRequest;
132 }
133
134
135 if (request.getScheme() == null) {
136 request.setScheme(routeTarget.getSchemeName());
137 }
138 if (request.getAuthority() == null) {
139 request.setAuthority(new URIAuthority(routeTarget));
140 }
141
142 final URIAuthority authority = request.getAuthority();
143 if (authority.getUserInfo() != null) {
144 throw new ProtocolException("Request URI authority contains deprecated userinfo component");
145 }
146
147 final HttpHost target = new HttpHost(
148 request.getScheme(),
149 authority.getHostName(),
150 schemePortResolver.resolve(request.getScheme(), authority));
151 final String pathPrefix = RequestSupport.extractPathPrefix(request);
152 final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target);
153 final AuthExchange proxyAuthExchange = proxy != null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
154
155 if (!targetAuthExchange.isConnectionBased() &&
156 targetAuthExchange.getPathPrefix() != null &&
157 !pathPrefix.startsWith(targetAuthExchange.getPathPrefix())) {
158
159
160 targetAuthExchange.reset();
161 }
162 if (targetAuthExchange.getPathPrefix() == null) {
163 targetAuthExchange.setPathPrefix(pathPrefix);
164 }
165
166 if (authCacheKeeper != null) {
167 authCacheKeeper.loadPreemptively(target, pathPrefix, targetAuthExchange, clientContext);
168 if (proxy != null) {
169 authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, clientContext);
170 }
171 }
172
173 final AtomicBoolean challenged = new AtomicBoolean(false);
174 internalExecute(target, pathPrefix, targetAuthExchange, proxyAuthExchange,
175 challenged, request, entityProducer, scope, chain, asyncExecCallback);
176 }
177
178 private void internalExecute(
179 final HttpHost target,
180 final String pathPrefix,
181 final AuthExchange targetAuthExchange,
182 final AuthExchange proxyAuthExchange,
183 final AtomicBoolean challenged,
184 final HttpRequest request,
185 final AsyncEntityProducer entityProducer,
186 final AsyncExecChain.Scope scope,
187 final AsyncExecChain chain,
188 final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
189 final String exchangeId = scope.exchangeId;
190 final HttpRoute route = scope.route;
191 final HttpClientContext clientContext = scope.clientContext;
192 final AsyncExecRuntime execRuntime = scope.execRuntime;
193
194 final HttpHost proxy = route.getProxyHost();
195
196 if (!request.containsHeader(HttpHeaders.AUTHORIZATION)) {
197 if (LOG.isDebugEnabled()) {
198 LOG.debug("{} target auth state: {}", exchangeId, targetAuthExchange.getState());
199 }
200 authenticator.addAuthResponse(target, ChallengeType.TARGET, request, targetAuthExchange, clientContext);
201 }
202 if (!request.containsHeader(HttpHeaders.PROXY_AUTHORIZATION) && !route.isTunnelled()) {
203 if (LOG.isDebugEnabled()) {
204 LOG.debug("{} proxy auth state: {}", exchangeId, proxyAuthExchange.getState());
205 }
206 authenticator.addAuthResponse(proxy, ChallengeType.PROXY, request, proxyAuthExchange, clientContext);
207 }
208
209 chain.proceed(request, entityProducer, scope, new AsyncExecCallback() {
210
211 @Override
212 public AsyncDataConsumer handleResponse(
213 final HttpResponse response,
214 final EntityDetails entityDetails) throws HttpException, IOException {
215
216 if (Method.TRACE.isSame(request.getMethod())) {
217
218 return asyncExecCallback.handleResponse(response, entityDetails);
219 }
220 if (needAuthentication(
221 targetAuthExchange,
222 proxyAuthExchange,
223 proxy != null ? proxy : target,
224 target,
225 pathPrefix,
226 response,
227 clientContext)) {
228 challenged.set(true);
229 return null;
230 }
231 challenged.set(false);
232 return asyncExecCallback.handleResponse(response, entityDetails);
233 }
234
235 @Override
236 public void handleInformationResponse(
237 final HttpResponse response) throws HttpException, IOException {
238 asyncExecCallback.handleInformationResponse(response);
239 }
240
241 @Override
242 public void completed() {
243 if (!execRuntime.isEndpointConnected()) {
244 if (proxyAuthExchange.getState() == AuthExchange.State.SUCCESS
245 && proxyAuthExchange.isConnectionBased()) {
246 if (LOG.isDebugEnabled()) {
247 LOG.debug("{} resetting proxy auth state", exchangeId);
248 }
249 proxyAuthExchange.reset();
250 }
251 if (targetAuthExchange.getState() == AuthExchange.State.SUCCESS
252 && targetAuthExchange.isConnectionBased()) {
253 if (LOG.isDebugEnabled()) {
254 LOG.debug("{} resetting target auth state", exchangeId);
255 }
256 targetAuthExchange.reset();
257 }
258 }
259
260 if (challenged.get()) {
261 if (entityProducer != null && !entityProducer.isRepeatable()) {
262 if (LOG.isDebugEnabled()) {
263 LOG.debug("{} cannot retry non-repeatable request", exchangeId);
264 }
265 asyncExecCallback.completed();
266 } else {
267
268 final HttpRequest original = scope.originalRequest;
269 request.setHeaders();
270 for (final Iterator<Header> it = original.headerIterator(); it.hasNext(); ) {
271 request.addHeader(it.next());
272 }
273 try {
274 if (entityProducer != null) {
275 entityProducer.releaseResources();
276 }
277 internalExecute(target, pathPrefix, targetAuthExchange, proxyAuthExchange,
278 challenged, request, entityProducer, scope, chain, asyncExecCallback);
279 } catch (final HttpException | IOException ex) {
280 asyncExecCallback.failed(ex);
281 }
282 }
283 } else {
284 asyncExecCallback.completed();
285 }
286 }
287
288 @Override
289 public void failed(final Exception cause) {
290 if (cause instanceof IOException || cause instanceof RuntimeException) {
291 for (final AuthExchange authExchange : clientContext.getAuthExchanges().values()) {
292 if (authExchange.isConnectionBased()) {
293 authExchange.reset();
294 }
295 }
296 }
297 asyncExecCallback.failed(cause);
298 }
299
300 });
301 }
302
303 private boolean needAuthentication(
304 final AuthExchange targetAuthExchange,
305 final AuthExchange proxyAuthExchange,
306 final HttpHost proxy,
307 final HttpHost target,
308 final String pathPrefix,
309 final HttpResponse response,
310 final HttpClientContext context) throws AuthenticationException, MalformedChallengeException {
311 final RequestConfig config = context.getRequestConfigOrDefault();
312 if (config.isAuthenticationEnabled()) {
313 final boolean targetAuthRequested = authenticator.isChallenged(
314 target, ChallengeType.TARGET, response, targetAuthExchange, context);
315 final boolean targetMutualAuthRequired = authenticator.isChallengeExpected(targetAuthExchange);
316
317 if (authCacheKeeper != null) {
318 if (targetAuthRequested) {
319 authCacheKeeper.updateOnChallenge(target, pathPrefix, targetAuthExchange, context);
320 } else {
321 authCacheKeeper.updateOnNoChallenge(target, pathPrefix, targetAuthExchange, context);
322 }
323 }
324
325 final boolean proxyAuthRequested = authenticator.isChallenged(
326 proxy, ChallengeType.PROXY, response, proxyAuthExchange, context);
327 final boolean proxyMutualAuthRequired = authenticator.isChallengeExpected(proxyAuthExchange);
328
329 if (authCacheKeeper != null) {
330 if (proxyAuthRequested) {
331 authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
332 } else {
333 authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
334 }
335 }
336
337 if (targetAuthRequested || targetMutualAuthRequired) {
338 final boolean updated = authenticator.handleResponse(target, ChallengeType.TARGET, response,
339 targetAuthStrategy, targetAuthExchange, context);
340
341 if (authCacheKeeper != null) {
342 authCacheKeeper.updateOnResponse(target, pathPrefix, targetAuthExchange, context);
343 }
344
345 return updated;
346 }
347 if (proxyAuthRequested || proxyMutualAuthRequired) {
348 final boolean updated = authenticator.handleResponse(proxy, ChallengeType.PROXY, response,
349 proxyAuthStrategy, proxyAuthExchange, context);
350
351 if (authCacheKeeper != null) {
352 authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
353 }
354
355 return updated;
356 }
357 }
358 return false;
359 }
360
361 }