基于 Atlassian 为根据 OpenAPI 规范验证请求和响应所做的工作,我想将此逻辑调整为 WebTestClient。 我的方法是使用特定的过滤器配置 WebTestClient,但是我找不到通过 ExchangeFilterFunction 访问请求正文的简单方法。
我不确定这些问题是否仍然相关,但我遇到了同样的问题,并且我能够通过实现自定义来解决它
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);
}
}
}
}