Как регистрировать тела запроса и ответа в Spring WebFlux

Я хочу иметь централизованное ведение журнала запросов и ответов в моем REST API на Spring WebFlux с Kotlin. До сих пор я пробовал такие подходы

@Bean fun apiRouter() = router { (accept(MediaType.APPLICATION_JSON) and "/api").nest { "/user".nest { GET("/", userHandler::listUsers) POST("/{userId}", userHandler::updateUser) } } }.filter { request, next -> logger.info { "Processing request $request with body ${request.bodyToMono<String>()}" } next.handle(request).doOnSuccess { logger.info { "Handling with response $it" } } } 

Здесь метод запроса и журнал путей успешно, но тело – Mono , так как мне его заносить? Если это будет наоборот, и я должен подписаться на запрос тела Mono и зарегистрировать его в обратном вызове? Другая проблема заключается в том, что интерфейс ServerResponse здесь не имеет доступа к телу ответа. Как я могу получить его здесь?


Другой подход, который я пробовал, заключается в использовании WebFilter

 @Bean fun loggingFilter(): WebFilter = WebFilter { exchange, chain -> val request = exchange.request logger.info { "Processing request method=${request.method} path=${request.path.pathWithinApplication()} params=[${request.queryParams}] body=[${request.body}]" } val result = chain.filter(exchange) logger.info { "Handling with response ${exchange.response}" } return@WebFilter result } 

Такая же проблема здесь: тело запроса – это Flux и тело ответа.

Есть ли способ получить доступ к полному запросу и ответам для регистрации из некоторых фильтров? Что я не понимаю?

Это более или менее похоже на ситуацию в Spring MVC.

В Spring MVC вы можете использовать фильтр AbstractRequestLoggingFilter и ContentCachingRequestWrapper и / или ContentCachingResponseWrapper . Здесь много компромиссов:

  • если вы хотите получить доступ к атрибутам запроса сервлета, вам нужно действительно прочитать и проанализировать тело запроса
  • регистрация тела запроса означает буферизацию тела запроса, который может использовать значительный объем памяти
  • если вы хотите получить доступ к телу ответа, вам необходимо обернуть ответ и закрепить тело ответа, как оно написано, для последующего поиска

ContentCaching*Wrapper Классы ContentCaching*Wrapper не существуют в WebFlux, но вы можете создавать похожие. Но имейте в виду другие моменты:

  • буферизация данных в памяти как-то идет против реактивного стека, так как мы пытаемся там быть очень эффективными с доступными ресурсами
  • вы не должны вмешиваться в фактический поток данных и делать флеш больше / меньше, чем ожидалось, иначе вы рискуете сломать потоки, используя случаи
  • на этом уровне у вас есть только доступ к экземплярам DataBuffer , которые представляют собой (грубо) массивы байтов с эффективной памятью. Они относятся к буферным пулам и перерабатываются для других обменов. Если они не сохраняются / освобождаются должным образом, создаются утечки памяти (и данные буферизации для последующего потребления, безусловно, подходят для этого сценария)
  • снова на этом уровне, это только байты, и у вас нет доступа к любому кодеку для синтаксического анализа тела HTTP. Я бы забыл о буферизации контента, если он не читается человеком в первую очередь

Другие ответы на ваш вопрос:

  • да, WebFilter , вероятно, лучший подход
  • нет, вы не должны подписываться на орган запроса, иначе вы будете использовать данные, которые обработчик не сможет прочитать; вы можете flatMap на данные запроса и буфера в doOn операторах
  • обертка ответа должна дать вам доступ к телу ответа, поскольку он написан; не забывайте о утечках памяти, хотя

Я не нашел хороший способ регистрировать тела запроса / ответа, но если вас интересуют только метаданные, вы можете сделать это, как показано ниже.

 import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.server.reactive.ServerHttpResponse import org.springframework.stereotype.Component import org.springframework.web.server.ServerWebExchange import org.springframework.web.server.WebFilter import org.springframework.web.server.WebFilterChain import reactor.core.publisher.Mono @Component class LoggingFilter(val requestLogger: RequestLogger, val requestIdFactory: RequestIdFactory) : WebFilter { val logger = logger() override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> { logger.info(requestLogger.getRequestMessage(exchange)) val filter = chain.filter(exchange) exchange.response.beforeCommit { logger.info(requestLogger.getResponseMessage(exchange)) Mono.empty() } return filter } } @Component class RequestLogger { fun getRequestMessage(exchange: ServerWebExchange): String { val request = exchange.request val method = request.method val path = request.uri.path val acceptableMediaTypes = request.headers.accept val contentType = request.headers.contentType return ">>> $method $path ${HttpHeaders.ACCEPT}: $acceptableMediaTypes ${HttpHeaders.CONTENT_TYPE}: $contentType" } fun getResponseMessage(exchange: ServerWebExchange): String { val request = exchange.request val response = exchange.response val method = request.method val path = request.uri.path val statusCode = getStatus(response) val contentType = response.headers.contentType return "<<< $method $path HTTP${statusCode.value()} ${statusCode.reasonPhrase} ${HttpHeaders.CONTENT_TYPE}: $contentType" } private fun getStatus(response: ServerHttpResponse): HttpStatus = try { response.statusCode } catch (ex: Exception) { HttpStatus.CONTINUE } } 

Я довольно новичок в Spring WebFlux, и я не знаю, как это сделать в Kotlin, но должен быть таким же, как в Java с помощью WebFilter:

 public class PayloadLoggingWebFilter implements WebFilter { public static final ByteArrayOutputStream EMPTY_BYTE_ARRAY_OUTPUT_STREAM = new ByteArrayOutputStream(0); private final Logger logger; private final boolean encodeBytes; public PayloadLoggingWebFilter(Logger logger) { this(logger, false); } public PayloadLoggingWebFilter(Logger logger, boolean encodeBytes) { this.logger = logger; this.encodeBytes = encodeBytes; } @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { if (logger.isInfoEnabled()) { return chain.filter(decorate(exchange)); } else { return chain.filter(exchange); } } private ServerWebExchange decorate(ServerWebExchange exchange) { final ServerHttpRequest decorated = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { if (logger.isDebugEnabled()) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); return super.getBody().map(dataBuffer -> { try { Channels.newChannel(baos).write(dataBuffer.asByteBuffer().asReadOnlyBuffer()); } catch (IOException e) { logger.error("Unable to log input request due to an error", e); } return dataBuffer; }).doOnComplete(() -> flushLog(baos)); } else { return super.getBody().doOnComplete(() -> flushLog(EMPTY_BYTE_ARRAY_OUTPUT_STREAM)); } } }; return new ServerWebExchangeDecorator(exchange) { @Override public ServerHttpRequest getRequest() { return decorated; } private void flushLog(ByteArrayOutputStream baos) { ServerHttpRequest request = super.getRequest(); if (logger.isInfoEnabled()) { StringBuffer data = new StringBuffer(); data.append('[').append(request.getMethodValue()) .append("] '").append(String.valueOf(request.getURI())) .append("' from ") .append( Optional.ofNullable(request.getRemoteAddress()) .map(addr -> addr.getHostString()) .orElse("null") ); if (logger.isDebugEnabled()) { data.append(" with payload [\n"); if (encodeBytes) { data.append(new HexBinaryAdapter().marshal(baos.toByteArray())); } else { data.append(baos.toString()); } data.append("\n]"); logger.debug(data.toString()); } else { logger.info(data.toString()); } } } }; } } 

Вот некоторые тесты по этому поводу : github

Я думаю, что это означает Брайан Клозель (@ brian-clozel).

Что сказал Брайан. Кроме того, органы запроса / ответа на регистрацию не имеют смысла для реактивной потоковой передачи. Если вы представляете данные, проходящие через канал в виде потока, у вас нет полного содержимого в любое время, если вы его не буферизируете, что наносит ущерб всей точке. Для небольшого запроса / ответа вы можете уйти с буферизацией, но тогда зачем использовать реактивную модель (кроме впечатления ваших коллег :-))?

Единственная причина для регистрации запроса / ответа, которую я могу вызвать, – это отладка, но с моделью реактивного программирования также необходимо изменить метод отладки. Project Reactor doc имеет отличный раздел по отладке, на который вы можете ссылаться: http://projectreactor.io/docs/core/snapshot/reference/#debugging

Intereting Posts
JVM – открыть веб-страницу и запустить код Javascript Как написать преобразование многократного использования для значения String to Enum в группе классов Enum? (Котлин) Как получить данные из более чем одного json api Вывод типа Kotlin faild Котлин объясняет мне, что касается полей подкачки Невозможно установить Content-Type в запросе SOAP Как помещать выражение лямбда после параметров на mapTo вызвать юридический синтаксис? Можно ли использовать Котлин в Граале? Kotlin – извлечение списка родительских объектов предметов в коллекции Наличие getter возвращает тип с нулевым значением, даже если поле поддержки является нулевым kotlin: общий для разных типов Исключение Java / Kotlin для шаблона посетителя с типичным типом возврата Составление сетевого запроса с RX и Kotlin Как я могу проверить отсутствие вызова AsyncTask? Производит ли вызов Kotlin метод вызова метода