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

io.gravitee.policy.retry.RetryPolicy Maven / Gradle / Ivy

There is a newer version: 2.1.3
Show newest version
/**
 * 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.policy.retry;

import io.gravitee.common.http.HttpHeadersValues;
import io.gravitee.common.http.HttpStatusCode;
import io.gravitee.gateway.api.ExecutionContext;
import io.gravitee.gateway.api.Invoker;
import io.gravitee.gateway.api.buffer.Buffer;
import io.gravitee.gateway.api.context.MutableExecutionContext;
import io.gravitee.gateway.api.el.EvaluableResponse;
import io.gravitee.gateway.api.handler.Handler;
import io.gravitee.gateway.api.http.HttpHeaderNames;
import io.gravitee.gateway.api.http.HttpHeaders;
import io.gravitee.gateway.api.proxy.ProxyConnection;
import io.gravitee.gateway.api.proxy.ProxyResponse;
import io.gravitee.gateway.api.stream.ReadStream;
import io.gravitee.gateway.api.stream.WriteStream;
import io.gravitee.policy.api.PolicyChain;
import io.gravitee.policy.api.annotations.OnRequest;
import io.gravitee.policy.retry.configuration.RetryPolicyConfiguration;
import io.gravitee.policy.retry.el.ProxyResponseWrapper;
import io.vertx.circuitbreaker.CircuitBreaker;
import io.vertx.circuitbreaker.CircuitBreakerOptions;
import io.vertx.core.AsyncResult;
import io.vertx.core.Vertx;
import java.util.concurrent.atomic.AtomicInteger;

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

    private final RetryPolicyConfiguration configuration;

    private static final String CIRCUIT_BREAKER_NAME = "retry-policy";
    private static final String TEMPLATE_RESPONSE_VARIABLE = "response";

    public RetryPolicy(RetryPolicyConfiguration configuration) {
        this.configuration = configuration;
    }

    @OnRequest
    public void onRequest(ExecutionContext context, PolicyChain chain) {
        final Invoker defaultInvoker = (Invoker) context.getAttribute(ExecutionContext.ATTR_INVOKER);
        context.setAttribute(ExecutionContext.ATTR_INVOKER, new RetryInvoker(defaultInvoker, configuration));

        ((MutableExecutionContext) context).request(new RetryRequest(context.request()));
        chain.doNext(context.request(), context.response());
    }

    public static class RetryInvoker implements Invoker {

        private final Invoker invoker;
        private final RetryPolicyConfiguration configuration;

        RetryInvoker(final Invoker invoker, final RetryPolicyConfiguration configuration) {
            this.invoker = invoker;
            this.configuration = configuration;
        }

        @Override
        public void invoke(ExecutionContext context, ReadStream readStream, Handler handler) {
            Vertx vertx = context.getComponent(Vertx.class);

            CircuitBreaker circuitBreaker = CircuitBreaker.create(
                CIRCUIT_BREAKER_NAME,
                vertx,
                new CircuitBreakerOptions()
                    .setMaxRetries(configuration.getMaxRetries()) // number of failure before opening the circuit
                    .setTimeout(configuration.getTimeout()) // consider a failure if the operation does not succeed in time
                    .setNotificationAddress(null)
                    .setNotificationPeriod(0)
            );

            if (configuration.getDelay() > 0) {
                circuitBreaker.retryPolicy(integer -> configuration.getDelay());
            }

            final AtomicInteger counter = new AtomicInteger();

            circuitBreaker.execute(
                event -> {
                    counter.incrementAndGet();

                    // Listen for the response from backend
                    invoker.invoke(
                        context,
                        readStream,
                        connection -> {
                            connection
                                .exceptionHandler(event::fail)
                                .responseHandler(proxyResponse -> {
                                    context
                                        .getTemplateEngine()
                                        .getTemplateContext()
                                        .setVariable(
                                            TEMPLATE_RESPONSE_VARIABLE,
                                            // Note: we must create a EvaluableResponse and a ProxyResponseWrapper to make sure classloader will be well released when the api is undeployed.
                                            new EvaluableResponse(new ProxyResponseWrapper(proxyResponse))
                                        );
                                    boolean retry = context.getTemplateEngine().getValue(configuration.getCondition(), boolean.class);
                                    if (retry) {
                                        if (configuration.isLastResponse() && counter.get() == configuration.getMaxRetries()) {
                                            event.complete(new RetryProxyConnection(connection, proxyResponse));
                                        } else {
                                            // Cleanup by cancelling the proxyResponse (request tracker, ...).
                                            proxyResponse.cancel();
                                            event.fail("");
                                        }
                                    } else {
                                        event.complete(new RetryProxyConnection(connection, proxyResponse));
                                    }
                                });
                        }
                    );
                },
                (io.vertx.core.Handler>) event -> {
                    circuitBreaker.close();

                    if (event.succeeded()) {
                        RetryProxyConnection connection = event.result();
                        handler.handle(connection);
                        connection.sendResponse();
                    } else {
                        DirectProxyConnection connection = new DirectProxyConnection(HttpStatusCode.BAD_GATEWAY_502);
                        handler.handle(connection);
                        connection.sendResponse();
                    }
                }
            );
        }
    }

    public static class RetryProxyConnection implements ProxyConnection {

        private final ProxyConnection wrapped;
        private final ProxyResponse response;
        private Handler responseHandler;

        RetryProxyConnection(ProxyConnection connection, ProxyResponse response) {
            this.wrapped = connection;
            this.response = response;
        }

        @Override
        public ProxyConnection cancel() {
            return wrapped.cancel();
        }

        @Override
        public ProxyConnection exceptionHandler(Handler exceptionHandler) {
            return wrapped.exceptionHandler(exceptionHandler);
        }

        @Override
        public ProxyConnection responseHandler(Handler responseHandler) {
            this.responseHandler = responseHandler;
            return this;
        }

        @Override
        public WriteStream write(Buffer buffer) {
            return wrapped.write(buffer);
        }

        @Override
        public void end() {
            wrapped.end();
        }

        @Override
        public void end(Buffer buffer) {
            wrapped.end(buffer);
        }

        @Override
        public WriteStream drainHandler(Handler drainHandler) {
            return wrapped.drainHandler(drainHandler);
        }

        @Override
        public boolean writeQueueFull() {
            return wrapped.writeQueueFull();
        }

        void sendResponse() {
            this.responseHandler.handle(response);
        }
    }

    public static class DirectProxyConnection implements ProxyConnection {

        private Handler responseHandler;

        private final ProxyResponse response;

        DirectProxyConnection(int statusCode) {
            this.response = new EmptyProxyResponse(statusCode);
        }

        @Override
        public WriteStream write(Buffer content) {
            throw new IllegalStateException();
        }

        @Override
        public void end() {
            // Nothing to do here...
        }

        @Override
        public ProxyConnection responseHandler(Handler responseHandler) {
            this.responseHandler = responseHandler;
            return this;
        }

        void sendResponse() {
            this.responseHandler.handle(response);
        }
    }

    public static class EmptyProxyResponse implements ProxyResponse {

        private Handler bodyHandler;
        private Handler endHandler;

        private final HttpHeaders httpHeaders = HttpHeaders.create();

        private final int statusCode;

        EmptyProxyResponse(int statusCode) {
            this.statusCode = statusCode;

            httpHeaders.set(HttpHeaderNames.CONNECTION, HttpHeadersValues.CONNECTION_CLOSE);
        }

        @Override
        public int status() {
            return statusCode;
        }

        @Override
        public HttpHeaders headers() {
            return httpHeaders;
        }

        @Override
        public ProxyResponse bodyHandler(Handler bodyHandler) {
            this.bodyHandler = bodyHandler;
            return this;
        }

        @Override
        public ProxyResponse endHandler(Handler endHandler) {
            this.endHandler = endHandler;
            return this;
        }

        @Override
        public ReadStream resume() {
            endHandler.handle(null);
            return this;
        }

        @Override
        public boolean connected() {
            return false;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy