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