我可以在同一 REST API 响应中发送带有文件描述的 excel 文件和 JSON 正文吗文件、正文、在同一、REST

2023-09-06 10:50:50 作者:活着就是折腾

我有一个 API,它返回 APPLICATION_OCTET_STREAM 作为媒体类型作为响应.我需要增强它以发送一个 JSON 正文,其中包含有关文件的一些详细信息,例如文件中正确和错误记录的计数.所以基本上我需要在同一个 API 中有两种响应.这可行吗?

I have an API which returns APPLICATION_OCTET_STREAM as Media Type in response. I need to enhance it to also send a JSON body with some details regarding the file, say counts of right and wrong records in the file. So basically I need two kinds of response in same API. Is this doable ?

推荐答案

这是可能的,但您需要使用 Multipart 响应.请记住,一些客户端将无法处理这种类型的响应.您通常会在上传文件时看到此数据类型,但不常用作响应数据类型.

It's possible, but you will need to use a Multipart response. Keep in mind though that some clients will not be able to handle this type of response. You'll normally see this data type using in uploading files, but is not very often used as a response data type.

话虽如此,下面是使用 Jersey 测试框架.在资源中,使用 Jersey 的 FormDataMultiPart

That being said, below is a complete example using the Jersey Test Framework. In the resource, a file and some extra data are being sent in the response, with the use of Jersey's FormDataMultiPart

@Path("test")
public static class TestResource {
    @GET
    @Produces(MediaType.MULTIPART_FORM_DATA)
    public Response get() throws Exception {
        final MultiPart multiPart = new FormDataMultiPart()
                .field("json-data", new Model("Test Value"), MediaType.APPLICATION_JSON_TYPE)
                .bodyPart(new FileDataBodyPart("file-data", new File("test.txt")));
        return Response.ok(multiPart).build();
    }
}

要使测试成功,您应该有一个名为 test.txt 的文件,该文件的第一行包含内容文件中的一些测试数据"(不带引号).这个多部分响应有两个部分,json-data 部分,它使用 Model 类对 JSON 进行建模,以及 file-data 部分其中包含文件的内容.

To make the test succeed, you should have a file called test.txt with the content "Some Test Data in File" (without quotes) on the first line of that file. This multipart response has two parts, the json-data part, which uses a Model class to model the JSON, and the file-data part which has the contents of the file.

为了使 Multipart 工作,我们需要在服务器和客户端上注册 MultiPartFeature(用于客户端反序列化),并且我们需要在项目中具有 multipart 依赖项.

To make the Multipart work, we need to have the MultiPartFeature register on the server and the client (for client side deserialization) and we need to have the multipart dependency in our project.

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
    <version>${jersey2.version}</version>
</dependency>

在客户端,要从响应中获取多部分,我们应该将实体读取为 FormDataMultiPart,然后我们可以通过名称获取各个部分并根据它们的数据类型提取它们.

On the client, to get the multipart out of the response, we should read the entity as FormDataMultiPart, then we can get individual parts by name and extract them by their data type.

Response res = target("test").request().get();
FormDataMultiPart multiPart = res.readEntity(FormDataMultiPart.class);
FormDataBodyPart jsonPart = multiPart.getField("json-data");
FormDataBodyPart filePart = multiPart.getField("file-data");

Model jsonData = jsonPart.getValueAs(Model.class);
InputStream file = filePart.getValueAs(InputStream.class);

下面是完整的测试.

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;

import static org.assertj.core.api.Assertions.assertThat;

public class MultipartResponseTest extends JerseyTest {

    public static class Model {
        private String value;
        public Model() {}
        public Model(String value) {
            this.value = value;
        }
        public String getValue() {
            return this.value;
        }
        public void setValue(String value) {
            this.value = value;
        }
    }

    @Path("test")
    public static class TestResource {
        @GET
        @Produces(MediaType.MULTIPART_FORM_DATA)
        public Response get() throws Exception {
            final MultiPart multiPart = new FormDataMultiPart()
                    .field("json-data", new Model("Test Value"), MediaType.APPLICATION_JSON_TYPE)
                    .bodyPart(new FileDataBodyPart("file-data", new File("test.txt")));
            return Response.ok(multiPart).build();
        }
    }

    @Override
    public ResourceConfig configure() {
        return new ResourceConfig()
                .register(TestResource.class)
                .register(MultiPartFeature.class);
    }

    @Override
    public void configureClient(ClientConfig config) {
        config.register(MultiPartFeature.class);
    }

    @Test
    public void testIt() throws Exception {
        final Response res = target("test")
                .request().get();
        FormDataMultiPart multiPart = res.readEntity(FormDataMultiPart.class);
        FormDataBodyPart jsonPart = multiPart.getField("json-data");
        FormDataBodyPart filePart = multiPart.getField("file-data");

        Model jsonData = jsonPart.getValueAs(Model.class);
        InputStream file = filePart.getValueAs(InputStream.class);

        BufferedReader fileReader = new BufferedReader(new InputStreamReader(file));
        String fileData = fileReader.readLine();

        file.close();
        fileReader.close();

        System.out.println(jsonData.getValue());
        System.out.println(fileData);

        assertThat(jsonData.getValue()).isEqualTo("Test Value");
        assertThat(fileData).isEqualTo("Some Test Data in File");
    }
}

要使用测试框架,需要添加如下依赖

To use the test framework, you should add the following dependency

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>${jersey2.version}</version>
</dependency>