Add interceptor SPI for transport command handler (#2587)

* support intercept specified command handler
* add unit test and demo
This commit is contained in:
一个不知名的Java靓仔 2022-03-29 11:40:30 +08:00 committed by Eric Zhao
parent 2cb9747e56
commit 3cd8970e07
10 changed files with 362 additions and 2 deletions

View File

@ -0,0 +1,50 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* 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
*
* https://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.alibaba.csp.sentinel.demo.commandhandler.interceptor;
import com.alibaba.csp.sentinel.command.CommandHandlerInterceptor;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandRequestExecution;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.spi.Spi;
/**
* @author icodening
* @date 2022.03.23
*/
@Spi(order = Spi.ORDER_HIGHEST)
public class AllCommandHandlerInterceptor implements CommandHandlerInterceptor {
@Override
public boolean shouldIntercept(String commandName) {
return true;
}
@Override
public CommandResponse intercept(CommandRequest request, CommandRequestExecution execution) {
System.out.println("[AllCommandHandlerInterceptor] start");
long begin = System.currentTimeMillis();
try {
return execution.execute(request);
} catch (Throwable throwable) {
System.out.println("[AllCommandHandlerInterceptor] catch exception: " + throwable.getMessage());
throw throwable;
} finally {
long cost = System.currentTimeMillis() - begin;
System.out.println("[AllCommandHandlerInterceptor] complete, cost " + cost + "ms");
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* 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
*
* https://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.alibaba.csp.sentinel.demo.commandhandler.interceptor;
import com.alibaba.csp.sentinel.command.CommandHandlerInterceptor;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandRequestExecution;
import com.alibaba.csp.sentinel.command.CommandResponse;
/**
* @author icodening
* @date 2022.03.23
*/
public class EchoCommandHandlerInterceptor implements CommandHandlerInterceptor<String> {
@Override
public boolean shouldIntercept(String commandName) {
return "echo".equals(commandName);
}
@Override
public CommandResponse<String> intercept(CommandRequest request, CommandRequestExecution<String> execution) {
System.out.println("[EchoCommandHandlerInterceptor] intercept 'echo' command,all params is " + request.getParameters() + "");
CommandResponse<String> response = execution.execute(request);
return CommandResponse.ofSuccess("intercept result : [" + response.getResult() + "]");
}
}

View File

@ -0,0 +1,2 @@
com.alibaba.csp.sentinel.demo.commandhandler.interceptor.EchoCommandHandlerInterceptor
com.alibaba.csp.sentinel.demo.commandhandler.interceptor.AllCommandHandlerInterceptor

View File

@ -0,0 +1,45 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* 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
*
* https://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.alibaba.csp.sentinel.command;
/**
* Intercepts specified command, and can be extended using SPI.
*
* @author icodening
* @since 1.8.4
* @see com.alibaba.csp.sentinel.spi.SpiLoader
* @see com.alibaba.csp.sentinel.spi.Spi
*/
public interface CommandHandlerInterceptor<R> {
/**
* whether to intercept the specified command
*
* @param commandName command name, eg. getRules
* @return "true" means intercept, "false" means skip
*/
boolean shouldIntercept(String commandName);
/**
* intercept the given command request, and return a command response
*
* @param request commandRequest
* @param execution interceptor chain execution
* @return command response
*/
CommandResponse<R> intercept(CommandRequest request, CommandRequestExecution<R> execution);
}

View File

@ -18,6 +18,7 @@ package com.alibaba.csp.sentinel.command;
import java.util.*;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
import com.alibaba.csp.sentinel.command.handler.InterceptingCommandHandler;
import com.alibaba.csp.sentinel.spi.SpiLoader;
import com.alibaba.csp.sentinel.util.StringUtil;
@ -38,11 +39,24 @@ public class CommandHandlerProvider implements Iterable<CommandHandler> {
public Map<String, CommandHandler> namedHandlers() {
Map<String, CommandHandler> map = new HashMap<String, CommandHandler>();
List<CommandHandler> handlers = spiLoader.loadInstanceList();
List<CommandHandlerInterceptor> commandHandlerInterceptors = SpiLoader.of(CommandHandlerInterceptor.class).loadInstanceListSorted();
for (CommandHandler handler : handlers) {
String name = parseCommandName(handler);
if (!StringUtil.isEmpty(name)) {
map.put(name, handler);
if (StringUtil.isEmpty(name)) {
continue;
}
if (!commandHandlerInterceptors.isEmpty()) {
List<CommandHandlerInterceptor> interceptors = new ArrayList<>();
for (CommandHandlerInterceptor commandHandlerInterceptor : commandHandlerInterceptors) {
if (commandHandlerInterceptor.shouldIntercept(name)) {
interceptors.add(commandHandlerInterceptor);
}
}
if (!interceptors.isEmpty()) {
handler = new InterceptingCommandHandler(handler, interceptors);
}
}
map.put(name, handler);
}
return map;
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* 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
*
* https://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.alibaba.csp.sentinel.command;
/**
* @author icodening
* @since 1.8.4
*/
public interface CommandRequestExecution<R> {
/**
* execute the command request and return the command response.
*
* @param request command request
* @return command response
*/
CommandResponse<R> execute(CommandRequest request);
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* 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
*
* https://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.alibaba.csp.sentinel.command.handler;
import com.alibaba.csp.sentinel.command.*;
import com.alibaba.csp.sentinel.util.AssertUtil;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
/**
* intercept specified command handler
*
* @author icodening
* @date 2022.03.03
*/
public class InterceptingCommandHandler<R> implements CommandHandler<R> {
private final CommandHandler<R> delegate;
private final List<CommandHandlerInterceptor<R>> commandHandlerInterceptors;
public InterceptingCommandHandler(CommandHandler<R> delegate, List<CommandHandlerInterceptor<R>> commandHandlerInterceptors) {
AssertUtil.notNull(delegate, "delegate cannot be null");
AssertUtil.notNull(commandHandlerInterceptors, "commandHandlerInterceptors cannot be null");
this.delegate = delegate;
this.commandHandlerInterceptors = commandHandlerInterceptors;
}
@Override
public CommandResponse<R> handle(CommandRequest request) {
return new InterceptingRequestExecution<>(commandHandlerInterceptors.iterator(), delegate::handle).execute(request);
}
private static class InterceptingRequestExecution<R> implements CommandRequestExecution<R> {
private final Iterator<CommandHandlerInterceptor<R>> iterator;
private final Function<CommandRequest, CommandResponse<R>> commandResponseFunction;
public InterceptingRequestExecution(Iterator<CommandHandlerInterceptor<R>> iterator,
Function<CommandRequest, CommandResponse<R>> commandResponseFunction) {
this.iterator = iterator;
this.commandResponseFunction = commandResponseFunction;
}
@Override
public CommandResponse<R> execute(CommandRequest request) {
if (this.iterator.hasNext()) {
CommandHandlerInterceptor<R> nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, this);
}
return commandResponseFunction.apply(request);
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* 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
*
* https://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.alibaba.csp.sentinel.transport.command;
import com.alibaba.csp.sentinel.command.CommandHandlerInterceptor;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandRequestExecution;
import com.alibaba.csp.sentinel.command.CommandResponse;
/**
* @author icodening
* @date 2022.03.23
*/
public class GetRulesCommandHandlerInterceptor implements CommandHandlerInterceptor {
@Override
public boolean shouldIntercept(String commandName) {
return "getRules".equals(commandName);
}
@Override
public CommandResponse intercept(CommandRequest request, CommandRequestExecution execution) {
String type = request.getParam("type");
System.out.println("[GetRulesCommandHandlerInterceptor] get rules for [" + type + "]");
return execution.execute(request);
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 1999-2022 Alibaba Group Holding Ltd.
*
* 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
*
* https://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.alibaba.csp.sentinel.transport.command;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandHandlerProvider;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.handler.InterceptingCommandHandler;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.transport.util.HttpCommandUtils;
import com.alibaba.csp.sentinel.util.AssertUtil;
import org.junit.Before;
import org.junit.Test;
import java.util.Collections;
import java.util.Map;
/**
* @author icodening
* @date 2022.03.23
*/
@SuppressWarnings("all")
public class InterceptingCommandHandlerTest {
private Map<String, CommandHandler> commandHandlerMap = CommandHandlerProvider.getInstance().namedHandlers();
@Before
public void setUp() throws Exception {
FlowRule flowRule = new FlowRule();
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setClusterMode(false)
.setCount(1)
.setResource("/test");
FlowRuleManager.loadRules(Collections.singletonList(flowRule));
}
@Test
public void testInterceptCommand() {
CommandHandler getRulesHandler = commandHandlerMap.get("getRules");
AssertUtil.assertState(getRulesHandler instanceof InterceptingCommandHandler, "getRulesHandler should be an InterceptingCommandHandler");
CommandHandler basicInfoHandler = commandHandlerMap.get("basicInfo");
AssertUtil.assertState(!(basicInfoHandler instanceof InterceptingCommandHandler), "basicInfoHandler should not be an InterceptingCommandHandler");
CommandRequest getRulesCommandRequest = new CommandRequest();
getRulesCommandRequest.addMetadata(HttpCommandUtils.REQUEST_TARGET, "getRules");
getRulesCommandRequest.addParam("type", "flow");
CommandResponse getRulesResponse = getRulesHandler.handle(getRulesCommandRequest);
System.out.println(getRulesResponse.getResult());
}
}

View File

@ -0,0 +1 @@
com.alibaba.csp.sentinel.transport.command.GetRulesCommandHandlerInterceptor