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.async.AsyncExecCallback;
36 import org.apache.hc.client5.http.async.AsyncExecChain;
37 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
38 import org.apache.hc.client5.http.async.AsyncExecRuntime;
39 import org.apache.hc.client5.http.auth.AuthExchange;
40 import org.apache.hc.client5.http.auth.ChallengeType;
41 import org.apache.hc.client5.http.auth.CredentialsProvider;
42 import org.apache.hc.client5.http.auth.CredentialsStore;
43 import org.apache.hc.client5.http.config.RequestConfig;
44 import org.apache.hc.client5.http.impl.AuthSupport;
45 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
46 import org.apache.hc.client5.http.protocol.HttpClientContext;
47 import org.apache.hc.core5.annotation.Contract;
48 import org.apache.hc.core5.annotation.Internal;
49 import org.apache.hc.core5.annotation.ThreadingBehavior;
50 import org.apache.hc.core5.http.EntityDetails;
51 import org.apache.hc.core5.http.Header;
52 import org.apache.hc.core5.http.HttpException;
53 import org.apache.hc.core5.http.HttpHeaders;
54 import org.apache.hc.core5.http.HttpHost;
55 import org.apache.hc.core5.http.HttpRequest;
56 import org.apache.hc.core5.http.HttpResponse;
57 import org.apache.hc.core5.http.Method;
58 import org.apache.hc.core5.http.ProtocolException;
59 import org.apache.hc.core5.http.nio.AsyncDataConsumer;
60 import org.apache.hc.core5.http.nio.AsyncEntityProducer;
61 import org.apache.hc.core5.http.protocol.HttpCoreContext;
62 import org.apache.hc.core5.http.protocol.HttpProcessor;
63 import org.apache.hc.core5.http.support.BasicRequestBuilder;
64 import org.apache.hc.core5.net.URIAuthority;
65 import org.apache.hc.core5.util.Args;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69
70
71
72
73
74
75
76
77
78
79
80 @Contract(threading = ThreadingBehavior.STATELESS)
81 @Internal
82 public final class AsyncProtocolExec implements AsyncExecChainHandler {
83
84 private static final Logger LOG = LoggerFactory.getLogger(AsyncProtocolExec.class);
85
86 private final HttpProcessor httpProcessor;
87 private final AuthenticationStrategy targetAuthStrategy;
88 private final AuthenticationStrategy proxyAuthStrategy;
89 private final HttpAuthenticator authenticator;
90
91 AsyncProtocolExec(
92 final HttpProcessor httpProcessor,
93 final AuthenticationStrategy targetAuthStrategy,
94 final AuthenticationStrategy proxyAuthStrategy) {
95 this.httpProcessor = Args.notNull(httpProcessor, "HTTP protocol processor");
96 this.targetAuthStrategy = Args.notNull(targetAuthStrategy, "Target authentication strategy");
97 this.proxyAuthStrategy = Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
98 this.authenticator = new HttpAuthenticator(LOG);
99 }
100
101 @Override
102 public void execute(
103 final HttpRequest userRequest,
104 final AsyncEntityProducer entityProducer,
105 final AsyncExecChain.Scope scope,
106 final AsyncExecChain chain,
107 final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
108
109 if (Method.CONNECT.isSame(userRequest.getMethod())) {
110 throw new ProtocolException("Direct execution of CONNECT is not allowed");
111 }
112
113 final HttpRoute route = scope.route;
114 final HttpHost routeTarget = route.getTargetHost();
115 final HttpHost proxy = route.getProxyHost();
116 final HttpClientContext clientContext = scope.clientContext;
117
118 final HttpRequest request;
119 if (proxy != null && !route.isTunnelled()) {
120 final BasicRequestBuilder requestBuilder = BasicRequestBuilder.copy(userRequest);
121 if (requestBuilder.getAuthority() == null) {
122 requestBuilder.setAuthority(new URIAuthority(routeTarget));
123 }
124 requestBuilder.setAbsoluteRequestUri(true);
125 request = requestBuilder.build();
126 } else {
127 request = userRequest;
128 }
129
130
131 if (request.getScheme() == null) {
132 request.setScheme(routeTarget.getSchemeName());
133 }
134 if (request.getAuthority() == null) {
135 request.setAuthority(new URIAuthority(routeTarget));
136 }
137
138 final URIAuthority authority = request.getAuthority();
139 final CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
140 if (credsProvider instanceof CredentialsStore) {
141 AuthSupport.extractFromAuthority(request.getScheme(), authority, (CredentialsStore) credsProvider);
142 }
143
144 final AtomicBoolean challenged = new AtomicBoolean(false);
145 internalExecute(challenged, request, entityProducer, scope, chain, asyncExecCallback);
146 }
147
148 private void internalExecute(
149 final AtomicBoolean challenged,
150 final HttpRequest request,
151 final AsyncEntityProducer entityProducer,
152 final AsyncExecChain.Scope scope,
153 final AsyncExecChain chain,
154 final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
155 final String exchangeId = scope.exchangeId;
156 final HttpRoute route = scope.route;
157 final HttpClientContext clientContext = scope.clientContext;
158 final AsyncExecRuntime execRuntime = scope.execRuntime;
159
160 final HttpHost proxy = route.getProxyHost();
161 final HttpHost target = new HttpHost(request.getScheme(), request.getAuthority());
162
163 final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target);
164 final AuthExchange proxyAuthExchange = proxy != AuthExchange_keyword">null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
165
166 clientContext.setAttribute(HttpClientContext.HTTP_ROUTE, route);
167 clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
168 httpProcessor.process(request, entityProducer, clientContext);
169
170 if (!request.containsHeader(HttpHeaders.AUTHORIZATION)) {
171 if (LOG.isDebugEnabled()) {
172 LOG.debug("{} target auth state: {}", exchangeId, targetAuthExchange.getState());
173 }
174 authenticator.addAuthResponse(target, ChallengeType.TARGET, request, targetAuthExchange, clientContext);
175 }
176 if (!request.containsHeader(HttpHeaders.PROXY_AUTHORIZATION) && !route.isTunnelled()) {
177 if (LOG.isDebugEnabled()) {
178 LOG.debug("{} proxy auth state: {}", exchangeId, proxyAuthExchange.getState());
179 }
180 authenticator.addAuthResponse(proxy, ChallengeType.PROXY, request, proxyAuthExchange, clientContext);
181 }
182
183 chain.proceed(request, entityProducer, scope, new AsyncExecCallback() {
184
185 @Override
186 public AsyncDataConsumer handleResponse(
187 final HttpResponse response,
188 final EntityDetails entityDetails) throws HttpException, IOException {
189
190 clientContext.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
191 httpProcessor.process(response, entityDetails, clientContext);
192
193 if (Method.TRACE.isSame(request.getMethod())) {
194
195 return asyncExecCallback.handleResponse(response, entityDetails);
196 }
197 if (needAuthentication(targetAuthExchange, proxyAuthExchange, route, request, response, clientContext)) {
198 challenged.set(true);
199 return null;
200 }
201 challenged.set(false);
202 return asyncExecCallback.handleResponse(response, entityDetails);
203 }
204
205 @Override
206 public void handleInformationResponse(
207 final HttpResponse response) throws HttpException, IOException {
208 asyncExecCallback.handleInformationResponse(response);
209 }
210
211 @Override
212 public void completed() {
213 if (!execRuntime.isEndpointConnected()) {
214 if (proxyAuthExchange.getState() == AuthExchange.State.SUCCESS
215 && proxyAuthExchange.isConnectionBased()) {
216 if (LOG.isDebugEnabled()) {
217 LOG.debug("{} resetting proxy auth state", exchangeId);
218 }
219 proxyAuthExchange.reset();
220 }
221 if (targetAuthExchange.getState() == AuthExchange.State.SUCCESS
222 && targetAuthExchange.isConnectionBased()) {
223 if (LOG.isDebugEnabled()) {
224 LOG.debug("{} resetting target auth state", exchangeId);
225 }
226 targetAuthExchange.reset();
227 }
228 }
229
230 if (challenged.get()) {
231 if (entityProducer != null && !entityProducer.isRepeatable()) {
232 if (LOG.isDebugEnabled()) {
233 LOG.debug("{} cannot retry non-repeatable request", exchangeId);
234 }
235 asyncExecCallback.completed();
236 } else {
237
238 final HttpRequest original = scope.originalRequest;
239 request.setHeaders();
240 for (final Iterator<Header> it = original.headerIterator(); it.hasNext(); ) {
241 request.addHeader(it.next());
242 }
243 try {
244 if (entityProducer != null) {
245 entityProducer.releaseResources();
246 }
247 internalExecute(challenged, request, entityProducer, scope, chain, asyncExecCallback);
248 } catch (final HttpException | IOException ex) {
249 asyncExecCallback.failed(ex);
250 }
251 }
252 } else {
253 asyncExecCallback.completed();
254 }
255 }
256
257 @Override
258 public void failed(final Exception cause) {
259 if (cause instanceof IOException || cause instanceof RuntimeException) {
260 for (final AuthExchange authExchange : clientContext.getAuthExchanges().values()) {
261 if (authExchange.isConnectionBased()) {
262 authExchange.reset();
263 }
264 }
265 }
266 asyncExecCallback.failed(cause);
267 }
268
269 });
270 }
271
272 private boolean needAuthentication(
273 final AuthExchange targetAuthExchange,
274 final AuthExchange proxyAuthExchange,
275 final HttpRoute route,
276 final HttpRequest request,
277 final HttpResponse response,
278 final HttpClientContext context) {
279 final RequestConfig config = context.getRequestConfig();
280 if (config.isAuthenticationEnabled()) {
281 final HttpHost target = AuthSupport.resolveAuthTarget(request, route);
282 final boolean targetAuthRequested = authenticator.isChallenged(
283 target, ChallengeType.TARGET, response, targetAuthExchange, context);
284
285 HttpHost proxy = route.getProxyHost();
286
287 if (proxy == null) {
288 proxy = route.getTargetHost();
289 }
290 final boolean proxyAuthRequested = authenticator.isChallenged(
291 proxy, ChallengeType.PROXY, response, proxyAuthExchange, context);
292
293 if (targetAuthRequested) {
294 return authenticator.updateAuthState(target, ChallengeType.TARGET, response,
295 targetAuthStrategy, targetAuthExchange, context);
296 }
297 if (proxyAuthRequested) {
298 return authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
299 proxyAuthStrategy, proxyAuthExchange, context);
300 }
301 }
302 return false;
303 }
304
305 }