All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavemaker.runtime.rest.service.RestConnector Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (C) 2022-2023 WaveMaker, Inc.
 *
 * 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.wavemaker.runtime.rest.service;

import java.io.ByteArrayInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.util.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.ResponseErrorHandler;

import com.wavemaker.commons.rest.error.WMDefaultResponseErrorHandler;
import com.wavemaker.runtime.rest.model.HttpRequestDetails;
import com.wavemaker.runtime.rest.model.HttpResponseDetails;

/**
 * @author Uday Shankar
 */

public class RestConnector {

    private static final Logger logger = LoggerFactory.getLogger(RestConnector.class);

    private CloseableHttpClient defaultHttpClient;
    private HttpConfiguration httpConfiguration;

    public static final RestConnector DEFAULT_INSTANCE = new RestConnector();

    @Deprecated
    public RestConnector() {
        this(new StandardEnvironment());
    }

    @Autowired
    public RestConnector(Environment environment) {
        httpConfiguration = new HttpConfiguration(environment);
        logger.info("Initialized http configuration {}", httpConfiguration);
    }

    public void invokeRestCall(HttpRequestDetails httpRequestDetails, Consumer extractDataConsumer) {
        final HttpClientContext httpClientContext = HttpClientContext.create();
        WMRestTemplate wmRestTemplate = new WMRestTemplate();
        wmRestTemplate.setExtractDataConsumer(extractDataConsumer);
        getResponseEntity(httpRequestDetails, httpClientContext, null, wmRestTemplate);
    }

    public HttpResponseDetails invokeRestCall(HttpRequestDetails httpRequestDetails) {
        final HttpClientContext httpClientContext = HttpClientContext.create();
        ResponseEntity responseEntity = getResponseEntity(httpRequestDetails, httpClientContext, byte[].class, new WMRestTemplate());
        return getHttpResponseDetails(responseEntity);
    }

    public  ResponseEntity invokeRestCall(HttpRequestDetails httpRequestDetails, Class t) {
        final HttpClientContext httpClientContext = HttpClientContext.create();
        return getResponseEntity(httpRequestDetails, httpClientContext, t, new WMRestTemplate());
    }

    private  ResponseEntity getResponseEntity(
        final HttpRequestDetails httpRequestDetails, final HttpClientContext
        httpClientContext, Class t, final WMRestTemplate wmRestTemplate) {

        String endpointAddress = httpRequestDetails.getEndpointAddress();
        HttpMethod httpMethod = HttpMethod.valueOf(httpRequestDetails.getMethod());
        logger.debug("Sending {} request to URL {}", httpMethod, endpointAddress);

        CloseableHttpClient httpClient = getHttpClient();

        final RequestConfig requestConfig = RequestConfig.custom()
            .setRedirectsEnabled(httpRequestDetails.isRedirectEnabled())
            .setCookieSpec(StandardCookieSpec.IGNORE)
            .setConnectionRequestTimeout(Timeout.ofSeconds(httpConfiguration.getConnectionRequestTimeoutInSeconds()))
            .setProtocolUpgradeEnabled(false)
            .build();
        httpClientContext.setRequestConfig(requestConfig);

        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        clientHttpRequestFactory.setHttpContextFactory((method, uri) -> httpClientContext);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.putAll(httpRequestDetails.getHeaders());

        wmRestTemplate.setRequestFactory(clientHttpRequestFactory);
        wmRestTemplate.setErrorHandler(getExceptionHandler());

        HttpEntity requestEntity;
        com.wavemaker.app.web.http.HttpMethod wmHttpMethod = com.wavemaker.app.web.http.HttpMethod.valueOf(httpRequestDetails.getMethod());
        if (wmHttpMethod.isRequestBodySupported() && httpRequestDetails.getBody() != null) {
            requestEntity = new HttpEntity(new InputStreamResource(httpRequestDetails.getBody()), httpHeaders);
        } else {
            requestEntity = new HttpEntity(httpHeaders);
        }
        return wmRestTemplate.exchange(endpointAddress, httpMethod, requestEntity, t);
    }

    public ResponseErrorHandler getExceptionHandler() {
        return new WMRestServicesErrorHandler();
    }

    private CloseableHttpClient getHttpClient() {
        synchronized (RestConnector.class) {
            if (defaultHttpClient == null) {
                HttpClientBuilder httpClientBuilder = HttpClients.custom()
                    .setConnectionManager(getConnectionManager());
                if (httpConfiguration.isUseSystemProperties()) {
                    httpClientBuilder = httpClientBuilder.useSystemProperties();
                }
                configureAppProxy(httpClientBuilder);
                defaultHttpClient = httpClientBuilder.build();
            }
        }
        return defaultHttpClient;
    }

    private void configureAppProxy(HttpClientBuilder httpClientBuilder) {
        if (httpConfiguration.isAppProxyEnabled()) {
            HttpHost proxyHost = new HttpHost(httpConfiguration.getAppProxyHost(), httpConfiguration.getAppProxyPort());
            BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
            credentialsProvider.setCredentials(new AuthScope(proxyHost),
                new UsernamePasswordCredentials(httpConfiguration.getAppProxyUsername(), httpConfiguration.getAppProxyPassword().toCharArray()));
            httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            logger.info("creating RoutePlanner with proxy url {}", proxyHost);
            HttpRoutePlanner httpRoutePlanner = createCustomRoutePlanner(proxyHost,
                parseCommaSeparatedUrlsToList(httpConfiguration.getAppProxyIncludeUrls()),
                parseCommaSeparatedUrlsToList(httpConfiguration.getAppProxyExcludeUrls()));
            httpClientBuilder.setRoutePlanner(httpRoutePlanner);
        }
    }

