diff --git a/pom.xml b/pom.xml index bc17898f..b9299afe 100755 --- a/pom.xml +++ b/pom.xml @@ -144,6 +144,11 @@ sentinel-transport-netty-http ${project.version} + + com.alibaba.csp + sentinel-transport-spring-mvc + ${project.version} + com.alibaba.csp sentinel-transport-common diff --git a/sentinel-demo/pom.xml b/sentinel-demo/pom.xml index 514af01a..68e06ff4 100755 --- a/sentinel-demo/pom.xml +++ b/sentinel-demo/pom.xml @@ -40,6 +40,7 @@ sentinel-demo-quarkus sentinel-demo-annotation-cdi-interceptor sentinel-demo-motan + sentinel-demo-transport-spring-mvc diff --git a/sentinel-demo/sentinel-demo-transport-spring-mvc/pom.xml b/sentinel-demo/sentinel-demo-transport-spring-mvc/pom.xml new file mode 100644 index 00000000..eff54095 --- /dev/null +++ b/sentinel-demo/sentinel-demo-transport-spring-mvc/pom.xml @@ -0,0 +1,34 @@ + + + + sentinel-demo + com.alibaba.csp + 1.8.2-SNAPSHOT + + 4.0.0 + + sentinel-demo-transport-spring-mvc + + + 2.1.3.RELEASE + + + + + com.alibaba.csp + sentinel-core + + + com.alibaba.csp + sentinel-transport-spring-mvc + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + \ No newline at end of file diff --git a/sentinel-demo/sentinel-demo-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/demo/transport/springmvc/TransportSpringMvcDemoApplication.java b/sentinel-demo/sentinel-demo-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/demo/transport/springmvc/TransportSpringMvcDemoApplication.java new file mode 100644 index 00000000..c360d0ad --- /dev/null +++ b/sentinel-demo/sentinel-demo-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/demo/transport/springmvc/TransportSpringMvcDemoApplication.java @@ -0,0 +1,96 @@ +/* + * 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.demo.transport.springmvc; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.init.InitExecutor; +import com.alibaba.csp.sentinel.slots.block.BlockException; +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.command.SentinelApiHandlerAdapter; +import com.alibaba.csp.sentinel.transport.command.SentinelApiHandlerMapping; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + *

Add the JVM parameter to connect to the dashboard:

+ * {@code -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dproject.name=sentinel-demo-transport-spring-mvc} + * + *

Add the JVM parameter to tell dashboard your application port:

+ * {@code -Dcsp.sentinel.api.port=10000} + * + * @author shenbaoyong + */ +@SpringBootApplication +@Controller +public class TransportSpringMvcDemoApplication { + + public static void main(String[] args) { + triggerSentinelInit(); + initFlowRules(); + SpringApplication.run(TransportSpringMvcDemoApplication.class); + } + + public static void initFlowRules() { + List rules = new ArrayList<>(); + FlowRule rule = new FlowRule(); + rule.setResource("demo-hello-api"); + rule.setGrade(RuleConstant.FLOW_GRADE_QPS); + rule.setCount(1); + rules.add(rule); + FlowRuleManager.loadRules(rules); + } + + @GetMapping("/hello") + @ResponseBody + public String hello() { + Entry entry = null; + try { + entry = SphU.entry("demo-hello-api"); + return "ok: " + LocalDateTime.now(); + } catch (BlockException e1) { + return "helloBlockHandler: " + LocalDateTime.now(); + } finally { + if (entry != null) { + entry.exit(); + } + } + } + + private static void triggerSentinelInit() { + new Thread(() -> InitExecutor.doInit()).start(); + } + + @Bean + public SentinelApiHandlerMapping sentinelApiHandlerMapping() { + return new SentinelApiHandlerMapping(); + } + + @Bean + public SentinelApiHandlerAdapter sentinelApiHandlerAdapter() { + return new SentinelApiHandlerAdapter(); + } +} diff --git a/sentinel-demo/sentinel-demo-transport-spring-mvc/src/main/resources/application.properties b/sentinel-demo/sentinel-demo-transport-spring-mvc/src/main/resources/application.properties new file mode 100644 index 00000000..3d7224ca --- /dev/null +++ b/sentinel-demo/sentinel-demo-transport-spring-mvc/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=10000 diff --git a/sentinel-transport/pom.xml b/sentinel-transport/pom.xml index 9d8a46bb..0756fb69 100755 --- a/sentinel-transport/pom.xml +++ b/sentinel-transport/pom.xml @@ -16,5 +16,6 @@ sentinel-transport-simple-http sentinel-transport-netty-http + sentinel-transport-spring-mvc diff --git a/sentinel-transport/sentinel-transport-spring-mvc/pom.xml b/sentinel-transport/sentinel-transport-spring-mvc/pom.xml new file mode 100644 index 00000000..6bb6c168 --- /dev/null +++ b/sentinel-transport/sentinel-transport-spring-mvc/pom.xml @@ -0,0 +1,50 @@ + + + + sentinel-transport + com.alibaba.csp + 1.8.2-SNAPSHOT + + 4.0.0 + + sentinel-transport-spring-mvc + + + 4.5.3 + 3.1.0 + 5.1.8.RELEASE + + + + + com.alibaba.csp + sentinel-transport-common + ${project.version} + + + junit + junit + test + + + org.springframework + spring-webmvc + ${spring.version} + provided + + + javax.servlet + javax.servlet-api + ${servlet.api.version} + provided + + + org.apache.httpcomponents + httpclient + ${apache.httpclient.version} + + + + \ No newline at end of file diff --git a/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandler.java b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandler.java new file mode 100644 index 00000000..7cbe2a11 --- /dev/null +++ b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandler.java @@ -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 parameterMap = httpServletRequest.getParameterMap(); + for (Map.Entry 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 void handleResponse(CommandResponse 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); + } + } + +} diff --git a/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandlerAdapter.java b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandlerAdapter.java new file mode 100644 index 00000000..50ee7462 --- /dev/null +++ b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandlerAdapter.java @@ -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; + } +} diff --git a/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandlerMapping.java b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandlerMapping.java new file mode 100644 index 00000000..568a460d --- /dev/null +++ b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SentinelApiHandlerMapping.java @@ -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 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 handlerMap) { + if (handlerMap != null) { + for (Map.Entry 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); + } + } + } +} diff --git a/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SpringMvcHttpCommandCenter.java b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SpringMvcHttpCommandCenter.java new file mode 100644 index 00000000..8dc9fb06 --- /dev/null +++ b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/SpringMvcHttpCommandCenter.java @@ -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 handlers = CommandHandlerProvider.getInstance().namedHandlers(); + SentinelApiHandlerMapping.registerCommands(handlers); + } +} diff --git a/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/http/StatusCode.java b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/http/StatusCode.java new file mode 100644 index 00000000..5f6e3a88 --- /dev/null +++ b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/command/http/StatusCode.java @@ -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; + } +} diff --git a/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/SpringMvcHttpHeartbeatSender.java b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/SpringMvcHttpHeartbeatSender.java new file mode 100644 index 00000000..0e00956e --- /dev/null +++ b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/SpringMvcHttpHeartbeatSender.java @@ -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 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; + } +} diff --git a/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/client/HttpClientsFactory.java b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/client/HttpClientsFactory.java new file mode 100644 index 00000000..649a2498 --- /dev/null +++ b/sentinel-transport/sentinel-transport-spring-mvc/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/client/HttpClientsFactory.java @@ -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(); + } +} diff --git a/sentinel-transport/sentinel-transport-spring-mvc/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.CommandCenter b/sentinel-transport/sentinel-transport-spring-mvc/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.CommandCenter new file mode 100644 index 00000000..0aec4880 --- /dev/null +++ b/sentinel-transport/sentinel-transport-spring-mvc/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.CommandCenter @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.transport.command.SpringMvcHttpCommandCenter \ No newline at end of file diff --git a/sentinel-transport/sentinel-transport-spring-mvc/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.HeartbeatSender b/sentinel-transport/sentinel-transport-spring-mvc/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.HeartbeatSender new file mode 100644 index 00000000..d5c96378 --- /dev/null +++ b/sentinel-transport/sentinel-transport-spring-mvc/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.transport.HeartbeatSender @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.transport.heartbeat.SpringMvcHttpHeartbeatSender \ No newline at end of file