This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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() {}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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()));
}
}

View File

@@ -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());
}
}
}
}

View File

@@ -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";
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

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

View File

@@ -0,0 +1 @@
com.alibaba.csp.sentinel.transport.heartbeat.HttpHeartbeatSender

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

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

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