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