Как правильно отправить Mono <ResponseEntity>как JSON в ответе HTTP-сервера Netty Reactor - PullRequest
0 голосов
/ 28 апреля 2020

Я пытаюсь выяснить, как правильно отправить ответ с ResponseEntity как JSON с HTTP-сервера Netty Reactor.

Моя текущая реализация реагирует на запрос от WebClient и должна отправить ответ с некоторым статусом ResponseEntity ( давайте предположим, что только HTTP хорошо).

К сожалению, я все еще получаю InvalidDefinitionException на стороне клиента, говоря, что невозможно создать экземпляр из-за отсутствия конструктора по умолчанию.

Я знаю, что это значит, но, например, Spring Webflux также может иметь тип возвращаемой конечной точки отдыха Mono, и никаких проблем на стороне клиента не возникнет. Так возможно ли как-нибудь правильно сериализовать сущность как JSON на стороне сервера и десериализовать ее на стороне клиента?

Это мой клиент

import org.springframework.web.reactive.function.client.WebClient;

public Mono<ResponseEntity> postRequest(final Object body, final String uri) {
        return webClient.post()
                .uri(uri)
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(body))
                .exchange()
                .flatMap(clientResponse -> clientResponse.toEntity(ResponseEntity.class));
    }

Это мой сервер

    public void runWithPost(final String endpointPath, final ServerCallback callback) {
        server = HttpServer.create()
                .host(this.host)
                .port(this.port)
                .route(routes ->
                        routes.post(endpointPath, (request, response) ->
                                response.addHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                                        .sendString(Mono.just(getJSON(callback.handleCallback())))))
                .wiretap(true)
                .bindNow();

        System.out.println("Starting server...");
    }

    private String getJSON(final ResponseEntity responseEntity) {
        String json = StringUtils.EMPTY;
        try {
            json = objectMapper.writeValueAsString(responseEntity);
            System.out.println("Serialized JSON: " + json);
        } catch (final JsonProcessingException ex) {
            System.err.println("JSON serializer error: " + ex.getMessage());
        }

        return json;
    }

Это обратный вызов

public interface ServerCallback {

    ResponseEntity handleCallback();

}

и использование

reactiveRestServer.runWithPost("/transaction", () -> ResponseEntity.ok().build());

К сожалению, на стороне клиента я не получаю статус HTTP в порядке, но исключение десериализации:

2020-04-28 16:09:35.345 ERROR 15136 --- [ctor-http-nio-2] c.a.t.t.inbound.ArpMessageServiceImpl    : Type definition error: [simple type, class org.springframework.http.ResponseEntity]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.http.ResponseEntity` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 2]
2020-04-28 16:09:35.349  WARN 15136 --- [ctor-http-nio-2] io.netty.util.ReferenceCountUtil         : Failed to release a message: DefaultLastHttpContent(data: PooledSlicedByteBuf(freed), decoderResult: success)

io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
    at io.netty.util.internal.ReferenceCountUpdater.toLiveRealRefCnt(ReferenceCountUpdater.java:74) ~[netty-common-4.1.45.Final.jar:4.1.45.Final]

Чего мне не хватает?

1 Ответ

0 голосов
/ 29 апреля 2020

Итак, я наконец решил эту проблему. Для тех, кто решил бы подобную проблему, вот ответ. Проблема состоит в том, что Spring Webflux преобразует ResponseEntity в DefaultFullHttpResponse, так что DefaultFullHttpResponse содержит заголовки, статус и тело. Я решил эту проблему, применив точно такой же подход.

    public void runWithPost(final String endpointPath, final ServerCallback callback) {
        if (server == null || server.isDisposed()) {
            server = HttpServer.create()
                    .host(this.host)
                    .port(this.port)
                    .route(routes ->
                            routes.post(endpointPath, (request, response) -> processResponse(response, callback)))
                    .wiretap(true)
                    .bindNow();

            logger.info("Starting server...");
        } else {
            logger.info("Couldn't start server because one is already running!");
        }
    }

и преобразование здесь

    private NettyOutbound processResponse(final HttpServerResponse response, final ServerCallback callback) {
        final ResponseEntity responseEntity = callback.handleCallback();

        // set status
        response.status(responseEntity.getStatusCodeValue());

        // set headers
        final HttpHeaders entityHeaders = responseEntity.getHeaders();

        if (!entityHeaders.isEmpty()) {
            entityHeaders.entrySet().stream()
                    .forEach(entry -> response.addHeader(entry.getKey(), buildValue(entry.getValue())));
        }

        if (responseEntity.hasBody()) {
            try {
                final Object body = responseEntity.getBody();

                if (body instanceof String) {
                    return response.sendString(Mono.just((String) body));
                } else {
                    return response.send(Mono.just(Unpooled.wrappedBuffer(getBytesFromObject(body))));
                }
            } catch (final IOException ex) {
                response.status(HttpResponseStatus.INTERNAL_SERVER_ERROR);
                return response.sendString(Mono.just(ex.getMessage()));
            }
        }

        // set body

        return response.send(Mono.empty());
    }

Использование выглядит следующим образом:

mockReactiveRestServer.runWithPost("/transaction", () -> ResponseEntity.ok().build());
...