Spring WebFlux - 流式 XML 响应的内容类型

问题描述

我成功地使用 application/x-ndjson 作为将返回的 Flux<> 格式化为以换行符分隔的独立 JSON 文档流的内容类型。

我寻求一种对 XML 执行相同操作的内容类型 - 将返回的 Flux<> 格式化为以换行符分隔的独立 XML 文档流。

我尝试了 application/stream+xmlapplication/xml-external-parsed-entity,但没有成功。

当我尝试 application/xml 时,只有 Flux<> 的第一个元素被格式化;流的其余部分被忽略,结果只是一个 xml 文档。

如果 Spring WebFlux 中不存在预配置的内容类型,我希望能够提供有关如何使用 WebFluxConfigurer 来实现此类内容类型的指导。

我使用的是 Spring Boot 2.4.2 (Spring WebFlux 5.3.3),并且很乐意将这些依赖项升级到最新版本。

更新:

我已经升级到 Spring Boot 2.5.3 和 Spring WebFlux 5.3.9。

@GetMapping 配置为多种响应类型:

@GetMapping(value = "/allevents",produces = {
            "application/stream+xml","application/x-ndjson","application/xml-external-parsed-entity","application/xml",})

当使用 -H 'accept: application/x-ndjson' 通过 curl 调用时,我会返回一个以换行符分隔的 JSON 文档流。因此该用例有效。

当使用 -H 'accept: application/stream+xml'-H 'accept: application/xml' 通过 curl 调用时,我只返回一个 XML 文档,该文档仅表示 Flux 中的一个项目,可能是第一个。所以这个用例失败了,因为我需要将完整的流作为独立的 XML 文档返回。

当使用 -H 'accept: application/xml-external-parsed-entity' 通过 curl 调用时,我收到带有消息 "No Encoder for [...] with preset Content-Type 'null' 的 HTTP 500 响应

更新:以下是完整的 curl 命令:

curl -X 'GET' 'http://localhost:9030/allevents' -H 'accept: application/xml' -s
curl -X 'GET' 'http://localhost:9030/allevents' -H 'accept: application/x-ndjson' -s
curl -X 'GET' 'http://localhost:9030/allevents' -H 'accept: application/xml-external-parsed-entity' -s
curl -X 'GET' 'http://localhost:9030/allevents' -H 'accept: application/stream+xml' -s

更新:

这是两次 curl 调用的示例输出。 XML 表示中没有 eventOccurred 属性,因为它是 Instant,我还没有研究它。

> curl -X 'GET' 'http://localhost:9030/allevents' -H 'accept: application/stream+xml' -s -N
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><datasetEvent><entryId>1625247900924</entryId><eventOccurred/><setId>1625247900924</setId><setBusinessDate/><setName>file24.txt</setName><setSize>0</setSize><status>Processed</status><context>GAAPC</context><transform>true</transform><isReload>false</isReload></datasetEvent>

> curl -X 'GET' 'http://localhost:9030/allevents' -H 'accept: application/x-ndjson' -s | head
{"entryId":"1625247900924","eventOccurred":"2021-07-02T17:51:55.618Z","setId":"1625247900924","setBusinessDate":"2021-01-29","setName":"file24.txt","setSize":0,"status":"Processed","context":"GAAPC","transform":true,"isReload":false}
{"entryId":"1625220390247","eventOccurred":"2021-07-02T10:12:20.446Z","setId":"1625220390247","setBusinessDate":"2021-07-01","setName":"file02.txt","status":"Archived","isReload":false}
{"entryId":"1625159850563","eventOccurred":"2021-07-01T17:24:20.800Z","setId":"1625159850563","isReload":false}
{"entryId":"1624288800694","eventOccurred":"2021-06-21T15:28:45.546Z","setId":"1624288800694","setBusinessDate":"2021-06-01","setName":"file15.txt","context":"GAAPP","isReload":false}
{"entryId":"1624287720608","eventOccurred":"2021-06-21T15:09:36.169Z","setId":"1624287720608","isReload":false}
{"entryId":"1624281600449","eventOccurred":"2021-06-21T13:28:18.725Z","setId":"1624281600449","isReload":false}
{"entryId":"1624279500722","eventOccurred":"2021-06-21T12:53:59.099Z","setId":"1624279500722","isReload":false}
{"entryId":"1615388165028","eventOccurred":"2021-03-10T14:56:34.171Z","setId":"1615388165028","setName":"file48.txt","isReload":false}

>

更新:

这是返回流的服务方法的签名:

Stream<DatasetEvent> allEvents(Optional<Integer> limit);

这是 REST 端点。调用服务方法,结果流的每个元素从 DatasetEvent 映射到 XMLDatasetEvent,结果流作为 Flux 返回。

    @GetMapping(value = "/allevents",})
    public Flux<XMLDatasetEvent> allEventsXML(
            @Parameter(description = "int - Maximum number of dataset events to return") @RequestParam(required = false) Optional<Integer> limit) {
        log.info("allEvents(limit={})",limit);
        return Flux.fromStream(service.allEvents(limit).map(x -> XMLDatasetEvent.of(x)));
    }

上述端点成功返回 JSON 记录流,但仅返回单个 XML 记录,具体取决于 HTTP 调用的接受标头。

Optional 限制从未指定,并且始终为 Optional.empty。这是由具有不同接受标头的一系列请求的日志输出所确认的:

2021-07-23 16:43:29.251  INFO 32184 --- [ctor-http-nio-6] Controller       : allEvents(limit=Optional.empty)
2021-07-23 16:44:12.579  INFO 32184 --- [ctor-http-nio-7] Controller       : allEvents(limit=Optional.empty)
2021-07-23 16:47:46.615  INFO 32184 --- [ctor-http-nio-8] Controller       : allEvents(limit=Optional.empty)
2021-07-23 16:48:33.305  INFO 32184 --- [ctor-http-nio-1] Controller       : allEvents(limit=Optional.empty)
2021-07-23 16:49:58.192  INFO 32184 --- [ctor-http-nio-2] Controller       : allEvents(limit=Optional.empty)
2021-07-23 16:50:25.445  INFO 32184 --- [ctor-http-nio-3] Controller       : allEvents(limit=Optional.empty)

XMLDatasetEvent 具有与 DatasetEvent 相同的公共字段结构,但添加了 @XMLRootElement 注释。它存在的唯一原因是因为 DatasetEvent 位于不同的 git 存储库中,我希望在尝试使 XML 流正常工作时尽量减少影响。

@XmlRootElement(name = "datasetEvent")
public class XMLDatasetEvent {

    public String entryId;
    public Instant eventOccurred;
    public String setId;
    public LocalDate setBusinessDate;
    public String setName;
    public int setSize;
    public String sourceSets;
    public String status;
    public String context = "";
    public boolean transform = true;
    public boolean isReload = false;

    public XMLDatasetEvent() {}

    public XMLDatasetEvent(DatasetEvent de) { ... copy fields one by one into a new instance ... }

    public static XMLDatasetEvent of(DatasetEvent datasetEvent) {
        return new XMLDatasetEvent(datasetEvent);
    }

}

复制器项目

域名已更改,所以我重申了完整的问题。

当我使用 accept: application/x-ndjson 调用 REST 端点时,我看到了预期的数据。流中的每条记录都作为单独的 JSON 文档返回。我寻求达到相同的效果,但使用 XML。

$ curl -X 'GET' 'http://localhost:9050/person/all' -H 'accept: application/x-ndjson' -s
{"name":"Robin","age":25}
{"name":"Jack","age":24}
{"name":"Peter","age":23}
{"name":"Sarah","age":22}

当我使用 accept: application/stream+xmlaccept: application/xml 调用一个 REST 端点时,我只收到流的第一条记录。

$ curl -X 'GET' 'http://localhost:9050/person/all' -H 'accept: application/stream+xml' -s -N

Robin25adl-benap01(定价)/opt/pricing/apps>

$ curl -X 'GET' 'http://localhost:9050/person/all' -H 'accept: application/xml' -s -N

Robin25adl-benap01(定价)/opt/pricing/apps>

Person 类定义一个 XMLRootElement:

package net.uk.roos.xmlflux.dto;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="person")
public class Person {
    public String name;
    public int age;

    public Person() {}

    public Person(String name,int age) {
        this.name = name;
        this.age = age;
    }
}

控制器在 /person/all 处暴露单个 REST 端点:

import java.util.List;
import java.util.stream.Stream;

@RestController
@RequestMapping("/person")
public class PersonController {

    @GetMapping(value = "/all",})
    public Flux<Person> all() {
        return Flux.fromStream(people());
    }

    Stream<Person> people() {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Robin",25));
        people.add(new Person("Jack",24));
        people.add(new Person("Peter",23));
        people.add(new Person("Sarah",22));
        return people.stream();
    }
}

application.yml 指定端口 9050:

server:
  port: 9050

pom.xml 指定 Java 8 和 spring-boot-starter-webflux 依赖项:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>net.uk.roos</groupId>
    <artifactId>xmlflux</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>xmlflux</name>
    <description>Demo project for Spring WebFlux xml</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

是否有我可以使用的 MIME 类型,以便流中的所有记录都将作为独立的 XML 文档返回?如果没有开箱即用的东西,谁能帮我通过 Web FluxConfigurer 配置一个

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)