目录
一、前言
gateway使用webflux,底层使用异步非阻塞IO模型,在获取请求报文信息时,经常为null。
本篇主要讲解以下两个方面:
1、如何异步获取请求报文
2、自定义请求报文数据类型
二、代码实现
1、参考源码
关于异步获取请求报文的代码,官方提供了参考:ReadBodyRoutePredicateFactory.java
public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory<ReadBodyRoutePredicateFactory.Config> {
protected static final Log log = LogFactory.getLog(ReadBodyRoutePredicateFactory.class);
private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute";
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
private final List<HttpMessageReader<?>> messageReaders;
public ReadBodyRoutePredicateFactory() {
super(ReadBodyRoutePredicateFactory.Config.class);
this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
}
public ReadBodyRoutePredicateFactory(List<HttpMessageReader<?>> messageReaders) {
super(ReadBodyRoutePredicateFactory.Config.class);
this.messageReaders = messageReaders;
}
public AsyncPredicate<ServerWebExchange> applyAsync(ReadBodyRoutePredicateFactory.Config config) {
return new AsyncPredicate<ServerWebExchange>() {
public Publisher<Boolean> apply(ServerWebExchange exchange) {
Class inClass = config.getInClass();
Object cachedBody = exchange.getAttribute("cachedRequestBodyObject");
if (cachedBody != null) {
try {
boolean test = config.predicate.test(cachedBody);
exchange.getAttributes().put("read_body_predicate_test_attribute", test);
return Mono.just(test);
} catch (ClassCastException var6) {
if (ReadBodyRoutePredicateFactory.log.isDebugEnabled()) {
ReadBodyRoutePredicateFactory.log.debug("Predicate test failed because class in predicate does not match the cached body object", var6);
}
return Mono.just(false);
}
} else {
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {
return ServerRequest.create(exchange.mutate().request(serverHttpRequest).build(), ReadBodyRoutePredicateFactory.this.messageReaders).bodyToMono(inClass).doOnNext((objectValue) -> {
exchange.getAttributes().put("cachedRequestBodyObject", objectValue);
}).map((objectValue) -> {
return config.getPredicate().test(objectValue);
});
});
}
}
public String toString() {
return String.format("ReadBody: %s", config.getInClass());
}
};
}
public Predicate<ServerWebExchange> apply(ReadBodyRoutePredicateFactory.Config config) {
throw new UnsupportedOperationException("ReadBodyPredicateFactory is only async.");
}
public static class Config {
private Class inClass;
private Predicate predicate;
private Map<String, Object> hints;
public Config() {
}
public Class getInClass() {
return this.inClass;
}
public ReadBodyRoutePredicateFactory.Config setInClass(Class inClass) {
this.inClass = inClass;
return this;
}
public Predicate getPredicate() {
return this.predicate;
}
public ReadBodyRoutePredicateFactory.Config setPredicate(Predicate predicate) {
this.predicate = predicate;
return this;
}
public <T> ReadBodyRoutePredicateFactory.Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
this.setInClass(inClass);
this.predicate = predicate;
return this;
}
public Map<String, Object> getHints() {
return this.hints;
}
public ReadBodyRoutePredicateFactory.Config setHints(Map<String, Object> hints) {
this.hints = hints;
return this;
}
}
}
源码中提供了异步获取方法、断言规则、对象类型,根据这里的逻辑,我们可以自定义一个解析j规则。
2、自定义请求报文解析
继承AbstractRoutePredicateFactory,自定义一个json请求报文解析工厂:
ZhufengJsonReadBodyRoutePredicateFactory.java
/**
* @ClassName: ZhufengJsonRoutePredicateFactory
* @Description 解析json请求
* @author 月夜烛峰
* @date 2022/9/14 19:52
*/
@Slf4j
@Component
public class ZhufengJsonReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory<ZhufengJsonReadBodyRoutePredicateFactory.Config> {
private static final String JSON_ATTRIBUTE = "msg_type_predicate_json_attribute";
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedMsgTypeObject";
private final List<HttpMessageReader<?>> messageReaders;
public ZhufengJsonReadBodyRoutePredicateFactory() {
super(Config.class);
this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
}
public ZhufengJsonReadBodyRoutePredicateFactory(List<HttpMessageReader<?>> messageReaders) {
super(Config.class);
this.messageReaders = messageReaders;
}
@Override
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("patterns");
}
@Override
public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
return new AsyncPredicate<ServerWebExchange>() {
@Override
public Publisher<Boolean> apply(ServerWebExchange exchange) {
Class<JSONObject> inClass = JSONObject.class;
Predicate<JSONObject> predicate = msgInfo -> {
log.info("请求数据:" + msgInfo);
log.info("patterns:" + JSONObject.toJSONString(config.patterns));
String msgtype = msgInfo.getString("msgType");
if (config.patterns.contains(msgtype)) {
log.info("验证成功");
return true;
}
log.error("报文格式错误");
return false;
};
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
log.info("打印属性cachedBody:" + cachedBody);
if (cachedBody != null) {
try {
boolean test = predicate.test((JSONObject) cachedBody);
exchange.getAttributes().put(JSON_ATTRIBUTE, test);
return Mono.just(test);
} catch (ClassCastException e) {
log.error("Predicate test failed because class in predicate "
+ "does not match the cached body object", e);
}
return Mono.just(false);
} else {
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
(serverHttpRequest) -> ServerRequest
.create(exchange.mutate().request(serverHttpRequest)
.build(), messageReaders)
.bodyToMono(inClass)
.doOnNext(objectValue -> exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue))
.map(objectValue -> predicate.test(objectValue)));
}
}
@Override
public String toString() {
return String.format("ReadBody: %s", String.class);
}
};
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
throw new UnsupportedOperationException(
"ReadBodyPredicateFactory is only async.");
}
public static class Config {
private List<String> patterns = new ArrayList<String>();
public List<String> getPatterns() {
return patterns;
}
public Config setPatterns(List<String> patterns) {
this.patterns = patterns;
return this;
}
}
}
在数据库新增请求报文的断言规则,断言代码和数据库结构可参考:
《从0到1学SpringCloud——12 gateway 动态配置网关路由规则》
新增数据:
INSERT INTO `zf_gateway_route` (`route_id`, `uri`, `path_name`, `path_pattern`, `method_name`, `method_pattern`, `msg_name`, `msg_type`, `filter_name`, `new_path`, `remark`, `status`, `index`) VALUES ('zhufeng-body-json', 'lb://zhufeng-web-msg', 'Path', '/msg/json', 'Method', 'Post', 'ZhufengJsonReadBody', 'json', NULL, NULL, '路由测试', 0, 5);
INSERT INTO `zf_gateway_route` (`route_id`, `uri`, `path_name`, `path_pattern`, `method_name`, `method_pattern`, `msg_name`, `msg_type`, `filter_name`, `new_path`, `remark`, `status`, `index`) VALUES ('zhufeng-body-user', 'lb://zhufeng-web-msg', 'Path', '/msg/user', 'Method', 'Post', 'ZhufengUserReadBody', 'user', NULL, NULL, '路由测试', 0, 5);
请求测试报文:
{
"message": "json测试",
"msgType":"json",
"name": "yueyezhufeng"
}
当发送 Post 请求 /msg/json 路径时,如果请求报文中 msgType 值为 json ,则校验通过。
控制台信息:
3、自定义请求报文对象
虽然请求可以正常获取,也可以正常校验,但是使用json格式容易引起误解,毕竟json格式的字符串一定场合也会解析,为了更好对比,我们定义一个UserInfo对象:
/**
* @ClassName: UserInfo
* @Description 自定义对象
* @author 月夜烛峰
* @date 2022/9/16 18:12
*/
public class UserInfo {
private String userId;
private String userName;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
在刚才新增数据时,添加了两条数据,一个是json请求报文的,一个就是用来解析UserInfo。
ZhufengUserReadBodyRoutePredicateFactory.java代码:
/**
* @ClassName: ZhufengUserReadBodyRoutePredicateFactory
* @Description 自定义User报文体解析类
* @author 月夜烛峰
* @date 2022/9/18 12:16
*/
@Slf4j
@Component
public class ZhufengUserReadBodyRoutePredicateFactory extends ReadBodyRoutePredicateFactory {
private static final String ZF_USER_ATTRIBUTE = "read_body_predicate_user_attribute";
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies
.withDefaults().messageReaders();
public ZhufengUserReadBodyRoutePredicateFactory() {
super();
}
@Override
public AsyncPredicate<ServerWebExchange> applyAsync(ReadBodyRoutePredicateFactory.Config config) {
return exchange -> {
Predicate<UserInfo> predicate = msgInfo -> {
log.info("请求数据:" + msgInfo.getUserName());
return true;
};
config.setPredicate(predicate);
config.setInClass(UserInfo.class);
log.info("打印属性formdata:{}",exchange.getFormData().toString());
log.info("打印属性queryparam:{}",exchange.getRequest().getQueryParams());
Class<UserInfo> inClass = config.getInClass();
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
if (cachedBody != null) {
try {
boolean test = config.getPredicate().test(cachedBody);
exchange.getAttributes().put(ZF_USER_ATTRIBUTE, test);
return Mono.just(test);
} catch (ClassCastException e) {
if (log.isDebugEnabled()) {
log.debug("Predicate test failed because class in predicate "
+ "does not match the cached body object", e);
}
}
return Mono.just(false);
} else {
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> ServerRequest.create(exchange.mutate().request(serverHttpRequest).build(), messageReaders).bodyToMono(inClass).doOnNext((objectValue) -> {
exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue);
}).map((objectValue) -> {
UserInfo userInfo = (UserInfo)objectValue;
log.info(userInfo.getUserId()+":"+userInfo.getUserName());
return config.getPredicate().test(objectValue);
}));
}
};
}
}
通过restTemplate发起测试:
@RequestMapping("postUser")
public String postUser() {
//请求参数,仅作为演示参数传递
UserInfo user = new UserInfo();
user.setUserId("zf1001");
user.setUserName("月夜烛峰");
//zhufeng-gateway-db 为访问的微服务名称
String pref = "http://zhufeng-gateway-db";
//uri为微服务中访问的地址
String uri = "/msg/user";
ResponseEntity<UserInfo> res = restTemplate.postForEntity(pref+uri, user, UserInfo.class);
return "postUser UserInfo";
}
User信息正常打印
4、使用RouteLocator路由
如果使用RouteLocator可以指定请求报文的对象类型,如下:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
// 构建多个路由routes
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("zhufeng-route-msg",
r -> r.readBody(UserInfo.class, requestBody -> {
log.info("requestBody is {}", requestBody);
return true;
}).and().path("/msg/**").
uri("lb://zhufeng-web-msg"));
return routes.build();
}
RouteLocator使用过程参考: