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