目录
gateway使用webflux,底层使用异步非阻塞IO模型,在获取请求报文信息时,经常为null。
本篇主要讲解以下两个方面:
1、如何异步获取请求报文
2、自定义请求报文数据类型
关于异步获取请求报文的代码,官方提供了参考:ReadBodyRoutePredicateFactory.java
- public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory
{ - 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
> messageReaders; -
- public ReadBodyRoutePredicateFactory() {
- super(ReadBodyRoutePredicateFactory.Config.class);
- this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
- }
-
- public ReadBodyRoutePredicateFactory(List
> messageReaders) { - super(ReadBodyRoutePredicateFactory.Config.class);
- this.messageReaders = messageReaders;
- }
-
- public AsyncPredicate
applyAsync(ReadBodyRoutePredicateFactory.Config config) { - return new AsyncPredicate
() { - public Publisher
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
apply(ReadBodyRoutePredicateFactory.Config config) { - throw new UnsupportedOperationException("ReadBodyPredicateFactory is only async.");
- }
-
- public static class Config {
- private Class inClass;
- private Predicate predicate;
- private Map
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
ReadBodyRoutePredicateFactory.Config setPredicate(Class inClass, Predicate predicate) { - this.setInClass(inClass);
- this.predicate = predicate;
- return this;
- }
-
- public Map
getHints() { - return this.hints;
- }
-
- public ReadBodyRoutePredicateFactory.Config setHints(Map
hints) { - this.hints = hints;
- return this;
- }
- }
- }
源码中提供了异步获取方法、断言规则、对象类型,根据这里的逻辑,我们可以自定义一个解析j规则。
继承AbstractRoutePredicateFactory,自定义一个json请求报文解析工厂:
ZhufengJsonReadBodyRoutePredicateFactory.java
- /**
- * @ClassName: ZhufengJsonRoutePredicateFactory
- * @Description 解析json请求
- * @author 月夜烛峰
- * @date 2022/9/14 19:52
- */
- @Slf4j
- @Component
- public class ZhufengJsonReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory
{ -
- private static final String JSON_ATTRIBUTE = "msg_type_predicate_json_attribute";
- private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedMsgTypeObject";
-
- private final List
> messageReaders; -
- public ZhufengJsonReadBodyRoutePredicateFactory() {
- super(Config.class);
- this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
- }
-
- public ZhufengJsonReadBodyRoutePredicateFactory(List
> messageReaders) { - super(Config.class);
- this.messageReaders = messageReaders;
- }
-
- @Override
- public ShortcutType shortcutType() {
- return ShortcutType.GATHER_LIST;
- }
-
- @Override
- public List
shortcutFieldOrder() { - return Arrays.asList("patterns");
- }
-
- @Override
- public AsyncPredicate
applyAsync(Config config) { - return new AsyncPredicate
() { - @Override
- public Publisher
apply(ServerWebExchange exchange) { -
- Class
inClass = JSONObject.class; -
- Predicate
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
apply(Config config) { - throw new UnsupportedOperationException(
- "ReadBodyPredicateFactory is only async.");
- }
-
- public static class Config {
-
- private List
patterns = new ArrayList(); -
-
- public List
getPatterns() { - return patterns;
- }
-
- public Config setPatterns(List
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 ,则校验通过。
控制台信息:
虽然请求可以正常获取,也可以正常校验,但是使用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
> messageReaders = HandlerStrategies - .withDefaults().messageReaders();
-
- public ZhufengUserReadBodyRoutePredicateFactory() {
- super();
- }
-
- @Override
- public AsyncPredicate
applyAsync(ReadBodyRoutePredicateFactory.Config config) { -
- return exchange -> {
- Predicate
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
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
res = restTemplate.postForEntity(pref+uri, user, UserInfo.class); - return "postUser UserInfo";
- }
User信息正常打印
如果使用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使用过程参考: