com.teamscale.commons.service.client.ServiceClientUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-commons Show documentation
Show all versions of teamscale-commons Show documentation
Provides common DTOs for Teamscale
The newest version!
/*
* Copyright (c) CQSE GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.teamscale.commons.service.client;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolException;
import org.apache.http.auth.AuthProtocolState;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.FormBodyPartBuilder;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.function.FunctionWithException;
import org.conqat.lib.commons.net.TrustAllCertificatesManager;
import org.conqat.lib.commons.string.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.teamscale.commons.service.EMimeType;
/**
* Provides utility methods for ServiceClient and IdeServiceClient.
*/
public class ServiceClientUtils {
/**
* Sets up the HTTP client and returns it. The caller of this method is responsible for closing the
* client when it is no longer needed.
*/
public static CloseableHttpClient getHttpClient(ServerDetails serverDetails, boolean disableRedirects) {
try {
SSLContext sslContext = SSLContexts.createDefault();
sslContext.init(null, new TrustManager[] { new TrustAllCertificatesManager() }, new SecureRandom());
return getHttpClient(serverDetails, sslContext, null, disableRedirects);
} catch (KeyManagementException e) {
throw new IllegalStateException("Error creating HTTP client: " + e.getMessage(), e);
}
}
/**
* See {@link #getHttpClient(ServerDetails, boolean)}
*/
public static CloseableHttpClient getHttpClient(ServerDetails serverDetails) {
return getHttpClient(serverDetails, false);
}
/**
* Sets up the HTTP client and returns it. The caller of this method is responsible for closing the
* client when it is no longer needed.
*/
public static CloseableHttpClient getHttpClient(ServerDetails serverDetails, SSLContext sslContext,
String userAgent, boolean disableRedirects) {
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(serverDetails.getUsername(), serverDetails.getPassword()));
RequestConfig.Builder requestBuilder = RequestConfig.custom();
int timeoutMilliseconds = (int) TimeUnit.SECONDS.toMillis(serverDetails.getTimeoutSeconds());
requestBuilder = requestBuilder.setConnectTimeout(timeoutMilliseconds);
requestBuilder = requestBuilder.setSocketTimeout(timeoutMilliseconds);
requestBuilder = requestBuilder.setConnectionRequestTimeout(timeoutMilliseconds);
RequestConfig requestConfig = requestBuilder.build();
HttpClientBuilder clientBuilder = HttpClients.custom() //
.setSSLSocketFactory(socketFactory) //
.setDefaultCredentialsProvider(credentialsProvider) //
.addInterceptorFirst(new PreemptiveAuthenticationInterceptor()) //
.setDefaultRequestConfig(requestConfig);
if (disableRedirects) {
clientBuilder.disableRedirectHandling();
} else {
clientBuilder.setRedirectStrategy(getRedirectStrategy());
}
if (!StringUtils.isEmpty(userAgent)) {
clientBuilder.setUserAgent(userAgent);
}
return clientBuilder.build();
}
private static RedirectStrategy getRedirectStrategy() {
// We use the LaxRedirectStrategy to also redirect on POST (and DELETE) requests
// (default is to only redirect GET and HEAD)
return new LaxRedirectStrategy() {
// We need to overwrite this method, as the DefaultRedirectStrategy would
// redirect other request types to a GET request (in the case of a 301, 302 and
// 303 redirect)
@Override
public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context)
throws ProtocolException {
URI uri = getLocationURI(request, response, context);
String method = request.getRequestLine().getMethod();
if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
return new HttpHead(uri);
} else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) {
return new HttpGet(uri);
} else {
return checkStatusRedirect(request, response, uri);
}
}
// Here we specify the status codes, where we use the same request method for
// the redirect
private HttpUriRequest checkStatusRedirect(HttpRequest request, HttpResponse response, URI uri) {
int status = response.getStatusLine().getStatusCode();
if (status == HttpStatus.SC_MOVED_PERMANENTLY //
|| status == HttpStatus.SC_MOVED_TEMPORARILY //
|| status == HttpStatus.SC_SEE_OTHER //
|| status == HttpStatus.SC_TEMPORARY_REDIRECT //
|| status == SC_PERMANENT_REDIRECT) {
return RequestBuilder.copy(request).setUri(uri).build();
} else {
return new HttpGet(uri);
}
}
};
}
/**
* Implements preemptive authentication, i.e., will send {@code Authorization} even if unchallenged
* by the server.
*
* @see Stack
* Overflow: Preemptive Basic authentication with Apache HttpClient 4
*/
private static class PreemptiveAuthenticationInterceptor implements HttpRequestInterceptor {
@Override
public void process(HttpRequest request, HttpContext context) {
AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
// We are doing preemptive authentication, i.e., if unchallenged we set Basic
// authentication credentials
if (authState.getState() == AuthProtocolState.UNCHALLENGED) {
CredentialsProvider credentialsProvider = (CredentialsProvider) context
.getAttribute(HttpClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
Credentials credentials = credentialsProvider.getCredentials(authScope);
if (credentials != null) {
authState.update(new BasicScheme(), credentials);
}
}
}
}
/**
* Creates a multi-part entity that uploads the given files under the given request parameter.
*/
public static HttpEntity createMultiPartEntityForFiles(String parameterName, List files) {
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
for (File file : files) {
FormBodyPart bodyPart = FormBodyPartBuilder
.create(parameterName, new FileBody(file, ContentType.APPLICATION_OCTET_STREAM)).build();
multipartEntityBuilder.addPart(bodyPart);
}
return multipartEntityBuilder.build();
}
/**
* Executes a request and handles the response. The request's header is not set to accept any
* specific type. Returns null in case of a "not found" HTTP return code.
*/
public static T executeRequest(HttpClient client, HttpRequestBase request, EMimeType contentType,
FunctionWithException deserializeFunction) throws ServiceCallException {
if (contentType != null) {
request.setHeader(HttpHeaders.ACCEPT, contentType.getType());
}
try {
HttpResponse response = client.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
switch (statusCode) {
case HttpStatus.SC_OK:
case HttpStatus.SC_ACCEPTED:
return deserializeEntity(response.getEntity(), deserializeFunction);
case HttpStatus.SC_NO_CONTENT:
case HttpStatus.SC_NOT_FOUND:
return null;
default:
String responseBody = EntityUtils.toString(response.getEntity());
throw new ServiceCallException(request.getURI(), response.getStatusLine().getReasonPhrase(), statusCode,
responseBody);
}
} catch (IOException | IllegalArgumentException e) {
// IllegalArgumentException raised when the server port is illegal. Note that
// this will also catch IllegalArgumentExceptions thrown for other reasons.
throw new ServiceCallException("Service call " + request.getURI() + " failed:" + e.getMessage(), e);
}
}
private static T deserializeEntity(HttpEntity entity,
FunctionWithException deserializeFunction) throws IOException {
if (entity == null) {
return null;
}
InputStream content = entity.getContent();
if (content == null) {
return null;
}
try (Reader reader = new InputStreamReader(content, getCharset(entity))) {
T result = deserializeFunction.apply(reader);
// As the reader is getting closed after this method,
// there is no point in returning it
CCSMAssert.isFalse(result == reader, "result must not be the same as input reader");
return result;
}
}
/**
* Copies the given array elements into a list, with {@code null} arrays being represented as the
* empty list.
*/
public static List arrayToList(T[] array) {
if (array == null) {
return Collections.emptyList();
}
return ImmutableList.copyOf(array);
}
/**
* Copies the given array elements into a set, with {@code null} arrays being represented as the
* empty set.
*/
public static Set arrayToSet(T[] array) {
if (array == null) {
return Collections.emptySet();
}
return ImmutableSet.copyOf(array);
}
private static Charset getCharset(HttpEntity entity) {
ContentType contentType = ContentType.get(entity);
Charset charset = null;
if (contentType != null) {
charset = contentType.getCharset();
if (charset == null) {
ContentType defaultContentType = ContentType.getByMimeType(contentType.getMimeType());
if (defaultContentType != null) {
charset = defaultContentType.getCharset();
}
}
}
if (charset == null) {
charset = HTTP.DEF_CONTENT_CHARSET;
}
return charset;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy