init
This commit is contained in:
4
sentinel/sentinel-transport/README.md
Normal file
4
sentinel/sentinel-transport/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Sentinel Transport
|
||||
|
||||
The Sentinel transport module provides basic interfaces about Sentinel monitoring API server and client
|
||||
(`CommandCenter` and `HeartbeatSender`) as well implementations using different libraries or protocols.
|
||||
21
sentinel/sentinel-transport/pom.xml
Normal file
21
sentinel/sentinel-transport/pom.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
<parent>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parent</artifactId>
|
||||
<version>1.8.3</version>
|
||||
</parent>
|
||||
<artifactId>sentinel-transport</artifactId>
|
||||
<description>The transport module of Sentinel</description>
|
||||
|
||||
<modules>
|
||||
<module>sentinel-transport-common</module>
|
||||
|
||||
<module>sentinel-transport-simple-http</module>
|
||||
<module>sentinel-transport-netty-http</module>
|
||||
<module>sentinel-transport-spring-mvc</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport</artifactId>
|
||||
<version>1.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>sentinel-transport-common</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-datasource-extension</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.command;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.CommandCenter;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
|
||||
/**
|
||||
* Provider for a universal {@link CommandCenter} instance.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public final class CommandCenterProvider {
|
||||
|
||||
private static CommandCenter commandCenter = null;
|
||||
|
||||
static {
|
||||
resolveInstance();
|
||||
}
|
||||
|
||||
private static void resolveInstance() {
|
||||
CommandCenter resolveCommandCenter = SpiLoader.of(CommandCenter.class).loadHighestPriorityInstance();
|
||||
|
||||
if (resolveCommandCenter == null) {
|
||||
RecordLog.warn("[CommandCenterProvider] WARN: No existing CommandCenter found");
|
||||
} else {
|
||||
commandCenter = resolveCommandCenter;
|
||||
RecordLog.info("[CommandCenterProvider] CommandCenter resolved: {}", resolveCommandCenter.getClass()
|
||||
.getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resolved {@link CommandCenter} instance.
|
||||
*
|
||||
* @return resolved {@code CommandCenter} instance
|
||||
*/
|
||||
public static CommandCenter getCommandCenter() {
|
||||
return commandCenter;
|
||||
}
|
||||
|
||||
private CommandCenterProvider() {}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.command;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public final class CommandConstants {
|
||||
|
||||
public static final String VERSION_COMMAND = "version";
|
||||
|
||||
public static final String MSG_INVALID_COMMAND = "Invalid command";
|
||||
public static final String MSG_UNKNOWN_COMMAND_PREFIX = "Unknown command";
|
||||
|
||||
public static final String MSG_SUCCESS = "success";
|
||||
public static final String MSG_FAIL = "failed";
|
||||
|
||||
private CommandConstants() {}
|
||||
}
|
||||
@@ -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.command;
|
||||
|
||||
/**
|
||||
* Represent a handler that handles a {@link CommandRequest}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface CommandHandler<R> {
|
||||
|
||||
/**
|
||||
* Handle the given Courier command request.
|
||||
*
|
||||
* @param request the request to handle
|
||||
* @return the response
|
||||
*/
|
||||
CommandResponse<R> handle(CommandRequest request);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.command;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Provides and filters command handlers registered via SPI.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class CommandHandlerProvider implements Iterable<CommandHandler> {
|
||||
|
||||
private final SpiLoader<CommandHandler> spiLoader = SpiLoader.of(CommandHandler.class);
|
||||
|
||||
/**
|
||||
* Get all command handlers annotated with {@link CommandMapping} with command name.
|
||||
*
|
||||
* @return list of all named command handlers
|
||||
*/
|
||||
public Map<String, CommandHandler> namedHandlers() {
|
||||
Map<String, CommandHandler> map = new HashMap<String, CommandHandler>();
|
||||
List<CommandHandler> handlers = spiLoader.loadInstanceList();
|
||||
for (CommandHandler handler : handlers) {
|
||||
String name = parseCommandName(handler);
|
||||
if (!StringUtil.isEmpty(name)) {
|
||||
map.put(name, handler);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private String parseCommandName(CommandHandler handler) {
|
||||
CommandMapping commandMapping = handler.getClass().getAnnotation(CommandMapping.class);
|
||||
if (commandMapping != null) {
|
||||
return commandMapping.name();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<CommandHandler> iterator() {
|
||||
return spiLoader.loadInstanceList().iterator();
|
||||
}
|
||||
|
||||
private static final CommandHandlerProvider INSTANCE = new CommandHandlerProvider();
|
||||
|
||||
public static CommandHandlerProvider getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.command;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Command request representation of command center.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class CommandRequest {
|
||||
|
||||
private final Map<String, String> metadata = new HashMap<String, String>();
|
||||
private final Map<String, String> parameters = new HashMap<String, String>();
|
||||
private byte[] body;
|
||||
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public CommandRequest setBody(byte[] body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public String getParam(String key) {
|
||||
return parameters.get(key);
|
||||
}
|
||||
|
||||
public String getParam(String key, String defaultValue) {
|
||||
String value = parameters.get(key);
|
||||
return StringUtil.isBlank(value) ? defaultValue : value;
|
||||
}
|
||||
|
||||
public CommandRequest addParam(String key, String value) {
|
||||
if (StringUtil.isBlank(key)) {
|
||||
throw new IllegalArgumentException("Parameter key cannot be empty");
|
||||
}
|
||||
parameters.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public CommandRequest addMetadata(String key, String value) {
|
||||
if (StringUtil.isBlank(key)) {
|
||||
throw new IllegalArgumentException("Metadata key cannot be empty");
|
||||
}
|
||||
metadata.put(key, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.command;
|
||||
|
||||
/**
|
||||
* Command response representation of command center.
|
||||
*
|
||||
* @param <R> type of the result
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class CommandResponse<R> {
|
||||
|
||||
private final boolean success;
|
||||
private final R result;
|
||||
private final Throwable exception;
|
||||
|
||||
private CommandResponse(R result) {
|
||||
this(result, true, null);
|
||||
}
|
||||
|
||||
private CommandResponse(R result, boolean success, Throwable exception) {
|
||||
this.success = success;
|
||||
this.result = result;
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a successful response with given object.
|
||||
*
|
||||
* @param result result object
|
||||
* @param <T> type of the result
|
||||
* @return constructed server response
|
||||
*/
|
||||
public static <T> CommandResponse<T> ofSuccess(T result) {
|
||||
return new CommandResponse<T>(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a failed response with given exception.
|
||||
*
|
||||
* @param ex cause of the failure
|
||||
* @return constructed server response
|
||||
*/
|
||||
public static <T> CommandResponse<T> ofFailure(Throwable ex) {
|
||||
return new CommandResponse<T>(null, false, ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a failed response with given exception.
|
||||
*
|
||||
* @param ex cause of the failure
|
||||
* @param result additional message of the failure
|
||||
* @return constructed server response
|
||||
*/
|
||||
public static <T> CommandResponse<T> ofFailure(Throwable ex, T result) {
|
||||
return new CommandResponse<T>(result, false, ex);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public R getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Throwable getException() {
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.command.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE})
|
||||
@Documented
|
||||
public @interface CommandMapping {
|
||||
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Get brief description of the command.
|
||||
*
|
||||
* @return brief description of the command
|
||||
* @since 1.5.0
|
||||
*/
|
||||
String desc();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.command.handler;
|
||||
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandlerProvider;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* List all available command handlers by request: </br>
|
||||
* {@code curl http://ip:commandPort/api}
|
||||
* </p>
|
||||
*
|
||||
* @author houyi
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@CommandMapping(name = "api", desc = "get all available command handlers")
|
||||
public class ApiCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
Map<String, CommandHandler> handlers = CommandHandlerProvider.getInstance().namedHandlers();
|
||||
JSONArray array = new JSONArray();
|
||||
if (handlers.isEmpty()) {
|
||||
return CommandResponse.ofSuccess(array.toJSONString());
|
||||
}
|
||||
for (CommandHandler handler : handlers.values()) {
|
||||
CommandMapping commandMapping = handler.getClass().getAnnotation(CommandMapping.class);
|
||||
if (commandMapping == null) {
|
||||
continue;
|
||||
}
|
||||
String api = commandMapping.name();
|
||||
String desc = commandMapping.desc();
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("url", "/" + api);
|
||||
obj.put("desc", desc);
|
||||
array.add(obj);
|
||||
}
|
||||
return CommandResponse.ofSuccess(array.toJSONString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.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;
|
||||
import com.alibaba.csp.sentinel.util.HostNameUtil;
|
||||
|
||||
/**
|
||||
* The basic info command returns the runtime properties.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@CommandMapping(name = "basicInfo", desc = "get sentinel config info")
|
||||
public class BasicInfoCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
return CommandResponse.ofSuccess(HostNameUtil.getConfigString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.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;
|
||||
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
@CommandMapping(name = "getRules", desc = "get all active rules by type, request param: type={ruleType}")
|
||||
public class FetchActiveRuleCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String type = request.getParam("type");
|
||||
if ("flow".equalsIgnoreCase(type)) {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(FlowRuleManager.getRules()));
|
||||
} else if ("degrade".equalsIgnoreCase(type)) {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(DegradeRuleManager.getRules()));
|
||||
} else if ("authority".equalsIgnoreCase(type)) {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(AuthorityRuleManager.getRules()));
|
||||
} else if ("system".equalsIgnoreCase(type)) {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(SystemRuleManager.getRules()));
|
||||
} else {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("invalid type"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.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;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.command.vo.NodeVo;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* @author qinan.qn
|
||||
*/
|
||||
@CommandMapping(name = "clusterNodeById", desc = "get clusterNode VO by id, request param: id={resourceName}")
|
||||
public class FetchClusterNodeByIdCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String id = request.getParam("id");
|
||||
if (StringUtil.isEmpty(id)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("Invalid parameter: empty clusterNode name"));
|
||||
}
|
||||
ClusterNode node = ClusterBuilderSlot.getClusterNode(id);
|
||||
if (node != null) {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(NodeVo.fromClusterNode(id, node)));
|
||||
} else {
|
||||
return CommandResponse.ofSuccess("{}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.command.handler;
|
||||
|
||||
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.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
|
||||
/**
|
||||
* @author qinan.qn
|
||||
*/
|
||||
@CommandMapping(name = "cnode", desc = "get clusterNode metrics by id, request param: id={resourceName}")
|
||||
public class FetchClusterNodeHumanCommandHandler implements CommandHandler<String> {
|
||||
|
||||
private final static String FORMAT = "%-4s%-80s%-10s%-10s%-10s%-11s%-9s%-6s%-10s%-11s%-9s%-11s";
|
||||
private final static int MAX_LEN = 79;
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String name = request.getParam("id");
|
||||
|
||||
if (StringUtil.isEmpty(name)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("Invalid parameter: empty clusterNode name"));
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int i = 0;
|
||||
int nameLength = 0;
|
||||
for (Entry<ResourceWrapper, ClusterNode> e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) {
|
||||
if (e.getKey().getName().contains(name)) {
|
||||
int l = e.getKey().getShowName().length();
|
||||
if (l > nameLength) {
|
||||
nameLength = l;
|
||||
}
|
||||
if (++i == 30) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
nameLength = nameLength > MAX_LEN ? MAX_LEN : nameLength;
|
||||
String format = FORMAT.replaceAll("80", String.valueOf(nameLength + 1));
|
||||
|
||||
sb.append(String.format(format, "idx", "id", "thread", "pass", "blocked", "success", "total", "aRt",
|
||||
"1m-pass", "1m-block", "1m-all", "exception")).append("\n");
|
||||
for (Entry<ResourceWrapper, ClusterNode> e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) {
|
||||
if (e.getKey().getName().contains(name)) {
|
||||
ClusterNode node = e.getValue();
|
||||
String id = e.getKey().getShowName();
|
||||
int lenNum = (int)Math.ceil((double)id.length() / nameLength) - 1;
|
||||
|
||||
sb.append(String.format(format, i + 1, lenNum == 0 ? id : id.substring(0, nameLength),
|
||||
node.curThreadNum(), node.passQps(), node.blockQps(), node.successQps(), node.totalQps(),
|
||||
node.avgRt(), node.totalRequest() - node.blockRequest(), node.blockRequest(),
|
||||
node.totalRequest(), node.exceptionQps())).append("\n");
|
||||
for (int j = 1; j <= lenNum; ++j) {
|
||||
int start = nameLength * j;
|
||||
int end = j == lenNum ? id.length() : nameLength * (j + 1);
|
||||
sb.append(String.format(format, "", id.substring(start, end), "", "", "", "", "", "", "", "", "",
|
||||
"", "", "")).append("\n");
|
||||
}
|
||||
|
||||
if (++i == 30) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CommandResponse.ofSuccess(sb.toString());
|
||||
}
|
||||
}
|
||||
@@ -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.command.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
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;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.node.Node;
|
||||
import com.alibaba.csp.sentinel.command.vo.NodeVo;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* @author leyou
|
||||
*/
|
||||
@CommandMapping(name = "jsonTree", desc = "get tree node VO start from root node")
|
||||
public class FetchJsonTreeCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
List<NodeVo> results = new ArrayList<NodeVo>();
|
||||
visit(Constants.ROOT, results, null);
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(results));
|
||||
}
|
||||
|
||||
/**
|
||||
* Preorder traversal.
|
||||
*/
|
||||
private void visit(DefaultNode node, List<NodeVo> results, String parentId) {
|
||||
NodeVo vo = NodeVo.fromDefaultNode(node, parentId);
|
||||
results.add(vo);
|
||||
String id = vo.getId();
|
||||
for (Node n : node.getChildList()) {
|
||||
visit((DefaultNode)n, results, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.command.handler;
|
||||
|
||||
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.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.node.StatisticNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
|
||||
/**
|
||||
* @author qinan.qn
|
||||
*/
|
||||
@CommandMapping(name = "origin", desc = "get origin clusterNode by id, request param: id={resourceName}")
|
||||
public class FetchOriginCommandHandler implements CommandHandler<String> {
|
||||
|
||||
private final static String FORMAT = "%-4s%-80s%-10s%-10s%-11s%-9s%-6s%-10s%-11s%-9s";
|
||||
private final static int MAX_LEN = 79;
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String name = request.getParam("id");
|
||||
|
||||
ClusterNode cNode = null;
|
||||
|
||||
boolean exactly = false;
|
||||
for (Entry<ResourceWrapper, ClusterNode> e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) {
|
||||
if (e.getKey().getName().equals(name)) {
|
||||
cNode = e.getValue();
|
||||
sb.append("id: ").append(e.getKey().getShowName()).append("\n");
|
||||
sb.append("\n");
|
||||
exactly = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exactly) {
|
||||
for (Entry<ResourceWrapper, ClusterNode> e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) {
|
||||
if (e.getKey().getName().indexOf(name) > 0) {
|
||||
cNode = e.getValue();
|
||||
sb.append("id: ").append(e.getKey().getShowName()).append("\n");
|
||||
sb.append("\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cNode == null) {
|
||||
return CommandResponse.ofSuccess("Not find cNode with id " + name);
|
||||
}
|
||||
int i = 0;
|
||||
int nameLength = 0;
|
||||
for (Entry<String, StatisticNode> e : cNode.getOriginCountMap().entrySet()) {
|
||||
int l = e.getKey().length();
|
||||
if (l > nameLength) {
|
||||
nameLength = l;
|
||||
}
|
||||
if (++i == 120) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
nameLength = nameLength > MAX_LEN ? MAX_LEN : nameLength;
|
||||
String format = FORMAT.replaceAll("80", String.valueOf(nameLength + 1));
|
||||
i = 0;
|
||||
sb.append(String
|
||||
.format(format, "idx", "origin", "threadNum", "passQps", "blockQps", "totalQps", "aRt", "1m-pass",
|
||||
"1m-block", "1m-total")).append("\n");
|
||||
|
||||
for (Entry<String, StatisticNode> e : cNode.getOriginCountMap().entrySet()) {
|
||||
StatisticNode node = e.getValue();
|
||||
String id = e.getKey();
|
||||
int lenNum = (int)Math.ceil((double)id.length() / nameLength) - 1;
|
||||
sb.append(String
|
||||
.format(format, i + 1, lenNum == 0 ? id : id.substring(0, nameLength), node.curThreadNum(),
|
||||
node.passQps(), node.blockQps(), node.totalQps(), node.avgRt(),
|
||||
node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest()))
|
||||
.append("\n");
|
||||
for (int j = 1; j <= lenNum; ++j) {
|
||||
int start = nameLength * j;
|
||||
int end = j == lenNum ? id.length() : nameLength * (j + 1);
|
||||
sb.append(String
|
||||
.format(format, "", id.substring(start, end), "", "", "", "", "", "", "", "", "", "", "", ""))
|
||||
.append("\n");
|
||||
}
|
||||
if (++i == 30) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return CommandResponse.ofSuccess(sb.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.command.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.command.vo.NodeVo;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
|
||||
/**
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
@CommandMapping(name = "clusterNode", desc = "get all clusterNode VO, use type=notZero to ignore those nodes with totalRequest <=0")
|
||||
public class FetchSimpleClusterNodeCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
/*
|
||||
* type==notZero means nodes whose totalRequest <= 0 will be ignored.
|
||||
*/
|
||||
String type = request.getParam("type");
|
||||
List<NodeVo> list = new ArrayList<NodeVo>();
|
||||
Map<ResourceWrapper, ClusterNode> map = ClusterBuilderSlot.getClusterNodeMap();
|
||||
if (map == null) {
|
||||
return CommandResponse.ofSuccess(JSONArray.toJSONString(list));
|
||||
}
|
||||
for (Map.Entry<ResourceWrapper, ClusterNode> entry : map.entrySet()) {
|
||||
if ("notZero".equalsIgnoreCase(type)) {
|
||||
if (entry.getValue().totalRequest() > 0) {
|
||||
list.add(NodeVo.fromClusterNode(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
} else {
|
||||
list.add(NodeVo.fromClusterNode(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
}
|
||||
return CommandResponse.ofSuccess(JSONArray.toJSONString(list));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.command.handler;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
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;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
/**
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
@CommandMapping(name = "systemStatus", desc = "get system status")
|
||||
public class FetchSystemStatusCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
|
||||
Map<String, Object> systemStatus = new HashMap<String, Object>();
|
||||
|
||||
systemStatus.put("rqps", Constants.ENTRY_NODE.successQps());
|
||||
systemStatus.put("qps", Constants.ENTRY_NODE.passQps());
|
||||
systemStatus.put("b", Constants.ENTRY_NODE.blockQps());
|
||||
systemStatus.put("r", Constants.ENTRY_NODE.avgRt());
|
||||
systemStatus.put("t", Constants.ENTRY_NODE.curThreadNum());
|
||||
|
||||
return CommandResponse.ofSuccess(JSONObject.toJSONString(systemStatus));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.command.handler;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
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;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.node.EntranceNode;
|
||||
import com.alibaba.csp.sentinel.node.Node;
|
||||
|
||||
/**
|
||||
* @author qinan.qn
|
||||
*/
|
||||
@CommandMapping(name = "tree", desc = "get metrics in tree mode, use id to specify detailed tree root")
|
||||
public class FetchTreeCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String id = request.getParam("id");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
DefaultNode start = Constants.ROOT;
|
||||
|
||||
if (id == null) {
|
||||
visitTree(0, start, sb);
|
||||
} else {
|
||||
boolean exactly = false;
|
||||
for (Node n : start.getChildList()) {
|
||||
DefaultNode dn = (DefaultNode)n;
|
||||
if (dn.getId().getName().equals(id)) {
|
||||
visitTree(0, dn, sb);
|
||||
exactly = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exactly) {
|
||||
for (Node n : start.getChildList()) {
|
||||
DefaultNode dn = (DefaultNode)n;
|
||||
if (dn.getId().getName().contains(id)) {
|
||||
visitTree(0, dn, sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append("\r\n\r\n");
|
||||
sb.append(
|
||||
"t:threadNum pq:passQps bq:blockQps tq:totalQps rt:averageRt prq: passRequestQps 1mp:1m-pass "
|
||||
+ "1mb:1m-block 1mt:1m-total").append("\r\n");
|
||||
return CommandResponse.ofSuccess(sb.toString());
|
||||
}
|
||||
|
||||
private void visitTree(int level, DefaultNode node, /*@NonNull*/ StringBuilder sb) {
|
||||
for (int i = 0; i < level; ++i) {
|
||||
sb.append("-");
|
||||
}
|
||||
if (!(node instanceof EntranceNode)) {
|
||||
sb.append(String.format("%s(t:%s pq:%s bq:%s tq:%s rt:%s prq:%s 1mp:%s 1mb:%s 1mt:%s)",
|
||||
node.getId().getShowName(), node.curThreadNum(), node.passQps(),
|
||||
node.blockQps(), node.totalQps(), node.avgRt(), node.successQps(),
|
||||
node.totalRequest() - node.blockRequest(), node.blockRequest(),
|
||||
node.totalRequest())).append("\n");
|
||||
} else {
|
||||
sb.append(String.format("EntranceNode: %s(t:%s pq:%s bq:%s tq:%s rt:%s prq:%s 1mp:%s 1mb:%s 1mt:%s)",
|
||||
node.getId().getShowName(), node.curThreadNum(), node.passQps(),
|
||||
node.blockQps(), node.totalQps(), node.avgRt(), node.successQps(),
|
||||
node.totalRequest() - node.blockRequest(), node.blockRequest(),
|
||||
node.totalRequest())).append("\n");
|
||||
}
|
||||
for (Node n : node.getChildList()) {
|
||||
DefaultNode dn = (DefaultNode)n;
|
||||
visitTree(level + 1, dn, sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.command.handler;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.util.VersionUtil;
|
||||
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRule;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
|
||||
import static com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry.*;
|
||||
|
||||
/**
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@CommandMapping(name = "setRules", desc = "modify the rules, accept param: type={ruleType}&data={ruleJson}")
|
||||
public class ModifyRulesCommandHandler implements CommandHandler<String> {
|
||||
private static final int FASTJSON_MINIMAL_VER = 0x01020C00;
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
// XXX from 1.7.2, force to fail when fastjson is older than 1.2.12
|
||||
// We may need a better solution on this.
|
||||
if (VersionUtil.fromVersionString(JSON.VERSION) < FASTJSON_MINIMAL_VER) {
|
||||
// fastjson too old
|
||||
return CommandResponse.ofFailure(new RuntimeException("The \"fastjson-" + JSON.VERSION
|
||||
+ "\" introduced in application is too old, you need fastjson-1.2.12 at least."));
|
||||
}
|
||||
String type = request.getParam("type");
|
||||
// rule data in get parameter
|
||||
String data = request.getParam("data");
|
||||
if (StringUtil.isNotEmpty(data)) {
|
||||
try {
|
||||
data = URLDecoder.decode(data, "utf-8");
|
||||
} catch (Exception e) {
|
||||
RecordLog.info("Decode rule data error", e);
|
||||
return CommandResponse.ofFailure(e, "decode rule data error");
|
||||
}
|
||||
}
|
||||
|
||||
RecordLog.info("Receiving rule change (type: {}): {}", type, data);
|
||||
|
||||
String result = "success";
|
||||
|
||||
if (FLOW_RULE_TYPE.equalsIgnoreCase(type)) {
|
||||
List<FlowRule> flowRules = JSONArray.parseArray(data, FlowRule.class);
|
||||
FlowRuleManager.loadRules(flowRules);
|
||||
if (!writeToDataSource(getFlowDataSource(), flowRules)) {
|
||||
result = WRITE_DS_FAILURE_MSG;
|
||||
}
|
||||
return CommandResponse.ofSuccess(result);
|
||||
} else if (AUTHORITY_RULE_TYPE.equalsIgnoreCase(type)) {
|
||||
List<AuthorityRule> rules = JSONArray.parseArray(data, AuthorityRule.class);
|
||||
AuthorityRuleManager.loadRules(rules);
|
||||
if (!writeToDataSource(getAuthorityDataSource(), rules)) {
|
||||
result = WRITE_DS_FAILURE_MSG;
|
||||
}
|
||||
return CommandResponse.ofSuccess(result);
|
||||
} else if (DEGRADE_RULE_TYPE.equalsIgnoreCase(type)) {
|
||||
List<DegradeRule> rules = JSONArray.parseArray(data, DegradeRule.class);
|
||||
DegradeRuleManager.loadRules(rules);
|
||||
if (!writeToDataSource(getDegradeDataSource(), rules)) {
|
||||
result = WRITE_DS_FAILURE_MSG;
|
||||
}
|
||||
return CommandResponse.ofSuccess(result);
|
||||
} else if (SYSTEM_RULE_TYPE.equalsIgnoreCase(type)) {
|
||||
List<SystemRule> rules = JSONArray.parseArray(data, SystemRule.class);
|
||||
SystemRuleManager.loadRules(rules);
|
||||
if (!writeToDataSource(getSystemSource(), rules)) {
|
||||
result = WRITE_DS_FAILURE_MSG;
|
||||
}
|
||||
return CommandResponse.ofSuccess(result);
|
||||
}
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("invalid type"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write target value to given data source.
|
||||
*
|
||||
* @param dataSource writable data source
|
||||
* @param value target value to save
|
||||
* @param <T> value type
|
||||
* @return true if write successful or data source is empty; false if error occurs
|
||||
*/
|
||||
private <T> boolean writeToDataSource(WritableDataSource<T> dataSource, T value) {
|
||||
if (dataSource != null) {
|
||||
try {
|
||||
dataSource.write(value);
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("Write data source failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)";
|
||||
private static final String FLOW_RULE_TYPE = "flow";
|
||||
private static final String DEGRADE_RULE_TYPE = "degrade";
|
||||
private static final String SYSTEM_RULE_TYPE = "system";
|
||||
private static final String AUTHORITY_RULE_TYPE = "authority";
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.command.handler;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
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 youji.zj
|
||||
*/
|
||||
@CommandMapping(name = "getSwitch", desc = "get sentinel switch status")
|
||||
public class OnOffGetCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
return CommandResponse.ofSuccess("Sentinel switch value: " + Constants.ON);
|
||||
}
|
||||
}
|
||||
@@ -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.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;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
|
||||
/**
|
||||
* @author youji.zj
|
||||
*/
|
||||
@CommandMapping(name = "setSwitch", desc = "set sentinel switch, accept param: value={true|false}")
|
||||
public class OnOffSetCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String value = request.getParam("value");
|
||||
|
||||
try {
|
||||
Constants.ON = Boolean.valueOf(value);
|
||||
} catch (Exception e) {
|
||||
RecordLog.info("Bad value when setting global switch", e);
|
||||
}
|
||||
|
||||
String info = "Sentinel set switch value: " + value;
|
||||
RecordLog.info(info);
|
||||
|
||||
return CommandResponse.ofSuccess(info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.command.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
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;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.node.metric.MetricNode;
|
||||
import com.alibaba.csp.sentinel.node.metric.MetricSearcher;
|
||||
import com.alibaba.csp.sentinel.node.metric.MetricWriter;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
|
||||
import com.alibaba.csp.sentinel.util.PidUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
|
||||
/**
|
||||
* Retrieve and aggregate {@link MetricNode} metrics.
|
||||
*
|
||||
* @author leyou
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@CommandMapping(name = "metric", desc = "get and aggregate metrics, accept param: "
|
||||
+ "startTime={startTime}&endTime={endTime}&maxLines={maxLines}&identify={resourceName}")
|
||||
public class SendMetricCommandHandler implements CommandHandler<String> {
|
||||
|
||||
private volatile MetricSearcher searcher;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
// Note: not thread-safe.
|
||||
if (searcher == null) {
|
||||
synchronized (lock) {
|
||||
String appName = SentinelConfig.getAppName();
|
||||
if (appName == null) {
|
||||
appName = "";
|
||||
}
|
||||
if (searcher == null) {
|
||||
searcher = new MetricSearcher(MetricWriter.METRIC_BASE_DIR,
|
||||
MetricWriter.formMetricFileName(appName, PidUtil.getPid()));
|
||||
}
|
||||
}
|
||||
}
|
||||
String startTimeStr = request.getParam("startTime");
|
||||
String endTimeStr = request.getParam("endTime");
|
||||
String maxLinesStr = request.getParam("maxLines");
|
||||
String identity = request.getParam("identity");
|
||||
long startTime = -1;
|
||||
int maxLines = 6000;
|
||||
if (StringUtil.isNotBlank(startTimeStr)) {
|
||||
startTime = Long.parseLong(startTimeStr);
|
||||
} else {
|
||||
return CommandResponse.ofSuccess("");
|
||||
}
|
||||
List<MetricNode> list;
|
||||
try {
|
||||
// Find by end time if set.
|
||||
if (StringUtil.isNotBlank(endTimeStr)) {
|
||||
long endTime = Long.parseLong(endTimeStr);
|
||||
list = searcher.findByTimeAndResource(startTime, endTime, identity);
|
||||
} else {
|
||||
if (StringUtil.isNotBlank(maxLinesStr)) {
|
||||
maxLines = Integer.parseInt(maxLinesStr);
|
||||
}
|
||||
maxLines = Math.min(maxLines, 12000);
|
||||
list = searcher.find(startTime, maxLines);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
return CommandResponse.ofFailure(new RuntimeException("Error when retrieving metrics", ex));
|
||||
}
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
if (StringUtil.isBlank(identity)) {
|
||||
addCpuUsageAndLoad(list);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (MetricNode node : list) {
|
||||
sb.append(node.toThinString()).append("\n");
|
||||
}
|
||||
return CommandResponse.ofSuccess(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* add current cpu usage and load to the metric list.
|
||||
*
|
||||
* @param list metric list, should not be null
|
||||
*/
|
||||
private void addCpuUsageAndLoad(List<MetricNode> list) {
|
||||
long time = TimeUtil.currentTimeMillis() / 1000 * 1000;
|
||||
double load = SystemRuleManager.getCurrentSystemAvgLoad();
|
||||
double usage = SystemRuleManager.getCurrentCpuUsage();
|
||||
if (load > 0) {
|
||||
MetricNode loadNode = toNode(load, time, Constants.SYSTEM_LOAD_RESOURCE_NAME);
|
||||
list.add(loadNode);
|
||||
}
|
||||
if (usage > 0) {
|
||||
MetricNode usageNode = toNode(usage, time, Constants.CPU_USAGE_RESOURCE_NAME);
|
||||
list.add(usageNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* transfer the value to a MetricNode, the value will multiply 10000 then truncate
|
||||
* to long value, and as the {@link MetricNode#passQps}.
|
||||
* <p>
|
||||
* This is an eclectic scheme before we have a standard metric format.
|
||||
* </p>
|
||||
*
|
||||
* @param value value to save.
|
||||
* @param ts timestamp
|
||||
* @param resource resource name.
|
||||
* @return a MetricNode represents the value.
|
||||
*/
|
||||
private MetricNode toNode(double value, long ts, String resource) {
|
||||
MetricNode node = new MetricNode();
|
||||
node.setPassQps((long)(value * 10000));
|
||||
node.setTimestamp(ts);
|
||||
node.setResource(resource);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.command.handler;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
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 jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@CommandMapping(name = "version", desc = "get sentinel version")
|
||||
public class VersionCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
return CommandResponse.ofSuccess(Constants.SENTINEL_VERSION);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.command.handler.cluster;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
|
||||
import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider;
|
||||
import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider;
|
||||
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;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "getClusterMode", desc = "get cluster mode status")
|
||||
public class FetchClusterModeCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
JSONObject res = new JSONObject()
|
||||
.fluentPut("mode", ClusterStateManager.getMode())
|
||||
.fluentPut("lastModified", ClusterStateManager.getLastModified())
|
||||
.fluentPut("clientAvailable", isClusterClientSpiAvailable())
|
||||
.fluentPut("serverAvailable", isClusterServerSpiAvailable());
|
||||
return CommandResponse.ofSuccess(res.toJSONString());
|
||||
}
|
||||
|
||||
private boolean isClusterClientSpiAvailable() {
|
||||
return TokenClientProvider.getClient() != null;
|
||||
}
|
||||
|
||||
private boolean isClusterServerSpiAvailable() {
|
||||
return EmbeddedClusterTokenServerProvider.getServer() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.command.handler.cluster;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
|
||||
import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider;
|
||||
import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider;
|
||||
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;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "setClusterMode", desc = "set cluster mode, accept param: mode={0|1} 0:client mode 1:server mode")
|
||||
public class ModifyClusterModeCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
try {
|
||||
int mode = Integer.valueOf(request.getParam("mode"));
|
||||
if (mode == ClusterStateManager.CLUSTER_CLIENT && !TokenClientProvider.isClientSpiAvailable()) {
|
||||
return CommandResponse.ofFailure(new IllegalStateException("token client mode not available: no SPI found"));
|
||||
}
|
||||
if (mode == ClusterStateManager.CLUSTER_SERVER && !isClusterServerSpiAvailable()) {
|
||||
return CommandResponse.ofFailure(new IllegalStateException("token server mode not available: no SPI found"));
|
||||
}
|
||||
RecordLog.info("[ModifyClusterModeCommandHandler] Modifying cluster mode to: {}", mode);
|
||||
|
||||
ClusterStateManager.applyState(mode);
|
||||
return CommandResponse.ofSuccess("success");
|
||||
} catch (NumberFormatException ex) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("invalid parameter"));
|
||||
} catch (Exception ex) {
|
||||
return CommandResponse.ofFailure(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClusterServerSpiAvailable() {
|
||||
return EmbeddedClusterTokenServerProvider.isServerSpiAvailable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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.command.vo;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
|
||||
/**
|
||||
* This class is view object of {@link DefaultNode} or {@link ClusterNode}.
|
||||
*
|
||||
* @author leyou
|
||||
*/
|
||||
public class NodeVo {
|
||||
|
||||
private String id;
|
||||
private String parentId;
|
||||
private String resource;
|
||||
|
||||
private Integer threadNum;
|
||||
private Long passQps;
|
||||
private Long blockQps;
|
||||
private Long totalQps;
|
||||
private Long averageRt;
|
||||
private Long successQps;
|
||||
private Long exceptionQps;
|
||||
private Long oneMinutePass;
|
||||
private Long oneMinuteBlock;
|
||||
private Long oneMinuteException;
|
||||
private Long oneMinuteTotal;
|
||||
|
||||
private Long timestamp;
|
||||
|
||||
/**
|
||||
* {@link DefaultNode} holds statistics of every node in the invoke tree.
|
||||
* We use parentId to hold the tree structure.
|
||||
*
|
||||
* @param node the DefaultNode to be presented.
|
||||
* @param parentId random generated parent node id, may be a random UUID
|
||||
* @return node view object.
|
||||
*/
|
||||
public static NodeVo fromDefaultNode(DefaultNode node, String parentId) {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
NodeVo vo = new NodeVo();
|
||||
vo.id = UUID.randomUUID().toString();
|
||||
vo.parentId = parentId;
|
||||
vo.resource = node.getId().getShowName();
|
||||
vo.threadNum = node.curThreadNum();
|
||||
vo.passQps = (long) node.passQps();
|
||||
vo.blockQps = (long) node.blockQps();
|
||||
vo.totalQps = (long) node.totalQps();
|
||||
vo.averageRt = (long) node.avgRt();
|
||||
vo.successQps = (long) node.successQps();
|
||||
vo.exceptionQps = (long) node.exceptionQps();
|
||||
vo.oneMinuteException = node.totalException();
|
||||
vo.oneMinutePass = node.totalRequest() - node.blockRequest();
|
||||
vo.oneMinuteBlock = node.blockRequest();
|
||||
vo.oneMinuteTotal = node.totalRequest();
|
||||
vo.timestamp = System.currentTimeMillis();
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClusterNode} holds total statistics of the same resource name.
|
||||
*
|
||||
* @param name resource name.
|
||||
* @param node the ClusterNode to be presented.
|
||||
* @return node view object.
|
||||
*/
|
||||
public static NodeVo fromClusterNode(ResourceWrapper name, ClusterNode node) {
|
||||
return fromClusterNode(name.getShowName(), node);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClusterNode} holds total statistics of the same resource name.
|
||||
*
|
||||
* @param name resource name.
|
||||
* @param node the ClusterNode to be presented.
|
||||
* @return node view object.
|
||||
*/
|
||||
public static NodeVo fromClusterNode(String name, ClusterNode node) {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
NodeVo vo = new NodeVo();
|
||||
vo.resource = name;
|
||||
vo.threadNum = node.curThreadNum();
|
||||
vo.passQps = (long) node.passQps();
|
||||
vo.blockQps = (long) node.blockQps();
|
||||
vo.totalQps = (long) node.totalQps();
|
||||
vo.averageRt = (long) node.avgRt();
|
||||
vo.successQps = (long) node.successQps();
|
||||
vo.exceptionQps = (long) node.exceptionQps();
|
||||
vo.oneMinuteException = node.totalException();
|
||||
vo.oneMinutePass = node.totalRequest() - node.blockRequest();
|
||||
vo.oneMinuteBlock = node.blockRequest();
|
||||
vo.oneMinuteTotal = node.totalRequest();
|
||||
vo.timestamp = System.currentTimeMillis();
|
||||
return vo;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getParentId() {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
public void setParentId(String parentId) {
|
||||
this.parentId = parentId;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public Integer getThreadNum() {
|
||||
return threadNum;
|
||||
}
|
||||
|
||||
public void setThreadNum(Integer threadNum) {
|
||||
this.threadNum = threadNum;
|
||||
}
|
||||
|
||||
public Long getPassQps() {
|
||||
return passQps;
|
||||
}
|
||||
|
||||
public void setPassQps(Long passQps) {
|
||||
this.passQps = passQps;
|
||||
}
|
||||
|
||||
public Long getBlockQps() {
|
||||
return blockQps;
|
||||
}
|
||||
|
||||
public void setBlockQps(Long blockQps) {
|
||||
this.blockQps = blockQps;
|
||||
}
|
||||
|
||||
public Long getTotalQps() {
|
||||
return totalQps;
|
||||
}
|
||||
|
||||
public void setTotalQps(Long totalQps) {
|
||||
this.totalQps = totalQps;
|
||||
}
|
||||
|
||||
public Long getAverageRt() {
|
||||
return averageRt;
|
||||
}
|
||||
|
||||
public void setAverageRt(Long averageRt) {
|
||||
this.averageRt = averageRt;
|
||||
}
|
||||
|
||||
public Long getSuccessQps() {
|
||||
return successQps;
|
||||
}
|
||||
|
||||
public void setSuccessQps(Long successQps) {
|
||||
this.successQps = successQps;
|
||||
}
|
||||
|
||||
public Long getExceptionQps() {
|
||||
return exceptionQps;
|
||||
}
|
||||
|
||||
public void setExceptionQps(Long exceptionQps) {
|
||||
this.exceptionQps = exceptionQps;
|
||||
}
|
||||
|
||||
public Long getOneMinuteException() {
|
||||
return oneMinuteException;
|
||||
}
|
||||
|
||||
public void setOneMinuteException(Long oneMinuteException) {
|
||||
this.oneMinuteException = oneMinuteException;
|
||||
}
|
||||
|
||||
public Long getOneMinutePass() {
|
||||
return oneMinutePass;
|
||||
}
|
||||
|
||||
public void setOneMinutePass(Long oneMinutePass) {
|
||||
this.oneMinutePass = oneMinutePass;
|
||||
}
|
||||
|
||||
public Long getOneMinuteBlock() {
|
||||
return oneMinuteBlock;
|
||||
}
|
||||
|
||||
public void setOneMinuteBlock(Long oneMinuteBlock) {
|
||||
this.oneMinuteBlock = oneMinuteBlock;
|
||||
}
|
||||
|
||||
public Long getOneMinuteTotal() {
|
||||
return oneMinuteTotal;
|
||||
}
|
||||
|
||||
public void setOneMinuteTotal(Long oneMinuteTotal) {
|
||||
this.oneMinuteTotal = oneMinuteTotal;
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(Long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.csp.sentinel.heartbeat;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.HeartbeatSender;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public final class HeartbeatSenderProvider {
|
||||
|
||||
private static HeartbeatSender heartbeatSender = null;
|
||||
|
||||
static {
|
||||
resolveInstance();
|
||||
}
|
||||
|
||||
private static void resolveInstance() {
|
||||
HeartbeatSender resolved = SpiLoader.of(HeartbeatSender.class).loadHighestPriorityInstance();
|
||||
if (resolved == null) {
|
||||
RecordLog.warn("[HeartbeatSenderProvider] WARN: No existing HeartbeatSender found");
|
||||
} else {
|
||||
heartbeatSender = resolved;
|
||||
RecordLog.info("[HeartbeatSenderProvider] HeartbeatSender activated: {}", resolved.getClass()
|
||||
.getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resolved {@link HeartbeatSender} instance.
|
||||
*
|
||||
* @return resolved {@code HeartbeatSender} instance
|
||||
*/
|
||||
public static HeartbeatSender getHeartbeatSender() {
|
||||
return heartbeatSender;
|
||||
}
|
||||
|
||||
private HeartbeatSenderProvider() {}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface CommandCenter {
|
||||
|
||||
/**
|
||||
* Prepare and init for the command center (e.g. register commands).
|
||||
* This will be executed before starting.
|
||||
*
|
||||
* @throws Exception if error occurs
|
||||
*/
|
||||
void beforeStart() throws Exception;
|
||||
|
||||
/**
|
||||
* Start the command center in the background.
|
||||
* This method should NOT block.
|
||||
*
|
||||
* @throws Exception if error occurs
|
||||
*/
|
||||
void start() throws Exception;
|
||||
|
||||
/**
|
||||
* Stop the command center and do cleanup.
|
||||
*
|
||||
* @throws Exception if error occurs
|
||||
*/
|
||||
void stop() throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The heartbeat sender which is responsible for sending heartbeat to remote dashboard
|
||||
* periodically per {@code interval}.
|
||||
*
|
||||
* @author leyou
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface HeartbeatSender {
|
||||
|
||||
/**
|
||||
* Send heartbeat to Sentinel Dashboard. Each invocation of this method will send
|
||||
* heartbeat once. Sentinel core is responsible for invoking this method
|
||||
* at every {@link #intervalMs()} interval.
|
||||
*
|
||||
* @return whether heartbeat is successfully send.
|
||||
* @throws Exception if error occurs
|
||||
*/
|
||||
boolean sendHeartbeat() throws Exception;
|
||||
|
||||
/**
|
||||
* Default interval in milliseconds of the sender. It would take effect only when
|
||||
* the heartbeat interval is not configured in Sentinel config property.
|
||||
*
|
||||
* @return default interval of the sender in milliseconds
|
||||
*/
|
||||
long intervalMs();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.client;
|
||||
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
|
||||
/**
|
||||
* Basic interface for clients that sending commands.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface CommandClient {
|
||||
|
||||
/**
|
||||
* Send a command to target destination.
|
||||
*
|
||||
* @param host target host
|
||||
* @param port target port
|
||||
* @param request command request
|
||||
* @return the response from target command server
|
||||
* @throws Exception when unexpected error occurs
|
||||
*/
|
||||
CommandResponse sendCommand(String host, int port, CommandRequest request) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.HostNameUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Carpenter Lee
|
||||
* @author Jason Joo
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class TransportConfig {
|
||||
|
||||
public static final String CONSOLE_SERVER = "csp.sentinel.dashboard.server";
|
||||
public static final String SERVER_PORT = "csp.sentinel.api.port";
|
||||
public static final String HEARTBEAT_INTERVAL_MS = "csp.sentinel.heartbeat.interval.ms";
|
||||
public static final String HEARTBEAT_CLIENT_IP = "csp.sentinel.heartbeat.client.ip";
|
||||
public static final String HEARTBEAT_API_PATH = "csp.sentinel.heartbeat.api.path";
|
||||
|
||||
public static final String HEARTBEAT_DEFAULT_PATH = "/registry/machine";
|
||||
|
||||
private static int runtimePort = -1;
|
||||
|
||||
/**
|
||||
* Get heartbeat interval in milliseconds.
|
||||
*
|
||||
* @return heartbeat interval in milliseconds if exists, or null if not configured or invalid config
|
||||
*/
|
||||
public static Long getHeartbeatIntervalMs() {
|
||||
String interval = SentinelConfig.getConfig(HEARTBEAT_INTERVAL_MS);
|
||||
try {
|
||||
return interval == null ? null : Long.parseLong(interval);
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[TransportConfig] Failed to parse heartbeat interval: " + interval);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of Endpoint(protocol, ip/domain, port) indicating Sentinel Dashboard's address.<br>
|
||||
* NOTE: only support <b>HTTP</b> and <b>HTTPS</b> protocol
|
||||
*
|
||||
* @return list of Endpoint(protocol, ip/domain, port). <br>
|
||||
* <b>May not be null</b>. <br>
|
||||
* An empty list returned when not configured.
|
||||
*/
|
||||
public static List<Endpoint> getConsoleServerList() {
|
||||
String config = SentinelConfig.getConfig(CONSOLE_SERVER);
|
||||
List<Endpoint> list = new ArrayList<Endpoint>();
|
||||
if (StringUtil.isBlank(config)) {
|
||||
return list;
|
||||
}
|
||||
|
||||
int pos = -1;
|
||||
int cur = 0;
|
||||
while (true) {
|
||||
pos = config.indexOf(',', cur);
|
||||
if (cur < config.length() - 1 && pos < 0) {
|
||||
// for single segment, pos move to the end
|
||||
pos = config.length();
|
||||
}
|
||||
if (pos < 0) {
|
||||
break;
|
||||
}
|
||||
if (pos <= cur) {
|
||||
cur ++;
|
||||
continue;
|
||||
}
|
||||
// parsing
|
||||
String ipPortStr = config.substring(cur, pos);
|
||||
cur = pos + 1;
|
||||
if (StringUtil.isBlank(ipPortStr)) {
|
||||
continue;
|
||||
}
|
||||
ipPortStr = ipPortStr.trim();
|
||||
int port = 80;
|
||||
Protocol protocol = Protocol.HTTP;
|
||||
if (ipPortStr.startsWith("http://")) {
|
||||
ipPortStr = ipPortStr.substring(7);
|
||||
} else if (ipPortStr.startsWith("https://")) {
|
||||
ipPortStr = ipPortStr.substring(8);
|
||||
port = 443;
|
||||
protocol = Protocol.HTTPS;
|
||||
}
|
||||
int index = ipPortStr.indexOf(":");
|
||||
if (index == 0) {
|
||||
// skip
|
||||
continue;
|
||||
}
|
||||
String host = ipPortStr;
|
||||
if (index >= 0) {
|
||||
try {
|
||||
port = Integer.parseInt(ipPortStr.substring(index + 1));
|
||||
if (port <= 1 || port >= 65535) {
|
||||
throw new RuntimeException("Port number [" + port + "] over range");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("Parse port of dashboard server failed: " + ipPortStr, e);
|
||||
// skip
|
||||
continue;
|
||||
}
|
||||
host = ipPortStr.substring(0, index);
|
||||
}
|
||||
list.add(new Endpoint(protocol, host, port));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static int getRuntimePort() {
|
||||
return runtimePort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Server port of this HTTP server.
|
||||
*
|
||||
* @return the port, maybe null if not configured.
|
||||
*/
|
||||
public static String getPort() {
|
||||
if (runtimePort > 0) {
|
||||
return String.valueOf(runtimePort);
|
||||
}
|
||||
return SentinelConfig.getConfig(SERVER_PORT, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set real port this HTTP server uses.
|
||||
*
|
||||
* @param port real port.
|
||||
*/
|
||||
public static void setRuntimePort(int port) {
|
||||
runtimePort = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get heartbeat client local ip.
|
||||
* If the client ip not configured,it will be the address of local host
|
||||
*
|
||||
* @return the local ip.
|
||||
*/
|
||||
public static String getHeartbeatClientIp() {
|
||||
String ip = SentinelConfig.getConfig(HEARTBEAT_CLIENT_IP, true);
|
||||
if (StringUtil.isBlank(ip)) {
|
||||
ip = HostNameUtil.getIp();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the heartbeat api path. If the machine registry path of the dashboard
|
||||
* is modified, then the API path should also be consistent with the API path of the dashboard.
|
||||
*
|
||||
* @return the heartbeat api path
|
||||
* @since 1.7.1
|
||||
*/
|
||||
public static String getHeartbeatApiPath() {
|
||||
String apiPath = SentinelConfig.getConfig(HEARTBEAT_API_PATH);
|
||||
if (StringUtil.isBlank(apiPath)) {
|
||||
return HEARTBEAT_DEFAULT_PATH;
|
||||
}
|
||||
if (!apiPath.startsWith("/")) {
|
||||
apiPath = "/" + apiPath;
|
||||
}
|
||||
return apiPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.alibaba.csp.sentinel.transport.endpoint;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class Endpoint {
|
||||
private Protocol protocol;
|
||||
|
||||
private String host;
|
||||
|
||||
private int port;
|
||||
|
||||
public Endpoint(Protocol protocol, String host, int port) {
|
||||
this.protocol = protocol;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public Protocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public void setProtocol(Protocol protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Endpoint{" + "protocol=" + protocol + ", host='" + host + ", port=" + port + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.alibaba.csp.sentinel.transport.endpoint;
|
||||
|
||||
/**
|
||||
* @author Leo Li
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public enum Protocol {
|
||||
HTTP,
|
||||
HTTPS;
|
||||
|
||||
public String getProtocol() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.init;
|
||||
|
||||
import com.alibaba.csp.sentinel.command.CommandCenterProvider;
|
||||
import com.alibaba.csp.sentinel.init.InitFunc;
|
||||
import com.alibaba.csp.sentinel.init.InitOrder;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.CommandCenter;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@InitOrder(-1)
|
||||
public class CommandCenterInitFunc implements InitFunc {
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
CommandCenter commandCenter = CommandCenterProvider.getCommandCenter();
|
||||
|
||||
if (commandCenter == null) {
|
||||
RecordLog.warn("[CommandCenterInitFunc] Cannot resolve CommandCenter");
|
||||
return;
|
||||
}
|
||||
|
||||
commandCenter.beforeStart();
|
||||
commandCenter.start();
|
||||
RecordLog.info("[CommandCenterInit] Starting command center: "
|
||||
+ commandCenter.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.init;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider;
|
||||
import com.alibaba.csp.sentinel.init.InitFunc;
|
||||
import com.alibaba.csp.sentinel.init.InitOrder;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.HeartbeatSender;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
|
||||
/**
|
||||
* Global init function for heartbeat sender.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@InitOrder(-1)
|
||||
public class HeartbeatSenderInitFunc implements InitFunc {
|
||||
|
||||
private ScheduledExecutorService pool = null;
|
||||
|
||||
private void initSchedulerIfNeeded() {
|
||||
if (pool == null) {
|
||||
pool = new ScheduledThreadPoolExecutor(2,
|
||||
new NamedThreadFactory("sentinel-heartbeat-send-task", true),
|
||||
new DiscardOldestPolicy());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
HeartbeatSender sender = HeartbeatSenderProvider.getHeartbeatSender();
|
||||
if (sender == null) {
|
||||
RecordLog.warn("[HeartbeatSenderInitFunc] WARN: No HeartbeatSender loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
initSchedulerIfNeeded();
|
||||
long interval = retrieveInterval(sender);
|
||||
setIntervalIfNotExists(interval);
|
||||
scheduleHeartbeatTask(sender, interval);
|
||||
}
|
||||
|
||||
private boolean isValidHeartbeatInterval(Long interval) {
|
||||
return interval != null && interval > 0;
|
||||
}
|
||||
|
||||
private void setIntervalIfNotExists(long interval) {
|
||||
SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, String.valueOf(interval));
|
||||
}
|
||||
|
||||
long retrieveInterval(/*@NonNull*/ HeartbeatSender sender) {
|
||||
Long intervalInConfig = TransportConfig.getHeartbeatIntervalMs();
|
||||
if (isValidHeartbeatInterval(intervalInConfig)) {
|
||||
RecordLog.info("[HeartbeatSenderInitFunc] Using heartbeat interval "
|
||||
+ "in Sentinel config property: " + intervalInConfig);
|
||||
return intervalInConfig;
|
||||
} else {
|
||||
long senderInterval = sender.intervalMs();
|
||||
RecordLog.info("[HeartbeatSenderInit] Heartbeat interval not configured in "
|
||||
+ "config property or invalid, using sender default: " + senderInterval);
|
||||
return senderInterval;
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleHeartbeatTask(/*@NonNull*/ final HeartbeatSender sender, /*@Valid*/ long interval) {
|
||||
pool.scheduleAtFixedRate(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
sender.sendHeartbeat();
|
||||
} catch (Throwable e) {
|
||||
RecordLog.warn("[HeartbeatSender] Send heartbeat error", e);
|
||||
}
|
||||
}
|
||||
}, 5000, interval, TimeUnit.MILLISECONDS);
|
||||
RecordLog.info("[HeartbeatSenderInit] HeartbeatSender started: "
|
||||
+ sender.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.log;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.LoggerSpiProvider;
|
||||
import com.alibaba.csp.sentinel.log.jul.JavaLoggingAdapter;
|
||||
|
||||
/**
|
||||
* Logger for command center.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class CommandCenterLog {
|
||||
|
||||
public static final String LOGGER_NAME = "sentinelCommandCenterLogger";
|
||||
public static final String DEFAULT_LOG_FILENAME = "command-center.log";
|
||||
|
||||
private static com.alibaba.csp.sentinel.log.Logger logger = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
// Load user-defined logger implementation first.
|
||||
logger = LoggerSpiProvider.getLogger(LOGGER_NAME);
|
||||
if (logger == null) {
|
||||
// If no customized loggers are provided, we use the default logger based on JUL.
|
||||
logger = new JavaLoggingAdapter(LOGGER_NAME, DEFAULT_LOG_FILENAME);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.err.println("Error: failed to initialize Sentinel CommandCenterLog");
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void info(String format, Object... arguments) {
|
||||
logger.info(format, arguments);
|
||||
}
|
||||
|
||||
public static void info(String msg, Throwable e) {
|
||||
logger.info(msg, e);
|
||||
}
|
||||
|
||||
public static void warn(String format, Object... arguments) {
|
||||
logger.warn(format, arguments);
|
||||
}
|
||||
|
||||
public static void warn(String msg, Throwable e) {
|
||||
logger.warn(msg, e);
|
||||
}
|
||||
|
||||
public static void trace(String format, Object... arguments) {
|
||||
logger.trace(format, arguments);
|
||||
}
|
||||
|
||||
public static void trace(String msg, Throwable e) {
|
||||
logger.trace(msg, e);
|
||||
}
|
||||
|
||||
public static void debug(String format, Object... arguments) {
|
||||
logger.debug(format, arguments);
|
||||
}
|
||||
|
||||
public static void debug(String msg, Throwable e) {
|
||||
logger.debug(msg, e);
|
||||
}
|
||||
|
||||
public static void error(String format, Object... arguments) {
|
||||
logger.error(format, arguments);
|
||||
}
|
||||
|
||||
public static void error(String msg, Throwable e) {
|
||||
logger.error(msg, e);
|
||||
}
|
||||
|
||||
private CommandCenterLog() {}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.alibaba.csp.sentinel.transport.ssl;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
/**
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class SslFactory {
|
||||
|
||||
private static class SslContextInstance {
|
||||
private static final SSLContext SSL_CONTEXT = initSslContext();
|
||||
}
|
||||
|
||||
private static SSLContext initSslContext() {
|
||||
SSLContext sslContext = null;
|
||||
try {
|
||||
sslContext = SSLContext.getInstance("TLS");
|
||||
X509TrustManager x509TrustManager = new X509TrustManager() {
|
||||
public boolean isServerTrusted(X509Certificate[] certs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isClientTrusted(X509Certificate[] certs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
sslContext.init(null, new TrustManager[] { x509TrustManager }, null);
|
||||
} catch (Exception e) {
|
||||
RecordLog.error("get ssl socket factory error", e);
|
||||
}
|
||||
return sslContext;
|
||||
}
|
||||
|
||||
public static SSLContext getSslConnectionSocketFactory() {
|
||||
return SslContextInstance.SSL_CONTEXT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
|
||||
/**
|
||||
* Util class for HTTP command center.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public final class HttpCommandUtils {
|
||||
|
||||
public static final String REQUEST_TARGET = "command-target";
|
||||
|
||||
public static String getTarget(CommandRequest request) {
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("Request cannot be null");
|
||||
}
|
||||
return request.getMetadata().get(REQUEST_TARGET);
|
||||
}
|
||||
|
||||
private HttpCommandUtils() {}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.alibaba.csp.sentinel.transport.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
|
||||
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemRule;
|
||||
|
||||
/**
|
||||
* Writable data source registry for modifying rules via HTTP API.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public final class WritableDataSourceRegistry {
|
||||
|
||||
private static WritableDataSource<List<FlowRule>> flowDataSource = null;
|
||||
private static WritableDataSource<List<AuthorityRule>> authorityDataSource = null;
|
||||
private static WritableDataSource<List<DegradeRule>> degradeDataSource = null;
|
||||
private static WritableDataSource<List<SystemRule>> systemSource = null;
|
||||
|
||||
public static synchronized void registerFlowDataSource(WritableDataSource<List<FlowRule>> datasource) {
|
||||
flowDataSource = datasource;
|
||||
}
|
||||
|
||||
public static synchronized void registerAuthorityDataSource(WritableDataSource<List<AuthorityRule>> dataSource) {
|
||||
authorityDataSource = dataSource;
|
||||
}
|
||||
|
||||
public static synchronized void registerDegradeDataSource(WritableDataSource<List<DegradeRule>> dataSource) {
|
||||
degradeDataSource = dataSource;
|
||||
}
|
||||
|
||||
public static synchronized void registerSystemDataSource(WritableDataSource<List<SystemRule>> dataSource) {
|
||||
systemSource = dataSource;
|
||||
}
|
||||
|
||||
public static WritableDataSource<List<FlowRule>> getFlowDataSource() {
|
||||
return flowDataSource;
|
||||
}
|
||||
|
||||
public static WritableDataSource<List<AuthorityRule>> getAuthorityDataSource() {
|
||||
return authorityDataSource;
|
||||
}
|
||||
|
||||
public static WritableDataSource<List<DegradeRule>> getDegradeDataSource() {
|
||||
return degradeDataSource;
|
||||
}
|
||||
|
||||
public static WritableDataSource<List<SystemRule>> getSystemSource() {
|
||||
return systemSource;
|
||||
}
|
||||
|
||||
private WritableDataSourceRegistry() {}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
com.alibaba.csp.sentinel.command.handler.BasicInfoCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.FetchActiveRuleCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.FetchClusterNodeByIdCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.FetchClusterNodeHumanCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.FetchJsonTreeCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.FetchOriginCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.FetchSimpleClusterNodeCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.FetchSystemStatusCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.FetchTreeCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.ModifyRulesCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.OnOffGetCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.OnOffSetCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.SendMetricCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.VersionCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.cluster.FetchClusterModeCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.cluster.ModifyClusterModeCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.ApiCommandHandler
|
||||
@@ -0,0 +1,2 @@
|
||||
com.alibaba.csp.sentinel.transport.init.CommandCenterInitFunc
|
||||
com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Protocol;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TransportConfigTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_INTERVAL_MS);
|
||||
SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_CLIENT_IP);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_INTERVAL_MS);
|
||||
SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_CLIENT_IP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeartbeatInterval() {
|
||||
long interval = 20000;
|
||||
assertNull(TransportConfig.getHeartbeatIntervalMs());
|
||||
// Set valid interval.
|
||||
SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, String.valueOf(interval));
|
||||
assertEquals(new Long(interval), TransportConfig.getHeartbeatIntervalMs());
|
||||
// Set invalid interval.
|
||||
SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, "Sentinel");
|
||||
assertNull(TransportConfig.getHeartbeatIntervalMs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeartbeatClientIp() {
|
||||
String clientIp = "10.10.10.10";
|
||||
SentinelConfig.setConfig(TransportConfig.HEARTBEAT_CLIENT_IP, clientIp);
|
||||
// Set heartbeat client ip to system property.
|
||||
String ip = TransportConfig.getHeartbeatClientIp();
|
||||
|
||||
assertNotNull(ip);
|
||||
assertEquals(clientIp, ip);
|
||||
|
||||
// Set no heartbeat client ip.
|
||||
SentinelConfig.setConfig(TransportConfig.HEARTBEAT_CLIENT_IP, "");
|
||||
assertTrue(StringUtil.isNotEmpty(TransportConfig.getHeartbeatClientIp()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeartbeatApiPath() {
|
||||
// use default heartbeat api path
|
||||
assertTrue(StringUtil.isNotEmpty(TransportConfig.getHeartbeatApiPath()));
|
||||
assertEquals(TransportConfig.HEARTBEAT_DEFAULT_PATH, TransportConfig.getHeartbeatApiPath());
|
||||
|
||||
// config heartbeat api path
|
||||
SentinelConfig.setConfig(TransportConfig.HEARTBEAT_API_PATH, "/demo");
|
||||
assertTrue(StringUtil.isNotEmpty(TransportConfig.getHeartbeatApiPath()));
|
||||
assertEquals("/demo", TransportConfig.getHeartbeatApiPath());
|
||||
|
||||
SentinelConfig.setConfig(TransportConfig.HEARTBEAT_API_PATH, "demo/registry");
|
||||
assertEquals("/demo/registry", TransportConfig.getHeartbeatApiPath());
|
||||
|
||||
SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_API_PATH);
|
||||
assertEquals(TransportConfig.HEARTBEAT_DEFAULT_PATH, TransportConfig.getHeartbeatApiPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetConsoleServerList() {
|
||||
// empty
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "");
|
||||
List<Endpoint> list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(0, list.size());
|
||||
|
||||
// single ip
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "112.13.223.3");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(1, list.size());
|
||||
assertEquals("112.13.223.3", list.get(0).getHost());
|
||||
assertEquals(80, list.get(0).getPort());
|
||||
|
||||
// single domain
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(1, list.size());
|
||||
assertEquals("www.dashboard.org", list.get(0).getHost());
|
||||
assertEquals(80, list.get(0).getPort());
|
||||
|
||||
// single ip including port
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:81");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(1, list.size());
|
||||
assertEquals("www.dashboard.org", list.get(0).getHost());
|
||||
assertEquals(81, list.get(0).getPort());
|
||||
|
||||
// mixed
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:81,112.13.223.3,112.13.223.4:8080,www.dashboard.org");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(4, list.size());
|
||||
assertEquals("www.dashboard.org", list.get(0).getHost());
|
||||
assertEquals(81, list.get(0).getPort());
|
||||
assertEquals("112.13.223.3", list.get(1).getHost());
|
||||
assertEquals(80, list.get(1).getPort());
|
||||
assertEquals("112.13.223.4", list.get(2).getHost());
|
||||
assertEquals(8080, list.get(2).getPort());
|
||||
assertEquals("www.dashboard.org", list.get(3).getHost());
|
||||
assertEquals(80, list.get(3).getPort());
|
||||
|
||||
// malformed
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:0");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(0, list.size());
|
||||
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:-1");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(0, list.size());
|
||||
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, ":80");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(0, list.size());
|
||||
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(0, list.size());
|
||||
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:80000");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(0, list.size());
|
||||
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "www.dashboard.org:80000,www.dashboard.org:81,:80");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(1, list.size());
|
||||
assertEquals("www.dashboard.org", list.get(0).getHost());
|
||||
assertEquals(81, list.get(0).getPort());
|
||||
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "https://www.dashboard.org,http://www.dashboard.org:8080,www.dashboard.org,www.dashboard.org:8080");
|
||||
list = TransportConfig.getConsoleServerList();
|
||||
assertNotNull(list);
|
||||
assertEquals(4, list.size());
|
||||
assertEquals(Protocol.HTTPS, list.get(0).getProtocol());
|
||||
assertEquals(Protocol.HTTP, list.get(1).getProtocol());
|
||||
assertEquals(Protocol.HTTP, list.get(2).getProtocol());
|
||||
assertEquals(Protocol.HTTP, list.get(3).getProtocol());
|
||||
assertEquals(443, list.get(0).getPort());
|
||||
assertEquals(80, list.get(2).getPort());
|
||||
}
|
||||
}
|
||||
@@ -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.init;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.transport.HeartbeatSender;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class HeartbeatSenderInitFuncTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_INTERVAL_MS);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
SentinelConfig.removeConfig(TransportConfig.HEARTBEAT_INTERVAL_MS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrieveInterval() {
|
||||
HeartbeatSender sender = mock(HeartbeatSender.class);
|
||||
|
||||
long senderInterval = 5666;
|
||||
long configInterval = 6777;
|
||||
|
||||
when(sender.intervalMs()).thenReturn(senderInterval);
|
||||
|
||||
HeartbeatSenderInitFunc func = new HeartbeatSenderInitFunc();
|
||||
assertEquals(senderInterval, func.retrieveInterval(sender));
|
||||
|
||||
// Invalid interval.
|
||||
SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, "-1");
|
||||
assertEquals(senderInterval, func.retrieveInterval(sender));
|
||||
|
||||
SentinelConfig.setConfig(TransportConfig.HEARTBEAT_INTERVAL_MS, String.valueOf(configInterval));
|
||||
assertEquals(configInterval, func.retrieveInterval(sender));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport</artifactId>
|
||||
<version>1.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>sentinel-transport-netty-http</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<netty.version>4.1.31.Final</netty.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -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
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>sentinel-transport</artifactId>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<version>1.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>sentinel-transport-simple-http</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* 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.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.transport.log.CommandCenterLog;
|
||||
import com.alibaba.csp.sentinel.transport.CommandCenter;
|
||||
import com.alibaba.csp.sentinel.transport.command.http.HttpEventTask;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/***
|
||||
* The simple command center provides service to exchange information.
|
||||
*
|
||||
* @author youji.zj
|
||||
*/
|
||||
public class SimpleHttpCommandCenter implements CommandCenter {
|
||||
|
||||
private static final int PORT_UNINITIALIZED = -1;
|
||||
|
||||
private static final int DEFAULT_SERVER_SO_TIMEOUT = 3000;
|
||||
private static final int DEFAULT_PORT = 8719;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final Map<String, CommandHandler> handlerMap = new ConcurrentHashMap<String, CommandHandler>();
|
||||
|
||||
@SuppressWarnings("PMD.ThreadPoolCreationRule")
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor(
|
||||
new NamedThreadFactory("sentinel-command-center-executor", true));
|
||||
private ExecutorService bizExecutor;
|
||||
|
||||
private ServerSocket socketReference;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void beforeStart() throws Exception {
|
||||
// Register handlers
|
||||
Map<String, CommandHandler> handlers = CommandHandlerProvider.getInstance().namedHandlers();
|
||||
registerCommands(handlers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
int nThreads = Runtime.getRuntime().availableProcessors();
|
||||
this.bizExecutor = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(10),
|
||||
new NamedThreadFactory("sentinel-command-center-service-executor", true),
|
||||
new RejectedExecutionHandler() {
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||
CommandCenterLog.info("EventTask rejected");
|
||||
throw new RejectedExecutionException();
|
||||
}
|
||||
});
|
||||
|
||||
Runnable serverInitTask = new Runnable() {
|
||||
int port;
|
||||
|
||||
{
|
||||
try {
|
||||
port = Integer.parseInt(TransportConfig.getPort());
|
||||
} catch (Exception e) {
|
||||
port = DEFAULT_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean success = false;
|
||||
ServerSocket serverSocket = getServerSocketFromBasePort(port);
|
||||
|
||||
if (serverSocket != null) {
|
||||
CommandCenterLog.info("[CommandCenter] Begin listening at port " + serverSocket.getLocalPort());
|
||||
socketReference = serverSocket;
|
||||
executor.submit(new ServerThread(serverSocket));
|
||||
success = true;
|
||||
port = serverSocket.getLocalPort();
|
||||
} else {
|
||||
CommandCenterLog.info("[CommandCenter] chooses port fail, http command center will not work");
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
port = PORT_UNINITIALIZED;
|
||||
}
|
||||
|
||||
TransportConfig.setRuntimePort(port);
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
new Thread(serverInitTask).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a server socket from an available port from a base port.<br>
|
||||
* Increasing on port number will occur when the port has already been used.
|
||||
*
|
||||
* @param basePort base port to start
|
||||
* @return new socket with available port
|
||||
*/
|
||||
private static ServerSocket getServerSocketFromBasePort(int basePort) {
|
||||
int tryCount = 0;
|
||||
while (true) {
|
||||
try {
|
||||
ServerSocket server = new ServerSocket(basePort + tryCount / 3, 100);
|
||||
server.setReuseAddress(true);
|
||||
return server;
|
||||
} catch (IOException e) {
|
||||
tryCount++;
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(30);
|
||||
} catch (InterruptedException e1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
if (socketReference != null) {
|
||||
try {
|
||||
socketReference.close();
|
||||
} catch (IOException e) {
|
||||
CommandCenterLog.warn("Error when releasing the server socket", e);
|
||||
}
|
||||
}
|
||||
bizExecutor.shutdownNow();
|
||||
executor.shutdownNow();
|
||||
TransportConfig.setRuntimePort(PORT_UNINITIALIZED);
|
||||
handlerMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name set of all registered commands.
|
||||
*/
|
||||
public static Set<String> getCommands() {
|
||||
return handlerMap.keySet();
|
||||
}
|
||||
|
||||
class ServerThread extends Thread {
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
ServerThread(ServerSocket s) {
|
||||
this.serverSocket = s;
|
||||
setName("sentinel-courier-server-accept-thread");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
Socket socket = null;
|
||||
try {
|
||||
socket = this.serverSocket.accept();
|
||||
setSocketSoTimeout(socket);
|
||||
HttpEventTask eventTask = new HttpEventTask(socket);
|
||||
bizExecutor.submit(eventTask);
|
||||
} catch (Exception e) {
|
||||
CommandCenterLog.info("Server error", e);
|
||||
if (socket != null) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (Exception e1) {
|
||||
CommandCenterLog.info("Error when closing an opened socket", e1);
|
||||
}
|
||||
}
|
||||
try {
|
||||
// In case of infinite log.
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e1) {
|
||||
// Indicates the task should stop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static CommandHandler getHandler(String commandName) {
|
||||
return handlerMap.get(commandName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void registerCommands(Map<String, CommandHandler> handlerMap) {
|
||||
if (handlerMap != null) {
|
||||
for (Entry<String, CommandHandler> e : handlerMap.entrySet()) {
|
||||
registerCommand(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void registerCommand(String commandName, CommandHandler handler) {
|
||||
if (StringUtil.isEmpty(commandName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (handlerMap.containsKey(commandName)) {
|
||||
CommandCenterLog.warn("Register failed (duplicate command): " + commandName);
|
||||
return;
|
||||
}
|
||||
|
||||
handlerMap.put(commandName, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid server thread hang, 3 seconds timeout by default.
|
||||
*/
|
||||
private void setSocketSoTimeout(Socket socket) throws SocketException {
|
||||
if (socket != null) {
|
||||
socket.setSoTimeout(DEFAULT_SERVER_SO_TIMEOUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
import com.alibaba.csp.sentinel.transport.command.http.StatusCode;
|
||||
|
||||
/**
|
||||
* Represent exception with status code processing a request
|
||||
*
|
||||
* @author jason
|
||||
*
|
||||
*/
|
||||
public class RequestException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private StatusCode statusCode = StatusCode.BAD_REQUEST;
|
||||
|
||||
public RequestException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public RequestException(StatusCode statusCode, String msg) {
|
||||
super(msg);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public StatusCode getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* 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.http;
|
||||
|
||||
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.SimpleHttpCommandCenter;
|
||||
import com.alibaba.csp.sentinel.transport.command.exception.RequestException;
|
||||
import com.alibaba.csp.sentinel.transport.util.HttpCommandUtils;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.Socket;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The task handles incoming command request in HTTP protocol.
|
||||
*
|
||||
* @author youji.zj
|
||||
* @author Eric Zhao
|
||||
* @author Jason Joo
|
||||
*/
|
||||
public class HttpEventTask implements Runnable {
|
||||
|
||||
public static final String SERVER_ERROR_MESSAGE = "Command server error";
|
||||
public static final String INVALID_COMMAND_MESSAGE = "Invalid command";
|
||||
|
||||
private final Socket socket;
|
||||
|
||||
private boolean writtenHead = false;
|
||||
|
||||
public HttpEventTask(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (socket == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PrintWriter printWriter = null;
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
inputStream = new BufferedInputStream(socket.getInputStream());
|
||||
OutputStream outputStream = socket.getOutputStream();
|
||||
|
||||
printWriter = new PrintWriter(
|
||||
new OutputStreamWriter(outputStream, Charset.forName(SentinelConfig.charset())));
|
||||
|
||||
String firstLine = readLine(inputStream);
|
||||
CommandCenterLog.info("[SimpleHttpCommandCenter] Socket income: " + firstLine
|
||||
+ ", addr: " + socket.getInetAddress());
|
||||
CommandRequest request = processQueryString(firstLine);
|
||||
|
||||
if (firstLine.length() > 4 && StringUtil.equalsIgnoreCase("POST", firstLine.substring(0, 4))) {
|
||||
// Deal with post method
|
||||
processPostRequest(inputStream, request);
|
||||
}
|
||||
|
||||
// Validate the target command.
|
||||
String commandName = HttpCommandUtils.getTarget(request);
|
||||
if (StringUtil.isBlank(commandName)) {
|
||||
writeResponse(printWriter, StatusCode.BAD_REQUEST, INVALID_COMMAND_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the matching command handler.
|
||||
CommandHandler<?> commandHandler = SimpleHttpCommandCenter.getHandler(commandName);
|
||||
if (commandHandler != null) {
|
||||
CommandResponse<?> response = commandHandler.handle(request);
|
||||
handleResponse(response, printWriter);
|
||||
} else {
|
||||
// No matching command handler.
|
||||
writeResponse(printWriter, StatusCode.BAD_REQUEST, "Unknown command `" + commandName + '`');
|
||||
}
|
||||
|
||||
long cost = System.currentTimeMillis() - start;
|
||||
CommandCenterLog.info("[SimpleHttpCommandCenter] Deal a socket task: " + firstLine
|
||||
+ ", address: " + socket.getInetAddress() + ", time cost: " + cost + " ms");
|
||||
} catch (RequestException e) {
|
||||
writeResponse(printWriter, e.getStatusCode(), e.getMessage());
|
||||
} catch (Throwable e) {
|
||||
CommandCenterLog.warn("[SimpleHttpCommandCenter] CommandCenter error", e);
|
||||
try {
|
||||
if (printWriter != null) {
|
||||
String errorMessage = SERVER_ERROR_MESSAGE;
|
||||
e.printStackTrace();
|
||||
if (!writtenHead) {
|
||||
writeResponse(printWriter, StatusCode.INTERNAL_SERVER_ERROR, errorMessage);
|
||||
} else {
|
||||
printWriter.println(errorMessage);
|
||||
}
|
||||
printWriter.flush();
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
CommandCenterLog.warn("Failed to write error response", e1);
|
||||
}
|
||||
} finally {
|
||||
closeResource(inputStream);
|
||||
closeResource(printWriter);
|
||||
closeResource(socket);
|
||||
}
|
||||
}
|
||||
|
||||
private static String readLine(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(64);
|
||||
int data;
|
||||
while (true) {
|
||||
data = in.read();
|
||||
if (data < 0) {
|
||||
break;
|
||||
}
|
||||
if (data == '\n') {
|
||||
break;
|
||||
}
|
||||
bos.write(data);
|
||||
}
|
||||
byte[] arr = bos.toByteArray();
|
||||
if (arr.length > 0 && arr[arr.length - 1] == '\r') {
|
||||
return new String(arr, 0, arr.length - 1, SentinelConfig.charset());
|
||||
}
|
||||
return new String(arr, SentinelConfig.charset());
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to process the body of POST request additionally.
|
||||
*
|
||||
* @param in
|
||||
* @param request
|
||||
* @throws RequestException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected static void processPostRequest(InputStream in, CommandRequest request)
|
||||
throws RequestException, IOException {
|
||||
Map<String, String> headerMap = parsePostHeaders(in);
|
||||
|
||||
if (headerMap == null) {
|
||||
// illegal request
|
||||
CommandCenterLog.warn("Illegal request read: null headerMap");
|
||||
throw new RequestException(StatusCode.BAD_REQUEST, "");
|
||||
}
|
||||
|
||||
if (headerMap.containsKey("content-type") && !checkContentTypeSupported(headerMap.get("content-type"))) {
|
||||
// not supported Content-type
|
||||
CommandCenterLog.warn("Request not supported: unsupported Content-Type: " + headerMap.get("content-type"));
|
||||
throw new RequestException(StatusCode.UNSUPPORTED_MEDIA_TYPE,
|
||||
"Only form-encoded post request is supported");
|
||||
}
|
||||
|
||||
int bodyLength = 0;
|
||||
try {
|
||||
bodyLength = Integer.parseInt(headerMap.get("content-length"));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (bodyLength < 1) {
|
||||
// illegal request without Content-length header
|
||||
CommandCenterLog.warn("Request not supported: no available Content-Length in headers");
|
||||
throw new RequestException(StatusCode.LENGTH_REQUIRED, "No legal Content-Length");
|
||||
}
|
||||
|
||||
parseParams(readBody(in, bodyLength), request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process header line in request
|
||||
*
|
||||
* @param in
|
||||
* @return return headers in a Map, null for illegal request
|
||||
* @throws IOException
|
||||
*/
|
||||
protected static Map<String, String> parsePostHeaders(InputStream in) throws IOException {
|
||||
Map<String, String> headerMap = new HashMap<String, String>(4);
|
||||
String line;
|
||||
while (true) {
|
||||
line = readLine(in);
|
||||
if (line == null || line.length() == 0) {
|
||||
// empty line
|
||||
return headerMap;
|
||||
}
|
||||
int index = line.indexOf(":");
|
||||
if (index < 1) {
|
||||
// empty value, abandon
|
||||
continue;
|
||||
}
|
||||
String headerName = line.substring(0, index).trim().toLowerCase();
|
||||
String headerValue = line.substring(index + 1).trim();
|
||||
if (headerValue.length() > 0) {
|
||||
headerMap.put(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkContentTypeSupported(String contentType) {
|
||||
int idx = contentType.indexOf(";");
|
||||
String type;
|
||||
if (idx > 0) {
|
||||
type = contentType.substring(0, idx).toLowerCase().trim();
|
||||
} else {
|
||||
type = contentType.toLowerCase();
|
||||
}
|
||||
// Actually in RFC "x-*" shouldn't have any properties like "type/subtype; key=val"
|
||||
// But some library do add it. So we will be compatible with that but force to
|
||||
// encoding specified in configuration as legacy processing will do.
|
||||
if (!type.contains("application/x-www-form-urlencoded")) {
|
||||
// Not supported request type
|
||||
// Now simple-http only support form-encoded post request.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String readBody(InputStream in, int bodyLength)
|
||||
throws IOException, RequestException {
|
||||
byte[] buf = new byte[bodyLength];
|
||||
int pos = 0;
|
||||
while (pos < bodyLength) {
|
||||
int l = in.read(buf, pos, Math.min(512, bodyLength - pos));
|
||||
if (l < 0) {
|
||||
break;
|
||||
}
|
||||
if (l == 0) {
|
||||
continue;
|
||||
}
|
||||
pos += l;
|
||||
}
|
||||
// Only allow partial
|
||||
return new String(buf, 0, pos, SentinelConfig.charset());
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume all the body submitted and parse params into {@link CommandRequest}
|
||||
*
|
||||
* @param queryString
|
||||
* @param request
|
||||
*/
|
||||
protected static void parseParams(String queryString, CommandRequest request) {
|
||||
if (queryString == null || queryString.length() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = 0, pos = -1;
|
||||
|
||||
// check anchor
|
||||
queryString = removeAnchor(queryString);
|
||||
|
||||
while (true) {
|
||||
offset = pos + 1;
|
||||
pos = queryString.indexOf('&', offset);
|
||||
if (offset == pos) {
|
||||
// empty
|
||||
continue;
|
||||
}
|
||||
parseSingleParam(queryString.substring(offset, pos == -1 ? queryString.length() : pos), request);
|
||||
|
||||
if (pos < 0) {
|
||||
// reach the end
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void closeResource(Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception e) {
|
||||
CommandCenterLog.warn("[SimpleHttpCommandCenter] Close resource failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void handleResponse(CommandResponse<T> response, final PrintWriter printWriter) throws Exception {
|
||||
if (response.isSuccess()) {
|
||||
if (response.getResult() == null) {
|
||||
writeResponse(printWriter, StatusCode.OK, null);
|
||||
return;
|
||||
}
|
||||
// Here we directly use `toString` to encode the result to plain text.
|
||||
byte[] buffer = response.getResult().toString().getBytes(SentinelConfig.charset());
|
||||
writeResponse(printWriter, StatusCode.OK, new String(buffer));
|
||||
} else {
|
||||
String msg = SERVER_ERROR_MESSAGE;
|
||||
if (response.getException() != null) {
|
||||
msg = response.getException().getMessage();
|
||||
}
|
||||
writeResponse(printWriter, StatusCode.BAD_REQUEST, msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResponse(PrintWriter out, StatusCode statusCode, String message) {
|
||||
out.print("HTTP/1.0 " + statusCode.toString() + "\r\n"
|
||||
+ "Content-Length: " + (message == null ? 0 : message.getBytes().length) + "\r\n"
|
||||
+ "Connection: close\r\n\r\n");
|
||||
if (message != null) {
|
||||
out.print(message);
|
||||
}
|
||||
out.flush();
|
||||
writtenHead = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse raw HTTP request line to a {@link CommandRequest}.
|
||||
*
|
||||
* @param line HTTP request line
|
||||
* @return parsed command request
|
||||
*/
|
||||
protected static CommandRequest processQueryString(String line) {
|
||||
CommandRequest request = new CommandRequest();
|
||||
if (StringUtil.isBlank(line)) {
|
||||
return request;
|
||||
}
|
||||
int start = line.indexOf('/');
|
||||
int ask = line.indexOf('?') == -1 ? line.lastIndexOf(' ') : line.indexOf('?');
|
||||
int space = line.lastIndexOf(' ');
|
||||
String target = line.substring(start != -1 ? start + 1 : 0, ask != -1 ? ask : line.length());
|
||||
request.addMetadata(HttpCommandUtils.REQUEST_TARGET, target);
|
||||
if (ask == -1 || ask == space) {
|
||||
return request;
|
||||
}
|
||||
String parameterStr = line.substring(ask != -1 ? ask + 1 : 0, space != -1 ? space : line.length());
|
||||
parseParams(parameterStr, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate query from "a=1&b=2#mark" to "a=1&b=2"
|
||||
*
|
||||
* @param str
|
||||
* @return
|
||||
*/
|
||||
protected static String removeAnchor(String str) {
|
||||
if (str == null || str.length() == 0) {
|
||||
return str;
|
||||
}
|
||||
|
||||
int anchor = str.indexOf('#');
|
||||
|
||||
if (anchor == 0) {
|
||||
return "";
|
||||
} else if (anchor > 0) {
|
||||
return str.substring(0, anchor);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
protected static void parseSingleParam(String single, CommandRequest request) {
|
||||
if (single == null || single.length() < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = single.indexOf('=');
|
||||
if (index <= 0 || index >= single.length() - 1) {
|
||||
// empty key/val or nothing found
|
||||
return;
|
||||
}
|
||||
|
||||
String value = StringUtil.trim(single.substring(index + 1));
|
||||
String key = StringUtil.trim(single.substring(0, index));
|
||||
try {
|
||||
key = URLDecoder.decode(key, SentinelConfig.charset());
|
||||
value = URLDecoder.decode(value, SentinelConfig.charset());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
}
|
||||
|
||||
request.addParam(key, value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.http;
|
||||
|
||||
/**
|
||||
* @author Jason Joo
|
||||
*/
|
||||
public enum StatusCode {
|
||||
/**
|
||||
* 200 OK.
|
||||
*/
|
||||
OK(200, "OK"),
|
||||
BAD_REQUEST(400, "Bad Request"),
|
||||
REQUEST_TIMEOUT(408, "Request Timeout"),
|
||||
LENGTH_REQUIRED(411, "Length Required"),
|
||||
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
|
||||
INTERNAL_SERVER_ERROR(500, "Internal Server Error");
|
||||
|
||||
private int code;
|
||||
private String desc;
|
||||
private String representation;
|
||||
|
||||
StatusCode(int code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
this.representation = code + " " + desc;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return representation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import com.alibaba.csp.sentinel.util.AppNameUtil;
|
||||
import com.alibaba.csp.sentinel.util.HostNameUtil;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
|
||||
/**
|
||||
* Heart beat message entity.
|
||||
* The message consists of key-value pair parameters.
|
||||
*
|
||||
* @author leyou
|
||||
*/
|
||||
public class HeartbeatMessage {
|
||||
|
||||
private final Map<String, String> message = new HashMap<String, String>();
|
||||
|
||||
public HeartbeatMessage() {
|
||||
message.put("hostname", HostNameUtil.getHostName());
|
||||
message.put("ip", TransportConfig.getHeartbeatClientIp());
|
||||
message.put("app", AppNameUtil.getAppName());
|
||||
// Put application type (since 1.6.0).
|
||||
message.put("app_type", String.valueOf(SentinelConfig.getAppType()));
|
||||
message.put("port", String.valueOf(TransportConfig.getPort()));
|
||||
}
|
||||
|
||||
public HeartbeatMessage registerInformation(String key, String value) {
|
||||
message.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> generateCurrentMessage() {
|
||||
// Version of Sentinel.
|
||||
message.put("v", Constants.SENTINEL_VERSION);
|
||||
// Actually timestamp.
|
||||
message.put("version", String.valueOf(TimeUtil.currentTimeMillis()));
|
||||
message.put("port", String.valueOf(TransportConfig.getPort()));
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.HeartbeatSender;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import com.alibaba.csp.sentinel.transport.heartbeat.client.SimpleHttpClient;
|
||||
import com.alibaba.csp.sentinel.transport.heartbeat.client.SimpleHttpRequest;
|
||||
import com.alibaba.csp.sentinel.transport.heartbeat.client.SimpleHttpResponse;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The heartbeat sender provides basic API for sending heartbeat request to provided target.
|
||||
* This implementation is based on a trivial HTTP client.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @author Carpenter Lee
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class SimpleHttpHeartbeatSender implements HeartbeatSender {
|
||||
|
||||
private static final int OK_STATUS = 200;
|
||||
|
||||
private static final long DEFAULT_INTERVAL = 1000 * 10;
|
||||
|
||||
private final HeartbeatMessage heartBeat = new HeartbeatMessage();
|
||||
private final SimpleHttpClient httpClient = new SimpleHttpClient();
|
||||
|
||||
private final List<Endpoint> addressList;
|
||||
|
||||
private int currentAddressIdx = 0;
|
||||
|
||||
public SimpleHttpHeartbeatSender() {
|
||||
// Retrieve the list of default addresses.
|
||||
List<Endpoint> newAddrs = TransportConfig.getConsoleServerList();
|
||||
if (newAddrs.isEmpty()) {
|
||||
RecordLog.warn("[SimpleHttpHeartbeatSender] Dashboard server address not configured or not available");
|
||||
} else {
|
||||
RecordLog.info("[SimpleHttpHeartbeatSender] Default console address list retrieved: {}", newAddrs);
|
||||
}
|
||||
this.addressList = newAddrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendHeartbeat() throws Exception {
|
||||
if (TransportConfig.getRuntimePort() <= 0) {
|
||||
RecordLog.info("[SimpleHttpHeartbeatSender] Command server port not initialized, won't send heartbeat");
|
||||
return false;
|
||||
}
|
||||
Endpoint addrInfo = getAvailableAddress();
|
||||
if (addrInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SimpleHttpRequest request = new SimpleHttpRequest(addrInfo, TransportConfig.getHeartbeatApiPath());
|
||||
request.setParams(heartBeat.generateCurrentMessage());
|
||||
try {
|
||||
SimpleHttpResponse response = httpClient.post(request);
|
||||
if (response.getStatusCode() == OK_STATUS) {
|
||||
return true;
|
||||
} else if (clientErrorCode(response.getStatusCode()) || serverErrorCode(response.getStatusCode())) {
|
||||
RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addrInfo
|
||||
+ ", http status code: " + response.getStatusCode());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addrInfo, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long intervalMs() {
|
||||
return DEFAULT_INTERVAL;
|
||||
}
|
||||
|
||||
private Endpoint getAvailableAddress() {
|
||||
if (addressList == null || addressList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (currentAddressIdx < 0) {
|
||||
currentAddressIdx = 0;
|
||||
}
|
||||
int index = currentAddressIdx % addressList.size();
|
||||
return addressList.get(index);
|
||||
}
|
||||
|
||||
private boolean clientErrorCode(int code) {
|
||||
return code > 399 && code < 500;
|
||||
}
|
||||
|
||||
private boolean serverErrorCode(int code) {
|
||||
return code > 499 && code < 600;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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.client;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A very simple HTTP client that only supports GET/POST method and plain text request body.
|
||||
* The Content-Type header is always set as <pre>application/x-www-form-urlencoded</pre>.
|
||||
* All parameters in the request will be encoded using {@link URLEncoder#encode(String, String)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* The result of a HTTP invocation will be wrapped as a {@link SimpleHttpResponse}. Content in response body
|
||||
* will be automatically decoded to string with provided charset.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is a blocking and synchronous client, so an invocation will await the response until timeout exceed.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this is a very NAIVE client, {@code Content-Length} must be specified in the
|
||||
* HTTP response header, otherwise, the response body will be dropped. All other body type such as
|
||||
* {@code Transfer-Encoding: chunked}, {@code Transfer-Encoding: deflate} are not supported.
|
||||
* </p>
|
||||
*
|
||||
* @author leyou
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class SimpleHttpClient {
|
||||
|
||||
/**
|
||||
* Execute a GET HTTP request.
|
||||
*
|
||||
* @param request HTTP request
|
||||
* @return the response if the request is successful
|
||||
* @throws IOException when connection cannot be established or the connection is interrupted
|
||||
*/
|
||||
public SimpleHttpResponse get(SimpleHttpRequest request) throws IOException {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return request(request.getEndpoint(),
|
||||
RequestMethod.GET, request.getRequestPath(), request.getParams(),
|
||||
request.getCharset(), request.getSoTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a POST HTTP request.
|
||||
*
|
||||
* @param request HTTP request
|
||||
* @return the response if the request is successful
|
||||
* @throws IOException when connection cannot be established or the connection is interrupted
|
||||
*/
|
||||
public SimpleHttpResponse post(SimpleHttpRequest request) throws IOException {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return request(request.getEndpoint(),
|
||||
RequestMethod.POST, request.getRequestPath(),
|
||||
request.getParams(), request.getCharset(),
|
||||
request.getSoTimeout());
|
||||
}
|
||||
|
||||
private SimpleHttpResponse request(Endpoint endpoint,
|
||||
RequestMethod type, String requestPath,
|
||||
Map<String, String> paramsMap, Charset charset, int soTimeout)
|
||||
throws IOException {
|
||||
Socket socket = null;
|
||||
BufferedWriter writer;
|
||||
InetSocketAddress socketAddress = new InetSocketAddress(endpoint.getHost(), endpoint.getPort());
|
||||
try {
|
||||
socket = SocketFactory.getSocket(endpoint.getProtocol());
|
||||
socket.setSoTimeout(soTimeout);
|
||||
socket.connect(socketAddress, soTimeout);
|
||||
|
||||
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), charset));
|
||||
requestPath = getRequestPath(type, requestPath, paramsMap, charset);
|
||||
writer.write(getStatusLine(type, requestPath) + "\r\n");
|
||||
if (charset != null) {
|
||||
writer.write("Content-Type: application/x-www-form-urlencoded; charset=" + charset.name() + "\r\n");
|
||||
} else {
|
||||
writer.write("Content-Type: application/x-www-form-urlencoded\r\n");
|
||||
}
|
||||
writer.write("Host: " + socketAddress.getHostName() + "\r\n");
|
||||
if (type == RequestMethod.GET) {
|
||||
writer.write("Content-Length: 0\r\n");
|
||||
writer.write("\r\n");
|
||||
} else {
|
||||
// POST method.
|
||||
String params = encodeRequestParams(paramsMap, charset);
|
||||
writer.write("Content-Length: " + params.getBytes(charset).length + "\r\n");
|
||||
writer.write("\r\n");
|
||||
writer.write(params);
|
||||
}
|
||||
writer.flush();
|
||||
|
||||
SimpleHttpResponse response = new SimpleHttpResponseParser().parse(socket.getInputStream());
|
||||
socket.close();
|
||||
socket = null;
|
||||
return response;
|
||||
} finally {
|
||||
if (socket != null) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("Error when closing {} request to {} in SimpleHttpClient", type, socketAddress, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getRequestPath(RequestMethod type, String requestPath,
|
||||
Map<String, String> paramsMap, Charset charset) {
|
||||
if (type == RequestMethod.GET) {
|
||||
if (requestPath.contains("?")) {
|
||||
return requestPath + "&" + encodeRequestParams(paramsMap, charset);
|
||||
}
|
||||
return requestPath + "?" + encodeRequestParams(paramsMap, charset);
|
||||
}
|
||||
return requestPath;
|
||||
}
|
||||
|
||||
private String getStatusLine(RequestMethod type, String requestPath) {
|
||||
if (type == RequestMethod.POST) {
|
||||
return "POST " + requestPath + " HTTP/1.1";
|
||||
}
|
||||
return "GET " + requestPath + " HTTP/1.1";
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode and get the URL request parameters.
|
||||
*
|
||||
* @param paramsMap pair of parameters
|
||||
* @param charset charset
|
||||
* @return encoded request parameters, or empty string ("") if no parameters are provided
|
||||
*/
|
||||
private String encodeRequestParams(Map<String, String> paramsMap, Charset charset) {
|
||||
if (charset == null) {
|
||||
throw new IllegalArgumentException("charset is not allowed to be null");
|
||||
}
|
||||
if (paramsMap == null || paramsMap.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
StringBuilder paramsBuilder = new StringBuilder();
|
||||
for (Entry<String, String> entry : paramsMap.entrySet()) {
|
||||
if (entry.getKey() == null || entry.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
paramsBuilder.append(URLEncoder.encode(entry.getKey(), charset.name()))
|
||||
.append("=")
|
||||
.append(URLEncoder.encode(entry.getValue(), charset.name()))
|
||||
.append("&");
|
||||
}
|
||||
if (paramsBuilder.length() > 0) {
|
||||
// Remove the last '&'.
|
||||
paramsBuilder.delete(paramsBuilder.length() - 1, paramsBuilder.length());
|
||||
}
|
||||
return paramsBuilder.toString();
|
||||
} catch (Throwable e) {
|
||||
RecordLog.warn("Encode request params fail", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private enum RequestMethod {
|
||||
GET,
|
||||
POST
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.client;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
|
||||
|
||||
/**
|
||||
* Simple HTTP request representation.
|
||||
*
|
||||
* @author leyou
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class SimpleHttpRequest {
|
||||
|
||||
private Endpoint endpoint;
|
||||
private String requestPath = "";
|
||||
private int soTimeout = 3000;
|
||||
private Map<String, String> params;
|
||||
private Charset charset = Charset.forName(SentinelConfig.charset());
|
||||
|
||||
public SimpleHttpRequest(Endpoint endpoint, String requestPath) {
|
||||
this.endpoint = endpoint;
|
||||
this.requestPath = requestPath;
|
||||
}
|
||||
|
||||
public Endpoint getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest setEndpoint(Endpoint endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getRequestPath() {
|
||||
return requestPath;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest setRequestPath(String requestPath) {
|
||||
this.requestPath = requestPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSoTimeout() {
|
||||
return soTimeout;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest setSoTimeout(int soTimeout) {
|
||||
this.soTimeout = soTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest setParams(Map<String, String> params) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Charset getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest addParam(String key, String value) {
|
||||
if (StringUtil.isBlank(key)) {
|
||||
throw new IllegalArgumentException("Parameter key cannot be empty");
|
||||
}
|
||||
if (params == null) {
|
||||
params = new HashMap<String, String>();
|
||||
}
|
||||
params.put(key, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.client;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Simple HTTP response representation.
|
||||
*
|
||||
* @author leyou
|
||||
*/
|
||||
public class SimpleHttpResponse {
|
||||
|
||||
private Charset charset = Charset.forName(SentinelConfig.charset());
|
||||
|
||||
private String statusLine;
|
||||
private int statusCode;
|
||||
private Map<String, String> headers;
|
||||
private byte[] body;
|
||||
|
||||
public SimpleHttpResponse(String statusLine, Map<String, String> headers) {
|
||||
this.statusLine = statusLine;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public SimpleHttpResponse(String statusLine, Map<String, String> headers, byte[] body) {
|
||||
this.statusLine = statusLine;
|
||||
this.headers = headers;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
private void parseCharset() {
|
||||
String contentType = getHeader("Content-Type");
|
||||
for (String str : contentType.split(" ")) {
|
||||
if (str.toLowerCase().startsWith("charset=")) {
|
||||
charset = Charset.forName(str.split("=")[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseCode() {
|
||||
this.statusCode = Integer.parseInt(statusLine.split(" ")[1]);
|
||||
}
|
||||
|
||||
public void setBody(byte[] body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public String getStatusLine() {
|
||||
return statusLine;
|
||||
}
|
||||
|
||||
public Integer getStatusCode() {
|
||||
if (statusCode == 0) {
|
||||
parseCode();
|
||||
}
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header of the key ignoring case.
|
||||
*
|
||||
* @param key header key
|
||||
* @return header value
|
||||
*/
|
||||
public String getHeader(String key) {
|
||||
if (headers == null) {
|
||||
return null;
|
||||
}
|
||||
String value = headers.get(key);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
if (entry.getKey().equalsIgnoreCase(key)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getBodyAsString() {
|
||||
parseCharset();
|
||||
return new String(body, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(statusLine)
|
||||
.append("\r\n");
|
||||
if (headers != null) {
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
buf.append(entry.getKey()).append(": ").append(entry.getValue())
|
||||
.append("\r\n");
|
||||
}
|
||||
}
|
||||
buf.append("\r\n");
|
||||
buf.append(getBodyAsString());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The parser provides functionality to parse raw bytes HTTP response to a {@link SimpleHttpResponse}.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this is a very NAIVE parser, {@code Content-Length} must be specified in the
|
||||
* HTTP response header, otherwise, the body will be dropped. All other body type such as
|
||||
* {@code Transfer-Encoding: chunked}, {@code Transfer-Encoding: deflate} are not supported.
|
||||
* </p>
|
||||
*
|
||||
* @author leyou
|
||||
*/
|
||||
public class SimpleHttpResponseParser {
|
||||
|
||||
private static final int MAX_BODY_SIZE = 1024 * 1024 * 4;
|
||||
private byte[] buf;
|
||||
|
||||
public SimpleHttpResponseParser(int maxSize) {
|
||||
if (maxSize < 0) {
|
||||
throw new IllegalArgumentException("maxSize must > 0");
|
||||
}
|
||||
this.buf = new byte[maxSize];
|
||||
}
|
||||
|
||||
public SimpleHttpResponseParser() {
|
||||
this(1024 * 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse bytes from an input stream to a {@link SimpleHttpResponse}.
|
||||
*
|
||||
* @param in input stream
|
||||
* @return parsed HTTP response entity
|
||||
* @throws IOException when an IO error occurs
|
||||
*/
|
||||
public SimpleHttpResponse parse(InputStream in) throws IOException {
|
||||
int bg = 0;
|
||||
int len;
|
||||
String statusLine = null;
|
||||
Map<String, String> headers = new HashMap<String, String>();
|
||||
Charset charset = Charset.forName("utf-8");
|
||||
int contentLength = -1;
|
||||
SimpleHttpResponse response;
|
||||
while (true) {
|
||||
if (bg >= buf.length) {
|
||||
throw new IndexOutOfBoundsException("buf index out of range: " + bg + ", buf.length=" + buf.length);
|
||||
}
|
||||
if ((len = in.read(buf, bg, buf.length - bg)) > 0) {
|
||||
bg += len;
|
||||
len = bg;
|
||||
int idx;
|
||||
int parseBg = 0;
|
||||
while ((idx = indexOfCRLF(parseBg, len)) >= 0) {
|
||||
String line = new String(buf, parseBg, idx - parseBg, charset);
|
||||
parseBg = idx + 2;
|
||||
if (statusLine == null) {
|
||||
statusLine = line;
|
||||
} else {
|
||||
if (line.isEmpty()) {
|
||||
//When the `Content-Length` is absent, parse the rest of the bytes as body directly.
|
||||
//if (contentLength == -1) {
|
||||
// contentLength = MAX_BODY_SIZE;
|
||||
//}
|
||||
|
||||
// Parse HTTP body.
|
||||
// When the `Content-Length` is absent, drop the body, return directly.
|
||||
response = new SimpleHttpResponse(statusLine, headers);
|
||||
if (contentLength <= 0) {
|
||||
return response;
|
||||
}
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
|
||||
// `Content-Length` is not equal to exact length.
|
||||
if (contentLength < len - parseBg) {
|
||||
throw new IllegalStateException("Invalid content length: " + contentLength);
|
||||
}
|
||||
out.write(buf, parseBg, len - parseBg);
|
||||
if (out.size() > MAX_BODY_SIZE) {
|
||||
throw new IllegalStateException(
|
||||
"Request body is too big, limit size is " + MAX_BODY_SIZE);
|
||||
}
|
||||
int cap = Math.min(contentLength - out.size(), buf.length);
|
||||
while (cap > 0 && (len = in.read(buf, 0, cap)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
cap = Math.min(contentLength - out.size(), buf.length);
|
||||
}
|
||||
response.setBody(out.toByteArray());
|
||||
return response;
|
||||
} else if (!line.trim().isEmpty()) {
|
||||
// Parse HTTP header.
|
||||
int idx2 = line.indexOf(":");
|
||||
String key = line.substring(0, idx2).trim();
|
||||
String value = line.substring(idx2 + 1).trim();
|
||||
headers.put(key, value);
|
||||
if ("Content-Length".equalsIgnoreCase(key)) {
|
||||
contentLength = Integer.parseInt(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Move remaining bytes to the beginning.
|
||||
if (parseBg != 0) {
|
||||
System.arraycopy(buf, parseBg, buf, 0, len - parseBg);
|
||||
}
|
||||
bg = len - parseBg;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of CRLF separator.
|
||||
*
|
||||
* @param bg begin offset
|
||||
* @param ed end offset
|
||||
* @return the index, or {@code -1} if no CRLF is found
|
||||
*/
|
||||
private int indexOfCRLF(int bg, int ed) {
|
||||
if (ed - bg < 2) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = bg; i < ed - 1; i++) {
|
||||
if (buf[i] == '\r' && buf[i + 1] == '\n') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.alibaba.csp.sentinel.transport.heartbeat.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Protocol;
|
||||
import com.alibaba.csp.sentinel.transport.ssl.SslFactory;
|
||||
|
||||
/**
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class SocketFactory {
|
||||
|
||||
private static class SSLSocketFactoryInstance {
|
||||
private static final SSLSocketFactory SSL_SOCKET_FACTORY = SslFactory.getSslConnectionSocketFactory().getSocketFactory();
|
||||
}
|
||||
|
||||
public static Socket getSocket(Protocol protocol) throws IOException {
|
||||
return protocol == Protocol.HTTP ? new Socket() : SSLSocketFactoryInstance.SSL_SOCKET_FACTORY.createSocket();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.transport.heartbeat.SimpleHttpHeartbeatSender
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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.http;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.transport.command.exception.RequestException;
|
||||
|
||||
public class HttpEventTaskTest {
|
||||
|
||||
@Test
|
||||
public void processQueryString() {
|
||||
CommandRequest request;
|
||||
|
||||
request = HttpEventTask.processQueryString(null);
|
||||
assertNotNull(request);
|
||||
|
||||
request = HttpEventTask.processQueryString(null);
|
||||
assertNotNull(request);
|
||||
|
||||
request = HttpEventTask.processQueryString("get /?a=1&b=2&c=3#mark HTTP/1.0");
|
||||
assertNotNull(request);
|
||||
assertEquals("1", request.getParam("a"));
|
||||
assertEquals("2", request.getParam("b"));
|
||||
assertEquals("3", request.getParam("c"));
|
||||
|
||||
request = HttpEventTask.processQueryString("post /test?a=3&b=4&c=3#mark HTTP/1.0");
|
||||
assertNotNull(request);
|
||||
assertEquals("3", request.getParam("a"));
|
||||
assertEquals("4", request.getParam("b"));
|
||||
assertEquals("3", request.getParam("c"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAnchor() {
|
||||
assertNull(HttpEventTask.removeAnchor(null));
|
||||
assertEquals("", HttpEventTask.removeAnchor(""));
|
||||
assertEquals("", HttpEventTask.removeAnchor("#mark"));
|
||||
assertEquals("a", HttpEventTask.removeAnchor("a#mark"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSingleParam() {
|
||||
CommandRequest request;
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseSingleParam(null, request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseSingleParam("", request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseSingleParam("a", request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseSingleParam("=", request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseSingleParam("a=", request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseSingleParam("=a", request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseSingleParam("test=", request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseSingleParam("=test", request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseSingleParam("a=1", request);
|
||||
assertEquals(1, request.getParameters().size());
|
||||
assertEquals("1", request.getParam("a"));
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseSingleParam("a_+=1+", request);
|
||||
assertEquals(1, request.getParameters().size());
|
||||
assertEquals("1 ", request.getParam("a_ "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseParams() {
|
||||
CommandRequest request;
|
||||
|
||||
// mixed
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseParams("a=1&&b&=3&&c=4&a_+1=3_3%20&%E7%9A%84=test%E7%9A%84#mark", request);
|
||||
assertEquals(4, request.getParameters().size());
|
||||
assertEquals("1", request.getParam("a"));
|
||||
assertNull(request.getParam("b"));
|
||||
assertEquals("4", request.getParam("c"));
|
||||
assertEquals("3_3 ", request.getParam("a_ 1"));
|
||||
assertEquals("test的", request.getParam("的"));
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseParams(null, request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseParams("", request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.parseParams("&&b&=3&", request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsePostHeaders() throws IOException {
|
||||
Map<String, String> map;
|
||||
|
||||
map = HttpEventTask.parsePostHeaders(new ByteArrayInputStream("".getBytes()));
|
||||
assertTrue(map.size() == 0);
|
||||
|
||||
map = HttpEventTask.parsePostHeaders(new ByteArrayInputStream("Content-type: test \r\n\r\nbody".getBytes()));
|
||||
assertEquals("test", map.get("content-type"));
|
||||
|
||||
map = HttpEventTask.parsePostHeaders(new ByteArrayInputStream("Content-Encoding: utf-8\r\n\r\nbody".getBytes()));
|
||||
assertEquals("utf-8", map.get("content-encoding"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processPostRequest() throws IOException {
|
||||
CommandRequest request;
|
||||
|
||||
request = new CommandRequest();
|
||||
request.addParam("a", "1");
|
||||
|
||||
// illegal(empty) request
|
||||
try {
|
||||
HttpEventTask.processPostRequest(new ByteArrayInputStream("".getBytes()), request);
|
||||
assertFalse(true); // should not reach here
|
||||
} catch (Exception e) {
|
||||
assertTrue(e instanceof RequestException);
|
||||
}
|
||||
assertEquals("1", request.getParam("a"));
|
||||
|
||||
// normal request
|
||||
try {
|
||||
HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" +
|
||||
"Accept: */*\r\n" +
|
||||
"Accept-Language: en-us\r\n" +
|
||||
"Accept-Encoding: gzip, deflate\r\n" +
|
||||
"Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"Content-Length: 10\r\n" +
|
||||
"\r\n" +
|
||||
"a=3&b=5的").getBytes()), request);
|
||||
assertEquals("3", request.getParam("a"));
|
||||
assertEquals("5的", request.getParam("b"));
|
||||
} catch (Exception e) {
|
||||
assertTrue(false); // should not reach here
|
||||
}
|
||||
|
||||
// not supported request
|
||||
try {
|
||||
HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" +
|
||||
"Accept: */*\r\n" +
|
||||
"Accept-Language: en-us\r\n" +
|
||||
"Accept-Encoding: gzip, deflate\r\n" +
|
||||
"Content-Type: application/json\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"Content-Length: 7\r\n" +
|
||||
"\r\n" +
|
||||
"a=1&b=2").getBytes()), request);
|
||||
assertTrue(false); // should not reach here
|
||||
} catch (RequestException e) {
|
||||
assertTrue(e.getStatusCode() == StatusCode.UNSUPPORTED_MEDIA_TYPE);
|
||||
}
|
||||
|
||||
// Capacity test
|
||||
char[] buf = new char[1024 * 1024];
|
||||
Arrays.fill(buf, '&');
|
||||
String padding = new String(buf);
|
||||
try {
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" +
|
||||
"Accept: */*\r\n" +
|
||||
"Accept-Language: en-us\r\n" +
|
||||
"Accept-Encoding: gzip, deflate\r\n" +
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"Content-Length: 7\r\n" +
|
||||
"\r\n" +
|
||||
padding +
|
||||
"a=1&b=2").getBytes()), request);
|
||||
assertEquals(0, request.getParameters().size());
|
||||
} catch (Exception e) {
|
||||
assertTrue(false);
|
||||
}
|
||||
try {
|
||||
String querystring = "a+=+&b=%E7%9A%84的";
|
||||
request = new CommandRequest();
|
||||
HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" +
|
||||
"Accept: */*\r\n" +
|
||||
"Accept-Language: en-us\r\n" +
|
||||
"Accept-Encoding: gzip, deflate\r\n" +
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"Content-Length: " + (padding.length() + querystring.getBytes().length) + "\r\n" +
|
||||
"\r\n" +
|
||||
padding +
|
||||
querystring).getBytes()), request);
|
||||
assertEquals(2, request.getParameters().size());
|
||||
assertEquals(" ", request.getParam("a "));
|
||||
assertEquals("的的", request.getParam("b"));
|
||||
} catch (Exception e) {
|
||||
assertTrue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>sentinel-transport</artifactId>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<version>1.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>sentinel-transport-spring-mvc</artifactId>
|
||||
|
||||
<properties>
|
||||
<apache.httpclient.version>4.5.3</apache.httpclient.version>
|
||||
<servlet.api.version>3.1.0</servlet.api.version>
|
||||
<spring.version>5.1.8.RELEASE</spring.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>${servlet.api.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${apache.httpclient.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 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.command.http.StatusCode;
|
||||
import com.alibaba.csp.sentinel.transport.log.CommandCenterLog;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author shenbaoyong
|
||||
*/
|
||||
public class SentinelApiHandler {
|
||||
|
||||
public static final String SERVER_ERROR_MESSAGE = "Command server error";
|
||||
|
||||
private CommandHandler commandHandler;
|
||||
|
||||
public SentinelApiHandler(CommandHandler commandHandler) {
|
||||
this.commandHandler = commandHandler;
|
||||
}
|
||||
|
||||
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
|
||||
PrintWriter printWriter = null;
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
printWriter = httpServletResponse.getWriter();
|
||||
CommandCenterLog.debug("[SentinelApiHandler] request income: {}", httpServletRequest.getRequestURL());
|
||||
CommandRequest request = new CommandRequest();
|
||||
Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
|
||||
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
|
||||
String[] value = entry.getValue();
|
||||
if (value != null && value.length >= 1) {
|
||||
request.addParam(entry.getKey(), value[0]);
|
||||
}
|
||||
}
|
||||
CommandResponse<?> response = commandHandler.handle(request);
|
||||
handleResponse(response, httpServletResponse, printWriter);
|
||||
|
||||
long cost = System.currentTimeMillis() - start;
|
||||
CommandCenterLog.debug("[SentinelApiHandler] Deal request: {}, time cost: {} ms", httpServletRequest.getRequestURL(), cost);
|
||||
} catch (Throwable e) {
|
||||
CommandCenterLog.warn("[SentinelApiHandler] error", e);
|
||||
try {
|
||||
if (printWriter != null) {
|
||||
writeResponse(httpServletResponse, printWriter, StatusCode.INTERNAL_SERVER_ERROR, SERVER_ERROR_MESSAGE);
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
CommandCenterLog.warn("Failed to write error response", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResponse(HttpServletResponse httpServletResponse, PrintWriter out, StatusCode statusCode, String message) {
|
||||
httpServletResponse.setStatus(statusCode.getCode());
|
||||
if (message != null) {
|
||||
out.print(message);
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private <T> void handleResponse(CommandResponse<T> response, HttpServletResponse httpServletResponse, final PrintWriter printWriter) throws Exception {
|
||||
if (response.isSuccess()) {
|
||||
if (response.getResult() == null) {
|
||||
writeResponse(httpServletResponse, printWriter, StatusCode.OK, null);
|
||||
return;
|
||||
}
|
||||
// Here we directly use `toString` to encode the result to plain text.
|
||||
byte[] buffer = response.getResult().toString().getBytes(SentinelConfig.charset());
|
||||
writeResponse(httpServletResponse, printWriter, StatusCode.OK, new String(buffer));
|
||||
} else {
|
||||
String msg = SERVER_ERROR_MESSAGE;
|
||||
if (response.getException() != null) {
|
||||
msg = response.getException().getMessage();
|
||||
}
|
||||
writeResponse(httpServletResponse, printWriter, StatusCode.BAD_REQUEST, msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 org.springframework.core.Ordered;
|
||||
import org.springframework.web.servlet.HandlerAdapter;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @author shenbaoyong
|
||||
*/
|
||||
public class SentinelApiHandlerAdapter implements HandlerAdapter, Ordered {
|
||||
|
||||
private int order = Ordered.LOWEST_PRECEDENCE;
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Object handler) {
|
||||
return handler instanceof SentinelApiHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
SentinelApiHandler sentinelApiHandler = (SentinelApiHandler) handler;
|
||||
sentinelApiHandler.handle(request, response);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified(HttpServletRequest request, Object handler) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import com.alibaba.csp.sentinel.transport.log.CommandCenterLog;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author shenbaoyong
|
||||
*/
|
||||
public class SentinelApiHandlerMapping extends AbstractHandlerMapping implements ApplicationListener {
|
||||
|
||||
private static final String SPRING_BOOT_WEB_SERVER_INITIALIZED_EVENT_CLASS = "org.springframework.boot.web.context.WebServerInitializedEvent";
|
||||
private static Class webServerInitializedEventClass;
|
||||
|
||||
static {
|
||||
try {
|
||||
webServerInitializedEventClass = ClassUtils.forName(SPRING_BOOT_WEB_SERVER_INITIALIZED_EVENT_CLASS, null);
|
||||
RecordLog.info("[SentinelApiHandlerMapping] class {} is present, this is a spring-boot app, we can auto detect port", SPRING_BOOT_WEB_SERVER_INITIALIZED_EVENT_CLASS);
|
||||
} catch (ClassNotFoundException e) {
|
||||
RecordLog.info("[SentinelApiHandlerMapping] class {} is not present, this is not a spring-boot app, we can not auto detect port", SPRING_BOOT_WEB_SERVER_INITIALIZED_EVENT_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
final static Map<String, CommandHandler> handlerMap = new ConcurrentHashMap<>();
|
||||
|
||||
private boolean ignoreInterceptor = true;
|
||||
|
||||
public SentinelApiHandlerMapping() {
|
||||
setOrder(Ordered.LOWEST_PRECEDENCE - 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
|
||||
String commandName = request.getRequestURI();
|
||||
if (commandName.startsWith("/")) {
|
||||
commandName = commandName.substring(1);
|
||||
}
|
||||
CommandHandler commandHandler = handlerMap.get(commandName);
|
||||
return commandHandler != null ? new SentinelApiHandler(commandHandler) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
|
||||
return ignoreInterceptor ? new HandlerExecutionChain(handler) : super.getHandlerExecutionChain(handler, request);
|
||||
}
|
||||
|
||||
public void setIgnoreInterceptor(boolean ignoreInterceptor) {
|
||||
this.ignoreInterceptor = ignoreInterceptor;
|
||||
}
|
||||
|
||||
public static void registerCommand(String commandName, CommandHandler handler) {
|
||||
if (StringUtil.isEmpty(commandName) || handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (handlerMap.containsKey(commandName)) {
|
||||
CommandCenterLog.warn("[SentinelApiHandlerMapping] Register failed (duplicate command): " + commandName);
|
||||
return;
|
||||
}
|
||||
|
||||
handlerMap.put(commandName, handler);
|
||||
}
|
||||
|
||||
public static void registerCommands(Map<String, CommandHandler> handlerMap) {
|
||||
if (handlerMap != null) {
|
||||
for (Map.Entry<String, CommandHandler> e : handlerMap.entrySet()) {
|
||||
registerCommand(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent applicationEvent) {
|
||||
if (webServerInitializedEventClass != null && webServerInitializedEventClass.isAssignableFrom(applicationEvent.getClass())) {
|
||||
Integer port = null;
|
||||
try {
|
||||
BeanWrapper beanWrapper = new BeanWrapperImpl(applicationEvent);
|
||||
port = (Integer) beanWrapper.getPropertyValue("webServer.port");
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("[SentinelApiHandlerMapping] resolve port from event " + applicationEvent + " fail", e);
|
||||
}
|
||||
if (port != null && TransportConfig.getPort() == null) {
|
||||
RecordLog.info("[SentinelApiHandlerMapping] resolve port {} from event {}", port, applicationEvent);
|
||||
TransportConfig.setRuntimePort(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandlerProvider;
|
||||
import com.alibaba.csp.sentinel.spi.Spi;
|
||||
import com.alibaba.csp.sentinel.transport.CommandCenter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author shenbaoyong
|
||||
*/
|
||||
@Spi(order = Spi.ORDER_LOWEST - 100)
|
||||
public class SpringMvcHttpCommandCenter implements CommandCenter {
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeStart() throws Exception {
|
||||
// Register handlers
|
||||
Map<String, CommandHandler> handlers = CommandHandlerProvider.getInstance().namedHandlers();
|
||||
SentinelApiHandlerMapping.registerCommands(handlers);
|
||||
}
|
||||
}
|
||||
@@ -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.http;
|
||||
|
||||
/**
|
||||
* @author Jason Joo
|
||||
*/
|
||||
public enum StatusCode {
|
||||
/**
|
||||
* 200 OK.
|
||||
*/
|
||||
OK(200, "OK"),
|
||||
BAD_REQUEST(400, "Bad Request"),
|
||||
REQUEST_TIMEOUT(408, "Request Timeout"),
|
||||
LENGTH_REQUIRED(411, "Length Required"),
|
||||
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
|
||||
INTERNAL_SERVER_ERROR(500, "Internal Server Error");
|
||||
|
||||
private int code;
|
||||
private String desc;
|
||||
private String representation;
|
||||
|
||||
StatusCode(int code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
this.representation = code + " " + desc;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return representation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.Endpoint;
|
||||
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 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 SpringMvcHttpHeartbeatSender 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 SpringMvcHttpHeartbeatSender() {
|
||||
List<Endpoint> dashboardList = TransportConfig.getConsoleServerList();
|
||||
if (dashboardList == null || dashboardList.isEmpty()) {
|
||||
RecordLog.info("[HttpHeartbeatSender] 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("[HttpHeartbeatSender] 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,38 @@
|
||||
/*
|
||||
* 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.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.SpringMvcHttpCommandCenter
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.transport.heartbeat.SpringMvcHttpHeartbeatSender
|
||||
Reference in New Issue
Block a user