Почему Flux <String>сворачивается в одну строку при возврате через ServerResponse, если каждый элемент String не заканчивается на \ n? - PullRequest
1 голос
/ 10 октября 2019

Играя с Spring Webflux и WebClient, я заметил поведение (демонстрируемое приведенным ниже кодом) при возврате ServerResponse , содержащего Flux<String>. Если элементы String не заканчиваются символом новой строки, возвращение Flux<String> через ServerResponse , по-видимому, объединяет все Flux<String>элементы в один String . Может кто-нибудь объяснить мне, почему я вижу это поведение и что я делаю, чтобы вызвать его?

Когда каждый элемент String заканчивается символом новой строки, Flux<String> возвращается «как ожидалось» через ServerResponse , и подписка на возвращенное Flux<String> дает ожидаемые результаты. Однако, если рассматривать его как простой JSON (через Postman), это также приводит к тому, что в тело JSON возвращается дополнительный пустой элемент String.

Вывод на консоль, показывающий описанное поведение ...

  1. Первый список String элементов содержится в StringProducerHandler.getAll () и указывает на результаты Flux<String>, содержащие 10 String элементов, где второе вхождение каждого значения String заканчивается символом новой строки, в результате чего выводится пустая строка.
  2. Второй список Элементы String находятся в StringClient.getAll () и демонстрируют, как исходные элементы String , которые не заканчивались символом новой строки, были объединены со следующим элементом.

    2019-10-10 10: 13: 37.225 INFO 8748 --- [main] osbweb.embedded.netty.NettyWebServer: Netty запущен на портах: 8080 2019-10-10 10: 13: 37.228 INFO8748 --- [основной] в. example.fluxtest.FluxTestApplication: Запущено приложение FluxTestApplication за 1,271 секунды (JVM работает в течение 1,796)

    ***** «Get» выдано для http: / localhost: 8080 / StringClient / String StringClientHandler.getAll (ServerRequest) StringClient.getAll () StringProducerHandler.getAll (ServerRequest) ListElement-0 ListElement-0

    ListElement-1 ListElement-1

    ListElement-2 ListElement-2

    ListElement-3 ListElement-3

    ListElement-4 ListElement-4

    ListElement-0ListElement-0 @ 1570727628948 ListElement-1ListElement-1 @ 1570727628948 ListElement-2ListElement-2 @ 1570727670Lelement-3 @ 1570727632Element 3-го уровня4ListElement-4 @ 1570727628949

Код для воспроизведения этого поведения приведен ниже ...

@SpringBootApplication
public class FluxTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(FluxTestApplication.class, args);
    }
}


@Configuration
public class StringClientRouter {
    @Bean
    public RouterFunction<ServerResponse> clientRoutes(StringClientHandler requestHandler) {
        return nest(path("/StringClient"),
                nest(accept(APPLICATION_JSON),
                        RouterFunctions.route(RequestPredicates.GET("/String"), requestHandler::getAll)));
    }
}


@Component
public class StringClientHandler {

    @Autowired
    StringClient stringClient;

    public Mono<ServerResponse> getAll(ServerRequest request) {
        System.out.println("StringClientHandler.getAll( ServerRequest )");

        Mono<Void> signal = stringClient.getAll();
            return ServerResponse.ok().build();
        }
    }


@Component
public class StringClient {

    private final WebClient client;

    public StringClient() {
        client = WebClient.create();
    }

    public Mono<Void> getAll() {
        System.out.println("StringClient.getAll()");

        // break chain to explicitly obtain the ClientResponse
        Mono<ClientResponse> monoCR = client.get().uri("http://localhost:8080/StringProducer/String")
                                                  .accept(MediaType.APPLICATION_JSON)
                                                  .exchange();

        // extract the Flux<String> and print to console
        Flux<String> fluxString = monoCR.flatMapMany(response -> response.bodyToFlux(String.class));
        // this statement iterates over the Flux<String> and outputs each element
        fluxString.subscribe(strVal -> System.out.println(strVal + " @ " + System.currentTimeMillis()));

        return Mono.empty();
    }
}


@Configuration
public class StringProducerRouter {
    @Bean
    public RouterFunction<ServerResponse> demoPOJORoute(StringProducerHandler requestHandler) {
        return nest(path("/StringProducer"),
                nest(accept(APPLICATION_JSON),
                        RouterFunctions.route(RequestPredicates.GET("/String"), requestHandler::getAll)));
    }
}


@Component
public class StringProducerHandler {

    public Mono<ServerResponse> getAll(ServerRequest request) {
        System.out.println("StringProducerHandler.getAll( ServerRequest )");

        int          listSize = 5;
        List<String> strList  = new ArrayList<String>();

        for (int i=0; i<listSize; i++) {
            strList.add("ListElement-" + i);         // add String value without newline termination
            strList.add("ListElement-" + i + "\n");  // add String value with    newline termination
        }

        // this statement produces the expected console output of String values
        Flux.fromIterable(strList).subscribe(System.out::println);

        return ServerResponse.ok()
                             .contentType(MediaType.APPLICATION_JSON)
                             .body(Flux.fromIterable(strList), String.class);
    }
}

1 Ответ

3 голосов
/ 10 октября 2019

Это связано с тем, как работает org.springframework.core.codec.StringDecoder.

При вызове response.bodyToFlux(String.class) тело ответа преобразуется в Flux из String с. org.springframework.core.codec.StringDecoder выполняет тяжелую работу и считает, что он должен делиться на разделители по умолчанию.

List<byte[]> delimiterBytes = getDelimiterBytes(mimeType);

Flux<DataBuffer> inputFlux = Flux.from(input)
    .flatMapIterable(buffer -> splitOnDelimiter(buffer, delimiterBytes))
    .bufferUntil(buffer -> buffer == END_FRAME)
    .map(StringDecoder::joinUntilEndFrame)
    .doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);

return super.decode(inputFlux, elementType, mimeType, hints);

Разделителями по умолчанию являются public static final List<String> DEFAULT_DELIMITERS = Arrays.asList("\r\n", "\n");

Поэтому выполучить:

ListElement-0ListElement-0 @ 1570732000374
ListElement-1ListElement-1 @ 1570732000375
...

вместо

ListElement-0 @ 1570732055461
ListElement-0 @ 1570732055461
ListElement-1 @ 1570732055462
ListElement-1 @ 1570732055462
...
...