diff --git a/README.md b/README.md index 94844a3..c0604d7 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,20 @@ API地址:http://demo.fizzgate.com/proxy/[服务名]/[API Path] | v1.0.0 | v1.0.0 | v1.0.0 | | v1.1.0 | v1.1.0 | v1.1.0 | | v1.1.1 | v1.1.1 | v1.1.1 | -| v1.2.0 | v1.2.0 | v1.1.1 | +| v1.2.0 | v1.2.0 | v1.2.0 | + +从v1.3.0开始管理后台的前端和服务端合并成一个包 + +- Fizz-gateway-community: 社区版 + +- Fizz-manager-professional:管理后台 + +| Fizz-gateway-community | Fizz-manager-professional | +| ---------------------- | ------------------------- | +| v1.3.0 | v1.3.0 | 请根据社区版的版本下载对应的管理后台版本 - ## 部署说明 [详细部署教程>>>](http://www.fizzgate.com/guide/installation/) @@ -100,40 +109,22 @@ API地址:http://demo.fizzgate.com/proxy/[服务名]/[API Path] #### 一、安装管理后台 -从github的releases(https://github.com/wehotel/fizz-gateway-community/releases) 下载 fizz-manager-professional 和 fizz-admin-professional 的安装包 +从github的releases(https://github.com/wehotel/fizz-gateway-community/releases) 下载 fizz-manager-professional 安装包 -##### 管理后台服务端(fizz-manager-professional) +##### 管理后台(fizz-manager-professional) -1. 首次安装执行`fizz-manager-professional-1.2.0-mysql.sql`数据库脚本 -2. 将`application-prod.yml`、`boot.sh`、`fizz-manager-professional-1.2.0.jar`拷贝到`/data/webapps/fizz-manager-professional`目录下 +说明: + +1. 以下安装步骤出现的`{version}`表示所使用管理后台的版本号,例如`1.3.0`。 + +安装: + +1. 解压`fizz-manager-professional-{version}.zip`安装包 +2. 首次安装执行`fizz-manager-professional-{version}-mysql.sql`数据库脚本,从低版本升级至高版本选择执行update目录下对应升级脚本 3. 修改`application-prod.yml`文件,将相关配置修改成部署环境的配置 -4. 修改`boot.sh`文件,将`RUN_CMD`变量值修改成部署环境的JAVA实际路径 -5. 执行 `chmod +x boot.sh` 命令给`boot.sh`增加执行权限 -6. 执行 `./boot.sh start` 命令启动服务,支持 start/stop/restart/status命令 -7. 服务启动后访问前端登录地址,使用超级管理员账户`admin`密码`Aa123!`登录 - -##### 管理后台前端(fizz-admin-professional) - -1.解压zip资源包,取文件夹【fizzAdmin】放置于服务器静态数据存放目录 如:/home/data/ -2.配置nginx服务器 -``` -server { - listen 9000; - server_name localhost:9000; - location / { - root /home/data/fizzAdmin; - } - location ^~ /api { - rewrite ^/api/(.*) /$1 break; - proxy_pass http://127.0.0.1:8000; - } -} -# 注:root中地址需与资源包存放目录路径一致 -# 注:http://127.0.0.1:8000 为管理后台(fizz-manager-professional)的访问地址 -``` -3.访问地址 -【资源部署服务器IP + 端口号】如:http://127.0.0.1:9000/ -(端口号与nginx配置端口号一致) +4. Linux启动 执行 `chmod +x boot.sh` 命令给`boot.sh`增加执行权限;执行 `./boot.sh start` 命令启动服务,支持 start/stop/restart/status命令 +5. Windows启动 执行`.\boot.cmd start` 命令启动服务,支持 start/stop/restart/status命令 +6. 服务启动后访问 http://{部署机器IP地址}:8000/#/login,使用超级管理员账户`admin`密码`Aa123!`登录 #### 二、安装fizz-gateway-community社区版 @@ -141,10 +132,11 @@ server { 1. 支持配置中心:apollo、nacos,支持注册中心:eureka、nacos,详细配置方法查看application.yml文件。 2. 如果使用apollo配置中心,可把application.yml文件内容迁到配置中心(apollo上应用名为:fizz-gateway);如果不使用apollo可去掉下面启动命令里的apollo参数。 +3. 以下安装步骤出现的`{version}`表示所使用网关的版本号,例如`1.3.0`。 安装方式一:脚本启动: -1. 下载fizz-gateway-community的最新代码,修改application.yml配置文件里配置中心、注册中心、redis(redis配置需与管理后台一致)的配置,使用maven命令`mvn clean package -DskipTests=true`构建并把构建好的fizz-gateway-community-1.2.0.jar和boot.sh放同一目录 +1. 下载fizz-gateway-community的最新代码,修改application.yml配置文件里配置中心、注册中心、redis(redis配置需与管理后台一致)的配置,使用maven命令`mvn clean package -DskipTests=true`构建并把构建好的fizz-gateway-community-{version}.jar和boot.sh放同一目录 2. 修改boot.sh脚本的apollo连接,JVM内存配置 3. 执行 `./boot.sh start` 命令启动服务,支持 start/stop/restart/status命令 @@ -158,7 +150,7 @@ server { 1. 本地clone仓库上的最新代码,修改application.yml配置文件里配置中心、注册中心、redis(redis配置需与管理后台一致)的配置 2. 在项目根目录fizz-gateway-community下执行Maven命令`mvn clean package -DskipTests=true`打包 -3. 进入target目录,使用命令`java -jar -Denv=DEV -Dapollo.meta=http://localhost:66 fizz-gateway-community-1.2.0.jar`启动服务 +3. 进入target目录,使用命令`java -jar -Denv=DEV -Dapollo.meta=http://localhost:66 fizz-gateway-community-{version}.jar`启动服务 最后访问网关,地址形式为:http://127.0.0.1:8600/proxy/[服务名]/[API Path] diff --git a/pom.xml b/pom.xml index 691a288..6f2a81c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ org.springframework.boot spring-boot-starter-parent - 2.2.10.RELEASE + 2.2.12.RELEASE we fizz-gateway-community - 1.2.0 + 1.3.0 fizz-gateway-community @@ -34,10 +34,13 @@ 1.8 - 2.13.3 - 4.1.53.Final - 4.5.13 + 5.2.12.RELEASE + Dysprosium-SR15 + 5.3.5.RELEASE 0.2.7 + 4.1.56.Final + 4.4.14 + 2.13.3 @@ -67,7 +70,7 @@ com.alibaba fastjson - 1.2.74 + 1.2.75 @@ -116,7 +119,7 @@ org.apache.tapestry tapestry-json - 5.4.4 + 5.4.5 @@ -128,7 +131,7 @@ org.codehaus.groovy groovy-all - 2.4.20 + 2.4.21 @@ -146,13 +149,19 @@ org.springframework.cloud spring-cloud-starter-netflix-eureka-client - 2.2.5.RELEASE + 2.2.6.RELEASE com.alibaba.boot nacos-config-spring-boot-starter ${nacos.version} + + + org.springframework.boot + spring-boot-starter-logging + + com.alibaba.boot @@ -166,7 +175,7 @@ org.springframework.boot - spring-boot-starter-data-redis + spring-boot-starter-data-redis-reactive diff --git a/sh/boot.sh b/sh/boot.sh index 5e6eb48..0220472 100644 --- a/sh/boot.sh +++ b/sh/boot.sh @@ -1,21 +1,57 @@ #!/bin/bash +cygwin=false +darwin=false +os400=false +case "`uname`" in +CYGWIN*) cygwin=true;; +Darwin*) darwin=true;; +OS400*) os400=true;; +esac + +[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java +[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java +[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/local/java +[ ! -e "$JAVA_HOME/bin/java" ] && unset JAVA_HOME + +if [ -z "$JAVA_HOME" ]; then + if $darwin; then + + if [ -x '/usr/libexec/java_home' ] ; then + export JAVA_HOME=`/usr/libexec/java_home` + + elif [ -d "/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home" ]; then + export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home" + fi + else + JAVA_PATH=`dirname $(readlink -f $(which javac))` + if [ "x$JAVA_PATH" != "x" ]; then + export JAVA_HOME=`dirname $JAVA_PATH 2>/dev/null` + fi + fi + if [ -z "$JAVA_HOME" ]; then + echo "ERROR: Please set the JAVA_HOME variable in your environment!!!" + exit 1 + fi +fi + #进入脚本所在目录 cd `dirname $0` #变量定义 APOLLO_META_SERVER=http://localhost:66 ENV=dev -APP_NAME=fizz-gateway-community-1.2.0.jar -APP_DEP_DIR=/data/webapps/fizz-gateway -APP_LOG_DIR=/data/logs/fizz-gateway -JAVA_CMD=/usr/local/java/bin/java +APP_NAME=fizz-gateway-community-1.3.0.jar +APP_DEP_DIR="` pwd`" +APP_LOG_DIR=${APP_DEP_DIR}'/logs' +JAVA_CMD=${JAVA_HOME}'/bin/java' PID_FILE="${APP_LOG_DIR}/tpid" CHECK_COUNT=3 -SERVER_IP="` ip a | egrep "brd" | grep inet | awk '{print $2}' | sed 's#/24##g'`" -#创建应用目录 -mkdir -p ${APP_DEP_DIR} +# 远程执行shell脚本初始化环境变量 +source '/etc/profile' + +SERVER_IP="` ip a |egrep "brd" |grep inet|awk '{print $2}'|sed 's#/24##g'|head -1`" #创建日志目录 mkdir -p ${APP_LOG_DIR} @@ -24,7 +60,7 @@ mkdir -p ${APP_LOG_DIR} cd ${APP_DEP_DIR} JAVA_OPTS="-Xms256m -Xmx4096m \ --XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m \ +-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m \ -XX:+AggressiveOpts \ -XX:+UseBiasedLocking \ -XX:+UseG1GC \ diff --git a/src/main/java/we/FizzGatewayApplication.java b/src/main/java/we/FizzGatewayApplication.java index 0229a3e..2986fc8 100644 --- a/src/main/java/we/FizzGatewayApplication.java +++ b/src/main/java/we/FizzGatewayApplication.java @@ -1,13 +1,42 @@ +/* + * Copyright (C) 2020 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package we; import com.alibaba.nacos.spring.context.annotation.config.NacosPropertySource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import we.config.AggregateRedisConfig; +import we.log.LogSendAppender; +/** + * fizz gateway application boot entrance + * + * @author linwaiwai + * @author francis + * @author hongqiaowei + * @author zhongjie + */ @SpringBootApplication( exclude = {ErrorWebFluxAutoConfiguration.class, RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class}, scanBasePackages = {"we"} @@ -15,8 +44,30 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @NacosPropertySource(dataId = "${nacos.config.data-id}", groupId = "${nacos.config.group}", autoRefreshed = true) @EnableDiscoveryClient public class FizzGatewayApplication { + private static final Logger LOGGER = LoggerFactory.getLogger(FizzGatewayApplication.class); public static void main(String[] args) { - FizzAppContext.appContext = SpringApplication.run(FizzGatewayApplication.class, args); + SpringApplication springApplication = new SpringApplication(FizzGatewayApplication.class); + springApplication.setApplicationContextClass(CustomReactiveWebServerApplicationContext.class); + FizzAppContext.appContext = springApplication.run(args); + } + + private static class CustomReactiveWebServerApplicationContext extends AnnotationConfigReactiveWebServerApplicationContext { + @Override + protected void onClose() { + super.onClose(); + if (AggregateRedisConfig.proxyLettuceConnectionFactory != null) { + LOGGER.info("FizzGatewayApplication stopped."); + // set LogSendAppender.logEnabled to false to stop send log to fizz-manager + LogSendAppender.logEnabled = Boolean.FALSE; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // ignore + } + // the ProxyLettuceConnectionFactory remove DisposableBean interface, so invoke destroy method here + AggregateRedisConfig.proxyLettuceConnectionFactory.destroy(); + } + } } } diff --git a/src/main/java/we/config/AggregateRedisConfig.java b/src/main/java/we/config/AggregateRedisConfig.java new file mode 100644 index 0000000..3cf7831 --- /dev/null +++ b/src/main/java/we/config/AggregateRedisConfig.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2020 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package we.config; + +import com.alibaba.nacos.api.config.annotation.NacosValue; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.connection.ReactiveRedisClusterConnection; +import org.springframework.data.redis.connection.ReactiveRedisConnection; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.connection.RedisClusterConnection; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisSentinelConnection; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.ReactiveStringRedisTemplate; +import org.springframework.data.redis.listener.ReactiveRedisMessageListenerContainer; +import we.log.LogSendAppender; +import we.log.RedisLogSendServiceImpl; + +/** + * aggregate Redis config + * + * @author zhongjie + */ +@Configuration +public class AggregateRedisConfig extends RedisReactiveConfig { + static final String AGGREGATE_REACTIVE_REDIS_PROPERTIES = "aggregateReactiveRedisProperties"; + private static final String AGGREGATE_REACTIVE_REDIS_CONNECTION_FACTORY = "aggregateReactiveRedisConnectionFactory"; + public static final String AGGREGATE_REACTIVE_REDIS_TEMPLATE = "aggregateReactiveRedisTemplate"; + public static final String AGGREGATE_REACTIVE_REDIS_MESSAGE_LISTENER_CONTAINER = "aggregateReactiveRedisMessageListenerContainer"; + + private static final String SEND_LOG_TYPE_REDIS = "redis"; + public static ProxyLettuceConnectionFactory proxyLettuceConnectionFactory; + + @NacosValue(value = "${send-log.open:false}", autoRefreshed = true) + @Value("${send-log.open:false}") + private boolean sendLogOpen; + + @NacosValue(value = "${send-log.channel:fizz_send_log_channel}", autoRefreshed = true) + @Value("${send-log.channel:fizz_log_channel}") + private String sendLogChannel; + + @NacosValue(value = "${send-log.type:redis}", autoRefreshed = true) + @Value("${send-log.type:redis}") + private String sendLogType; + + @ConfigurationProperties(prefix = "aggregate.redis") + @Configuration(AGGREGATE_REACTIVE_REDIS_PROPERTIES) + public static class AggregateRedisReactiveProperties extends RedisReactiveProperties { + } + + public AggregateRedisConfig(@Qualifier(AGGREGATE_REACTIVE_REDIS_PROPERTIES) RedisReactiveProperties properties) { + super(properties); + } + + @Override + @Bean(AGGREGATE_REACTIVE_REDIS_CONNECTION_FACTORY) + public ReactiveRedisConnectionFactory lettuceConnectionFactory() { + LettuceConnectionFactory lettuceConnectionFactory = (LettuceConnectionFactory) super.lettuceConnectionFactory(); + if (SEND_LOG_TYPE_REDIS.equals(sendLogType)) { + proxyLettuceConnectionFactory = new ProxyLettuceConnectionFactory(lettuceConnectionFactory); + proxyLettuceConnectionFactory.afterPropertiesSet(); + return proxyLettuceConnectionFactory; + } else { + return lettuceConnectionFactory; + } + } + + @Override + @Bean(AGGREGATE_REACTIVE_REDIS_TEMPLATE) + public ReactiveStringRedisTemplate reactiveStringRedisTemplate( + @Qualifier(AGGREGATE_REACTIVE_REDIS_CONNECTION_FACTORY) ReactiveRedisConnectionFactory factory) { + ReactiveStringRedisTemplate reactiveStringRedisTemplate = super.reactiveStringRedisTemplate(factory); + + // test redis can connect + reactiveStringRedisTemplate.getConnectionFactory().getReactiveConnection().ping().block(); + + if (SEND_LOG_TYPE_REDIS.equals(sendLogType)) { + // set LogSendAppender.logSendService here to let send log as early as possible + LogSendAppender.logSendService = new RedisLogSendServiceImpl(this, reactiveStringRedisTemplate); + } + + return reactiveStringRedisTemplate; + } + + @Bean(AGGREGATE_REACTIVE_REDIS_MESSAGE_LISTENER_CONTAINER) + public ReactiveRedisMessageListenerContainer aggregateReactiveRedisMessageListenerContainer( + @Qualifier(AGGREGATE_REACTIVE_REDIS_CONNECTION_FACTORY) ReactiveRedisConnectionFactory factory) { + return new ReactiveRedisMessageListenerContainer(factory); + } + + public boolean getSendLogOpen() { + return sendLogOpen; + } + + public String getSendLogChannel() { + return sendLogChannel; + } + + public static class ProxyLettuceConnectionFactory implements RedisConnectionFactory, ReactiveRedisConnectionFactory { + ProxyLettuceConnectionFactory(LettuceConnectionFactory lettuceConnectionFactory) { + this.lettuceConnectionFactory = lettuceConnectionFactory; + } + + private LettuceConnectionFactory lettuceConnectionFactory; + + public void destroy() { + lettuceConnectionFactory.destroy(); + } + + void afterPropertiesSet() { + lettuceConnectionFactory.afterPropertiesSet(); + } + + @Override + public ReactiveRedisConnection getReactiveConnection() { + return lettuceConnectionFactory.getReactiveConnection(); + } + + @Override + public ReactiveRedisClusterConnection getReactiveClusterConnection() { + return lettuceConnectionFactory.getReactiveClusterConnection(); + } + + @Override + public RedisConnection getConnection() { + return lettuceConnectionFactory.getConnection(); + } + + @Override + public RedisClusterConnection getClusterConnection() { + return lettuceConnectionFactory.getClusterConnection(); + } + + @Override + public boolean getConvertPipelineAndTxResults() { + return lettuceConnectionFactory.getConvertPipelineAndTxResults(); + } + + @Override + public RedisSentinelConnection getSentinelConnection() { + return lettuceConnectionFactory.getSentinelConnection(); + } + + @Override + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + return lettuceConnectionFactory.translateExceptionIfPossible(ex); + } + } +} diff --git a/src/main/java/we/constants/CommonConstants.java b/src/main/java/we/constants/CommonConstants.java index 3174154..47ba0d2 100644 --- a/src/main/java/we/constants/CommonConstants.java +++ b/src/main/java/we/constants/CommonConstants.java @@ -33,6 +33,12 @@ public class CommonConstants { public static final String HEADER_TRACE_ID = "X-TRACE-ID"; + /** + * Prefix of traceId + */ + public static final String TRACE_ID_PREFIX = "fizz-"; + + /** * WildCard for PathMapping */ diff --git a/src/main/java/we/filter/FilterExceptionHandlerConfig.java b/src/main/java/we/filter/FilterExceptionHandlerConfig.java index ac8eea1..a714090 100644 --- a/src/main/java/we/filter/FilterExceptionHandlerConfig.java +++ b/src/main/java/we/filter/FilterExceptionHandlerConfig.java @@ -55,8 +55,8 @@ public class FilterExceptionHandlerConfig { return resp.writeWith(Mono.just(resp.bufferFactory().wrap(ex.getData().toString().getBytes()))); } } - if (t instanceof RedirectException) { - RedirectException ex = (RedirectException) t; + if (t instanceof RedirectException) { + RedirectException ex = (RedirectException) t; if (ex.getRedirectUrl() != null) { ServerHttpResponse resp = exchange.getResponse(); resp.setStatusCode(HttpStatus.MOVED_PERMANENTLY); @@ -64,23 +64,22 @@ public class FilterExceptionHandlerConfig { return Mono.empty(); } } - if (t instanceof ExecuteScriptException) { - ExecuteScriptException ex = (ExecuteScriptException) t; - ServerHttpResponse resp = exchange.getResponse(); - resp.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - - RespEntity rs = null; - String reqId = exchange.getRequest().getId(); - if (ex.getStepContext() != null && ex.getStepContext().returnContext()) { - rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), reqId, ex.getStepContext()); - return resp.writeWith(Mono.just(resp.bufferFactory().wrap(JacksonUtils.writeValueAsString(rs).getBytes()))); - }else { - rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), reqId); - return resp.writeWith(Mono.just(resp.bufferFactory().wrap(rs.toString().getBytes()))); + if (t instanceof ExecuteScriptException) { + ExecuteScriptException ex = (ExecuteScriptException) t; + ServerHttpResponse resp = exchange.getResponse(); + resp.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + RespEntity rs = null; + String reqId = exchange.getRequest().getId(); + if (ex.getStepContext() != null && ex.getStepContext().returnContext()) { + rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), reqId, ex.getStepContext()); + return resp.writeWith(Mono.just(resp.bufferFactory().wrap(JacksonUtils.writeValueAsString(rs).getBytes()))); + } else { + rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), reqId); + return resp.writeWith(Mono.just(resp.bufferFactory().wrap(rs.toString().getBytes()))); } } - Mono vm = WebUtils.responseError(exchange, filterExceptionHandler, HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), t); - return vm; + Mono vm = WebUtils.responseError(exchange, filterExceptionHandler, HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), t); + return vm; } } diff --git a/src/main/java/we/filter/FizzGatewayFilter.java b/src/main/java/we/filter/FizzGatewayFilter.java index ae72814..02ae3c3 100644 --- a/src/main/java/we/filter/FizzGatewayFilter.java +++ b/src/main/java/we/filter/FizzGatewayFilter.java @@ -19,17 +19,22 @@ package we.filter; import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.annotation.Resource; +import com.alibaba.nacos.api.config.annotation.NacosValue; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; @@ -50,6 +55,7 @@ import we.fizz.ConfigLoader; import we.fizz.Pipeline; import we.fizz.input.Input; import we.flume.clients.log4j2appender.LogService; +import we.plugin.auth.ApiConfig; import we.util.Constants; import we.util.MapUtil; import we.util.WebUtils; @@ -64,25 +70,35 @@ public class FizzGatewayFilter implements WebFilter { private static final Logger LOGGER = LoggerFactory.getLogger(FizzGatewayFilter.class); private static final DataBuffer emptyBody = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false, true)).wrap(Constants.Symbol.EMPTY.getBytes()); - + @Resource private ConfigLoader configLoader; - + + @NacosValue(value = "${need-auth:false}", autoRefreshed = true) + @Value("${need-auth:false}") + private boolean needAuth; + @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + + String serviceId = WebUtils.getBackendService(exchange); + if ( serviceId == null || (ApiConfig.Type.SERVICE_ARRANGE != WebUtils.getApiConfigType(exchange) && needAuth) ) { + return chain.filter(exchange); + } + long start = System.currentTimeMillis(); ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse serverHttpResponse = exchange.getResponse(); - - if (WebUtils.getServiceId(exchange) == null) { - return chain.filter(exchange); - } - - String path = WebUtils.getPathPrefix(exchange) + WebUtils.getServiceId(exchange) + WebUtils.getReqPath(exchange); + + String path = WebUtils.getClientReqPathPrefix(exchange) + serviceId + WebUtils.getBackendPath(exchange); String method = request.getMethodValue(); AggregateResource aggregateResource = configLoader.matchAggregateResource(method, path); if (aggregateResource == null) { - return chain.filter(exchange); + if (WebUtils.getApiConfigType(exchange) == ApiConfig.Type.SERVICE_ARRANGE) { + return WebUtils.responseError(exchange, HttpStatus.INTERNAL_SERVER_ERROR.value(), "no aggregate resource: " + path); + } else { + return chain.filter(exchange); + } } Pipeline pipeline = aggregateResource.getPipeline(); @@ -93,11 +109,16 @@ public class FizzGatewayFilter implements WebFilter { if(fizzHeaders != null && !fizzHeaders.isEmpty()) { headers.putAll(fizzHeaders); } - + // traceId - String traceId = exchange.getRequest().getId(); + String tmpTraceId = CommonConstants.TRACE_ID_PREFIX + exchange.getRequest().getId(); + if (StringUtils.isNotBlank(request.getHeaders().getFirst(CommonConstants.HEADER_TRACE_ID))) { + tmpTraceId = request.getHeaders().getFirst(CommonConstants.HEADER_TRACE_ID); + } + final String traceId = tmpTraceId; LogService.setBizId(traceId); - serverHttpResponse.getHeaders().add(CommonConstants.HEADER_TRACE_ID, traceId); + + LOGGER.debug("matched aggregation api: {}", path); // 客户端提交上来的信息 Map clientInput = new HashMap<>(); @@ -105,8 +126,8 @@ public class FizzGatewayFilter implements WebFilter { clientInput.put("method", method); clientInput.put("headers", headers); clientInput.put("params", MapUtil.toHashMap(request.getQueryParams())); - - + + Mono result = null; if (HttpMethod.POST.name().equalsIgnoreCase(method)) { result = DataBufferUtils.join(request.getBody()).defaultIfEmpty(emptyBody).flatMap(buf -> { @@ -125,6 +146,7 @@ public class FizzGatewayFilter implements WebFilter { return result.subscribeOn(Schedulers.elastic()).flatMap(aggResult -> { LogService.setBizId(traceId); String jsonString = JSON.toJSONString(aggResult.getBody()); + LOGGER.debug("response body: {}", jsonString); if (aggResult.getHeaders() != null && !aggResult.getHeaders().isEmpty()) { aggResult.getHeaders().remove("Content-Length"); serverHttpResponse.getHeaders().addAll(aggResult.getHeaders()); @@ -133,7 +155,11 @@ public class FizzGatewayFilter implements WebFilter { // defalut content-type serverHttpResponse.getHeaders().add("Content-Type", "application/json; charset=UTF-8"); } - + List headerTraceIds = serverHttpResponse.getHeaders().get(CommonConstants.HEADER_TRACE_ID); + if (headerTraceIds == null || !headerTraceIds.contains(traceId)) { + serverHttpResponse.getHeaders().add(CommonConstants.HEADER_TRACE_ID, traceId); + } + long end = System.currentTimeMillis(); pipeline.getStepContext().addElapsedTime("总耗时", end - start); LOGGER.info("ElapsedTimes={}", JSON.toJSONString(pipeline.getStepContext().getElapsedTimes())); diff --git a/src/main/java/we/filter/PreFilter.java b/src/main/java/we/filter/PreFilter.java index 7d7dcc1..2c163cb 100644 --- a/src/main/java/we/filter/PreFilter.java +++ b/src/main/java/we/filter/PreFilter.java @@ -92,6 +92,7 @@ public class PreFilter extends ProxyAggrFilter { Mono m; if (authRes instanceof ApiConfig) { ApiConfig ac = (ApiConfig) authRes; + afterAuth(exchange, ac); m = executeFixedPluginFilters(exchange); m = m.defaultIfEmpty(ReactorUtils.NULL); if (ac.pluginConfigs == null || ac.pluginConfigs.isEmpty()) { @@ -101,6 +102,7 @@ public class PreFilter extends ProxyAggrFilter { .defaultIfEmpty(ReactorUtils.NULL).flatMap(func(exchange, chain)); } } else if (authRes == ApiConfigService.Access.YES) { + afterAuth(exchange, null); m = executeFixedPluginFilters(exchange); return m.defaultIfEmpty(ReactorUtils.NULL).flatMap(func(exchange, chain)); } else { @@ -111,6 +113,19 @@ public class PreFilter extends ProxyAggrFilter { ); } + private void afterAuth(ServerWebExchange exchange, ApiConfig ac) { + String bs, bp; + if (ac == null) { + bs = WebUtils.getClientService(exchange); + bp = WebUtils.getClientReqPath(exchange); + } else { + bs = ac.backendService; + bp = ac.transform(WebUtils.getClientReqPath(exchange)); + } + WebUtils.setBackendService(exchange, bs); + WebUtils.setBackendPath(exchange, bp); + } + private Mono chain(ServerWebExchange exchange, Mono m, PluginFilter pf) { return m.defaultIfEmpty(ReactorUtils.NULL).flatMap( v -> { diff --git a/src/main/java/we/filter/ProxyAggrFilter.java b/src/main/java/we/filter/ProxyAggrFilter.java index 99529ca..03c061a 100644 --- a/src/main/java/we/filter/ProxyAggrFilter.java +++ b/src/main/java/we/filter/ProxyAggrFilter.java @@ -32,7 +32,7 @@ public abstract class ProxyAggrFilter implements WebFilter { @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - String serviceId = WebUtils.getServiceId(exchange); + String serviceId = WebUtils.getClientService(exchange); if (serviceId == null) { return chain.filter(exchange); } else { diff --git a/src/main/java/we/filter/RouteFilter.java b/src/main/java/we/filter/RouteFilter.java index 1c8581f..06471f5 100644 --- a/src/main/java/we/filter/RouteFilter.java +++ b/src/main/java/we/filter/RouteFilter.java @@ -34,8 +34,8 @@ import reactor.core.publisher.Mono; import we.flume.clients.log4j2appender.LogService; import we.legacy.RespEntity; import we.plugin.auth.ApiConfig; -import we.plugin.auth.AuthPluginFilter; import we.proxy.FizzWebClient; +import we.util.Constants; import we.util.ThreadContext; import we.util.WebUtils; @@ -104,26 +104,26 @@ public class RouteFilter extends ProxyAggrFilter { ); } - ApiConfig ac = null; - Object authRes = WebUtils.getFilterResultDataItem(exchange, AuthPluginFilter.AUTH_PLUGIN_FILTER, AuthPluginFilter.RESULT); - if (authRes instanceof ApiConfig) { - ac = (ApiConfig) authRes; - } + String rid = clientReq.getId(); + ApiConfig ac = WebUtils.getApiConfig(exchange); + if (ac == null) { + String pathQuery = WebUtils.getClientReqPathQuery(exchange); + return send(exchange, WebUtils.getClientService(exchange), pathQuery, hdrs); + + } else if (ac.type == ApiConfig.Type.SERVICE_DISCOVERY) { + String pathQuery = WebUtils.appendQuery(WebUtils.getBackendPath(exchange), exchange); + return send(exchange, WebUtils.getBackendService(exchange), pathQuery, hdrs); + + } else if (ac.type == ApiConfig.Type.REVERSE_PROXY) { + String uri = ac.getNextHttpHostPort() + WebUtils.appendQuery(WebUtils.getBackendPath(exchange), exchange); + return fizzWebClient.send(rid, clientReq.getMethod(), uri, hdrs, clientReq.getBody()).flatMap(genServerResponse(exchange)); - String relativeUri = WebUtils.getRelativeUri(exchange); - if (ac == null || ac.proxyMode == ApiConfig.DIRECT_PROXY_MODE) { - return send(exchange, WebUtils.getServiceId(exchange), relativeUri, hdrs); } else { - String realUri; - String backendUrl = ac.getNextBackendUrl(); - int acpLen = ac.path.length(); - if (acpLen == 1) { - realUri = backendUrl + relativeUri; - } else { - realUri = backendUrl + relativeUri.substring(acpLen); - } - relativeUri.substring(acpLen); - return fizzWebClient.send(clientReq.getId(), clientReq.getMethod(), realUri, hdrs, clientReq.getBody()).flatMap(genServerResponse(exchange)); + String err = "cant handle api config type " + ac.type; + StringBuilder b = ThreadContext.getStringBuilder(); + WebUtils.request2stringBuilder(exchange, b); + log.error(b.append(Constants.Symbol.LF).append(err).toString(), LogService.BIZ_ID, rid); + return WebUtils.buildJsonDirectResponseAndBindContext(exchange, HttpStatus.OK, null, RespEntity.toJson(HttpStatus.INTERNAL_SERVER_ERROR.value(), err, rid)); } } diff --git a/src/main/java/we/fizz/ConfigLoader.java b/src/main/java/we/fizz/ConfigLoader.java index 81254a9..db67f7c 100644 --- a/src/main/java/we/fizz/ConfigLoader.java +++ b/src/main/java/we/fizz/ConfigLoader.java @@ -21,6 +21,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.nacos.api.config.annotation.NacosValue; + import we.config.AppConfigProperties; import we.fizz.input.ClientInputConfig; import we.fizz.input.Input; @@ -28,15 +29,18 @@ import we.fizz.input.InputType; import org.apache.commons.io.FileUtils; import org.noear.snack.ONode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.ReactiveStringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import javax.annotation.PostConstruct; import javax.annotation.Resource; -import static we.listener.AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE; +import static we.config.AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE; import static we.util.Constants.Symbol.FORWARD_SLASH; import java.io.File; @@ -48,6 +52,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; + /** * * @author francis @@ -56,17 +61,21 @@ import java.util.concurrent.ConcurrentHashMap; */ @Component public class ConfigLoader { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigLoader.class); + /** * 聚合配置存放Hash的Key */ private static final String AGGREGATE_HASH_KEY = "fizz_aggregate_config"; - + private static Map aggregateResources = null; private static Map resourceKey2ConfigInfoMap = null; private static Map aggregateId2ResourceKeyMap = null; - + @Resource private AppConfigProperties appConfigProperties; + @Resource(name = AGGREGATE_REACTIVE_REDIS_TEMPLATE) private ReactiveStringRedisTemplate reactiveStringRedisTemplate; @@ -85,11 +94,11 @@ public class ConfigLoader { clientInputConfig.setHeaders(cfgNode.select("$.headers").toObject(Map.class)); clientInputConfig.setMethod(cfgNode.select("$.method").getString()); clientInputConfig.setPath(cfgNode.select("$.path").getString()); - if(clientInputConfig.getPath().startsWith(TEST_PATH_PREFIX)) { + if (clientInputConfig.getPath().startsWith(TEST_PATH_PREFIX)) { // always enable debug for testing clientInputConfig.setDebug(true); - }else { - if(cfgNode.select("$.debug") != null) { + } else { + if (cfgNode.select("$.debug") != null) { clientInputConfig.setDebug(cfgNode.select("$.debug").getBoolean()); } } @@ -112,7 +121,7 @@ public class ConfigLoader { for (Map stepConfig : stepConfigs) { // set the specified env URL this.handleRequestURL(stepConfig); - + Step step = new Step.Builder().read(stepConfig); step.setName((String) stepConfig.get("name")); if (stepConfig.get("stop") != null) { @@ -152,22 +161,23 @@ public class ConfigLoader { } return aggregateResources.get(resourceKey); } - + private void handleRequestURL(Map stepConfig) { List requests = (List) stepConfig.get("requests"); for (Object obj : requests) { Map request = (Map) obj; String envUrl = (String) request.get(appConfigProperties.getEnv() + "Url"); - if(!StringUtils.isEmpty(envUrl)) { + if (!StringUtils.isEmpty(envUrl)) { request.put("url", request.get(appConfigProperties.getEnv() + "Url")); } } } + @PostConstruct public synchronized void init() throws Exception { if (aggregateResources == null) { aggregateResources = new ConcurrentHashMap<>(1024); - resourceKey2ConfigInfoMap = new ConcurrentHashMap<>(1024); + resourceKey2ConfigInfoMap = new ConcurrentHashMap<>(1024); aggregateId2ResourceKeyMap = new ConcurrentHashMap<>(1024); } @@ -210,6 +220,8 @@ public class ConfigLoader { String configId = cfgNode.select("$.id").getString(); String configName = cfgNode.select("$.name").getString(); long version = cfgNode.select("$.version").getLong(); + + LOGGER.debug("add aggregation config, key={} config={}", resourceKey, configStr); if (StringUtils.hasText(configId)) { String existResourceKey = aggregateId2ResourceKeyMap.get(configId); if (StringUtils.hasText(existResourceKey)) { @@ -231,8 +243,9 @@ public class ConfigLoader { JSONArray idArray = JSON.parseArray(configIds); idArray.forEach(it -> { String configId = (String) it; - String existResourceKey =aggregateId2ResourceKeyMap.get(configId); + String existResourceKey = aggregateId2ResourceKeyMap.get(configId); if (StringUtils.hasText(existResourceKey)) { + LOGGER.debug("delete aggregation config: {}", existResourceKey); aggregateResources.remove(existResourceKey); resourceKey2ConfigInfoMap.remove(existResourceKey); aggregateId2ResourceKeyMap.remove(configId); @@ -250,7 +263,7 @@ public class ConfigLoader { } } String key = method.toUpperCase() + ":" + path; - if(aggregateResources.containsKey(key) && aggregateResources.get(key) != null) { + if (aggregateResources.containsKey(key) && aggregateResources.get(key) != null) { String configStr = aggregateResources.get(key); Input input = null; Pipeline pipeline = null; @@ -268,6 +281,7 @@ public class ConfigLoader { } return null; } + private ConfigInfo buildConfigInfo(String configId, String configName, String method, String path, long version) { String serviceName = this.extractServiceName(path); ConfigInfo configInfo = new ConfigInfo(); @@ -280,26 +294,27 @@ public class ConfigLoader { return configInfo; } - private static final String FORMAL_PATH_PREFIX = "/proxy/"; - private static final int FORMAL_PATH_SERVICE_NAME_START_INDEX = 7; - private static final String TEST_PATH_PREFIX = "/proxytest/"; - private static final int TEST_PATH_SERVICE_NAME_START_INDEX = 11; + private static final String FORMAL_PATH_PREFIX = "/proxy/"; + private static final int FORMAL_PATH_SERVICE_NAME_START_INDEX = 7; + private static final String TEST_PATH_PREFIX = "/proxytest/"; + private static final int TEST_PATH_SERVICE_NAME_START_INDEX = 11; + private String extractServiceName(String path) { - if (path != null) { - if (path.startsWith(FORMAL_PATH_PREFIX)) { - int endIndex = path.indexOf(FORWARD_SLASH, FORMAL_PATH_SERVICE_NAME_START_INDEX); - if (endIndex > FORMAL_PATH_SERVICE_NAME_START_INDEX) { - return path.substring(FORMAL_PATH_SERVICE_NAME_START_INDEX, endIndex); - } - } else if (path.startsWith(TEST_PATH_PREFIX)) { - int endIndex = path.indexOf(FORWARD_SLASH, TEST_PATH_SERVICE_NAME_START_INDEX); - if (endIndex > TEST_PATH_SERVICE_NAME_START_INDEX) { - return path.substring(TEST_PATH_SERVICE_NAME_START_INDEX, endIndex); - } - } - } - return null; - } + if (path != null) { + if (path.startsWith(FORMAL_PATH_PREFIX)) { + int endIndex = path.indexOf(FORWARD_SLASH, FORMAL_PATH_SERVICE_NAME_START_INDEX); + if (endIndex > FORMAL_PATH_SERVICE_NAME_START_INDEX) { + return path.substring(FORMAL_PATH_SERVICE_NAME_START_INDEX, endIndex); + } + } else if (path.startsWith(TEST_PATH_PREFIX)) { + int endIndex = path.indexOf(FORWARD_SLASH, TEST_PATH_SERVICE_NAME_START_INDEX); + if (endIndex > TEST_PATH_SERVICE_NAME_START_INDEX) { + return path.substring(TEST_PATH_SERVICE_NAME_START_INDEX, endIndex); + } + } + } + return null; + } public static class ConfigInfo implements Serializable { private static final long serialVersionUID = 1L; @@ -339,12 +354,9 @@ public class ConfigLoader { return false; } ConfigInfo that = (ConfigInfo) o; - return Objects.equals(configId, that.configId) && - Objects.equals(configName, that.configName) && - Objects.equals(serviceName, that.serviceName) && - Objects.equals(method, that.method) && - Objects.equals(path, that.path) && - Objects.equals(version, that.version); + return Objects.equals(configId, that.configId) && Objects.equals(configName, that.configName) + && Objects.equals(serviceName, that.serviceName) && Objects.equals(method, that.method) + && Objects.equals(path, that.path) && Objects.equals(version, that.version); } @Override diff --git a/src/main/java/we/fizz/Pipeline.java b/src/main/java/we/fizz/Pipeline.java index 3fa6564..6f4bdbd 100644 --- a/src/main/java/we/fizz/Pipeline.java +++ b/src/main/java/we/fizz/Pipeline.java @@ -95,30 +95,43 @@ public class Pipeline { return Mono.just(aggregateResult); } - LinkedList opSteps = (LinkedList) steps.clone(); - Step step1 = opSteps.removeFirst(); - step1.beforeRun(stepContext, null); - Mono> result = createStep(step1).expand(response -> { - if (opSteps.isEmpty() || response.isStop()) { - return Mono.empty(); - } - Step step = opSteps.pop(); - step.beforeRun(stepContext, response); - return createStep(step); - }).flatMap(response -> Flux.just(response)).collectList(); - return result.flatMap(clientResponse -> { - // 数据转换 - long t3 = System.currentTimeMillis(); - AggregateResult aggResult = this.doInputDataMapping(input, null); - this.stepContext.addElapsedTime(input.getName()+"聚合接口响应结果数据转换", System.currentTimeMillis() - t3); - if(this.stepContext.isDebug()) { - LogService.setBizId(this.stepContext.getTraceId()); - String jsonString = JSON.toJSONString(aggResult); + if(CollectionUtils.isEmpty(steps)) { + return handleOutput(input); + }else { + LinkedList opSteps = (LinkedList) steps.clone(); + Step step1 = opSteps.removeFirst(); + step1.beforeRun(stepContext, null); + Mono> result = createStep(step1).expand(response -> { + if (opSteps.isEmpty() || response.isStop()) { + return Mono.empty(); + } + Step step = opSteps.pop(); + step.beforeRun(stepContext, response); + return createStep(step); + }).flatMap(response -> Flux.just(response)).collectList(); + return result.flatMap(clientResponse -> { + return handleOutput(input); + }); + } + } + + private Mono handleOutput(Input input){ + // 数据转换 + long t3 = System.currentTimeMillis(); + AggregateResult aggResult = this.doInputDataMapping(input, null); + this.stepContext.addElapsedTime(input.getName()+"聚合接口响应结果数据转换", System.currentTimeMillis() - t3); + if(this.stepContext.isDebug() || LOGGER.isDebugEnabled()) { + LogService.setBizId(this.stepContext.getTraceId()); + String jsonString = JSON.toJSONString(aggResult); + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("aggResult {}", jsonString); + LOGGER.debug("stepContext {}", JSON.toJSONString(stepContext)); + }else { LOGGER.info("aggResult {}", jsonString); - LOGGER.info("stepContext {}", JSON.toJSONString(stepContext)); + LOGGER.info("stepContext {}", JSON.toJSONString(stepContext)); } - return Mono.just(aggResult); - }); + } + return Mono.just(aggResult); } @SuppressWarnings("unchecked") diff --git a/src/main/java/we/flume/clients/log4j2appender/LogService.java b/src/main/java/we/flume/clients/log4j2appender/LogService.java index 5ae538d..fe9f72f 100644 --- a/src/main/java/we/flume/clients/log4j2appender/LogService.java +++ b/src/main/java/we/flume/clients/log4j2appender/LogService.java @@ -1,5 +1,7 @@ package we.flume.clients.log4j2appender; +import we.constants.CommonConstants; + public enum LogService { BIZ_ID, HANDLE_STGY, APP; @@ -14,6 +16,9 @@ public enum LogService { public static void setBizId(Object bizId) { ThreadContext.set(Constants.BIZ_ID, bizId); + if (bizId != null) { + org.apache.logging.log4j.ThreadContext.put(CommonConstants.TRACE_ID, String.valueOf(bizId)); + } } public static String toKF(String topic) { diff --git a/src/main/java/we/legacy/RespEntity.java b/src/main/java/we/legacy/RespEntity.java index a85d7af..a3155b4 100644 --- a/src/main/java/we/legacy/RespEntity.java +++ b/src/main/java/we/legacy/RespEntity.java @@ -32,7 +32,7 @@ public class RespEntity { private static final String f1 = ",\"message\":\""; private static final String f2 = "\"}"; - public int msgCode; + public int msgCode; public String message; diff --git a/src/main/java/we/listener/AggregateChannelListener.java b/src/main/java/we/listener/AggregateChannelListener.java index 77c3db7..9527e2a 100644 --- a/src/main/java/we/listener/AggregateChannelListener.java +++ b/src/main/java/we/listener/AggregateChannelListener.java @@ -36,7 +36,7 @@ import we.fizz.ConfigLoader; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import static we.listener.AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_MESSAGE_LISTENER_CONTAINER; +import static we.config.AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_MESSAGE_LISTENER_CONTAINER; import java.net.InetAddress; import java.util.List; diff --git a/src/main/java/we/listener/AggregateRedisConfig.java b/src/main/java/we/listener/AggregateRedisConfig.java deleted file mode 100644 index 86c7d31..0000000 --- a/src/main/java/we/listener/AggregateRedisConfig.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package we.listener; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; -import org.springframework.data.redis.core.ReactiveStringRedisTemplate; -import org.springframework.data.redis.listener.ReactiveRedisMessageListenerContainer; - -import we.config.RedisReactiveConfig; -import we.config.RedisReactiveProperties; - -/** - * 聚合配置Redis配置 - * @author zhongjie - */ -@Configuration -public class AggregateRedisConfig extends RedisReactiveConfig { - static final String AGGREGATE_REACTIVE_REDIS_PROPERTIES = "aggregateReactiveRedisProperties"; - private static final String AGGREGATE_REACTIVE_REDIS_CONNECTION_FACTORY = "aggregateReactiveRedisConnectionFactory"; - public static final String AGGREGATE_REACTIVE_REDIS_TEMPLATE = "aggregateReactiveRedisTemplate"; - static final String AGGREGATE_REACTIVE_REDIS_MESSAGE_LISTENER_CONTAINER = "aggregateReactiveRedisMessageListenerContainer"; - - @ConfigurationProperties(prefix = "aggregate.redis") - @Configuration(AGGREGATE_REACTIVE_REDIS_PROPERTIES) - public static class AggregateRedisReactiveProperties extends RedisReactiveProperties { - } - - public AggregateRedisConfig(@Qualifier(AGGREGATE_REACTIVE_REDIS_PROPERTIES) RedisReactiveProperties properties) { - super(properties); - } - - @Override - @Bean(AGGREGATE_REACTIVE_REDIS_CONNECTION_FACTORY) - public ReactiveRedisConnectionFactory lettuceConnectionFactory() { - return super.lettuceConnectionFactory(); - } - - @Override - @Bean(AGGREGATE_REACTIVE_REDIS_TEMPLATE) - public ReactiveStringRedisTemplate reactiveStringRedisTemplate( - @Qualifier(AGGREGATE_REACTIVE_REDIS_CONNECTION_FACTORY) ReactiveRedisConnectionFactory factory) { - return super.reactiveStringRedisTemplate(factory); - } - - @Bean(AGGREGATE_REACTIVE_REDIS_MESSAGE_LISTENER_CONTAINER) - public ReactiveRedisMessageListenerContainer aggregateReactiveRedisMessageListenerContainer( - @Qualifier(AGGREGATE_REACTIVE_REDIS_CONNECTION_FACTORY) ReactiveRedisConnectionFactory factory) { - return new ReactiveRedisMessageListenerContainer(factory); - } -} diff --git a/src/main/java/we/log/LogSend.java b/src/main/java/we/log/LogSend.java new file mode 100644 index 0000000..d274583 --- /dev/null +++ b/src/main/java/we/log/LogSend.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.log; + +/** + * log send data entity + * + * @author zhongjie + */ +public class LogSend { + public LogSend(String bizId, String serverIp, int level, Long timeMillis, String content) { + this.bizId = bizId; + this.serverIp = serverIp; + this.level = level; + this.timeMillis = timeMillis; + this.content = content; + } + + private String bizId; + private String serverIp; + private int level; + private Long timeMillis; + private String content; + + public String getBizId() { + return bizId; + } + + public void setBizId(String bizId) { + this.bizId = bizId; + } + + public String getServerIp() { + return serverIp; + } + + public void setServerIp(String serverIp) { + this.serverIp = serverIp; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + + public Long getTimeMillis() { + return timeMillis; + } + + public void setTimeMillis(Long timeMillis) { + this.timeMillis = timeMillis; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/src/main/java/we/log/LogSendAppender.java b/src/main/java/we/log/LogSendAppender.java new file mode 100644 index 0000000..a4f8b4c --- /dev/null +++ b/src/main/java/we/log/LogSendAppender.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.log; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.layout.PatternLayout; +import we.FizzAppContext; +import we.flume.clients.log4j2appender.LogService; +import we.util.NetworkUtils; + +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * log send appender + * + * @author zhongjie + */ +@Plugin(name = LogSendAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +public class LogSendAppender extends AbstractAppender { + + static final String PLUGIN_NAME = "LogSend"; + public static LogSendService logSendService; + public static Boolean logEnabled; + private static LogSend[] logSends = new LogSend[1000]; + private static AtomicInteger logSendIndex = new AtomicInteger(0); + + private LogSendAppender(String name, Filter filter, Layout layout, boolean ignoreExceptions, Property[] properties) { + super(name, filter, layout, ignoreExceptions, properties); + } + + @Override + public void append(LogEvent event) { + if (logEnabled != null && !logEnabled) { + return; + } + + if (logEnabled == null && FizzAppContext.appContext == null && logSendService == null) { + // local cache + logSends[logSendIndex.getAndIncrement() % logSends.length] = new LogSend(this.getBizId(event.getMessage().getParameters()), + NetworkUtils.getServerIp(), event.getLevel().intLevel(), event.getTimeMillis(), new String(this.getLayout().toByteArray(event))); + return; + } + + if (logEnabled == null && logSendService == null) { + // no legal logSendService, discard the local cache + logEnabled = Boolean.FALSE; + logSends = null; + return; + } + + if (logEnabled == null) { + logEnabled = Boolean.TRUE; + + LogSend[] logSends; + synchronized (LogSendAppender.class) { + logSends = LogSendAppender.logSends; + LogSendAppender.logSends = null; + } + + // logSendService is ready, send the local cache + if (logSends != null) { + int size = Math.min(logSendIndex.get(), logSends.length); + for (int i = 0; i < size; i++) { + logSendService.send(logSends[i]); + } + } + } + + LogSend logSend = new LogSend(this.getBizId(event.getMessage().getParameters()), NetworkUtils.getServerIp(), + event.getLevel().intLevel(), event.getTimeMillis(), new String(this.getLayout().toByteArray(event))); + logSendService.send(logSend); + } + + private String getBizId(Object[] parameters) { + Object bizId = LogService.getBizId(); + if (parameters != null) { + for (int i = parameters.length - 1; i > -1; --i) { + Object p = parameters[i]; + if (p == LogService.BIZ_ID) { + if (i != parameters.length - 1) { + bizId = parameters[i + 1]; + } + break; + } + } + } + if (bizId == null) { + return ""; + } + return bizId.toString(); + } + + @PluginFactory + public static LogSendAppender createAppender(@PluginAttribute("name") String name, + @PluginElement("Filter") final Filter filter, + @PluginElement("Layout") Layout layout, + @PluginAttribute("ignoreExceptions") boolean ignoreExceptions) { + if (name == null) { + LOGGER.error("No name provided for LogSendAppender!"); + return null; + } + + if (layout == null) { + layout = PatternLayout.createDefaultLayout(); + } + return new LogSendAppender(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY); + } +} diff --git a/src/main/java/we/log/LogSendService.java b/src/main/java/we/log/LogSendService.java new file mode 100644 index 0000000..5df7dbd --- /dev/null +++ b/src/main/java/we/log/LogSendService.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.log; + +/** + * log send service interface, used by {@link LogSendAppender} to send log to fizz-manager + * + * @author zhongjie + */ +public interface LogSendService { + /** + * send log + * @param logSend log data + */ + void send(LogSend logSend); +} diff --git a/src/main/java/we/log/RedisLogSendServiceImpl.java b/src/main/java/we/log/RedisLogSendServiceImpl.java new file mode 100644 index 0000000..700e82e --- /dev/null +++ b/src/main/java/we/log/RedisLogSendServiceImpl.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.log; + +import com.alibaba.fastjson.JSON; +import org.springframework.data.redis.core.ReactiveStringRedisTemplate; +import we.config.AggregateRedisConfig; + +/** + * {@link LogSendService} impl class, using redis channel to send log + * + * @author zhongjie + */ +public class RedisLogSendServiceImpl implements LogSendService { + public RedisLogSendServiceImpl(AggregateRedisConfig aggregateRedisConfig, ReactiveStringRedisTemplate reactiveStringRedisTemplate) { + this.aggregateRedisConfig = aggregateRedisConfig; + this.reactiveStringRedisTemplate = reactiveStringRedisTemplate; + } + + private AggregateRedisConfig aggregateRedisConfig; + private ReactiveStringRedisTemplate reactiveStringRedisTemplate; + + @Override + public void send(LogSend logSend) { + if (aggregateRedisConfig.getSendLogOpen()) { + reactiveStringRedisTemplate.convertAndSend(aggregateRedisConfig.getSendLogChannel(), JSON.toJSONString(logSend)).subscribe(); + } + } +} diff --git a/src/main/java/we/plugin/auth/ApiConfig.java b/src/main/java/we/plugin/auth/ApiConfig.java index ba00d58..21b3798 100644 --- a/src/main/java/we/plugin/auth/ApiConfig.java +++ b/src/main/java/we/plugin/auth/ApiConfig.java @@ -18,16 +18,17 @@ package we.plugin.auth; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpMethod; - import we.plugin.PluginConfig; -import we.util.Constants; import we.util.JacksonUtils; +import we.util.UrlTransformUtils; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -37,15 +38,20 @@ import java.util.stream.Stream; public class ApiConfig { - public static final int DELETED = 1; + public static interface Type { + static final byte UNDEFINED = 0; + static final byte SERVICE_ARRANGE = 1; + static final byte SERVICE_DISCOVERY = 2; + static final byte REVERSE_PROXY = 3; + } - public static final char ALLOW = 'a'; + public static final int DELETED = 1; - public static final char FORBID = 'f'; + public static final char ALLOW = 'a'; - public static final byte DIRECT_PROXY_MODE = 1; + public static final char FORBID = 'f'; - public static final byte PREFIX_REWRITE_PROXY_MODE = 2; + private static final String match_all = "/**"; // @JsonIgnore public int id; // tb_api_auth.id @@ -57,22 +63,50 @@ public class ApiConfig { public String service; + public String backendService; + public HttpMethod method = HttpMethod.X; - public String path = String.valueOf(Constants.Symbol.FORWARD_SLASH); +// public String path = String.valueOf(Constants.Symbol.FORWARD_SLASH); + public String path = match_all; + + public boolean exactMatch = false; + + public String backendPath; public Set apps = Stream.of(App.ALL_APP).collect(Collectors.toSet()); - public byte proxyMode = DIRECT_PROXY_MODE; + @JsonProperty("proxyMode") + public byte type = Type.SERVICE_DISCOVERY; private AtomicInteger counter = new AtomicInteger(-1); - public List backendUrls; +// public List backendUrls; + + public List httpHostPorts; public char access = ALLOW; public List pluginConfigs; + public static boolean isAntPathPattern(String path) { + boolean uriVar = false; + for (int i = 0; i < path.length(); i++) { + char c = path.charAt(i); + if (c == '*' || c == '?') { + return true; + } + if (c == '{') { + uriVar = true; + continue; + } + if (c == '}' && uriVar) { + return true; + } + } + return false; + } + public void setGatewayGroup(String ggs) { gatewayGroups.remove(GatewayGroup.DEFAULT); if (StringUtils.isBlank(ggs)) { @@ -101,7 +135,16 @@ public class ApiConfig { public void setPath(String p) { if (StringUtils.isNotBlank(p)) { - path = p.trim(); + if ("/".equals(p)) { + path = match_all; + } else { + path = p.trim(); + if (!isAntPathPattern(path)) { + exactMatch = true; + } + } + } else { + path = match_all; } } @@ -112,14 +155,31 @@ public class ApiConfig { } } + // @JsonIgnore + // public String getNextBackendUrl() { + // int idx = counter.incrementAndGet(); + // if (idx < 0) { + // counter.set(0); + // idx = 0; + // } + // return backendUrls.get(idx % backendUrls.size()); + // } + @JsonIgnore - public String getNextBackendUrl() { + public String getNextHttpHostPort() { int idx = counter.incrementAndGet(); if (idx < 0) { counter.set(0); idx = 0; } - return backendUrls.get(idx % backendUrls.size()); + return httpHostPorts.get(idx % httpHostPorts.size()); + } + + public String transform(String reqPath) { + if (exactMatch) { + return backendPath; + } + return UrlTransformUtils.transform(path, backendPath, reqPath); } @Override diff --git a/src/main/java/we/plugin/auth/ApiConfigService.java b/src/main/java/we/plugin/auth/ApiConfigService.java index 31e6b8d..8935d2d 100644 --- a/src/main/java/we/plugin/auth/ApiConfigService.java +++ b/src/main/java/we/plugin/auth/ApiConfigService.java @@ -36,7 +36,7 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import we.flume.clients.log4j2appender.LogService; -import we.listener.AggregateRedisConfig; +import we.config.AggregateRedisConfig; import we.util.*; import javax.annotation.PostConstruct; @@ -53,16 +53,20 @@ public class ApiConfigService { private static final Logger log = LoggerFactory.getLogger(ApiConfigService.class); - private static final String fizzApiConfig = "fizz_api_config"; - - private static final String fizzApiConfigChannel = "fizz_api_config_channel"; - private static final String signHeader = "fizz-sign"; private static final String timestampHeader = "fizz-ts"; private static final String secretKeyHeader = "fizz-secretkey"; + @NacosValue(value = "${fizz-api-config.key:fizz_api_config_route}", autoRefreshed = true) + @Value("${fizz-api-config.key:fizz_api_config_route}") + private String fizzApiConfig; + + @NacosValue(value = "${fizz-api-config.channel:fizz_api_config_channel_route}", autoRefreshed = true) + @Value("${fizz-api-config.channel:fizz_api_config_channel_route}") + private String fizzApiConfigChannel; + public Map serviceConfigMap = new HashMap<>(128); private Map apiConfigMap = new HashMap<>(128); @@ -109,9 +113,9 @@ public class ApiConfigService { } } - @NacosValue(value = "${auth.compatible-wh:false}", autoRefreshed = true) - @Value("${auth.compatible-wh:false}") - private boolean compatibleWh; + @NacosValue(value = "${need-auth:false}", autoRefreshed = true) + @Value("${need-auth:false}") + private boolean needAuth; @Resource(name = AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE) private ReactiveStringRedisTemplate rt; @@ -275,21 +279,21 @@ public class ApiConfigService { ServerHttpRequest req = exchange.getRequest(); HttpHeaders hdrs = req.getHeaders(); LogService.setBizId(req.getId()); - return canAccess(exchange, WebUtils.getAppId(exchange), WebUtils.getOriginIp(exchange), hdrs.getFirst(timestampHeader), hdrs.getFirst(signHeader), hdrs.getFirst(secretKeyHeader), - WebUtils.getServiceId(exchange), req.getMethod(), WebUtils.getReqPath(exchange)); + return canAccess(exchange, WebUtils.getAppId(exchange), WebUtils.getOriginIp(exchange), hdrs.getFirst(timestampHeader), hdrs.getFirst(signHeader), hdrs.getFirst(secretKeyHeader), + WebUtils.getClientService(exchange), req.getMethod(), WebUtils.getClientReqPath(exchange)); } private Mono canAccess(ServerWebExchange exchange, String app, String ip, String timestamp, String sign, String secretKey, String service, HttpMethod method, String path) { - if (openServiceWhiteList) { - if (!whiteListSet.contains(service)) { // TODO XXX - return Mono.just(Access.SERVICE_NOT_OPEN); - } - } + // if (openServiceWhiteList) { + // if (!whiteListSet.contains(service)) { // TODO XXX + // return Mono.just(Access.SERVICE_NOT_OPEN); + // } + // } ServiceConfig sc = serviceConfigMap.get(service); if (sc == null) { - if (compatibleWh) { + if (!needAuth) { return Mono.just(Access.YES); } else { return logWarnAndResult(service + Constants.Symbol.BLANK + Access.NO_SERVICE_CONFIG.getReason(), Access.NO_SERVICE_CONFIG); @@ -305,7 +309,7 @@ public class ApiConfigService { } ApiConfig ac = ac0; if (ac == null) { - if (compatibleWh) { + if (!needAuth) { return Mono.just(Access.YES); } else { return logWarnAndResult(api + " no api config", Access.NO_API_CONFIG); @@ -369,6 +373,6 @@ public class ApiConfigService { private static boolean validate(String app, String timestamp, String secretKey, String sign) { StringBuilder b = ThreadContext.getStringBuilder(); b.append(app).append(Constants.Symbol.UNDERLINE).append(timestamp).append(Constants.Symbol.UNDERLINE).append(secretKey); - return sign.equals(DigestUtils.md532(b.toString())); + return sign.equalsIgnoreCase(DigestUtils.md532(b.toString())); } } diff --git a/src/main/java/we/plugin/auth/AppService.java b/src/main/java/we/plugin/auth/AppService.java index 7ce09e1..0ed0e4a 100644 --- a/src/main/java/we/plugin/auth/AppService.java +++ b/src/main/java/we/plugin/auth/AppService.java @@ -24,7 +24,7 @@ import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import we.flume.clients.log4j2appender.LogService; -import we.listener.AggregateRedisConfig; +import we.config.AggregateRedisConfig; import we.util.Constants; import we.util.JacksonUtils; import we.util.ReactorUtils; diff --git a/src/main/java/we/plugin/auth/GatewayGroup2appsToApiConfig.java b/src/main/java/we/plugin/auth/GatewayGroup2appsToApiConfig.java index 91ff728..504f503 100644 --- a/src/main/java/we/plugin/auth/GatewayGroup2appsToApiConfig.java +++ b/src/main/java/we/plugin/auth/GatewayGroup2appsToApiConfig.java @@ -51,7 +51,7 @@ public class GatewayGroup2appsToApiConfig { } for (String a : ac.apps) { app2apiConfigMap.put(a, ac); - log.info(gg + " add " + a + " -> " + ac); + log.info("expose " + ac + " to " + gg + " group and " + a + " app"); } } } @@ -62,7 +62,7 @@ public class GatewayGroup2appsToApiConfig { if (app2apiConfigMap != null) { for (String a : ac.apps) { ApiConfig r = app2apiConfigMap.remove(a); - log.info(gg + " remove " + a + " -> " + r); + log.info("remove " + r + " from " + gg + " group and " + a + " app"); } } } @@ -77,7 +77,7 @@ public class GatewayGroup2appsToApiConfig { } for (String a : ac.apps) { ApiConfig old = app2apiConfigMap.put(a, ac); - log.info(gg + " update " + a + " -> " + old + " with " + ac); + log.info(gg + " group and " + a + " app update " + old + " with " + ac); } } } diff --git a/src/main/java/we/plugin/auth/GatewayGroupService.java b/src/main/java/we/plugin/auth/GatewayGroupService.java index a78c09d..91d0002 100644 --- a/src/main/java/we/plugin/auth/GatewayGroupService.java +++ b/src/main/java/we/plugin/auth/GatewayGroupService.java @@ -24,7 +24,7 @@ import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import we.flume.clients.log4j2appender.LogService; -import we.listener.AggregateRedisConfig; +import we.config.AggregateRedisConfig; import we.util.Constants; import we.util.JacksonUtils; import we.util.NetworkUtils; diff --git a/src/main/java/we/plugin/auth/ServiceConfig.java b/src/main/java/we/plugin/auth/ServiceConfig.java index 594a611..27f22d0 100644 --- a/src/main/java/we/plugin/auth/ServiceConfig.java +++ b/src/main/java/we/plugin/auth/ServiceConfig.java @@ -20,15 +20,14 @@ package we.plugin.auth; import com.fasterxml.jackson.annotation.JsonIgnore; import org.apache.commons.lang3.StringUtils; -import we.util.Constants; +import org.springframework.util.AntPathMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpMethod; +import we.util.ThreadContext; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * @author hongqiaowei @@ -36,7 +35,11 @@ import java.util.Map; public class ServiceConfig { - private static final Logger log = LoggerFactory.getLogger(ServiceConfig.class); + private static final Logger log = LoggerFactory.getLogger(ServiceConfig.class); + + private static final AntPathMatcher antPathMatcher = new AntPathMatcher(); + + private static final String mpps = "$mpps"; public String id; @@ -110,7 +113,8 @@ public class ServiceConfig { @JsonIgnore public ApiConfig getApiConfig(HttpMethod method, String path, String gatewayGroup, String app) { - GatewayGroup2appsToApiConfig r = getApiConfig0(method, path); +// GatewayGroup2appsToApiConfig r = getApiConfig0(method, path); + GatewayGroup2appsToApiConfig r = getApiConfig(method, path); if (r == null) { return null; } @@ -120,28 +124,57 @@ public class ServiceConfig { return r.get(gatewayGroup, app); } - private GatewayGroup2appsToApiConfig getApiConfig0(HttpMethod method, String path) { - while (true) { - EnumMap method2apiConfigMap = path2methodToApiConfigMapMap.get(path); - if (method2apiConfigMap == null) { - int i = path.lastIndexOf(Constants.Symbol.FORWARD_SLASH); - if (i == 0) { - method2apiConfigMap = path2methodToApiConfigMapMap.get(Constants.Symbol.FORWARD_SLASH_STR); - if (method2apiConfigMap == null) { - return null; - } else { - return getApiConfig1(method, method2apiConfigMap); - } - } else { - path = path.substring(0, i); + private GatewayGroup2appsToApiConfig getApiConfig(HttpMethod method, String reqPath) { + + List matchPathPatterns = ThreadContext.getArrayList(mpps, String.class); + + Set>> es = path2methodToApiConfigMapMap.entrySet(); + for (Map.Entry> e : es) { + String pathPattern = e.getKey(); + if (ApiConfig.isAntPathPattern(pathPattern)) { + if (antPathMatcher.match(pathPattern, reqPath)) { + matchPathPatterns.add(pathPattern); } - } else { - return getApiConfig1(method, method2apiConfigMap); + } else if (reqPath.equals(pathPattern)) { + return getGatewayGroup2appsToApiConfig(method, e.getValue()); } } + if (matchPathPatterns.isEmpty()) { + return null; + } else { + Collections.sort(matchPathPatterns, antPathMatcher.getPatternComparator(reqPath)); + String bestPattern = matchPathPatterns.get(0); + if (log.isDebugEnabled()) { + log.debug("req path: " + reqPath + + "\nmatch patterns: " + matchPathPatterns + + "\nbest one: " + bestPattern); + } + return getGatewayGroup2appsToApiConfig(method, path2methodToApiConfigMapMap.get(bestPattern)); + } } - private GatewayGroup2appsToApiConfig getApiConfig1(HttpMethod method, EnumMap method2apiConfigMap) { + // private GatewayGroup2appsToApiConfig getApiConfig0(HttpMethod method, String path) { + // while (true) { + // EnumMap method2apiConfigMap = path2methodToApiConfigMapMap.get(path); + // if (method2apiConfigMap == null) { + // int i = path.lastIndexOf(Constants.Symbol.FORWARD_SLASH); + // if (i == 0) { + // method2apiConfigMap = path2methodToApiConfigMapMap.get(Constants.Symbol.FORWARD_SLASH_STR); + // if (method2apiConfigMap == null) { + // return null; + // } else { + // return getGatewayGroup2appsToApiConfig(method, method2apiConfigMap); + // } + // } else { + // path = path.substring(0, i); + // } + // } else { + // return getGatewayGroup2appsToApiConfig(method, method2apiConfigMap); + // } + // } + // } + + private GatewayGroup2appsToApiConfig getGatewayGroup2appsToApiConfig(HttpMethod method, EnumMap method2apiConfigMap) { GatewayGroup2appsToApiConfig r = method2apiConfigMap.get(method); if (r == null) { return method2apiConfigMap.get(HttpMethod.X); diff --git a/src/main/java/we/plugin/stat/StatPluginFilter.java b/src/main/java/we/plugin/stat/StatPluginFilter.java index c682faf..9100a7c 100644 --- a/src/main/java/we/plugin/stat/StatPluginFilter.java +++ b/src/main/java/we/plugin/stat/StatPluginFilter.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import we.flume.clients.log4j2appender.LogService; -import we.listener.AggregateRedisConfig; +import we.config.AggregateRedisConfig; import we.plugin.PluginFilter; import we.plugin.auth.GatewayGroupService; import we.util.Constants; @@ -104,10 +104,15 @@ public class StatPluginFilter extends PluginFilter { b.append(Constants.Symbol.LEFT_BRACE); b.append(ip); toJsonStringValue(b, WebUtils.getOriginIp(exchange)); b.append(Constants.Symbol.COMMA); b.append(gatewayGroup); toJsonStringValue(b, currentGatewayGroups); b.append(Constants.Symbol.COMMA); - b.append(service); toJsonStringValue(b, WebUtils.getServiceId(exchange)); b.append(Constants.Symbol.COMMA); - b.append(appid); toJsonStringValue(b, WebUtils.getAppId(exchange)); b.append(Constants.Symbol.COMMA); + b.append(service); toJsonStringValue(b, WebUtils.getClientService(exchange)); b.append(Constants.Symbol.COMMA); + + String appId = WebUtils.getAppId(exchange); + if (appId != null) { + b.append(appid); toJsonStringValue(b, appId); b.append(Constants.Symbol.COMMA); + } + b.append(apiMethod); toJsonStringValue(b, exchange.getRequest().getMethodValue()); b.append(Constants.Symbol.COMMA); - b.append(apiPath); toJsonStringValue(b, WebUtils.getReqPath(exchange)); b.append(Constants.Symbol.COMMA); + b.append(apiPath); toJsonStringValue(b, WebUtils.getClientReqPath(exchange)); b.append(Constants.Symbol.COMMA); b.append(reqTime) .append(System.currentTimeMillis()); b.append(Constants.Symbol.RIGHT_BRACE); diff --git a/src/main/java/we/proxy/EurekaUriSelector.java b/src/main/java/we/proxy/EurekaUriSelector.java index a355549..80c489d 100644 --- a/src/main/java/we/proxy/EurekaUriSelector.java +++ b/src/main/java/we/proxy/EurekaUriSelector.java @@ -7,7 +7,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; /** * The Eureka implementation of {@code DiscoveryClientUriSelector} @@ -30,11 +32,11 @@ public class EurekaUriSelector extends AbstractDiscoveryClientUriSelector { // private static List aggrMemberInsts = new ArrayList<>(); // static { - // InstanceInfo i0 = InstanceInfo.Builder.newBuilder().setAppName("TRIP-MINI").setIPAddr("xxx.25.63.192").setPort(7094).build(); + // InstanceInfo i0 = InstanceInfo.Builder.newBuilder().setAppName("MINITRIP").setIPAddr("xxx.xxx.63.192").setPort(7094).build(); // aggrMemberInsts.add(i0); // } // private static AtomicLong counter = new AtomicLong(0); - // private static final String aggrMember = "trip-mini"; + // private static final String aggrMember = "minitrip"; private InstanceInfo roundRobinChoose1instFrom(String service) { diff --git a/src/main/java/we/proxy/FizzWebClient.java b/src/main/java/we/proxy/FizzWebClient.java index 90a550e..c9e8cac 100644 --- a/src/main/java/we/proxy/FizzWebClient.java +++ b/src/main/java/we/proxy/FizzWebClient.java @@ -18,6 +18,7 @@ package we.proxy; import com.alibaba.nacos.api.config.annotation.NacosValue; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -219,14 +220,13 @@ public class FizzWebClient { // TODO 请求完成后,做metric, 以反哺后续的请求转发 } - - private String extractServiceOrAddress(String uriOrSvc) { return uriOrSvc.substring(7, uriOrSvc.indexOf(Constants.Symbol.FORWARD_SLASH, 10)); } private boolean isService(String s) { - if (s.indexOf(Constants.Symbol.DOT) > 0 || s.equals(localhost)) { + if (StringUtils.indexOfAny(s, Constants.Symbol.DOT, Constants.Symbol.COLON) > 0 + || StringUtils.indexOfIgnoreCase(s, localhost) > 0) { return false; } else { return true; diff --git a/src/main/java/we/util/Constants.java b/src/main/java/we/util/Constants.java index b17404f..ae3100a 100644 --- a/src/main/java/we/util/Constants.java +++ b/src/main/java/we/util/Constants.java @@ -28,11 +28,12 @@ public final class Constants { public static final class Symbol { public static final String EMPTY = ""; public static final String SPACE_STR = " "; - public static final String TWO_SPACE_STR = " "; - public static final char COMMA = ','; - public static final char COLON = ':'; public static final char BLANK = ' '; public static final char SPACE = BLANK; + public static final String TWO_SPACE_STR = " "; + + public static final char COMMA = ','; + public static final char COLON = ':'; public static final char FORWARD_SLASH = '/'; public static final String FORWARD_SLASH_STR = "/"; public static final char BACK_SLASH = '\\'; @@ -51,15 +52,17 @@ public final class Constants { public static final char RIGHT_SQUARE_BRACKET = ']'; public static final char LEFT_BRACE = '{'; public static final char RIGHT_BRACE = '}'; - public static final String LINE_SEPARATOR = System.lineSeparator(); - public static final String COMMA_SPACE = ", "; + public static final char LF = '\n'; public static final char TAB = '\t'; public static final char NUL = '\u0000'; - public static final String HTTP_PROTOCOL_PREFIX = "http://"; static final char c0 = SystemUtils.IS_OS_WINDOWS ? Constants.Symbol.BACK_SLASH : Constants.Symbol.FORWARD_SLASH; public static final char PATH_SEPARATOR = c0; + public static final String LINE_SEPARATOR = System.lineSeparator(); + + public static final String COMMA_SPACE = ", "; + public static final String HTTP_PROTOCOL_PREFIX = "http://"; } public static final class Charset { @@ -79,22 +82,28 @@ public final class Constants { public static final class Profiles { public static final String LOCAL = "local"; public static final String DEV = "dev"; - public static final String TEST = "test"; - public static final String PREPROD = "preprod"; - public static final String PRE = "pre"; - public static final String PROD = "prod"; - public static final String HTTP_SERVER = "http_server"; - public static final String HTTP_CLIENT = "http_client"; - public static final String MYSQL = "mysql"; - public static final String REDIS = "redis"; - public static final String CODIS = "codis"; - public static final String MONGO = "mongo"; - public static final String ACTIVEMQ = "activemq"; - public static final String KAFKA = "kafka"; - public static final String ELASTICSEARCH = "elasticsearch"; - public static final String SCHED = "sched"; + public static final String TEST = "test"; + public static final String FAT = "fat"; + + public static final String PREPROD = "preprod"; + public static final String UAT = "uat"; + public static final String PRE = "pre"; + + public static final String PROD = "prod"; + public static final String PRO = "pro"; } + public static final String HTTP_SERVER = "http_server"; + public static final String HTTP_CLIENT = "http_client"; + public static final String MYSQL = "mysql"; + public static final String REDIS = "redis"; + public static final String CODIS = "codis"; + public static final String MONGO = "mongo"; + public static final String ACTIVEMQ = "activemq"; + public static final String KAFKA = "kafka"; + public static final String ELASTICSEARCH = "elasticsearch"; + public static final String SCHED = "sched"; + public static final String BIZ_ID = "bizId"; } diff --git a/src/main/java/we/util/ThreadContext.java b/src/main/java/we/util/ThreadContext.java index f654f47..86565eb 100644 --- a/src/main/java/we/util/ThreadContext.java +++ b/src/main/java/we/util/ThreadContext.java @@ -18,6 +18,7 @@ package we.util; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -111,4 +112,19 @@ public abstract class ThreadContext { public static Object remove(String key) { return getMap().remove(key); } + + public static ArrayList getArrayList(String key, Class elementType) { + return getArrayList(key, elementType, true); + } + + public static ArrayList getArrayList(String key, Class elementType, boolean clear) { + ArrayList l = (ArrayList) get(key); + if (l == null) { + l = new ArrayList<>(); + set(key, l); + } else if (clear) { + l.clear(); + } + return l; + } } diff --git a/src/main/java/we/util/UrlTransformUtils.java b/src/main/java/we/util/UrlTransformUtils.java new file mode 100644 index 0000000..3381729 --- /dev/null +++ b/src/main/java/we/util/UrlTransformUtils.java @@ -0,0 +1,306 @@ +package we.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.Nullable; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.Assert; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Fizz gateway url transform util class + * + * @author zhongjie + */ +public class UrlTransformUtils { + + private static final Logger log = LoggerFactory.getLogger(UrlTransformUtils.class); + + private UrlTransformUtils() {} + + private static final FizzGatewayUrlAntPathMatcher ANT_PATH_MATCHER = new FizzGatewayUrlAntPathMatcher(); + + /** + * transform the backend path to the real backend request path + * @param frontendPath frontend path + * @param backendPath backend path + * @param reqPath request path + * @return the transformed backend path + * @throws IllegalStateException when the request path does not match the frontend path pattern + * @throws IllegalArgumentException The number of capturing groups in the pattern segment does not match the number of URI template variables it defines + */ + public static String transform(String frontendPath, String backendPath, String reqPath) { + Assert.hasText(frontendPath, "frontend path cannot be null"); + Assert.hasText(backendPath, "backend path cannot be null"); + Assert.hasText(reqPath, "req path cannot be null"); + String bp = backendPath; + Map variables = ANT_PATH_MATCHER.extractUriTemplateVariables(frontendPath, reqPath); + for (Map.Entry entry : variables.entrySet()) { + backendPath = backendPath.replaceAll("\\{" + Matcher.quoteReplacement(entry.getKey()) + "}", Matcher.quoteReplacement(entry.getValue())); + } + + if (backendPath.indexOf('{') != -1) { + backendPath = backendPath.replaceAll("\\{[^/]*}", ""); + } + + if (log.isDebugEnabled()) { + log.debug("req: " + reqPath + ", frontend: " + frontendPath + ", backend: " + bp + ", target: " + backendPath); + } + + return backendPath; + } + + /** + * 自定义Ant风格路径匹配器 + * 设置默认路径分隔符为{@code #} + * 使用{@link FizzGatewayUrlAntPathMatcher.FizzGatewayAntPathStringMatcher}设置自定义的参数变量值(额外返回变量名为$1...n的键值对) + * + * @author zhongjie + */ + static class FizzGatewayUrlAntPathMatcher extends AntPathMatcher { + private static final String DEFAULT_PATH_SEPARATOR = "#"; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private volatile Boolean cachePatterns; + + private final Map replaceDoubleStarPatternCache = new ConcurrentHashMap<>(256); + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + private boolean caseSensitive = true; + + private static AntPathMatcher DEFAULT_ANT_PATH_MATCHER = new AntPathMatcher(); + + public FizzGatewayUrlAntPathMatcher() { + // 设置默认路径分隔符为# + super(DEFAULT_PATH_SEPARATOR); + } + + @Override + public void setPathSeparator(String pathSeparator) { + throw new RuntimeException("operation not support"); + } + + @Override + public void setTrimTokens(boolean trimTokens) { + throw new RuntimeException("operation not support"); + } + + @Override + public void setCaseSensitive(boolean caseSensitive) { + super.setCaseSensitive(caseSensitive); + this.caseSensitive = caseSensitive; + } + + @Override + public void setCachePatterns(boolean cachePatterns) { + super.setCachePatterns(cachePatterns); + this.cachePatterns = cachePatterns; + } + + @Override + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new FizzGatewayAntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + @Override + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + this.replaceDoubleStarPatternCache.clear(); + } + + @Override + public String extractPathWithinPattern(String pattern, String path) { + return DEFAULT_ANT_PATH_MATCHER.extractPathWithinPattern(pattern, path); + } + + @Override + public String combine(String pattern1, String pattern2) { + return DEFAULT_ANT_PATH_MATCHER.combine(pattern1, pattern2); + } + + @Override + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + String replaceDoubleStarPattern = null; + if (pattern != null) { + replaceDoubleStarPattern = getReplaceDoubleStarPattern(pattern); + } + return super.doMatch(replaceDoubleStarPattern, path, fullMatch, uriTemplateVariables); + } + + private String getReplaceDoubleStarPattern(String pattern) { + String replaceDoubleStarPattern = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + replaceDoubleStarPattern = this.replaceDoubleStarPatternCache.get(pattern); + } + if (replaceDoubleStarPattern == null) { + // by-zhongjie 替换**为.*正则模式 + replaceDoubleStarPattern = pattern.replaceAll("/\\*\\*$", "/{\\$:.*}") + .replaceAll("/\\*\\*/", "/{\\$:.*}/") + .replaceAll("^\\*\\*/", "{\\$:.*}/"); + if (cachePatterns == null && this.replaceDoubleStarPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return replaceDoubleStarPattern; + } + if (cachePatterns == null || cachePatterns) { + this.replaceDoubleStarPatternCache.put(pattern, replaceDoubleStarPattern); + } + } + return replaceDoubleStarPattern; + } + + protected static class FizzGatewayAntPathStringMatcher extends AntPathStringMatcher { + // by-zhongjie 将 \?|\*|\{((?:\{[^/]+?\}|[^/{}]|\\[{}])+?)\} 改为 \?|\*|\{((?:\{[^/]+?\}|[^{}]|\\[{}])+?)\},排除/的限制 + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^{}]|\\\\[{}])+?)\\}"); + + // by-zhongjie 将 (.*) 改为 ([^/]*),限制变量只能匹配在非/的字符内 + private static final String DEFAULT_VARIABLE_PATTERN = "([^/]*)"; + + private final Pattern pattern; + + private final List variableNames = new LinkedList<>(); + + // by-zhongjie 匿名占位符 + private final String ANONYMOUS_PLACEHOLDER = "$"; + + public FizzGatewayAntPathStringMatcher(String pattern) { + this(pattern, true); + } + + public FizzGatewayAntPathStringMatcher(String pattern, boolean caseSensitive) { + super(pattern, caseSensitive); + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + // by-zhongjie 对 ? 也使用模式匹配 + patternBuilder.append('('); + patternBuilder.append('.'); + patternBuilder.append(')'); + this.variableNames.add(ANONYMOUS_PLACEHOLDER); + } + else if ("*".equals(match)) { + // by-zhongjie 对 * 也使用模式匹配 + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(ANONYMOUS_PLACEHOLDER); + } + else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } + else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + + @Override + public boolean matchStrings(String str, @Nullable Map uriTemplateVariables) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + // SPR-8455 + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + String value = matcher.group(i); + + if (!ANONYMOUS_PLACEHOLDER.equals(name)) { + uriTemplateVariables.put(name, value); + } + // by-zhongjie 对提取到的变量按序号输出 + uriTemplateVariables.put(ANONYMOUS_PLACEHOLDER + i, value); + } + } + return true; + } + else { + return false; + } + } + } + } +} diff --git a/src/main/java/we/util/WebUtils.java b/src/main/java/we/util/WebUtils.java index 775d10d..1d812de 100644 --- a/src/main/java/we/util/WebUtils.java +++ b/src/main/java/we/util/WebUtils.java @@ -33,7 +33,10 @@ import reactor.core.publisher.Mono; import we.filter.FilterResult; import we.flume.clients.log4j2appender.LogService; import we.legacy.RespEntity; +import we.plugin.auth.ApiConfig; +import we.plugin.auth.AuthPluginFilter; +import java.net.URI; import java.util.*; /** @@ -42,40 +45,46 @@ import java.util.*; public abstract class WebUtils { - private static final Logger log = LoggerFactory.getLogger(WebUtils.class); + private static final Logger log = LoggerFactory.getLogger(WebUtils.class); - public static final String APP_HEADER = "fizz-appid"; + private static final String clientService = "clientService"; - private static final String directResponse = "directResponse"; + public static final String BACKEND_SERVICE = "backendService"; - public static final String FILTER_CONTEXT = "filterContext"; + private static final String xForwardedFor = "X-FORWARDED-FOR"; - public static final String APPEND_HEADERS = "appendHeaders"; + private static final String unknown = "unknown"; - public static final String PREV_FILTER_RESULT = "prevFilterResult"; + private static final String loopBack = "127.0.0.1"; - public static final String request_path = "reqPath"; + private static final String binaryAddress = "0:0:0:0:0:0:0:1"; - private static final String SERVICE_ID = "serviceId"; + private static final String directResponse = "directResponse"; - private static final String xForwardedFor = "X-FORWARDED-FOR"; + private static final String response = " response "; - private static final String unknown = "unknown"; + private static final String originIp = "originIp"; - private static final String loopBack = "127.0.0.1"; + public static final String APP_HEADER = "fizz-appid"; - private static final String binaryAddress = "0:0:0:0:0:0:0:1"; + public static final String FILTER_CONTEXT = "filterContext"; - public static boolean logResponseBody = false; + public static final String APPEND_HEADERS = "appendHeaders"; - public static Set logHeaderSet = Collections.EMPTY_SET; + public static final String PREV_FILTER_RESULT = "prevFilterResult"; - private static final String response = " response "; + private static final String CLIENT_REQUEST_PATH = "clientRequestPath"; - private static final String originIp = "originIp"; + private static final String CLIENT_REQUEST_QUERY = "clientRequestQuery"; + + public static final String BACKEND_PATH = "backendPath"; + + public static boolean logResponseBody = false; + + public static Set logHeaderSet = Collections.EMPTY_SET; + + public static final String PATH_PREFIX = "/proxy/"; - public static final String PATH_PREFIX = "/proxy/"; - public static String getHeaderValue(ServerWebExchange exchange, String header) { return exchange.getRequest().getHeaders().getFirst(header); } @@ -88,8 +97,8 @@ public abstract class WebUtils { return exchange.getAttribute(APP_HEADER); } - public static String getServiceId(ServerWebExchange exchange) { - String svc = exchange.getAttribute(SERVICE_ID); + public static String getClientService(ServerWebExchange exchange) { + String svc = exchange.getAttribute(clientService); if (svc == null) { String p = exchange.getRequest().getPath().value(); int pl = p.length(); @@ -114,18 +123,39 @@ public abstract class WebUtils { break; } } - exchange.getAttributes().put(SERVICE_ID, svc); + exchange.getAttributes().put(clientService, svc.toLowerCase()); } } } return svc; } - - public static String getPathPrefix(ServerWebExchange exchange) { - String p = exchange.getRequest().getPath().value(); - return p.substring(0, p.indexOf(getServiceId(exchange))); - } + public static void setBackendService(ServerWebExchange exchange, String service) { + exchange.getAttributes().put(BACKEND_SERVICE, service); + } + + public static String getBackendService(ServerWebExchange exchange) { + return exchange.getAttribute(BACKEND_SERVICE); + } + + public static byte getApiConfigType(ServerWebExchange exchange) { + ApiConfig ac = getApiConfig(exchange); + if (ac == null) { + return ApiConfig.Type.UNDEFINED; + } else { + return ac.type; + } + } + + public static ApiConfig getApiConfig(ServerWebExchange exchange) { + Object authRes = getFilterResultDataItem(exchange, AuthPluginFilter.AUTH_PLUGIN_FILTER, AuthPluginFilter.RESULT); + if (authRes != null && authRes instanceof ApiConfig) { + return (ApiConfig) authRes; + } else { + return null; + } + } + public static Mono getDirectResponse(ServerWebExchange exchange) { return (Mono) exchange.getAttributes().get(WebUtils.directResponse); } @@ -217,28 +247,67 @@ public abstract class WebUtils { return getFilterContext(exchange).get(PREV_FILTER_RESULT); } - public static String getReqPath(ServerWebExchange exchange) { - String path = exchange.getAttribute(request_path); + public static String getClientReqPath(ServerWebExchange exchange) { + String path = exchange.getAttribute(CLIENT_REQUEST_PATH); if (path == null) { path = exchange.getRequest().getPath().value(); path = path.substring(path.indexOf(Constants.Symbol.FORWARD_SLASH, 11), path.length()); - exchange.getAttributes().put(request_path, path); + exchange.getAttributes().put(CLIENT_REQUEST_PATH, path); } return path; } - public static String getRelativeUri(ServerWebExchange exchange) { - String relativeUri = getReqPath(exchange); - String qry = exchange.getRequest().getURI().getQuery(); - if (qry != null) { - if (StringUtils.indexOfAny(qry, Constants.Symbol.LEFT_BRACE, Constants.Symbol.FORWARD_SLASH, Constants.Symbol.HASH) > 0) { - qry = exchange.getRequest().getURI().getRawQuery(); + public static void setBackendPath(ServerWebExchange exchange, String path) { + exchange.getAttributes().put(BACKEND_PATH, path); + } + + public static String getBackendPath(ServerWebExchange exchange) { + return exchange.getAttribute(BACKEND_PATH); + } + + public static String getClientReqPathPrefix(ServerWebExchange exchange) { + String p = exchange.getRequest().getPath().value(); + return p.substring(0, p.indexOf(getClientService(exchange))); + } + + public static String getClientReqQuery(ServerWebExchange exchange) { + String qry = exchange.getAttribute(CLIENT_REQUEST_QUERY); + if (qry != null && StringUtils.EMPTY.equals(qry)) { + return null; + } else { + if (qry == null) { + URI uri = exchange.getRequest().getURI(); + qry = uri.getQuery(); + if (qry == null) { + exchange.getAttributes().put(CLIENT_REQUEST_QUERY, StringUtils.EMPTY); + } else { + if (StringUtils.indexOfAny(qry, Constants.Symbol.LEFT_BRACE, Constants.Symbol.FORWARD_SLASH, Constants.Symbol.HASH) > 0) { + qry = uri.getRawQuery(); + } + exchange.getAttributes().put(CLIENT_REQUEST_QUERY, qry); + } } + return qry; + } + } + + public static String getClientReqPathQuery(ServerWebExchange exchange) { + String relativeUri = getClientReqPath(exchange); + String qry = getClientReqQuery(exchange); + if (qry != null) { relativeUri = relativeUri + Constants.Symbol.QUESTION + qry; } return relativeUri; } + public static String appendQuery(String path, ServerWebExchange exchange) { + String qry = getClientReqQuery(exchange); + if (qry != null) { + return path + Constants.Symbol.QUESTION + qry; + } + return path; + } + public static Map getAppendHeaders(ServerWebExchange exchange) { return (Map) exchange.getAttributes().get(APPEND_HEADERS); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f9a03bc..bd38346 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -82,4 +82,7 @@ log: stat: # switch for push access stat data + open: true +send-log: + # switch for push log data open: true \ No newline at end of file diff --git a/src/main/resources/log4j2-spring.xml b/src/main/resources/log4j2-spring.xml index d72cd53..ebf9d1b 100644 --- a/src/main/resources/log4j2-spring.xml +++ b/src/main/resources/log4j2-spring.xml @@ -1,17 +1,22 @@ - + ${sys:APP_NAME} - + + + + + +