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