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.net.URI;
31 import java.net.URISyntaxException;
32 import java.util.Iterator;
33 import java.util.concurrent.atomic.AtomicBoolean;
34
35 import org.apache.hc.client5.http.AuthenticationStrategy;
36 import org.apache.hc.client5.http.HttpRoute;
37 import org.apache.hc.client5.http.async.AsyncExecCallback;
38 import org.apache.hc.client5.http.async.AsyncExecChain;
39 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
40 import org.apache.hc.client5.http.async.AsyncExecRuntime;
41 import org.apache.hc.client5.http.auth.AuthExchange;
42 import org.apache.hc.client5.http.auth.ChallengeType;
43 import org.apache.hc.client5.http.auth.CredentialsProvider;
44 import org.apache.hc.client5.http.auth.CredentialsStore;
45 import org.apache.hc.client5.http.config.RequestConfig;
46 import org.apache.hc.client5.http.impl.AuthSupport;
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.utils.URIUtils;
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.net.URIAuthority;
67 import org.apache.hc.core5.util.Args;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71
72
73
74
75
76
77
78
79
80
81
82 @Contract(threading = ThreadingBehavior.STATELESS)
83 @Internal
84 public final class AsyncProtocolExec implements AsyncExecChainHandler {
85
86 private static final Logger LOG = LoggerFactory.getLogger(AsyncProtocolExec.class);
87
88 private final HttpProcessor httpProcessor;
89 private final AuthenticationStrategy targetAuthStrategy;
90 private final AuthenticationStrategy proxyAuthStrategy;
91 private final HttpAuthenticator authenticator;
92
93 AsyncProtocolExec(
94 final HttpProcessor httpProcessor,
95 final AuthenticationStrategy targetAuthStrategy,
96 final AuthenticationStrategy proxyAuthStrategy) {
97 this.httpProcessor = Args.notNull(httpProcessor, "HTTP protocol processor");
98 this.targetAuthStrategy = Args.notNull(targetAuthStrategy, "Target authentication strategy");
99 this.proxyAuthStrategy = Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
100 this.authenticator = new HttpAuthenticator(LOG);
101 }
102
103 @Override
104 public void execute(
105 final HttpRequest userRequest,
106 final AsyncEntityProducer entityProducer,
107 final AsyncExecChain.Scope scope,
108 final AsyncExecChain chain,
109 final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
110
111 if (Method.CONNECT.isSame(userRequest.getMethod())) {
112 throw new ProtocolException("Direct execution of CONNECT is not allowed");
113 }
114
115 final HttpRoute route = scope.route;
116 final HttpClientContext clientContext = scope.clientContext;
117
118 final HttpRequest request;
119 if (route.getProxyHost() != null && !route.isTunnelled()) {
120 try {
121 URI uri = userRequest.getUri();
122 if (!uri.isAbsolute()) {
123 uri = URIUtils.rewriteURI(uri, route.getTargetHost(), true);
124 } else {
125 uri = URIUtils.rewriteURI(uri);
126 }
127 request = HttpProxyRequest.rewrite(userRequest, uri);
128 } catch (final URISyntaxException ex) {
129 throw new ProtocolException("Invalid request URI: " + userRequest.getRequestUri(), ex);
130 }
131 } else {
132 request = userRequest;
133 }
134
135 final URIAuthority authority = request.getAuthority();
136 if (authority != null) {
137 final CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
138 if (credsProvider instanceof CredentialsStore) {
139 AuthSupport.extractFromAuthority(request.getScheme(), authority, (CredentialsStore) credsProvider);
140 }
141 }
142
143 final AtomicBoolean challenged = new AtomicBoolean(false);
144 internalExecute(challenged, request, entityProducer, scope, chain, asyncExecCallback);
145 }
146
147 private void internalExecute(
148 final AtomicBoolean challenged,
149 final HttpRequest request,
150 final AsyncEntityProducer entityProducer,
151 final AsyncExecChain.Scope scope,
152 final AsyncExecChain chain,
153 final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
154 final String exchangeId = scope.exchangeId;
155 final HttpRoute route = scope.route;
156 final HttpClientContext clientContext = scope.clientContext;
157 final AsyncExecRuntime execRuntime = scope.execRuntime;
158
159 final HttpHost target = route.getTargetHost();
160 final HttpHost proxy = route.getProxyHost();
161
162 final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target);
163 final AuthExchange proxyAuthExchange = proxy != AuthExchange_keyword">null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
164
165 clientContext.setAttribute(HttpClientContext.HTTP_ROUTE, route);
166 clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
167 httpProcessor.process(request, entityProducer, clientContext);
168
169 if (!request.containsHeader(HttpHeaders.AUTHORIZATION)) {
170 if (LOG.isDebugEnabled()) {
171 LOG.debug("{}: target auth state: {}", exchangeId, targetAuthExchange.getState());
172 }
173 authenticator.addAuthResponse(target, ChallengeType.TARGET, request, targetAuthExchange, clientContext);
174 }
175 if (!request.containsHeader(HttpHeaders.PROXY_AUTHORIZATION) && !route.isTunnelled()) {
176 if (LOG.isDebugEnabled()) {
177 LOG.debug("{}: proxy auth state: {}", exchangeId, proxyAuthExchange.getState());
178 }
179 authenticator.addAuthResponse(proxy, ChallengeType.PROXY, request, proxyAuthExchange, clientContext);
180 }
181
182 chain.proceed(request, entityProducer, scope, new AsyncExecCallback() {
183
184 @Override
185 public AsyncDataConsumer handleResponse(
186 final HttpResponse response,
187 final EntityDetails entityDetails) throws HttpException, IOException {
188
189 clientContext.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
190 httpProcessor.process(response, entityDetails, clientContext);
191
192 if (Method.TRACE.isSame(request.getMethod())) {
193
194 return asyncExecCallback.handleResponse(response, entityDetails);
195 }
196 if (needAuthentication(targetAuthExchange, proxyAuthExchange, route, request, response, clientContext)) {
197 challenged.set(true);
198 return null;
199 }
200 challenged.set(false);
201 return asyncExecCallback.handleResponse(response, entityDetails);
202 }
203
204 @Override
205 public void handleInformationResponse(
206 final HttpResponse response) throws HttpException, IOException {
207 asyncExecCallback.handleInformationResponse(response);
208 }
209
210 @Override
211 public void completed() {
212 if (!execRuntime.isEndpointConnected()) {
213 if (proxyAuthExchange.getState() == AuthExchange.State.SUCCESS
214 && proxyAuthExchange.isConnectionBased()) {
215 if (LOG.isDebugEnabled()) {
216 LOG.debug("{}: resetting proxy auth state", exchangeId);
217 }
218 proxyAuthExchange.reset();
219 }
220 if (targetAuthExchange.getState() == AuthExchange.State.SUCCESS
221 && targetAuthExchange.isConnectionBased()) {
222 if (LOG.isDebugEnabled()) {
223 LOG.debug("{}: resetting target auth state", exchangeId);
224 }
225 targetAuthExchange.reset();
226 }
227 }
228
229 if (challenged.get()) {
230 if (entityProducer != null && !entityProducer.isRepeatable()) {
231 if (LOG.isDebugEnabled()) {
232 LOG.debug("{}: cannot retry non-repeatable request", exchangeId);
233 }
234 asyncExecCallback.completed();
235 } else {
236
237 final HttpRequest original = scope.originalRequest;
238 request.setHeaders();
239 for (final Iterator<Header> it = original.headerIterator(); it.hasNext(); ) {
240 request.addHeader(it.next());
241 }
242 try {
243 if (entityProducer != null) {
244 entityProducer.releaseResources();
245 }
246 internalExecute(challenged, request, entityProducer, scope, chain, asyncExecCallback);
247 } catch (final HttpException | IOException ex) {
248 asyncExecCallback.failed(ex);
249 }
250 }
251 } else {
252 asyncExecCallback.completed();
253 }
254 }
255
256 @Override
257 public void failed(final Exception cause) {
258 if (cause instanceof IOException || cause instanceof RuntimeException) {
259 if (proxyAuthExchange.isConnectionBased()) {
260 proxyAuthExchange.reset();
261 }
262 if (targetAuthExchange.isConnectionBased()) {
263 targetAuthExchange.reset();
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 }