    private PoolingHttpClientConnectionManager getConnectionManager() {
        SSLContextProvider sslContextProvider = new SSLContextProvider(httpConfiguration);
        Registry registry = RegistryBuilder.create()
            .register("http", PlainConnectionSocketFactory.getSocketFactory())
            .register("https", new SSLConnectionSocketFactory(sslContextProvider.getSslContext(),
                httpConfiguration.getTlsVersions(), null, sslContextProvider.getHostnameVerifier()))
            .build();

        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);
        poolingHttpClientConnectionManager.setMaxTotal(httpConfiguration.getMaxTotalConnections());
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpConfiguration.getMaxTotalConnectionsPerRoute());
        ConnectionConfig connectionConfig = ConnectionConfig.custom().setConnectTimeout(Timeout.ofSeconds(httpConfiguration.getConnectionTimeoutInSeconds()))
            .setSocketTimeout(Timeout.ofSeconds(httpConfiguration.getConnectionSocketTimeoutInSeconds())).build();
        poolingHttpClientConnectionManager.setDefaultConnectionConfig(connectionConfig);
        return poolingHttpClientConnectionManager;
    }

    private HttpResponseDetails getHttpResponseDetails(ResponseEntity responseEntity) {
        HttpResponseDetails httpResponseDetails = new HttpResponseDetails();
        httpResponseDetails.setStatusCode(responseEntity.getStatusCode().value());
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.putAll(responseEntity.getHeaders());
        httpResponseDetails.setHeaders(httpHeaders);
        byte[] bytes = (responseEntity.getBody() != null) ? responseEntity.getBody() : new byte[0];
        httpResponseDetails.setBody(new ByteArrayInputStream(bytes));
        return httpResponseDetails;
    }

    private HttpRoutePlanner createCustomRoutePlanner(HttpHost proxyHost, List includedUrls, List excludedUrls) {
        DefaultProxyRoutePlanner defaultRoutePlanner = new DefaultProxyRoutePlanner(proxyHost);

        return (target, context) -> {
            boolean explicitlyIncluded = includedUrls.stream().anyMatch(url -> matches(target, url));

            boolean useProxy = true;
            if (explicitlyIncluded) {
                logger.debug("Url {} is explicitly included for proxying", target);
            } else if (includedUrls.isEmpty()) {
                boolean explicitlyExcluded = excludedUrls.stream().anyMatch(url -> matches(target, url));
                if (explicitlyExcluded) {
                    useProxy = false;
                    logger.debug("Url {} is explicitly excluded from proxying", target);
                }
            } else {
                useProxy = false;
            }
            if (useProxy) {
                logger.debug("Using proxy {} for target {}", proxyHost, target);
                return defaultRoutePlanner.determineRoute(target, context);
            } else {
                return new HttpRoute(target);
            }
        };
    }

    private boolean matches(HttpHost target, String url) {
        String urlHostName = extractHostName(url);
        int urlPort = extractPort(url);
        String urlScheme = extractScheme(url);
        return Objects.equals(target.getHostName(), urlHostName) && (urlPort == -1 || urlPort == target.getPort()) &&
            (urlScheme == null || StringUtils.equalsIgnoreCase(urlScheme, target.getSchemeName()));
    }

    private List parseCommaSeparatedUrlsToList(String commaSeparatedUrls) {
        return commaSeparatedUrls.trim().isEmpty() ? Collections.emptyList() : Arrays.stream(commaSeparatedUrls.split(","))
            .map(String::trim).filter(url -> !url.isEmpty()).collect(Collectors.toList());
    }

    private String extractHostName(String url) {
        int start = url.indexOf("://");
        if (start != -1) {
            start += 3;
        } else {
            start = 0;
        }
        int pathStart = url.indexOf('/', start);
        int portStart = url.indexOf(':', start);
        int end = (pathStart != -1 && (portStart == -1 || portStart > pathStart)) ? pathStart : portStart;
        if (end == -1) {
            end = url.length();
        }
        return url.substring(start, end);
    }

    private String extractScheme(String urlString) {
        try {
            URL url = new URL(urlString);
            return url.getProtocol();
        } catch (MalformedURLException e) {
            return null;
        }
    }

    private int extractPort(String url) {
        int portStart = url.lastIndexOf(':');
        int pathStart = url.indexOf('/', portStart);
        if (portStart != -1 && (pathStart == -1 || portStart < pathStart)) {
            String portString = url.substring(portStart + 1, pathStart != -1 ? pathStart : url.length());
            try {
                return Integer.parseInt(portString);
            } catch (NumberFormatException e) {
                return -1;
            }
        }
        return -1;
    }

    static class WMRestServicesErrorHandler extends WMDefaultResponseErrorHandler {

        @Override
        protected boolean hasError(HttpStatusCode statusCode) {
            return false;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy