Fix multiple slash command name issue in sentinel-transport-netty-http module (#817)

This commit is contained in:
cdfive 2019-06-12 09:36:25 +08:00 committed by Eric Zhao
parent e3b9f99f9d
commit e810ab849e
8 changed files with 143 additions and 67 deletions

View File

@ -125,6 +125,11 @@
<artifactId>sentinel-transport-simple-http</artifactId> <artifactId>sentinel-transport-simple-http</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-netty-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.alibaba.csp</groupId> <groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-common</artifactId> <artifactId>sentinel-transport-common</artifactId>

View File

@ -221,11 +221,15 @@ public class HttpServerHandler extends SimpleChannelInboundHandler<Object> {
if (StringUtil.isEmpty(uri)) { if (StringUtil.isEmpty(uri)) {
return ""; return "";
} }
String[] arr = uri.split("/");
if (arr.length < 2) { // Remove the / of the uri as the target(command name)
return ""; // Usually the uri is start with /
int start = uri.indexOf('/');
if (start != -1) {
return uri.substring(start + 1);
} }
return arr[1];
return uri;
} }
private CommandHandler getHandler(String commandName) { private CommandHandler getHandler(String commandName) {

View File

@ -0,0 +1,32 @@
/*
* Copyright 1999-2018 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
*
* 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 com.alibaba.csp.sentinel.transport.command.handler;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
/**
* @author cdfive
*/
@CommandMapping(name = "aa/bb/cc", desc = "a test handler with multiple / in its name")
public class MultipleSlashNameCommandTestHandler implements CommandHandler<String> {
@Override
public CommandResponse<String> handle(CommandRequest request) {
return CommandResponse.ofSuccess("MultipleSlashNameCommandTestHandler result");
}
}

View File

@ -23,6 +23,8 @@ import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.transport.CommandCenter; import com.alibaba.csp.sentinel.transport.CommandCenter;
import com.alibaba.csp.sentinel.transport.command.NettyHttpCommandCenter; import com.alibaba.csp.sentinel.transport.command.NettyHttpCommandCenter;
import com.alibaba.csp.sentinel.transport.command.handler.MultipleSlashNameCommandTestHandler;
import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.embedded.EmbeddedChannel;
@ -34,6 +36,7 @@ import org.junit.Test;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -45,7 +48,6 @@ import static org.junit.Assert.assertEquals;
* Test cases for {@link HttpServerHandler}. * Test cases for {@link HttpServerHandler}.
* *
* @author cdfive * @author cdfive
* @date 2018-12-17
*/ */
public class HttpServerHandlerTest { public class HttpServerHandlerTest {
@ -59,7 +61,7 @@ public class HttpServerHandlerTest {
@BeforeClass @BeforeClass
public static void beforeClass() throws Exception { public static void beforeClass() throws Exception {
// don't execute InitExecutor.doInit, to avoid CommandCenter SPI loaded // Don't execute InitExecutor.doInit, to avoid CommandCenter SPI loaded
Field[] declaredFields = InitExecutor.class.getDeclaredFields(); Field[] declaredFields = InitExecutor.class.getDeclaredFields();
for (Field declaredField : declaredFields) { for (Field declaredField : declaredFields) {
if (declaredField.getName().equals("initialized")) { if (declaredField.getName().equals("initialized")) {
@ -68,23 +70,26 @@ public class HttpServerHandlerTest {
} }
} }
// create NettyHttpCommandCenter to create HttpServer // Create NettyHttpCommandCenter to create HttpServer
CommandCenter commandCenter = new NettyHttpCommandCenter(); CommandCenter commandCenter = new NettyHttpCommandCenter();
// call beforeStart to register handlers // Call beforeStart to register handlers
commandCenter.beforeStart(); commandCenter.beforeStart();
} }
@Before @Before
public void before() { public void before() {
// the same Handlers in order as the ChannelPipeline in HttpServerInitializer // The same Handlers in order as the ChannelPipeline in HttpServerInitializer
HttpRequestDecoder httpRequestDecoder = new HttpRequestDecoder(); HttpRequestDecoder httpRequestDecoder = new HttpRequestDecoder();
HttpObjectAggregator httpObjectAggregator = new HttpObjectAggregator(1024 * 1024); HttpObjectAggregator httpObjectAggregator = new HttpObjectAggregator(1024 * 1024);
HttpResponseEncoder httpResponseEncoder = new HttpResponseEncoder(); HttpResponseEncoder httpResponseEncoder = new HttpResponseEncoder();
HttpServerHandler httpServerHandler = new HttpServerHandler(); HttpServerHandler httpServerHandler = new HttpServerHandler();
// create new EmbeddedChannel every method call // Create new EmbeddedChannel every method call
embeddedChannel = new EmbeddedChannel(httpRequestDecoder, httpObjectAggregator, httpResponseEncoder, httpServerHandler); embeddedChannel = new EmbeddedChannel(httpRequestDecoder, httpObjectAggregator, httpResponseEncoder, httpServerHandler);
// Clear flow rules
FlowRuleManager.loadRules(Collections.EMPTY_LIST);
} }
@Test @Test
@ -92,9 +97,9 @@ public class HttpServerHandlerTest {
String httpRequestStr = "GET / HTTP/1.1" + CRLF String httpRequestStr = "GET / HTTP/1.1" + CRLF
+ "Host: localhost:8719" + CRLF + "Host: localhost:8719" + CRLF
+ CRLF; + CRLF;
String body = "Invalid command"; String expectedBody = "Invalid command";
processError(httpRequestStr, body); processError(httpRequestStr, expectedBody);
} }
@Test @Test
@ -102,44 +107,49 @@ public class HttpServerHandlerTest {
String httpRequestStr = "GET /aaa HTTP/1.1" + CRLF String httpRequestStr = "GET /aaa HTTP/1.1" + CRLF
+ "Host: localhost:8719" + CRLF + "Host: localhost:8719" + CRLF
+ CRLF; + CRLF;
String body = String.format("Unknown command \"%s\"", "aaa"); String expectedBody = String.format("Unknown command \"%s\"", "aaa");
processError(httpRequestStr, body); processError(httpRequestStr, expectedBody);
} }
/**
* {@link com.alibaba.csp.sentinel.command.handler.VersionCommandHandler}
*/
@Test @Test
public void testVersionCommand() { public void testVersionCommand() {
String httpRequestStr = "GET /version HTTP/1.1" + CRLF String httpRequestStr = "GET /version HTTP/1.1" + CRLF
+ "Host: localhost:8719" + CRLF + "Host: localhost:8719" + CRLF
+ CRLF; + CRLF;
String body = Constants.SENTINEL_VERSION; String expectedBody = Constants.SENTINEL_VERSION;
processSuccess(httpRequestStr, body); processSuccess(httpRequestStr, expectedBody);
} }
/**
* {@link com.alibaba.csp.sentinel.command.handler.FetchActiveRuleCommandHandler}
*/
@Test @Test
public void testGetRuleCommandInvalidType() { public void testFetchActiveRuleCommandInvalidType() {
String httpRequestStr = "GET /getRules HTTP/1.1" + CRLF String httpRequestStr = "GET /getRules HTTP/1.1" + CRLF
+ "Host: localhost:8719" + CRLF + "Host: localhost:8719" + CRLF
+ CRLF; + CRLF;
String body = "invalid type"; String expectedBody = "invalid type";
processFailed(httpRequestStr, body); processFailed(httpRequestStr, expectedBody);
} }
@Test @Test
public void testGetRuleCommandFlowEmptyRule() { public void testFetchActiveRuleCommandEmptyRule() {
String httpRequestStr = "GET /getRules?type=flow HTTP/1.1" + CRLF String httpRequestStr = "GET /getRules?type=flow HTTP/1.1" + CRLF
+ "Host: localhost:8719" + CRLF + "Host: localhost:8719" + CRLF
+ CRLF; + CRLF;
String body = "[]"; String expectedBody = "[]";
processSuccess(httpRequestStr, body); processSuccess(httpRequestStr, expectedBody);
} }
// FIXME byteBuf.toString can't get body response now, need to find another way @Test
// @Test public void testFetchActiveRuleCommandSomeFlowRules() {
public void testGetRuleCommandFlowSomeRules() {
List<FlowRule> rules = new ArrayList<FlowRule>(); List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule(); FlowRule rule1 = new FlowRule();
rule1.setResource("key"); rule1.setResource("key");
@ -152,60 +162,86 @@ public class HttpServerHandlerTest {
String httpRequestStr = "GET /getRules?type=flow HTTP/1.1" + CRLF String httpRequestStr = "GET /getRules?type=flow HTTP/1.1" + CRLF
+ "Host: localhost:8719" + CRLF + "Host: localhost:8719" + CRLF
+ CRLF; + CRLF;
String body = "";
processSuccess(httpRequestStr, body); // body json
/*
String expectedBody = "[{\"clusterMode\":false,\"controlBehavior\":0,\"count\":20.0"
+ ",\"grade\":1,\"limitApp\":\"default\",\"maxQueueingTimeMs\":500"
+ ",\"resource\":\"key\",\"strategy\":0,\"warmUpPeriodSec\":10}]";
*/
String expectedBody = JSON.toJSONString(rules);
processSuccess(httpRequestStr, expectedBody);
} }
private void processError(String httpRequestStr, String body) { /**
processError(httpRequestStr, BAD_REQUEST, body); * {@link MultipleSlashNameCommandTestHandler}
*
* Test command whose mapping path and command name contain multiple /
*/
@Test
public void testMultipleSlashNameCommand() {
String httpRequestStr = "GET /aa/bb/cc HTTP/1.1" + CRLF
+ "Host: localhost:8719" + CRLF
+ CRLF;
String expectedBody = "MultipleSlashNameCommandTestHandler result";
processSuccess(httpRequestStr, expectedBody);
} }
private void processError(String httpRequestStr, HttpResponseStatus status, String body) { private void processError(String httpRequestStr, String expectedBody) {
processError(httpRequestStr, BAD_REQUEST, expectedBody);
}
private void processError(String httpRequestStr, HttpResponseStatus status, String expectedBody) {
String httpResponseStr = processResponse(httpRequestStr); String httpResponseStr = processResponse(httpRequestStr);
assertErrorStatusAndBody(status, body, httpResponseStr); assertErrorStatusAndBody(status, expectedBody, httpResponseStr);
} }
private void processSuccess(String httpRequestStr, String body) { private void processSuccess(String httpRequestStr, String expectedBody) {
process(httpRequestStr, OK, body); process(httpRequestStr, OK, expectedBody);
} }
private void processFailed(String httpRequestStr, String body) { private void processFailed(String httpRequestStr, String expectedBody) {
process(httpRequestStr, BAD_REQUEST, body); process(httpRequestStr, BAD_REQUEST, expectedBody);
} }
private void process(String httpRequestStr, HttpResponseStatus status, String body) { private void process(String httpRequestStr, HttpResponseStatus status, String expectedBody) {
String responseStr = processResponse(httpRequestStr); String responseStr = processResponse(httpRequestStr);
assertStatusAndBody(status, body, responseStr); assertStatusAndBody(status, expectedBody, responseStr);
} }
private String processResponse(String httpRequestStr) { private String processResponse(String httpRequestStr) {
embeddedChannel.writeInbound(Unpooled.wrappedBuffer(httpRequestStr.getBytes(SENTINEL_CHARSET))); embeddedChannel.writeInbound(Unpooled.wrappedBuffer(httpRequestStr.getBytes(SENTINEL_CHARSET)));
ByteBuf byteBuf = embeddedChannel.readOutbound(); StringBuilder sb = new StringBuilder();
String responseStr = byteBuf.toString(SENTINEL_CHARSET); ByteBuf byteBuf;
return responseStr; while ((byteBuf = embeddedChannel.readOutbound()) != null) {
sb.append(byteBuf.toString(SENTINEL_CHARSET));
}
return sb.toString();
} }
private void assertErrorStatusAndBody(HttpResponseStatus status, String body, String httpResponseStr) { private void assertErrorStatusAndBody(HttpResponseStatus status, String expectedBody, String httpResponseStr) {
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
text.append(HttpVersion.HTTP_1_1.toString()).append(' ').append(status.toString()).append(CRLF); text.append(HttpVersion.HTTP_1_1.toString()).append(' ').append(status.toString()).append(CRLF);
text.append("Content-Type: text/plain; charset=").append(SENTINEL_CHARSET_NAME).append(CRLF); text.append("Content-Type: text/plain; charset=").append(SENTINEL_CHARSET_NAME).append(CRLF);
text.append(CRLF); text.append(CRLF);
text.append(body); text.append(expectedBody);
assertEquals(text.toString(), httpResponseStr); assertEquals(text.toString(), httpResponseStr);
} }
private void assertStatusAndBody(HttpResponseStatus status, String body, String httpResponseStr) { private void assertStatusAndBody(HttpResponseStatus status, String expectedBody, String httpResponseStr) {
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
text.append(HttpVersion.HTTP_1_1.toString()).append(' ').append(status.toString()).append(CRLF); text.append(HttpVersion.HTTP_1_1.toString()).append(' ').append(status.toString()).append(CRLF);
text.append("Content-Type: text/plain; charset=").append(SENTINEL_CHARSET_NAME).append(CRLF); text.append("Content-Type: text/plain; charset=").append(SENTINEL_CHARSET_NAME).append(CRLF);
text.append("content-length: " + body.length()).append(CRLF); text.append("content-length: " + expectedBody.length()).append(CRLF);
text.append("connection: close").append(CRLF); text.append("connection: close").append(CRLF);
text.append(CRLF); text.append(CRLF);
text.append(body); text.append(expectedBody);
assertEquals(text.toString(), httpResponseStr); assertEquals(text.toString(), httpResponseStr);
} }

View File

@ -30,30 +30,29 @@ import static org.mockito.Mockito.*;
* Test cases for {@link HttpServerInitializer}. * Test cases for {@link HttpServerInitializer}.
* *
* @author cdfive * @author cdfive
* @date 2018-12-19
*/ */
public class HttpServerInitializerTest { public class HttpServerInitializerTest {
@Test @Test
public void testInitChannel() throws Exception { public void testInitChannel() throws Exception {
// mock Objects // Mock Objects
HttpServerInitializer httpServerInitializer = mock(HttpServerInitializer.class); HttpServerInitializer httpServerInitializer = mock(HttpServerInitializer.class);
SocketChannel socketChannel = mock(SocketChannel.class); SocketChannel socketChannel = mock(SocketChannel.class);
ChannelPipeline channelPipeline = mock(ChannelPipeline.class); ChannelPipeline channelPipeline = mock(ChannelPipeline.class);
// mock SocketChannel#pipeline() method // Mock SocketChannel#pipeline() method
when(socketChannel.pipeline()).thenReturn(channelPipeline); when(socketChannel.pipeline()).thenReturn(channelPipeline);
// HttpServerInitializer#initChannel(SocketChannel) call real method // HttpServerInitializer#initChannel(SocketChannel) call real method
doCallRealMethod().when(httpServerInitializer).initChannel(socketChannel); doCallRealMethod().when(httpServerInitializer).initChannel(socketChannel);
// start test for HttpServerInitializer#initChannel(SocketChannel) // Start test for HttpServerInitializer#initChannel(SocketChannel)
httpServerInitializer.initChannel(socketChannel); httpServerInitializer.initChannel(socketChannel);
// verify 4 times calling ChannelPipeline#addLast() method // Verify 4 times calling ChannelPipeline#addLast() method
verify(channelPipeline, times(4)).addLast(any(ChannelHandler.class)); verify(channelPipeline, times(4)).addLast(any(ChannelHandler.class));
// verify the order of calling ChannelPipeline#addLast() method // Verify the order of calling ChannelPipeline#addLast() method
InOrder inOrder = inOrder(channelPipeline); InOrder inOrder = inOrder(channelPipeline);
inOrder.verify(channelPipeline).addLast(any(HttpRequestDecoder.class)); inOrder.verify(channelPipeline).addLast(any(HttpRequestDecoder.class));
inOrder.verify(channelPipeline).addLast(any(HttpObjectAggregator.class)); inOrder.verify(channelPipeline).addLast(any(HttpObjectAggregator.class));

View File

@ -31,7 +31,6 @@ import static org.junit.Assert.*;
* Test cases for {@link HttpServer}. * Test cases for {@link HttpServer}.
* *
* @author cdfive * @author cdfive
* @date 2018-12-19
*/ */
public class HttpServerTest { public class HttpServerTest {
@ -39,20 +38,20 @@ public class HttpServerTest {
@BeforeClass @BeforeClass
public static void beforeClass() { public static void beforeClass() {
// note: clear handlerMap first, as other test case may init HttpServer.handlerMap // Note: clear handlerMap first, as other test case may init HttpServer.handlerMap
// if not, run mvn test, the next assertEquals(0, HttpServer.handlerMap.size()) may fail // if not, run mvn test, the next assertEquals(0, HttpServer.handlerMap.size()) may fail
HttpServer.handlerMap.clear(); HttpServer.handlerMap.clear();
// create new HttpServer // Create new HttpServer
httpServer = new HttpServer(); httpServer = new HttpServer();
// no handler in handlerMap at first // No handler in handlerMap at first
assertEquals(0, HttpServer.handlerMap.size()); assertEquals(0, HttpServer.handlerMap.size());
} }
@Before @Before
public void before() { public void before() {
// clear handlerMap every method call // Clear handlerMap every method call
HttpServer.handlerMap.clear(); HttpServer.handlerMap.clear();
} }
@ -61,37 +60,37 @@ public class HttpServerTest {
String commandName; String commandName;
CommandHandler handler; CommandHandler handler;
// if commandName is null, no handler added in handlerMap // If commandName is null, no handler added in handlerMap
commandName = null; commandName = null;
handler = new VersionCommandHandler(); handler = new VersionCommandHandler();
httpServer.registerCommand(commandName, handler); httpServer.registerCommand(commandName, handler);
assertEquals(0, HttpServer.handlerMap.size()); assertEquals(0, HttpServer.handlerMap.size());
// if commandName is "", no handler added in handlerMap // If commandName is "", no handler added in handlerMap
commandName = ""; commandName = "";
handler = new VersionCommandHandler(); handler = new VersionCommandHandler();
httpServer.registerCommand(commandName, handler); httpServer.registerCommand(commandName, handler);
assertEquals(0, HttpServer.handlerMap.size()); assertEquals(0, HttpServer.handlerMap.size());
// if handler is null, no handler added in handlerMap // If handler is null, no handler added in handlerMap
commandName = "version"; commandName = "version";
handler = null; handler = null;
httpServer.registerCommand(commandName, handler); httpServer.registerCommand(commandName, handler);
assertEquals(0, HttpServer.handlerMap.size()); assertEquals(0, HttpServer.handlerMap.size());
// add one handler, commandName:version, handler:VersionCommandHandler // Add one handler, commandName:version, handler:VersionCommandHandler
commandName = "version"; commandName = "version";
handler = new VersionCommandHandler(); handler = new VersionCommandHandler();
httpServer.registerCommand(commandName, handler); httpServer.registerCommand(commandName, handler);
assertEquals(1, HttpServer.handlerMap.size()); assertEquals(1, HttpServer.handlerMap.size());
// add the same name Handler, no handler added in handlerMap // Add the same name Handler, no handler added in handlerMap
commandName = "version"; commandName = "version";
handler = new VersionCommandHandler(); handler = new VersionCommandHandler();
httpServer.registerCommand(commandName, handler); httpServer.registerCommand(commandName, handler);
assertEquals(1, HttpServer.handlerMap.size()); assertEquals(1, HttpServer.handlerMap.size());
// add another handler, commandName:basicInfo, handler:BasicInfoCommandHandler // Add another handler, commandName:basicInfo, handler:BasicInfoCommandHandler
commandName = "basicInfo"; commandName = "basicInfo";
handler = new BasicInfoCommandHandler(); handler = new BasicInfoCommandHandler();
httpServer.registerCommand(commandName, handler); httpServer.registerCommand(commandName, handler);
@ -102,16 +101,16 @@ public class HttpServerTest {
public void testRegisterCommands() { public void testRegisterCommands() {
Map<String, CommandHandler> handlerMap = null; Map<String, CommandHandler> handlerMap = null;
// if handlerMap is null, no handler added in handlerMap // If handlerMap is null, no handler added in handlerMap
httpServer.registerCommands(handlerMap); httpServer.registerCommands(handlerMap);
assertEquals(0, HttpServer.handlerMap.size()); assertEquals(0, HttpServer.handlerMap.size());
// add handler from CommandHandlerProvider // Add handler from CommandHandlerProvider
handlerMap = CommandHandlerProvider.getInstance().namedHandlers(); handlerMap = CommandHandlerProvider.getInstance().namedHandlers();
httpServer.registerCommands(handlerMap); httpServer.registerCommands(handlerMap);
// check same size // Check same size
assertEquals(handlerMap.size(), HttpServer.handlerMap.size()); assertEquals(handlerMap.size(), HttpServer.handlerMap.size());
// check not same reference // Check not same reference
assertTrue(handlerMap != HttpServer.handlerMap); assertTrue(handlerMap != HttpServer.handlerMap);
} }
} }

View File

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

View File

@ -100,7 +100,7 @@ public class SimpleHttpHeartbeatSender implements HeartbeatSender {
try { try {
String ipsStr = TransportConfig.getConsoleServer(); String ipsStr = TransportConfig.getConsoleServer();
if (StringUtil.isEmpty(ipsStr)) { if (StringUtil.isEmpty(ipsStr)) {
RecordLog.warn("[NettyHttpHeartbeatSender] Dashboard server address not configured"); RecordLog.warn("[SimpleHttpHeartbeatSender] Dashboard server address not configured");
return newAddrs; return newAddrs;
} }