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

io.gravitee.gateway.debug.reactor.DebugReactor Maven / Gradle / Ivy

/**
 * 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.debug.reactor;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.gravitee.common.event.Event;
import io.gravitee.definition.model.HttpRequest;
import io.gravitee.definition.model.HttpResponse;
import io.gravitee.gateway.debug.definition.DebugApi;
import io.gravitee.gateway.debug.vertx.VertxDebugHttpConfiguration;
import io.gravitee.gateway.reactor.Reactable;
import io.gravitee.gateway.reactor.ReactorEvent;
import io.gravitee.gateway.reactor.handler.ReactorHandlerRegistry;
import io.gravitee.gateway.reactor.impl.DefaultReactor;
import io.gravitee.gateway.reactor.impl.ReactableWrapper;
import io.gravitee.repository.exceptions.TechnicalException;
import io.gravitee.repository.management.api.EventRepository;
import io.gravitee.repository.management.model.ApiDebugStatus;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.http.*;
import io.vertx.core.http.impl.headers.HeadersMultiMap;
import io.vertx.core.net.*;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class DebugReactor extends DefaultReactor {

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

    @Autowired
    private EventRepository eventRepository;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private Vertx vertx;

    @Autowired
    private VertxDebugHttpConfiguration debugHttpConfiguration;

    @Autowired
    @Qualifier("debugReactorHandlerRegistry")
    private ReactorHandlerRegistry reactorHandlerRegistry;

    @Override
    public void onEvent(Event reactorEvent) {
        if (reactorEvent.type() == ReactorEvent.DEBUG) {
            logger.debug("Try to deploy api for debug...");
            ReactableWrapper reactableWrapper = (ReactableWrapper) reactorEvent.content();
            io.gravitee.repository.management.model.Event debugEvent = reactableWrapper.getContent();
            DebugApi reactableDebugApi = toReactableDebugApi(reactableWrapper.getContent());
            if (reactableDebugApi != null) {
                if (reactorHandlerRegistry.contains(reactableDebugApi)) {
                    logger.debug("Reactable already deployed. No need to do it again.");
                    return;
                }
                logger.info("Deploy api for debug...");

                logger.debug("Creating ReactorHandler");
                reactorHandlerRegistry.create(reactableDebugApi);

                try {
                    HttpRequest req = reactableDebugApi.getRequest();
                    HttpResponse response = new HttpResponse();

                    updateEvent(debugEvent, ApiDebugStatus.DEBUGGING);

                    logger.info("Sending request to debug...");
                    HttpClient httpClient = vertx.createHttpClient(buildClientOptions());

                    Future requestFuture = prepareRequest(reactableDebugApi, req, httpClient);

                    requestFuture
                        .flatMap(reqEvent -> req.getBody() == null ? reqEvent.send() : reqEvent.send(req.getBody()))
                        .flatMap(
                            result -> {
                                Map> headers = convertHeaders(result.headers());

                                response.setHeaders(headers);
                                response.statusCode(result.statusCode());

                                logger.debug("Response status: {}", result.statusCode());

                                return result.body();
                            }
                        )
                        .onSuccess(
                            bodyEvent -> {
                                try {
                                    response.setBody(bodyEvent.toString());
                                    logger.debug("Response body: {}", bodyEvent);
                                    reactableDebugApi.setResponse(response);
                                    debugEvent.setPayload(objectMapper.writeValueAsString(convert(reactableDebugApi)));
                                    updateEvent(debugEvent, ApiDebugStatus.SUCCESS);
                                    logger.info("Debugging successful, removing the handler.");
                                } catch (TechnicalException | JsonProcessingException e) {
                                    logger.error("Error when saving debug response...");
                                    failEvent(debugEvent);
                                } finally {
                                    reactorHandlerRegistry.remove(reactableDebugApi);
                                    logger.info("The debug handler has been removed");
                                }
                            }
                        )
                        .onFailure(
                            throwable -> {
                                logger.error("Debugging API has failed, removing the handler.", throwable);
                                reactorHandlerRegistry.remove(reactableDebugApi);
                                failEvent(debugEvent);
                            }
                        );
                } catch (TechnicalException e) {
                    logger.error(
                        "An error occurred when debugging api for event {}, removing the handler.",
                        reactableDebugApi.getEventId(),
                        e
                    );
                    reactorHandlerRegistry.remove(reactableDebugApi);
                    failEvent(debugEvent);
                }
            }
        }
    }

    private DebugApi toReactableDebugApi(io.gravitee.repository.management.model.Event event) {
        try {
            // Read API definition from event
            io.gravitee.definition.model.DebugApi eventPayload = objectMapper.readValue(
                event.getPayload(),
                io.gravitee.definition.model.DebugApi.class
            );

            DebugApi debugApi = new DebugApi(event.getId(), eventPayload);
            debugApi.setEnabled(true);
            debugApi.setDeployedAt(new Date());

            return debugApi;
        } catch (Exception e) {
            // Log the error and ignore this event.
            logger.error("Unable to extract api definition from event [{}].", event.getId());
            failEvent(event);
            return null;
        }
    }

    @Override
    protected void doStart() throws Exception {
        super.doStart();
        eventManager.subscribeForEvents(this, ReactorEvent.class);
    }

    @Override
    protected void doStop() throws Exception {
        super.doStop();
        reactorHandlerRegistry.clear();
    }

    private HttpClientOptions buildClientOptions() {
        HttpClientOptions options = new HttpClientOptions();
        options.setDefaultHost(debugHttpConfiguration.getHost());
        options.setDefaultPort(debugHttpConfiguration.getPort());
        options.setConnectTimeout(debugHttpConfiguration.getConnectTimeout());
        options.setTryUseCompression(debugHttpConfiguration.isCompressionSupported());
        options.setUseAlpn(debugHttpConfiguration.isAlpn());
        if (debugHttpConfiguration.isSecured()) {
            options.setSsl(debugHttpConfiguration.isSecured());
            options.setTrustAll(true);
            if (debugHttpConfiguration.isOpenssl()) {
                options.setSslEngineOptions(new OpenSSLEngineOptions());
            }
        }
        return options;
    }

    private Future prepareRequest(
        DebugApi debugApi,
        io.gravitee.definition.model.HttpRequest req,
        io.vertx.core.http.HttpClient httpClient
    ) {
        Future future = httpClient
            .request(
                new RequestOptions()
                    .setMethod(HttpMethod.valueOf(req.getMethod()))
                    .setHeaders(buildHeaders(debugApi, req))
                    // TODO: Need to manage entrypoints in future release: https://github.com/gravitee-io/issues/issues/6143
                    .setURI(debugApi.getProxy().getVirtualHosts().get(0).getPath() + req.getPath())
                    .setTimeout(debugHttpConfiguration.getRequestTimeout())
            )
            .map(
                httpClientRequest -> {
                    // Always set chunked mode for gRPC transport
                    return httpClientRequest.setChunked(true);
                }
            );
        return future;
    }

    private MultiMap buildHeaders(DebugApi debugApi, HttpRequest req) {
        final HeadersMultiMap headers = new HeadersMultiMap();
        // If API is configured in virtual hosts mode, we force the Host header
        if (debugApi.getProxy().getVirtualHosts().size() > 1) {
            String host = debugApi.getProxy().getVirtualHosts().get(0).getHost();
            if (host != null) {
                // TODO: Need to manage entrypoints in future release: https://github.com/gravitee-io/issues/issues/6143
                headers.add(io.gravitee.common.http.HttpHeaders.HOST, host);
            }
        }
        return headers.addAll(convertHeaders(req.getHeaders()));
    }

    private void failEvent(io.gravitee.repository.management.model.Event debugEvent) {
        try {
            if (debugEvent != null) {
                updateEvent(debugEvent, ApiDebugStatus.ERROR);
            }
        } catch (TechnicalException e) {
            logger.error("Error when updating event {} with ERROR status", debugEvent.getId());
        }
    }

    private void updateEvent(io.gravitee.repository.management.model.Event debugEvent, ApiDebugStatus apiDebugStatus)
        throws TechnicalException {
        debugEvent
            .getProperties()
            .put(io.gravitee.repository.management.model.Event.EventProperties.API_DEBUG_STATUS.getValue(), apiDebugStatus.name());
        eventRepository.update(debugEvent);
    }

    private io.gravitee.definition.model.DebugApi convert(DebugApi content) {
        io.gravitee.definition.model.DebugApi debugAPI = new io.gravitee.definition.model.DebugApi();
        debugAPI.setName(content.getName());
        debugAPI.setId(content.getId());
        debugAPI.setDefinitionVersion(content.getDefinitionVersion());
        debugAPI.setResponse(content.getResponse());
        debugAPI.setRequest(content.getRequest());
        debugAPI.setFlowMode(content.getFlowMode());
        debugAPI.setFlows(content.getFlows());
        debugAPI.setPathMappings(content.getPathMappings());
        debugAPI.setPlans(content.getPlans());
        debugAPI.setPaths(content.getPaths());
        debugAPI.setServices(content.getServices());
        debugAPI.setProxy(content.getProxy());
        debugAPI.setProperties(content.getProperties());
        debugAPI.setResources(content.getResources());
        debugAPI.setServices(content.getServices());
        debugAPI.setResponseTemplates(content.getResponseTemplates());
        return debugAPI;
    }

    Map> convertHeaders(MultiMap headersMultimap) {
        Map> headers = new HashMap<>();
        if (headersMultimap != null) {
            headersMultimap.forEach(e -> headers.put(e.getKey(), headersMultimap.getAll(e.getKey())));
        }
        return headers;
    }

    MultiMap convertHeaders(Map> headers) {
        final HeadersMultiMap headersMultiMap = new HeadersMultiMap();
        if (headers != null) {
            headers.forEach((key, value) -> headersMultiMap.add(key, String.join(", ", value)));
        }
        return headersMultiMap;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy