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