blob: 8ae3df80a2922e480dc74e118c573e3716ee96b6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.servicecomb.handler.governance;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Supplier;
import org.apache.servicecomb.core.Handler;
import org.apache.servicecomb.core.Invocation;
import org.apache.servicecomb.core.governance.MatchType;
import org.apache.servicecomb.foundation.common.utils.BeanUtils;
import org.apache.servicecomb.governance.handler.BulkheadHandler;
import org.apache.servicecomb.governance.handler.CircuitBreakerHandler;
import org.apache.servicecomb.governance.handler.RateLimitingHandler;
import org.apache.servicecomb.governance.marker.GovernanceRequest;
import org.apache.servicecomb.swagger.invocation.AsyncResponse;
import org.apache.servicecomb.swagger.invocation.Response;
import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.resilience4j.bulkhead.Bulkhead;
import io.github.resilience4j.bulkhead.BulkheadFullException;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.decorators.Decorators;
import io.github.resilience4j.decorators.Decorators.DecorateCompletionStage;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
public class ProviderGovernanceHandler implements Handler {
private static final Logger LOGGER = LoggerFactory.getLogger(ProviderGovernanceHandler.class);
private final RateLimitingHandler rateLimitingHandler = BeanUtils.getBean(RateLimitingHandler.class);
private final CircuitBreakerHandler circuitBreakerHandler = BeanUtils.getBean(CircuitBreakerHandler.class);
private final BulkheadHandler bulkheadHandler = BeanUtils.getBean(BulkheadHandler.class);
@Override
public void handle(Invocation invocation, AsyncResponse asyncResp) throws Exception {
Supplier<CompletionStage<Response>> next = createBusinessCompletionStageSupplier(invocation);
DecorateCompletionStage<Response> dcs = Decorators.ofCompletionStage(next);
GovernanceRequest request = MatchType.createGovHttpRequest(invocation);
addRateLimiting(dcs, request);
addCircuitBreaker(dcs, request);
addBulkhead(dcs, request);
dcs.get().whenComplete((r, e) -> {
if (e == null) {
asyncResp.complete(r);
return;
}
if (e instanceof RequestNotPermitted) {
asyncResp.complete(
Response.failResp(new InvocationException(429, "rate limited.", new CommonExceptionData("rate limited."))));
LOGGER.warn("the request is rate limit by policy : {}", e.getMessage());
} else if (e instanceof CallNotPermittedException) {
asyncResp.complete(
Response.failResp(new InvocationException(429, "circuitBreaker is open.",
new CommonExceptionData("circuitBreaker is open."))));
LOGGER.warn("circuitBreaker is open by policy : {}", e.getMessage());
} else if (e instanceof BulkheadFullException) {
asyncResp.complete(
Response.failResp(new InvocationException(429, "bulkhead is full and does not permit further calls.",
new CommonExceptionData("bulkhead is full and does not permit further calls."))));
LOGGER.warn("bulkhead is full and does not permit further calls by policy : {}", e.getMessage());
} else {
asyncResp.complete(Response.createProducerFail(e));
}
});
}
private void addBulkhead(DecorateCompletionStage<Response> dcs, GovernanceRequest request) {
Bulkhead bulkhead = bulkheadHandler.getActuator(request);
if (bulkhead != null) {
dcs.withBulkhead(bulkhead);
}
}
private void addCircuitBreaker(DecorateCompletionStage<Response> dcs, GovernanceRequest request) {
CircuitBreaker circuitBreaker = circuitBreakerHandler.getActuator(request);
if (circuitBreaker != null) {
dcs.withCircuitBreaker(circuitBreaker);
}
}
private void addRateLimiting(DecorateCompletionStage<Response> dcs, GovernanceRequest request) {
RateLimiter rateLimiter = rateLimitingHandler.getActuator(request);
if (rateLimiter != null) {
dcs.withRateLimiter(rateLimiter);
}
}
private Supplier<CompletionStage<Response>> createBusinessCompletionStageSupplier(Invocation invocation) {
return () -> {
CompletableFuture<Response> result = new CompletableFuture<>();
try {
invocation.next(response -> {
if (response.isFailed()) {
// For failed response, create a fail to make circuit breaker work.
// Users application maybe much complicated than this simple logic,
// while they need to customize which error will cause circuit breaker.
result.completeExceptionally(response.getResult());
} else {
result.complete(response);
}
});
} catch (Exception e) {
result.completeExceptionally(e);
}
return result;
};
}
}