View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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   * Request execution handler in the classic request execution chain
70   * that is responsible for implementation of HTTP specification requirements.
71   * <p>
72   * Further responsibilities such as communication with the opposite
73   * endpoint is delegated to the next executor in the request execution
74   * chain.
75   * </p>
76   *
77   * @since 4.3
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             // Ensure the request has a scheme and an authority
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                 // Run request protocol interceptors
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                     // Do not perform authentication for TRACE request
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                     // Make sure the response body is fully consumed, if present
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                     // Reset request headers
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             // if proxy is not set use target host instead
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 }