package org.apereo.cas.util.http; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.FutureRequestExecutionService; import org.apache.http.impl.client.HttpRequestFutureTask; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.util.Assert; import java.io.Serializable; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.concurrent.RejectedExecutionException; import java.util.stream.Collectors; /** * Implementation of CAS {@link HttpClient} * which delegates requests to a {@link #httpClient} instance. * * @author Jerome Leleu * @author Scott Battaglia * @author Misagh Moayyed * @since 3.1 */ public class SimpleHttpClient implements HttpClient, Serializable, DisposableBean { /** * Unique Id for serialization. */ private static final long serialVersionUID = -4949380008568071855L; private static final Logger LOGGER = LoggerFactory.getLogger(SimpleHttpClient.class); /** * the acceptable codes supported by this client. */ private List<Integer> acceptableCodes; /** * the HTTP client for this client. */ private CloseableHttpClient httpClient; /** * the request executor service for this client. */ private FutureRequestExecutionService requestExecutorService; /** * Instantiates a new Simple HTTP client, based on the provided inputs. * * @param acceptableCodes the acceptable codes of the client * @param httpClient the HTTP client used by the client * @param requestExecutorService the request executor service used by the client */ SimpleHttpClient(final List<Integer> acceptableCodes, final CloseableHttpClient httpClient, final FutureRequestExecutionService requestExecutorService) { this.acceptableCodes = acceptableCodes.stream().sorted().collect(Collectors.toList()); this.httpClient = httpClient; this.requestExecutorService = requestExecutorService; } @Override public boolean sendMessageToEndPoint(final HttpMessage message) { Assert.notNull(this.httpClient); try { final HttpPost request = new HttpPost(message.getUrl().toURI()); request.addHeader("Content-Type", message.getContentType()); final StringEntity entity = new StringEntity(message.getMessage(), ContentType.create(message.getContentType())); request.setEntity(entity); final ResponseHandler<Boolean> handler = response -> response.getStatusLine().getStatusCode() == HttpStatus.SC_OK; LOGGER.debug("Created HTTP post message payload [{}]", request); final HttpRequestFutureTask<Boolean> task = this.requestExecutorService.execute(request, HttpClientContext.create(), handler); if (message.isAsynchronous()) { return true; } return task.get(); } catch (final RejectedExecutionException e) { LOGGER.warn(e.getMessage(), e); return false; } catch (final Exception e) { LOGGER.debug(e.getMessage(), e); return false; } } @Override public HttpMessage sendMessageToEndPoint(final URL url) { Assert.notNull(this.httpClient); HttpEntity entity = null; try (CloseableHttpResponse response = this.httpClient.execute(new HttpGet(url.toURI()))) { final int responseCode = response.getStatusLine().getStatusCode(); for (final int acceptableCode : this.acceptableCodes) { if (responseCode == acceptableCode) { LOGGER.debug("Response code received from server matched [{}].", responseCode); entity = response.getEntity(); final HttpMessage msg = new HttpMessage(url, IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8)); msg.setContentType(entity.getContentType().getValue()); msg.setResponseCode(responseCode); return msg; } } LOGGER.warn("Response code [{}] from [{}] did not match any of the acceptable response codes.", responseCode, url); if (responseCode == HttpStatus.SC_INTERNAL_SERVER_ERROR) { final String value = response.getStatusLine().getReasonPhrase(); LOGGER.error("There was an error contacting the endpoint: [{}]; The error:\n[{}]", url.toExternalForm(), value); } } catch (final Exception e) { LOGGER.error(e.getMessage(), e); } finally { EntityUtils.consumeQuietly(entity); } return null; } @Override public boolean isValidEndPoint(final String url) { try { final URL u = new URL(url); return isValidEndPoint(u); } catch (final MalformedURLException e) { LOGGER.error(e.getMessage(), e); return false; } } @Override public boolean isValidEndPoint(final URL url) { Assert.notNull(this.httpClient); HttpEntity entity = null; try (CloseableHttpResponse response = this.httpClient.execute(new HttpGet(url.toURI()))) { final int responseCode = response.getStatusLine().getStatusCode(); final int idx = Collections.binarySearch(this.acceptableCodes, responseCode); if (idx >= 0) { LOGGER.debug("Response code from server matched [{}].", responseCode); return true; } LOGGER.debug("Response code did not match any of the acceptable response codes. Code returned was [{}]", responseCode); if (responseCode == HttpStatus.SC_INTERNAL_SERVER_ERROR) { final String value = response.getStatusLine().getReasonPhrase(); LOGGER.error("There was an error contacting the endpoint: [{}]; The error was:\n[{}]", url.toExternalForm(), value); } entity = response.getEntity(); } catch (final Exception e) { LOGGER.error(e.getMessage(), e); } finally { EntityUtils.consumeQuietly(entity); } return false; } /** * Shutdown the executor service and close the http client. * * @throws Exception if the executor cannot properly shut down */ @Override public void destroy() throws Exception { IOUtils.closeQuietly(this.requestExecutorService); } @Override public org.apache.http.client.HttpClient getWrappedHttpClient() { return this.httpClient; } }