io.gravitee.gateway.http.vertx.VertxHttpClient.java Source code

Java tutorial

Introduction

Here is the source code for io.gravitee.gateway.http.vertx.VertxHttpClient.java

Source

/**
 * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
 *
 * 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 io.gravitee.gateway.http.vertx;

import io.gravitee.common.http.HttpHeaders;
import io.gravitee.common.http.HttpHeadersValues;
import io.gravitee.common.http.HttpStatusCode;
import io.gravitee.definition.model.Endpoint;
import io.gravitee.definition.model.HttpClientSslOptions;
import io.gravitee.definition.model.HttpProxy;
import io.gravitee.gateway.api.ClientRequest;
import io.gravitee.gateway.api.ClientResponse;
import io.gravitee.gateway.api.buffer.Buffer;
import io.gravitee.gateway.api.handler.Handler;
import io.gravitee.gateway.http.core.client.AbstractHttpClient;
import io.netty.channel.ConnectTimeoutException;
import io.vertx.core.Context;
import io.vertx.core.Vertx;
import io.vertx.core.http.*;
import io.vertx.core.net.PemTrustOptions;
import io.vertx.core.net.ProxyOptions;
import io.vertx.core.net.ProxyType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;

/**
 * @author David BRASSELY (david.brassely at graviteesource.com)
 * @author GraviteeSource Team
 */
public class VertxHttpClient extends AbstractHttpClient {

    private final Logger LOGGER = LoggerFactory.getLogger(VertxHttpClient.class);

    private static final String HTTPS_SCHEME = "https";

    @Resource
    private Vertx vertx;

    private final Endpoint endpoint;

    private HttpClientOptions httpClientOptions;

    @Autowired
    public VertxHttpClient(Endpoint endpoint) {
        this.endpoint = endpoint;
    }

    private final Map<Context, HttpClient> httpClients = new HashMap<>();

    @Override
    public ClientRequest request(io.gravitee.common.http.HttpMethod method, URI uri, HttpHeaders headers,
            Handler<ClientResponse> responseHandler) {
        HttpClient httpClient = httpClients.computeIfAbsent(Vertx.currentContext(), createHttpClient());

        final int port = uri.getPort() != -1 ? uri.getPort() : (HTTPS_SCHEME.equals(uri.getScheme()) ? 443 : 80);

        String relativeUri = (uri.getRawQuery() == null) ? uri.getRawPath()
                : uri.getRawPath() + '?' + uri.getRawQuery();

        HttpClientRequest clientRequest = httpClient.request(convert(method), port, uri.getHost(), relativeUri,
                clientResponse -> handleClientResponse(clientResponse, responseHandler));

        clientRequest.setTimeout(endpoint.getHttpClientOptions().getReadTimeout());

        VertxClientRequest invokerRequest = new VertxClientRequest(clientRequest);

        clientRequest.exceptionHandler(event -> {
            LOGGER.error("Server proxying failed: {}", event.getMessage());

            if (invokerRequest.connectTimeoutHandler() != null && event instanceof ConnectTimeoutException) {
                invokerRequest.connectTimeoutHandler().handle(event);
            } else {
                VertxClientResponse clientResponse = new VertxClientResponse(
                        ((event instanceof ConnectTimeoutException) || (event instanceof TimeoutException))
                                ? HttpStatusCode.GATEWAY_TIMEOUT_504
                                : HttpStatusCode.BAD_GATEWAY_502);

                clientResponse.headers().set(HttpHeaders.CONNECTION, HttpHeadersValues.CONNECTION_CLOSE);

                Buffer buffer = null;

                if (event.getMessage() != null) {
                    // Create body content with error message
                    buffer = Buffer.buffer(event.getMessage());
                    clientResponse.headers().set(HttpHeaders.CONTENT_LENGTH, Integer.toString(buffer.length()));
                }

                responseHandler.handle(clientResponse);

                if (buffer != null) {
                    clientResponse.bodyHandler().handle(buffer);
                }

                clientResponse.endHandler().handle(null);
            }
        });

        // Copy headers to final API
        copyRequestHeaders(headers, clientRequest,
                (port == 80 || port == 443) ? uri.getHost() : uri.getHost() + ':' + port);

        // Check chunk flag on the request if there are some content to push and if transfer_encoding is set
        // with chunk value
        if (hasContent(headers)) {
            String transferEncoding = headers.getFirst(HttpHeaders.TRANSFER_ENCODING);
            if (HttpHeadersValues.TRANSFER_ENCODING_CHUNKED.equalsIgnoreCase(transferEncoding)) {
                clientRequest.setChunked(true);
            }
        }

        // Send HTTP head as soon as possible
        clientRequest.sendHead();

        return invokerRequest;
    }

    private void handleClientResponse(HttpClientResponse clientResponse,
            Handler<ClientResponse> clientResponseHandler) {
        VertxClientResponse proxyClientResponse = new VertxClientResponse(clientResponse.statusCode());

        // Copy HTTP headers
        clientResponse.headers().names().forEach(headerName -> proxyClientResponse.headers().put(headerName,
                clientResponse.headers().getAll(headerName)));

        // Copy body content
        clientResponse.handler(event -> proxyClientResponse.bodyHandler().handle(Buffer.buffer(event.getBytes())));

        // Signal end of the response
        clientResponse.endHandler(v -> proxyClientResponse.endHandler().handle(null));

        clientResponseHandler.handle(proxyClientResponse);
    }

    private void copyRequestHeaders(HttpHeaders headers, HttpClientRequest httpClientRequest, String host) {
        for (Map.Entry<String, List<String>> headerValues : headers.entrySet()) {
            String headerName = headerValues.getKey();
            String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);

            // Remove hop-by-hop headers.
            if (HOP_HEADERS.contains(lowerHeaderName)) {
                continue;
            }

            httpClientRequest.putHeader(headerName, headerValues.getValue());
        }

