从0到1学SpringCloud——14 gateway 获取请求报文RequestBody

目录

一、前言

二、代码实现

1、参考源码

2、自定义请求报文解析

3、自定义请求报文对象

4、使用RouteLocator路由


一、前言

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使用过程参考:

从0到1学SpringCloud——13 gateway RouteLocator配置路由规则

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月夜烛峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值