init
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandlerProvider;
|
||||
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
|
||||
import com.alibaba.csp.sentinel.spi.Spi;
|
||||
import com.alibaba.csp.sentinel.transport.command.netty.HttpServer;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.CommandCenter;
|
||||
|
||||
/**
|
||||
* Implementation of {@link CommandCenter} based on Netty HTTP library.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@Spi(order = Spi.ORDER_LOWEST - 100)
|
||||
public class NettyHttpCommandCenter implements CommandCenter {
|
||||
|
||||
private final HttpServer server = new HttpServer();
|
||||
|
||||
@SuppressWarnings("PMD.ThreadPoolCreationRule")
|
||||
private final ExecutorService pool = Executors.newSingleThreadExecutor(
|
||||
new NamedThreadFactory("sentinel-netty-command-center-executor", true));
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
pool.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
server.start();
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[NettyHttpCommandCenter] Failed to start Netty transport server", ex);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
server.close();
|
||||
pool.shutdownNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeStart() throws Exception {
|
||||
// Register handlers
|
||||
Map<String, CommandHandler> handlers = CommandHandlerProvider.getInstance().namedHandlers();
|
||||
server.registerCommands(handlers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.codec;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public final class CodecRegistry {
|
||||
|
||||
private final List<Encoder<?>> encoderList = new ArrayList<Encoder<?>>();
|
||||
private final List<Decoder<?>> decoderList = new ArrayList<Decoder<?>>();
|
||||
|
||||
public CodecRegistry() {
|
||||
// Register default codecs.
|
||||
registerEncoder(DefaultCodecs.STRING_ENCODER);
|
||||
|
||||
registerDecoder(DefaultCodecs.STRING_DECODER);
|
||||
}
|
||||
|
||||
public void registerEncoder(Encoder<?> encoder) {
|
||||
encoderList.add(encoder);
|
||||
}
|
||||
|
||||
public void registerDecoder(Decoder<?> decoder) {
|
||||
decoderList.add(decoder);
|
||||
}
|
||||
|
||||
public List<Encoder<?>> getEncoderList() {
|
||||
return encoderList;
|
||||
}
|
||||
|
||||
public List<Decoder<?>> getDecoderList() {
|
||||
return decoderList;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
encoderList.clear();
|
||||
decoderList.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.codec;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* The decoder decodes bytes into an object of type {@code <R>}.
|
||||
*
|
||||
* @param <R> target type
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface Decoder<R> {
|
||||
|
||||
/**
|
||||
* Check whether the decoder supports the given target type.
|
||||
*
|
||||
* @param clazz type of the class
|
||||
* @return {@code true} if supported, {@code false} otherwise
|
||||
*/
|
||||
boolean canDecode(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Decode the given byte array into an object of type {@code R} with the default charset.
|
||||
*
|
||||
* @param bytes raw byte buffer
|
||||
* @return the decoded target object
|
||||
* @throws Exception error occurs when decoding the object (e.g. IO fails)
|
||||
*/
|
||||
R decode(byte[] bytes) throws Exception;
|
||||
|
||||
/**
|
||||
* Decode the given byte array into an object of type {@code R} with the given charset.
|
||||
*
|
||||
* @param bytes raw byte buffer
|
||||
* @param charset the charset
|
||||
* @return the decoded target object
|
||||
* @throws Exception error occurs when decoding the object (e.g. IO fails)
|
||||
*/
|
||||
R decode(byte[] bytes, Charset charset) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.codec;
|
||||
|
||||
/**
|
||||
* Caches default encoders and decoders.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
final class DefaultCodecs {
|
||||
|
||||
public static final Encoder<String> STRING_ENCODER = new StringEncoder();
|
||||
|
||||
public static final Decoder<String> STRING_DECODER = new StringDecoder();
|
||||
|
||||
private DefaultCodecs() {}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.codec;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* The encoder encodes an object of type {@code <R>} into byte array.
|
||||
*
|
||||
* @param <R> source type
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface Encoder<R> {
|
||||
|
||||
/**
|
||||
* Check whether the encoder supports the given source type.
|
||||
*
|
||||
* @param clazz type of the class
|
||||
* @return {@code true} if supported, {@code false} otherwise
|
||||
*/
|
||||
boolean canEncode(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Encode the given object into a byte array with the given charset.
|
||||
*
|
||||
* @param r the object to encode
|
||||
* @param charset the charset
|
||||
* @return the encoded byte buffer
|
||||
* @throws Exception error occurs when encoding the object (e.g. IO fails)
|
||||
*/
|
||||
byte[] encode(R r, Charset charset) throws Exception;
|
||||
|
||||
/**
|
||||
* Encode the given object into a byte array with the default charset.
|
||||
*
|
||||
* @param r the object to encode
|
||||
* @return the encoded byte buffer, which is already flipped.
|
||||
* @throws Exception error occurs when encoding the object (e.g. IO fails)
|
||||
*/
|
||||
byte[] encode(R r) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.codec;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
|
||||
/**
|
||||
* Decodes from a byte array to string.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class StringDecoder implements Decoder<String> {
|
||||
|
||||
@Override
|
||||
public boolean canDecode(Class<?> clazz) {
|
||||
return String.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decode(byte[] bytes) throws Exception {
|
||||
return decode(bytes, Charset.forName(SentinelConfig.charset()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decode(byte[] bytes, Charset charset) {
|
||||
if (bytes == null || bytes.length <= 0) {
|
||||
throw new IllegalArgumentException("Bad byte array");
|
||||
}
|
||||
return new String(bytes, charset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.codec;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
|
||||
/**
|
||||
* Encode a string to a byte array.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class StringEncoder implements Encoder<String> {
|
||||
|
||||
@Override
|
||||
public boolean canEncode(Class<?> clazz) {
|
||||
return String.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] encode(String string, Charset charset) {
|
||||
return string.getBytes(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode(String s) {
|
||||
return encode(s, Charset.forName(SentinelConfig.charset()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.netty;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.transport.log.CommandCenterLog;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public final class HttpServer {
|
||||
|
||||
private static final int DEFAULT_PORT = 8719;
|
||||
|
||||
private Channel channel;
|
||||
|
||||
final static Map<String, CommandHandler> handlerMap = new ConcurrentHashMap<String, CommandHandler>();
|
||||
|
||||
public void start() throws Exception {
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
try {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new HttpServerInitializer());
|
||||
int port;
|
||||
try {
|
||||
if (StringUtil.isEmpty(TransportConfig.getPort())) {
|
||||
CommandCenterLog.info("Port not configured, using default port: " + DEFAULT_PORT);
|
||||
port = DEFAULT_PORT;
|
||||
} else {
|
||||
port = Integer.parseInt(TransportConfig.getPort());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Will cause the application exit.
|
||||
throw new IllegalArgumentException("Illegal port: " + TransportConfig.getPort());
|
||||
}
|
||||
|
||||
int retryCount = 0;
|
||||
ChannelFuture channelFuture = null;
|
||||
// loop for an successful binding
|
||||
while (true) {
|
||||
int newPort = getNewPort(port, retryCount);
|
||||
try {
|
||||
channelFuture = b.bind(newPort).sync();
|
||||
TransportConfig.setRuntimePort(newPort);
|
||||
CommandCenterLog.info("[NettyHttpCommandCenter] Begin listening at port " + newPort);
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
TimeUnit.MILLISECONDS.sleep(30);
|
||||
RecordLog.warn("[HttpServer] Netty server bind error, port={}, retry={}", newPort, retryCount);
|
||||
retryCount ++;
|
||||
}
|
||||
}
|
||||
channel = channelFuture.channel();
|
||||
channel.closeFuture().sync();
|
||||
} finally {
|
||||
workerGroup.shutdownGracefully();
|
||||
bossGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase port number every 3 tries.
|
||||
*
|
||||
* @param basePort base port to start
|
||||
* @param retryCount retry count
|
||||
* @return next calculated port
|
||||
*/
|
||||
private int getNewPort(int basePort, int retryCount) {
|
||||
return basePort + retryCount / 3;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
channel.close();
|
||||
}
|
||||
|
||||
public void registerCommand(String commandName, CommandHandler handler) {
|
||||
if (StringUtil.isEmpty(commandName) || handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (handlerMap.containsKey(commandName)) {
|
||||
CommandCenterLog.warn("[NettyHttpCommandCenter] Register failed (duplicate command): " + commandName);
|
||||
return;
|
||||
}
|
||||
|
||||
handlerMap.put(commandName, handler);
|
||||
}
|
||||
|
||||
public void registerCommands(Map<String, CommandHandler> handlerMap) {
|
||||
if (handlerMap != null) {
|
||||
for (Entry<String, CommandHandler> e : handlerMap.entrySet()) {
|
||||
registerCommand(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* 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.netty;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
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.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.transport.log.CommandCenterLog;
|
||||
import com.alibaba.csp.sentinel.transport.command.codec.CodecRegistry;
|
||||
import com.alibaba.csp.sentinel.transport.command.codec.Encoder;
|
||||
import com.alibaba.csp.sentinel.transport.util.HttpCommandUtils;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import io.netty.handler.codec.http.multipart.HttpData;
|
||||
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
|
||||
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
|
||||
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
|
||||
|
||||
/**
|
||||
* Netty-based HTTP server handler for command center.
|
||||
*
|
||||
* Note: HTTP chunked is not tested!
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class HttpServerHandler extends SimpleChannelInboundHandler<Object> {
|
||||
|
||||
private final CodecRegistry codecRegistry = new CodecRegistry();
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
FullHttpRequest httpRequest = (FullHttpRequest)msg;
|
||||
try {
|
||||
CommandRequest request = parseRequest(httpRequest);
|
||||
if (StringUtil.isBlank(HttpCommandUtils.getTarget(request))) {
|
||||
writeErrorResponse(BAD_REQUEST.code(), "Invalid command", ctx);
|
||||
return;
|
||||
}
|
||||
handleRequest(request, ctx, HttpUtil.isKeepAlive(httpRequest));
|
||||
|
||||
} catch (Exception ex) {
|
||||
writeErrorResponse(INTERNAL_SERVER_ERROR.code(), SERVER_ERROR_MESSAGE, ctx);
|
||||
CommandCenterLog.warn("Internal error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRequest(CommandRequest request, ChannelHandlerContext ctx, boolean keepAlive)
|
||||
throws Exception {
|
||||
String commandName = HttpCommandUtils.getTarget(request);
|
||||
// Find the matching command handler.
|
||||
CommandHandler<?> commandHandler = getHandler(commandName);
|
||||
if (commandHandler != null) {
|
||||
CommandResponse<?> response = commandHandler.handle(request);
|
||||
writeResponse(response, ctx, keepAlive);
|
||||
} else {
|
||||
// No matching command handler.
|
||||
writeErrorResponse(BAD_REQUEST.code(), String.format("Unknown command \"%s\"", commandName), ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private Encoder<?> pickEncoder(Class<?> clazz) {
|
||||
if (clazz == null) {
|
||||
throw new IllegalArgumentException("Bad class metadata");
|
||||
}
|
||||
for (Encoder<?> encoder : codecRegistry.getEncoderList()) {
|
||||
if (encoder.canEncode(clazz)) {
|
||||
return encoder;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void writeErrorResponse(int statusCode, String message, ChannelHandlerContext ctx) {
|
||||
FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.valueOf(statusCode),
|
||||
Unpooled.copiedBuffer(message, Charset.forName(SentinelConfig.charset())));
|
||||
|
||||
httpResponse.headers().set("Content-Type", "text/plain; charset=" + SentinelConfig.charset());
|
||||
ctx.write(httpResponse);
|
||||
|
||||
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
private void writeResponse(CommandResponse response, ChannelHandlerContext ctx, boolean keepAlive)
|
||||
throws Exception {
|
||||
byte[] body;
|
||||
if (response.isSuccess()) {
|
||||
if (response.getResult() == null) {
|
||||
body = new byte[] {};
|
||||
} else {
|
||||
Encoder encoder = pickEncoder(response.getResult().getClass());
|
||||
if (encoder == null) {
|
||||
writeErrorResponse(INTERNAL_SERVER_ERROR.code(), SERVER_ERROR_MESSAGE, ctx);
|
||||
CommandCenterLog.warn("Error when encoding object",
|
||||
new IllegalStateException("No compatible encoder"));
|
||||
return;
|
||||
}
|
||||
body = encoder.encode(response.getResult());
|
||||
}
|
||||
} else {
|
||||
body = response.getException().getMessage().getBytes(SentinelConfig.charset());
|
||||
}
|
||||
|
||||
HttpResponseStatus status = response.isSuccess() ? OK : BAD_REQUEST;
|
||||
|
||||
FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
|
||||
Unpooled.copiedBuffer(body));
|
||||
|
||||
httpResponse.headers().set("Content-Type", "text/plain; charset=" + SentinelConfig.charset());
|
||||
|
||||
//if (keepAlive) {
|
||||
// httpResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpResponse.content().readableBytes());
|
||||
// httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
|
||||
//}
|
||||
//ctx.write(httpResponse);
|
||||
//if (!keepAlive) {
|
||||
// ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
//}
|
||||
httpResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpResponse.content().readableBytes());
|
||||
httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
|
||||
ctx.write(httpResponse);
|
||||
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
private CommandRequest parseRequest(FullHttpRequest request) {
|
||||
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri());
|
||||
CommandRequest serverRequest = new CommandRequest();
|
||||
Map<String, List<String>> paramMap = queryStringDecoder.parameters();
|
||||
// Parse request parameters.
|
||||
if (!paramMap.isEmpty()) {
|
||||
for (Entry<String, List<String>> p : paramMap.entrySet()) {
|
||||
if (!p.getValue().isEmpty()) {
|
||||
serverRequest.addParam(p.getKey(), p.getValue().get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Deal with post method, parameter in post has more privilege compared to that in querystring
|
||||
if (request.method().equals(HttpMethod.POST)) {
|
||||
// support multi-part and form-urlencoded
|
||||
HttpPostRequestDecoder postRequestDecoder = null;
|
||||
try {
|
||||
postRequestDecoder = new HttpPostRequestDecoder(request);
|
||||
for (InterfaceHttpData data : postRequestDecoder.getBodyHttpDatas()) {
|
||||
data.retain(); // must retain each attr before destroy
|
||||
if (data.getHttpDataType() == HttpDataType.Attribute) {
|
||||
if (data instanceof HttpData) {
|
||||
HttpData httpData = (HttpData) data;
|
||||
try {
|
||||
String name = httpData.getName();
|
||||
String value = httpData.getString();
|
||||
serverRequest.addParam(name, value);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (postRequestDecoder != null) {
|
||||
postRequestDecoder.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse command name.
|
||||
String target = parseTarget(queryStringDecoder.rawPath());
|
||||
serverRequest.addMetadata(HttpCommandUtils.REQUEST_TARGET, target);
|
||||
// Parse body.
|
||||
if (request.content().readableBytes() <= 0) {
|
||||
serverRequest.setBody(null);
|
||||
} else {
|
||||
byte[] body = new byte[request.content().readableBytes()];
|
||||
request.content().getBytes(0, body);
|
||||
serverRequest.setBody(body);
|
||||
}
|
||||
return serverRequest;
|
||||
}
|
||||
|
||||
private String parseTarget(String uri) {
|
||||
if (StringUtil.isEmpty(uri)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Remove the / of the uri as the target(command name)
|
||||
// Usually the uri is start with /
|
||||
int start = uri.indexOf('/');
|
||||
if (start != -1) {
|
||||
return uri.substring(start + 1);
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
private CommandHandler getHandler(String commandName) {
|
||||
if (StringUtil.isEmpty(commandName)) {
|
||||
return null;
|
||||
}
|
||||
return HttpServer.handlerMap.get(commandName);
|
||||
}
|
||||
|
||||
private void send100Continue(ChannelHandlerContext ctx) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
|
||||
ctx.write(response);
|
||||
}
|
||||
|
||||
private static final String SERVER_ERROR_MESSAGE = "Command server error";
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.netty;
|
||||
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel socketChannel) throws Exception {
|
||||
ChannelPipeline p = socketChannel.pipeline();
|
||||
|
||||
p.addLast(new HttpRequestDecoder());
|
||||
p.addLast(new HttpObjectAggregator(1024 * 1024));
|
||||
p.addLast(new HttpResponseEncoder());
|
||||
|
||||
p.addLast(new HttpServerHandler());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.heartbeat;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.spi.Spi;
|
||||
import com.alibaba.csp.sentinel.transport.HeartbeatSender;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Protocol;
|
||||
import com.alibaba.csp.sentinel.transport.heartbeat.client.HttpClientsFactory;
|
||||
import com.alibaba.csp.sentinel.util.AppNameUtil;
|
||||
import com.alibaba.csp.sentinel.util.HostNameUtil;
|
||||
import com.alibaba.csp.sentinel.util.PidUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
|
||||
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @author Carpenter Lee
|
||||
* @author Leo Li
|
||||
*/
|
||||
@Spi(order = Spi.ORDER_LOWEST - 100)
|
||||
public class HttpHeartbeatSender implements HeartbeatSender {
|
||||
|
||||
private final CloseableHttpClient client;
|
||||
|
||||
private static final int OK_STATUS = 200;
|
||||
|
||||
private final int timeoutMs = 3000;
|
||||
private final RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectionRequestTimeout(timeoutMs)
|
||||
.setConnectTimeout(timeoutMs)
|
||||
.setSocketTimeout(timeoutMs)
|
||||
.build();
|
||||
|
||||
private final Protocol consoleProtocol;
|
||||
private final String consoleHost;
|
||||
private final int consolePort;
|
||||
|
||||
public HttpHeartbeatSender() {
|
||||
List<Endpoint> dashboardList = TransportConfig.getConsoleServerList();
|
||||
if (dashboardList == null || dashboardList.isEmpty()) {
|
||||
RecordLog.info("[NettyHttpHeartbeatSender] No dashboard server available");
|
||||
consoleProtocol = Protocol.HTTP;
|
||||
consoleHost = null;
|
||||
consolePort = -1;
|
||||
} else {
|
||||
consoleProtocol = dashboardList.get(0).getProtocol();
|
||||
consoleHost = dashboardList.get(0).getHost();
|
||||
consolePort = dashboardList.get(0).getPort();
|
||||
RecordLog.info("[NettyHttpHeartbeatSender] Dashboard address parsed: <{}:{}>", consoleHost, consolePort);
|
||||
}
|
||||
this.client = HttpClientsFactory.getHttpClientsByProtocol(consoleProtocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendHeartbeat() throws Exception {
|
||||
if (StringUtil.isEmpty(consoleHost)) {
|
||||
return false;
|
||||
}
|
||||
URIBuilder uriBuilder = new URIBuilder();
|
||||
uriBuilder.setScheme(consoleProtocol.getProtocol()).setHost(consoleHost).setPort(consolePort)
|
||||
.setPath(TransportConfig.getHeartbeatApiPath())
|
||||
.setParameter("app", AppNameUtil.getAppName())
|
||||
.setParameter("app_type", String.valueOf(SentinelConfig.getAppType()))
|
||||
.setParameter("v", Constants.SENTINEL_VERSION)
|
||||
.setParameter("version", String.valueOf(System.currentTimeMillis()))
|
||||
.setParameter("hostname", HostNameUtil.getHostName())
|
||||
.setParameter("ip", TransportConfig.getHeartbeatClientIp())
|
||||
.setParameter("port", TransportConfig.getPort())
|
||||
.setParameter("pid", String.valueOf(PidUtil.getPid()));
|
||||
|
||||
HttpGet request = new HttpGet(uriBuilder.build());
|
||||
request.setConfig(requestConfig);
|
||||
// Send heartbeat request.
|
||||
CloseableHttpResponse response = client.execute(request);
|
||||
response.close();
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == OK_STATUS) {
|
||||
return true;
|
||||
} else if (clientErrorCode(statusCode) || serverErrorCode(statusCode)) {
|
||||
RecordLog.warn("[HttpHeartbeatSender] Failed to send heartbeat to "
|
||||
+ consoleHost + ":" + consolePort + ", http status code: " + statusCode);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long intervalMs() {
|
||||
return 5000;
|
||||
}
|
||||
|
||||
private boolean clientErrorCode(int code) {
|
||||
return code > 399 && code < 500;
|
||||
}
|
||||
|
||||
private boolean serverErrorCode(int code) {
|
||||
return code > 499 && code < 600;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.alibaba.csp.sentinel.transport.heartbeat.client;
|
||||
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Protocol;
|
||||
import com.alibaba.csp.sentinel.transport.ssl.SslFactory;
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
|
||||
/**
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class HttpClientsFactory {
|
||||
|
||||
private static class SslConnectionSocketFactoryInstance {
|
||||
private static final SSLConnectionSocketFactory SSL_CONNECTION_SOCKET_FACTORY = new SSLConnectionSocketFactory(SslFactory.getSslConnectionSocketFactory(), NoopHostnameVerifier.INSTANCE);
|
||||
}
|
||||
|
||||
public static CloseableHttpClient getHttpClientsByProtocol(Protocol protocol) {
|
||||
return protocol == Protocol.HTTP ? HttpClients.createDefault() : HttpClients.custom().
|
||||
setSSLSocketFactory(SslConnectionSocketFactoryInstance.SSL_CONNECTION_SOCKET_FACTORY).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.transport.command.NettyHttpCommandCenter
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.transport.heartbeat.HttpHeartbeatSender
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* 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.netty;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.init.InitExecutor;
|
||||
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.CommandCenter;
|
||||
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.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Test cases for {@link HttpServerHandler}.
|
||||
*
|
||||
* @author cdfive
|
||||
*/
|
||||
public class HttpServerHandlerTest {
|
||||
|
||||
private static String CRLF = "\r\n";
|
||||
|
||||
private static String SENTINEL_CHARSET_NAME = SentinelConfig.charset();
|
||||
|
||||
private static Charset SENTINEL_CHARSET = Charset.forName(SENTINEL_CHARSET_NAME);
|
||||
|
||||
private static EmbeddedChannel embeddedChannel;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
// Don't execute InitExecutor.doInit, to avoid CommandCenter SPI loaded
|
||||
Field[] declaredFields = InitExecutor.class.getDeclaredFields();
|
||||
for (Field declaredField : declaredFields) {
|
||||
if (declaredField.getName().equals("initialized")) {
|
||||
declaredField.setAccessible(true);
|
||||
((AtomicBoolean)declaredField.get(InitExecutor.class)).set(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Create NettyHttpCommandCenter to create HttpServer
|
||||
CommandCenter commandCenter = new NettyHttpCommandCenter();
|
||||
// Call beforeStart to register handlers
|
||||
commandCenter.beforeStart();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
// The same Handlers in order as the ChannelPipeline in HttpServerInitializer
|
||||
HttpRequestDecoder httpRequestDecoder = new HttpRequestDecoder();
|
||||
HttpObjectAggregator httpObjectAggregator = new HttpObjectAggregator(1024 * 1024);
|
||||
HttpResponseEncoder httpResponseEncoder = new HttpResponseEncoder();
|
||||
|
||||
HttpServerHandler httpServerHandler = new HttpServerHandler();
|
||||
|
||||
// Create new EmbeddedChannel every method call
|
||||
embeddedChannel = new EmbeddedChannel(httpRequestDecoder, httpObjectAggregator, httpResponseEncoder, httpServerHandler);
|
||||
|
||||
// Clear flow rules
|
||||
FlowRuleManager.loadRules(Collections.EMPTY_LIST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidCommand() {
|
||||
String httpRequestStr = "GET / HTTP/1.1" + CRLF
|
||||
+ "Host: localhost:8719" + CRLF
|
||||
+ CRLF;
|
||||
String expectedBody = "Invalid command";
|
||||
|
||||
processError(httpRequestStr, expectedBody);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownCommand() {
|
||||
String httpRequestStr = "GET /aaa HTTP/1.1" + CRLF
|
||||
+ "Host: localhost:8719" + CRLF
|
||||
+ CRLF;
|
||||
String expectedBody = String.format("Unknown command \"%s\"", "aaa");
|
||||
|
||||
processError(httpRequestStr, expectedBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link com.alibaba.csp.sentinel.command.handler.VersionCommandHandler}
|
||||
*/
|
||||
@Test
|
||||
public void testVersionCommand() {
|
||||
String httpRequestStr = "GET /version HTTP/1.1" + CRLF
|
||||
+ "Host: localhost:8719" + CRLF
|
||||
+ CRLF;
|
||||
String expectedBody = Constants.SENTINEL_VERSION;
|
||||
|
||||
processSuccess(httpRequestStr, expectedBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link com.alibaba.csp.sentinel.command.handler.FetchActiveRuleCommandHandler}
|
||||
*/
|
||||
@Test
|
||||
public void testFetchActiveRuleCommandInvalidType() {
|
||||
String httpRequestStr = "GET /getRules HTTP/1.1" + CRLF
|
||||
+ "Host: localhost:8719" + CRLF
|
||||
+ CRLF;
|
||||
String expectedBody = "invalid type";
|
||||
|
||||
processFailed(httpRequestStr, expectedBody);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFetchActiveRuleCommandEmptyRule() {
|
||||
String httpRequestStr = "GET /getRules?type=flow HTTP/1.1" + CRLF
|
||||
+ "Host: localhost:8719" + CRLF
|
||||
+ CRLF;
|
||||
String expectedBody = "[]";
|
||||
|
||||
processSuccess(httpRequestStr, expectedBody);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFetchActiveRuleCommandSomeFlowRules() {
|
||||
List<FlowRule> rules = new ArrayList<FlowRule>();
|
||||
FlowRule rule1 = new FlowRule();
|
||||
rule1.setResource("key");
|
||||
rule1.setCount(20);
|
||||
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
|
||||
rule1.setLimitApp("default");
|
||||
rules.add(rule1);
|
||||
FlowRuleManager.loadRules(rules);
|
||||
|
||||
String httpRequestStr = "GET /getRules?type=flow HTTP/1.1" + CRLF
|
||||
+ "Host: localhost:8719" + CRLF
|
||||
+ CRLF;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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, String expectedBody) {
|
||||
processError(httpRequestStr, BAD_REQUEST, expectedBody);
|
||||
}
|
||||
|
||||
private void processError(String httpRequestStr, HttpResponseStatus status, String expectedBody) {
|
||||
String httpResponseStr = processResponse(httpRequestStr);
|
||||
assertErrorStatusAndBody(status, expectedBody, httpResponseStr);
|
||||
}
|
||||
|
||||
private void processSuccess(String httpRequestStr, String expectedBody) {
|
||||
process(httpRequestStr, OK, expectedBody);
|
||||
}
|
||||
|
||||
private void processFailed(String httpRequestStr, String expectedBody) {
|
||||
process(httpRequestStr, BAD_REQUEST, expectedBody);
|
||||
}
|
||||
|
||||
private void process(String httpRequestStr, HttpResponseStatus status, String expectedBody) {
|
||||
String responseStr = processResponse(httpRequestStr);
|
||||
assertStatusAndBody(status, expectedBody, responseStr);
|
||||
}
|
||||
|
||||
private String processResponse(String httpRequestStr) {
|
||||
embeddedChannel.writeInbound(Unpooled.wrappedBuffer(httpRequestStr.getBytes(SENTINEL_CHARSET)));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
ByteBuf byteBuf;
|
||||
while ((byteBuf = embeddedChannel.readOutbound()) != null) {
|
||||
sb.append(byteBuf.toString(SENTINEL_CHARSET));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void assertErrorStatusAndBody(HttpResponseStatus status, String expectedBody, String httpResponseStr) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
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(CRLF);
|
||||
text.append(expectedBody);
|
||||
|
||||
assertEquals(text.toString(), httpResponseStr);
|
||||
}
|
||||
|
||||
private void assertStatusAndBody(HttpResponseStatus status, String expectedBody, String httpResponseStr) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
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-length: " + expectedBody.length()).append(CRLF);
|
||||
text.append("connection: close").append(CRLF);
|
||||
text.append(CRLF);
|
||||
text.append(expectedBody);
|
||||
|
||||
assertEquals(text.toString(), httpResponseStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.netty;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Test cases for {@link HttpServerInitializer}.
|
||||
*
|
||||
* @author cdfive
|
||||
*/
|
||||
public class HttpServerInitializerTest {
|
||||
|
||||
@Test
|
||||
public void testInitChannel() throws Exception {
|
||||
// Mock Objects
|
||||
HttpServerInitializer httpServerInitializer = mock(HttpServerInitializer.class);
|
||||
SocketChannel socketChannel = mock(SocketChannel.class);
|
||||
ChannelPipeline channelPipeline = mock(ChannelPipeline.class);
|
||||
|
||||
// Mock SocketChannel#pipeline() method
|
||||
when(socketChannel.pipeline()).thenReturn(channelPipeline);
|
||||
|
||||
// HttpServerInitializer#initChannel(SocketChannel) call real method
|
||||
doCallRealMethod().when(httpServerInitializer).initChannel(socketChannel);
|
||||
|
||||
// Start test for HttpServerInitializer#initChannel(SocketChannel)
|
||||
httpServerInitializer.initChannel(socketChannel);
|
||||
|
||||
// Verify 4 times calling ChannelPipeline#addLast() method
|
||||
verify(channelPipeline, times(4)).addLast(any(ChannelHandler.class));
|
||||
|
||||
// Verify the order of calling ChannelPipeline#addLast() method
|
||||
InOrder inOrder = inOrder(channelPipeline);
|
||||
inOrder.verify(channelPipeline).addLast(any(HttpRequestDecoder.class));
|
||||
inOrder.verify(channelPipeline).addLast(any(HttpObjectAggregator.class));
|
||||
inOrder.verify(channelPipeline).addLast(any(HttpResponseEncoder.class));
|
||||
|
||||
inOrder.verify(channelPipeline).addLast(any(HttpServerHandler.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.netty;
|
||||
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandlerProvider;
|
||||
import com.alibaba.csp.sentinel.command.handler.BasicInfoCommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.handler.VersionCommandHandler;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test cases for {@link HttpServer}.
|
||||
*
|
||||
* @author cdfive
|
||||
*/
|
||||
public class HttpServerTest {
|
||||
|
||||
private static HttpServer httpServer;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
// 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
|
||||
HttpServer.handlerMap.clear();
|
||||
|
||||
// Create new HttpServer
|
||||
httpServer = new HttpServer();
|
||||
|
||||
// No handler in handlerMap at first
|
||||
assertEquals(0, HttpServer.handlerMap.size());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
// Clear handlerMap every method call
|
||||
HttpServer.handlerMap.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterCommand() {
|
||||
String commandName;
|
||||
CommandHandler handler;
|
||||
|
||||
// If commandName is null, no handler added in handlerMap
|
||||
commandName = null;
|
||||
handler = new VersionCommandHandler();
|
||||
httpServer.registerCommand(commandName, handler);
|
||||
assertEquals(0, HttpServer.handlerMap.size());
|
||||
|
||||
// If commandName is "", no handler added in handlerMap
|
||||
commandName = "";
|
||||
handler = new VersionCommandHandler();
|
||||
httpServer.registerCommand(commandName, handler);
|
||||
assertEquals(0, HttpServer.handlerMap.size());
|
||||
|
||||
// If handler is null, no handler added in handlerMap
|
||||
commandName = "version";
|
||||
handler = null;
|
||||
httpServer.registerCommand(commandName, handler);
|
||||
assertEquals(0, HttpServer.handlerMap.size());
|
||||
|
||||
// Add one handler, commandName:version, handler:VersionCommandHandler
|
||||
commandName = "version";
|
||||
handler = new VersionCommandHandler();
|
||||
httpServer.registerCommand(commandName, handler);
|
||||
assertEquals(1, HttpServer.handlerMap.size());
|
||||
|
||||
// Add the same name Handler, no handler added in handlerMap
|
||||
commandName = "version";
|
||||
handler = new VersionCommandHandler();
|
||||
httpServer.registerCommand(commandName, handler);
|
||||
assertEquals(1, HttpServer.handlerMap.size());
|
||||
|
||||
// Add another handler, commandName:basicInfo, handler:BasicInfoCommandHandler
|
||||
commandName = "basicInfo";
|
||||
handler = new BasicInfoCommandHandler();
|
||||
httpServer.registerCommand(commandName, handler);
|
||||
assertEquals(2, HttpServer.handlerMap.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterCommands() {
|
||||
Map<String, CommandHandler> handlerMap = null;
|
||||
|
||||
// If handlerMap is null, no handler added in handlerMap
|
||||
httpServer.registerCommands(handlerMap);
|
||||
assertEquals(0, HttpServer.handlerMap.size());
|
||||
|
||||
// Add handler from CommandHandlerProvider
|
||||
handlerMap = CommandHandlerProvider.getInstance().namedHandlers();
|
||||
httpServer.registerCommands(handlerMap);
|
||||
// Check same size
|
||||
assertEquals(handlerMap.size(), HttpServer.handlerMap.size());
|
||||
// Check not same reference
|
||||
assertTrue(handlerMap != HttpServer.handlerMap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.transport.command.handler.MultipleSlashNameCommandTestHandler
|
||||
Reference in New Issue
Block a user