        httpClientRequest.putHeader(HttpHeaders.HOST, host);
    }

    private HttpMethod convert(io.gravitee.common.http.HttpMethod httpMethod) {
        switch (httpMethod) {
        case CONNECT:
            return HttpMethod.CONNECT;
        case DELETE:
            return HttpMethod.DELETE;
        case GET:
            return HttpMethod.GET;
        case HEAD:
            return HttpMethod.HEAD;
        case OPTIONS:
            return HttpMethod.OPTIONS;
        case PATCH:
            return HttpMethod.PATCH;
        case POST:
            return HttpMethod.POST;
        case PUT:
            return HttpMethod.PUT;
        case TRACE:
            return HttpMethod.TRACE;
        }

        return null;
    }

    @Override
    protected void doStart() throws Exception {
        // TODO: Prepare HttpClientOptions according to the endpoint to improve performance when creating a new
        // instance of the Vertx client
        httpClientOptions = new HttpClientOptions();

        httpClientOptions.setPipelining(endpoint.getHttpClientOptions().isPipelining());
        httpClientOptions.setKeepAlive(endpoint.getHttpClientOptions().isKeepAlive());
        httpClientOptions.setIdleTimeout((int) (endpoint.getHttpClientOptions().getIdleTimeout() / 1000));
        httpClientOptions.setConnectTimeout((int) endpoint.getHttpClientOptions().getConnectTimeout());
        httpClientOptions.setUsePooledBuffers(true);
        httpClientOptions.setMaxPoolSize(endpoint.getHttpClientOptions().getMaxConcurrentConnections());
        httpClientOptions.setTryUseCompression(endpoint.getHttpClientOptions().isUseCompression());

        // Configure proxy
        HttpProxy proxy = endpoint.getHttpProxy();
        if (proxy != null && proxy.isEnabled()) {
            ProxyOptions proxyOptions = new ProxyOptions();
            proxyOptions.setHost(proxy.getHost());
            proxyOptions.setPort(proxy.getPort());
            proxyOptions.setUsername(proxy.getUsername());
            proxyOptions.setPassword(proxy.getPassword());
            proxyOptions.setType(ProxyType.valueOf(proxy.getType().name()));

            httpClientOptions.setProxyOptions(proxyOptions);
        }

        URI target = URI.create(endpoint.getTarget());
        // Configure SSL
        HttpClientSslOptions sslOptions = endpoint.getHttpClientSslOptions();
        if (sslOptions != null && sslOptions.isEnabled()) {
            httpClientOptions.setSsl(sslOptions.isEnabled()).setVerifyHost(sslOptions.isHostnameVerifier())
                    .setTrustAll(sslOptions.isTrustAll());

            if (sslOptions.getPem() != null && !sslOptions.getPem().isEmpty()) {
                httpClientOptions.setPemTrustOptions(new PemTrustOptions()
                        .addCertValue(io.vertx.core.buffer.Buffer.buffer(sslOptions.getPem())));
            }
        } else if (HTTPS_SCHEME.equalsIgnoreCase(target.getScheme())) {
            // SSL is not configured but the endpoint scheme is HTTPS so let's enable the SSL on Vert.x HTTP client
            // automatically
            httpClientOptions.setSsl(true).setTrustAll(true);
        }

        printHttpClientConfiguration(httpClientOptions);
    }

    @Override
    protected void doStop() throws Exception {
        LOGGER.info("Closing HTTP Client for '{}' endpoint [{}]", endpoint.getName(), endpoint.getTarget());

        httpClients.values().stream().forEach(httpClient -> {
            try {
                httpClient.close();
            } catch (IllegalStateException ise) {
                LOGGER.warn(ise.getMessage());
            }
        });
    }

    private Function<Context, HttpClient> createHttpClient() {
        return context -> vertx.createHttpClient(httpClientOptions);
    }

    private void printHttpClientConfiguration(HttpClientOptions httpClientOptions) {
        LOGGER.info("Create HTTP Client with configuration: ");
        LOGGER.info("\tHTTP {" + "ConnectTimeout='" + httpClientOptions.getConnectTimeout() + '\'' + ", KeepAlive='"
                + httpClientOptions.isKeepAlive() + '\'' + ", IdleTimeout='" + httpClientOptions.getIdleTimeout()
                + '\'' + ", MaxChunkSize='" + httpClientOptions.getMaxChunkSize() + '\'' + ", MaxPoolSize='"
                + httpClientOptions.getMaxPoolSize() + '\'' + ", MaxWaitQueueSize='"
                + httpClientOptions.getMaxWaitQueueSize() + '\'' + ", Pipelining='"
                + httpClientOptions.isPipelining() + '\'' + ", PipeliningLimit='"
                + httpClientOptions.getPipeliningLimit() + '\'' + ", TryUseCompression='"
                + httpClientOptions.isTryUseCompression() + '\'' + '}');

        if (httpClientOptions.isSsl()) {
            LOGGER.info("\tSSL {" + "TrustAll='" + httpClientOptions.isTrustAll() + '\'' + ", VerifyHost='"
                    + httpClientOptions.isVerifyHost() + '\'' + '}');
        }

        if (httpClientOptions.getProxyOptions() != null) {
            LOGGER.info("\tProxy {" + "Type='" + httpClientOptions.getProxyOptions().getType() + ", Host='"
                    + httpClientOptions.getProxyOptions().getHost() + '\'' + ", Port='"
                    + httpClientOptions.getProxyOptions().getPort() + '\'' + ", Username='"
                    + httpClientOptions.getProxyOptions().getUsername() + '\'' + '}');
        }
    }
}