Spring WebTestClient 请求和响应验证

问题描述 投票:0回答:1

基于 Atlassian 为根据 OpenAPI 规范验证请求和响应所做的工作,我想将此逻辑调整为 WebTestClient。 我的方法是使用特定的过滤器配置 WebTestClient,但是我找不到通过 ExchangeFilterFunction 访问请求正文的简单方法。

spring-webflux openapi
1个回答
0
投票

我不确定这些问题是否仍然相关,但我遇到了同样的问题,并且我能够通过实现自定义来解决它

ClientHttpRequest
。当调用
writeWith
方法时,验证将在这些对象内进行。

public class OpenApiValidationFilter implements ExchangeFilterFunction {

    private final OpenApiInteractionValidator validator;
    private boolean requestValidationEnabled = true;

    public OpenApiValidationFilter(final String specUrl) {
        this(specUrl, LevelResolver.defaultResolver());
    }

    public OpenApiValidationFilter(final String specUrl, final LevelResolver levelResolver) {
        this.validator = createForSpecificationUrl(specUrl)
                .withLevelResolver(levelResolver)
                .build();
    }

    @Override
    public @Nonnull Mono<ClientResponse> filter(final ClientRequest request, @Nonnull final ExchangeFunction next) {
        final Request.Method method = Request.Method.valueOf(request.method().name());
        return Mono.fromSupplier(() -> new SimpleRequest.Builder(method, request.url().getPath()).build())
                .flatMap(simpleRequest -> {
                    if (requestValidationEnabled) {
                        return validateRequest(request, next);
                    } else {
                        return next.exchange(request);
                    }
                })
                .flatMap(clientResponse -> validateResponse(request, clientResponse));
    }

    private Mono<ClientResponse> validateRequest(
            final ClientRequest clientRequest,
            final ExchangeFunction next
    ) {
        final Request.Method method = Request.Method.valueOf(clientRequest.method().name());
        final UriComponents uriComponents = UriComponentsBuilder.fromUri(clientRequest.url()).build();
        final SimpleRequest.Builder builder = new SimpleRequest.Builder(method, clientRequest.url().getPath());

        clientRequest.headers().forEach(builder::withHeader);
        uriComponents.getQueryParams().forEach(builder::withQueryParam);

        final BodyInserter<?, ? super ClientHttpRequest> bodyInserter = clientRequest.body();

        final ClientRequest requestWithBodyInserter = ClientRequest.from(clientRequest)
                .body((outputMessage, context) ->
                                bodyInserter
                                        .insert(new ClientHttpRequestWIthValidation(outputMessage, builder, validator), context))
                .build();

        return next.exchange(requestWithBodyInserter);
    }

    private Mono<ClientResponse> validateResponse(
            final ClientRequest clientRequest,
            final ClientResponse clientResponse
    ) {
        final Request.Method method = Request.Method.valueOf(clientRequest.method().name());
        final SimpleResponse.Builder builder = new SimpleResponse.Builder(clientResponse.statusCode().value());

        clientResponse.headers()
                .asHttpHeaders()
                .forEach(builder::withHeader);

        return clientResponse.bodyToMono(String.class)
                .flatMap(responseBody -> Mono.just(builder.withBody(responseBody).build())
                        .map(simpleResponse -> validator.validateResponse(clientRequest.url().getPath(), method, simpleResponse))
                        .flatMap(validationReport -> {
                            if (validationReport.hasErrors()) {
                                return Mono.error(new OpenApiValidationException(validationReport));
                            } else {
                                return Mono.just(cloneResponseWithBody(clientResponse, responseBody));
                            }
                        })
                );
    }

    private static ClientResponse cloneResponseWithBody(final ClientResponse clientResponse, final String responseBody) {
        return ClientResponse.create(clientResponse.statusCode())
                .headers(headers -> headers.putAll(clientResponse.headers().asHttpHeaders()))
                .cookies(cookies -> cookies.putAll(clientResponse.cookies()))
                .body(responseBody)
                .build();
    }

    public static class OpenApiValidationException extends WebClientException {

        private final ValidationReport report;

        public OpenApiValidationException(@Nonnull final ValidationReport report) {
            super(JsonValidationReportFormat
                    .getInstance()
                    .apply(requireNonNull(report, "ValidationReport is required"))
            );
            this.report = report;
        }

        public ValidationReport getValidationReport() {
            return report;
        }
    }

    public void enableRequestValidation() {
        requestValidationEnabled = true;
    }

    public void disableRequestValidation() {
        requestValidationEnabled = false;
    }

    @RequiredArgsConstructor
    public static class ClientHttpRequestWIthValidation implements ClientHttpRequest {

        private final ClientHttpRequest clientRequest;
        private final SimpleRequest.Builder requestBuilder;
        private final OpenApiInteractionValidator validator;

        @Override
        @NotNull
        public HttpHeaders getHeaders() {
            return this.clientRequest.getHeaders();
        }

        @Override
        @NotNull
        public DataBufferFactory bufferFactory() {
            return this.clientRequest.bufferFactory();
        }

        @Override
        public void beforeCommit(@NotNull Supplier<? extends Mono<Void>> action) {
            this.clientRequest.beforeCommit(action);
        }

        @Override
        public boolean isCommitted() {
            return this.clientRequest.isCommitted();
        }

        @Override
        @NotNull
        public Mono<Void> writeWith(@NotNull Publisher<? extends DataBuffer> body) {
            return this.clientRequest.writeWith(DataBufferUtils.join(body)
                    .doOnNext(this::validateRequest));
        }

        @Override
        @NotNull
        public Mono<Void> writeAndFlushWith(@NotNull Publisher<? extends Publisher<? extends DataBuffer>> body) {
            return this.clientRequest.writeAndFlushWith(Flux.from(body)
                    .map(b -> DataBufferUtils.join(b)
                            .doOnNext(this::validateRequest)));
        }

        @Override
        @NotNull
        public Mono<Void> setComplete() {
            return this.clientRequest.setComplete();
        }

        @Override
        @NotNull
        public HttpMethod getMethod() {
            return this.clientRequest.getMethod();
        }

        @Override
        @NotNull
        public URI getURI() {
            return this.clientRequest.getURI();
        }

        @Override
        @NotNull
        public MultiValueMap<String, HttpCookie> getCookies() {
            return this.clientRequest.getCookies();
        }

        @Override
        @NotNull
        public <T> T getNativeRequest() {
            return this.clientRequest.getNativeRequest();
        }

        private void validateRequest(final DataBuffer data) {
            final SimpleRequest simpleRequest = requestBuilder
                    .withBody(data.toString(StandardCharsets.UTF_8))
                    .build();

            final ValidationReport validationReport = validator.validateRequest(simpleRequest);

            if (validationReport.hasErrors()) {
                throw new OpenApiValidationException(validationReport);
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.