blob: 12bd58dc46a77e1ca14184aaf7ab731a0db64c6e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2018 The Eclipse Foundation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* The Eclipse Foundation - initial API and implementation
*******************************************************************************/
package org.eclipse.epp.internal.mpc.core.transport.httpclient;
import java.util.List;
import org.apache.hc.client5.http.auth.CredentialsStore;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.util.Timeout;
import org.eclipse.userstorage.internal.StorageProperties;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.FieldOption;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
@Component(name = "org.eclipse.epp.mpc.core.http.client.factory", service = { HttpClientFactory.class })
public class HttpClientFactory {
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policyOption = ReferencePolicyOption.GREEDY, policy = ReferencePolicy.STATIC, fieldOption = FieldOption.REPLACE)
private List<HttpClientCustomizer> customizers;
public List<HttpClientCustomizer> getCustomizers() {
return customizers;
}
public void setCustomizers(List<HttpClientCustomizer> customizers) {
this.customizers = customizers;
}
public HttpServiceContext build() {
return build(null);
}
public HttpServiceContext build(HttpServiceContext oldContext) {
HttpClientBuilder clientBuilder = builder();
CookieStore cookieStore = oldContext == null ? null : oldContext.getCookieStore();
if (cookieStore == null) {
cookieStore = createCookieStore();
}
CredentialsStore cacheProvider = oldContext == null ? null
: oldContext.getCredentialsCacheProvider();
if (cacheProvider == null) {
cacheProvider = createCredentialsCacheProvider();
}
CredentialsStore initialCredentialsProvider = oldContext == null ? null : oldContext.getInitialCredentialsProvider();
if (initialCredentialsProvider == null) {
initialCredentialsProvider = createCredentialsProvider();
}
CredentialsStore credentialsProvider = initialCredentialsProvider;
if (credentialsProvider != null) {
credentialsProvider = customizeCredentialsProvider(clientBuilder, credentialsProvider, cacheProvider);
}
clientBuilder.setDefaultCredentialsProvider(credentialsProvider);
clientBuilder.setDefaultCookieStore(cookieStore);
clientBuilder = customizeBuilder(clientBuilder);
return new HttpServiceContext(clientBuilder.build(), cookieStore, credentialsProvider, initialCredentialsProvider,
cacheProvider);
}
protected CredentialsStore createCredentialsProvider() {
return new SystemCredentialsProvider();
}
protected CredentialsStore createCredentialsCacheProvider() {
return new CacheCredentialsProvider();
}
protected CookieStore createCookieStore() {
return new BasicCookieStore();
}
private CredentialsStore customizeCredentialsProvider(HttpClientBuilder clientBuilder,
CredentialsStore credentialsProvider, CredentialsStore cacheProvider) {
//TODO we should handle configured proxy passwords and dialogs to prompt for unknown credentials on our own...
credentialsProvider = customizeCredentialsProvider(credentialsProvider);
if (cacheProvider != null) {
credentialsProvider = new ChainedCredentialsProvider(cacheProvider, credentialsProvider);
clientBuilder
.addRequestInterceptorFirst((HttpRequestInterceptor) (request, entityDetails, context) -> context
.setAttribute(CacheCredentialsAuthenticationStrategy.CREDENTIALS_CACHE_ATTRIBUTE, cacheProvider));
}
credentialsProvider = new SynchronizedCredentialsProvider(credentialsProvider);
return credentialsProvider;
}
protected HttpClientBuilder builder() {
HttpClientBuilder builder = HttpClientBuilder.create();
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
connManager.setDefaultMaxPerRoute(100);
connManager.setMaxTotal(200);
builder.setConnectionManager(connManager);
setClientDefaultTimeouts(builder);
builder.addResponseInterceptorLast(new CacheCredentialsAuthenticationStrategy());
builder.setUserAgent(HttpClientTransport.USER_AGENT);
return builder;
}
private static void setClientDefaultTimeouts(HttpClientBuilder builder) {
@SuppressWarnings("restriction")
int connectTimeoutUssDefault = StorageProperties.getProperty(StorageProperties.CONNECT_TIMEOUT,
HttpClientTransport.DEFAULT_CONNECT_TIMEOUT);
@SuppressWarnings("restriction")
int readTimeoutUssDefault = StorageProperties.getProperty(StorageProperties.SOCKET_TIMEOUT,
HttpClientTransport.DEFAULT_READ_TIMEOUT);
int connectTimeout = getTimeoutValue(HttpClientTransport.CONNECT_TIMEOUT_PROPERTY, connectTimeoutUssDefault);
int readTimeout = getTimeoutValue(HttpClientTransport.READ_TIMEOUT_PROPERTY, readTimeoutUssDefault);
int connectionRequestTimeout = getTimeoutValue(HttpClientTransport.CONNECTION_REQUEST_TIMEOUT_PROPERTY,
HttpClientTransport.DEFAULT_CONNECTION_REQUEST_TIMEOUT);
SocketConfig defaultSocketConfig = SocketConfig.copy(SocketConfig.DEFAULT)
.setSoTimeout(Timeout.ofMilliseconds(readTimeout))
.setTcpNoDelay(true)//Disable Nagle - see https://en.wikipedia.org/wiki/Nagle%27s_algorithm#Negative_effect_on_larger_writes
//.setSoLinger(0)
//TODO is it safe to set this to 0? This will forcefully terminate sockets on close instead of waiting for graceful close
//See http://docs.oracle.com/javase/6/docs/api/java/net/SocketOptions.html?is-external=true#SO_LINGER
//and https://issues.apache.org/jira/browse/HTTPCLIENT-1497
.build();
RequestConfig defaultRequestConfig = RequestConfig.copy(RequestConfig.DEFAULT)
.setResponseTimeout(Timeout.ofMilliseconds(readTimeout))
.setConnectTimeout(Timeout.ofMilliseconds(connectTimeout))
.setConnectionRequestTimeout(Timeout.ofMilliseconds(connectionRequestTimeout))
.build();
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
connManager.setDefaultSocketConfig(defaultSocketConfig);
builder.setConnectionManager(connManager);
builder.setDefaultRequestConfig(defaultRequestConfig);
}
private static int getTimeoutValue(String property, int defaultValue) {
String propertyValue = FrameworkUtil.getBundle(HttpClientTransport.class)
.getBundleContext()
.getProperty(property);
if (propertyValue == null || "".equals(propertyValue)) { //$NON-NLS-1$
return defaultValue;
}
try {
return Integer.parseInt(propertyValue);
} catch (NumberFormatException ex) {
//TODO log
return defaultValue;
}
}
protected HttpClientBuilder customizeBuilder(HttpClientBuilder builder) {
HttpClientBuilder customBuilder = builder;
for (HttpClientCustomizer customizer : this.customizers) {
customBuilder = customizeBuilder(customizer, customBuilder);
}
return customBuilder;
}
private static HttpClientBuilder customizeBuilder(HttpClientCustomizer customizer, HttpClientBuilder builder) {
if (customizer == null) {
return builder;
}
HttpClientBuilder customBuilder = customizer.customizeBuilder(builder);
return customBuilder == null ? builder : customBuilder;
}
private CredentialsStore customizeCredentialsProvider(CredentialsStore credentialsProvider) {
CredentialsStore customizedCredentialsProvider = credentialsProvider;
for (HttpClientCustomizer customizer : this.customizers) {
customizedCredentialsProvider = customizeCredentialsProvider(customizer, customizedCredentialsProvider);
}
return customizedCredentialsProvider;
}
private static CredentialsStore customizeCredentialsProvider(HttpClientCustomizer customizer,
CredentialsStore credentialsProvider) {
if (customizer == null) {
return credentialsProvider;
}
CredentialsStore customCredentialsProvider = customizer.customizeCredentialsProvider(credentialsProvider);
return customCredentialsProvider == null ? credentialsProvider : customCredentialsProvider;
}
}