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.io.InterruptedIOException;
32  
33  import org.apache.hc.client5.http.ClientProtocolException;
34  import org.apache.hc.client5.http.HttpRoute;
35  import org.apache.hc.client5.http.SchemePortResolver;
36  import org.apache.hc.client5.http.classic.ExecRuntime;
37  import org.apache.hc.client5.http.config.Configurable;
38  import org.apache.hc.client5.http.config.RequestConfig;
39  import org.apache.hc.client5.http.impl.ConnectionShutdownException;
40  import org.apache.hc.client5.http.impl.DefaultClientConnectionReuseStrategy;
41  import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
42  import org.apache.hc.client5.http.impl.ExecSupport;
43  import org.apache.hc.client5.http.io.HttpClientConnectionManager;
44  import org.apache.hc.client5.http.protocol.HttpClientContext;
45  import org.apache.hc.client5.http.protocol.RequestClientConnControl;
46  import org.apache.hc.client5.http.routing.RoutingSupport;
47  import org.apache.hc.core5.annotation.Contract;
48  import org.apache.hc.core5.annotation.ThreadingBehavior;
49  import org.apache.hc.core5.concurrent.CancellableDependency;
50  import org.apache.hc.core5.http.ClassicHttpRequest;
51  import org.apache.hc.core5.http.ClassicHttpResponse;
52  import org.apache.hc.core5.http.ConnectionReuseStrategy;
53  import org.apache.hc.core5.http.HttpEntity;
54  import org.apache.hc.core5.http.HttpException;
55  import org.apache.hc.core5.http.HttpHost;
56  import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
57  import org.apache.hc.core5.http.protocol.BasicHttpContext;
58  import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
59  import org.apache.hc.core5.http.protocol.HttpContext;
60  import org.apache.hc.core5.http.protocol.HttpCoreContext;
61  import org.apache.hc.core5.http.protocol.HttpProcessor;
62  import org.apache.hc.core5.http.protocol.RequestContent;
63  import org.apache.hc.core5.http.protocol.RequestTargetHost;
64  import org.apache.hc.core5.http.protocol.RequestUserAgent;
65  import org.apache.hc.core5.io.CloseMode;
66  import org.apache.hc.core5.net.URIAuthority;
67  import org.apache.hc.core5.util.Args;
68  import org.apache.hc.core5.util.TimeValue;
69  import org.apache.hc.core5.util.VersionInfo;
70  import org.slf4j.Logger;
71  import org.slf4j.LoggerFactory;
72  
73  /**
74   * Minimal implementation of {@link CloseableHttpClient}. This client is
75   * optimized for HTTP/1.1 message transport and does not support advanced
76   * HTTP protocol functionality such as request execution via a proxy, state
77   * management, authentication and request redirects.
78   * <p>
79   * Concurrent message exchanges executed by this client will get assigned to
80   * separate connections leased from the connection pool.
81   * </p>
82   *
83   * @since 4.3
84   */
85  @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
86  public class MinimalHttpClient extends CloseableHttpClient {
87  
88      private static final Logger LOG = LoggerFactory.getLogger(MinimalHttpClient.class);
89  
90      private final HttpClientConnectionManager connManager;
91      private final ConnectionReuseStrategy reuseStrategy;
92      private final SchemePortResolver schemePortResolver;
93      private final HttpRequestExecutor requestExecutor;
94      private final HttpProcessor httpProcessor;
95  
96      MinimalHttpClient(final HttpClientConnectionManager connManager) {
97          super();
98          this.connManager = Args.notNull(connManager, "HTTP connection manager");
99          this.reuseStrategy = DefaultClientConnectionReuseStrategy.INSTANCE;
100         this.schemePortResolver = DefaultSchemePortResolver.INSTANCE;
101         this.requestExecutor = new HttpRequestExecutor(this.reuseStrategy);
102         this.httpProcessor = new DefaultHttpProcessor(
103                 new RequestContent(),
104                 new RequestTargetHost(),
105                 new RequestClientConnControl(),
106                 new RequestUserAgent(VersionInfo.getSoftwareInfo(
107                         "Apache-HttpClient", "org.apache.hc.client5", getClass())));
108     }
109 
110     @Override
111     protected CloseableHttpResponse doExecute(
112             final HttpHost target,
113             final ClassicHttpRequest request,
114             final HttpContext context) throws IOException {
115         Args.notNull(target, "Target host");
116         Args.notNull(request, "HTTP request");
117         if (request.getScheme() == null) {
118             request.setScheme(target.getSchemeName());
119         }
120         if (request.getAuthority() == null) {
121             request.setAuthority(new URIAuthority(target));
122         }
123         final HttpClientContext clientContext = HttpClientContext.adapt(
124                 context != null ? context : new BasicHttpContext());
125         RequestConfig config = null;
126         if (request instanceof Configurable) {
127             config = ((Configurable) request).getConfig();
128         }
129         if (config != null) {
130             clientContext.setRequestConfig(config);
131         }
132 
133         final HttpRoute route = new HttpRoute(RoutingSupport.normalize(target, schemePortResolver));
134         final String exchangeId = ExecSupport.getNextExchangeId();
135         clientContext.setExchangeId(exchangeId);
136         final ExecRuntime execRuntime = new InternalExecRuntime(LOG, connManager, requestExecutor,
137                 request instanceof CancellableDependency ? (CancellableDependency) request : null);
138         try {
139             if (!execRuntime.isEndpointAcquired()) {
140                 execRuntime.acquireEndpoint(exchangeId, route, null, clientContext);
141             }
142             if (!execRuntime.isEndpointConnected()) {
143                 execRuntime.connectEndpoint(clientContext);
144             }
145 
146             clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
147             clientContext.setAttribute(HttpClientContext.HTTP_ROUTE, route);
148 
149             httpProcessor.process(request, request.getEntity(), clientContext);
150             final ClassicHttpResponse response = execRuntime.execute(exchangeId, request, clientContext);
151             httpProcessor.process(response, response.getEntity(), clientContext);
152 
153             if (reuseStrategy.keepAlive(request, response, clientContext)) {
154                 execRuntime.markConnectionReusable(null, TimeValue.NEG_ONE_MILLISECOND);
155             } else {
156                 execRuntime.markConnectionNonReusable();
157             }
158 
159             // check for entity, release connection if possible
160             final HttpEntity entity = response.getEntity();
161             if (entity == null || !entity.isStreaming()) {
162                 // connection not needed and (assumed to be) in re-usable state
163                 execRuntime.releaseEndpoint();
164                 return new CloseableHttpResponse(response, null);
165             }
166             ResponseEntityProxy.enhance(response, execRuntime);
167             return new CloseableHttpResponse(response, execRuntime);
168         } catch (final ConnectionShutdownException ex) {
169             final InterruptedIOException ioex = new InterruptedIOException("Connection has been shut down");
170             ioex.initCause(ex);
171             execRuntime.discardEndpoint();
172             throw ioex;
173         } catch (final HttpException httpException) {
174             execRuntime.discardEndpoint();
175             throw new ClientProtocolException(httpException);
176         } catch (final RuntimeException | IOException ex) {
177             execRuntime.discardEndpoint();
178             throw ex;
179         } catch (final Error error) {
180             connManager.close(CloseMode.IMMEDIATE);
181             throw error;
182         }
183     }
184 
185     @Override
186     public void close() throws IOException {
187         this.connManager.close();
188     }
189 
190     @Override
191     public void close(final CloseMode closeMode) {
192         this.connManager.close(closeMode);
193     }
194 
195 }