如何从 java jax-rs 服务器获取 post body 数据

How to get post body data from java jax-rs server

我遵循了一个简单的 java 服务器设置 guide

所以我有这样的东西:

MyApp.java

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

@ApplicationPath("/")
public class MyApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> h = new HashSet<>();
        h.add( HelloWorld.class );
        return h;
    }
}

和HelloWord.java

import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Path("/helloworld")
public class HelloWorld {
    @Context
    private HttpServletRequest httpRequest;

    @GET
    @Produces("text/plain")
    public String getClichedMessage() {
        return "Hello, World!";
    }

    @POST
    @Consumes()
    @Produces("text/plain")
    public String doThis(String x) {
        System.out.println(x);
        //....
    }
}

传入请求的类型为

content-type = multipart/form-data; boundary=somerandstuff

看起来像:

--somerandstuff
Content-Disposition: form-data; name="somedata1"

data text 1here
--somerandstuff
Content-Disposition: form-data; name="somedata2"

more data text here

--somerandstuff
Content-Disposition: form-data; name="numberoffiles"

3
--somerandstuff
Content-Disposition: form-data; name="file1"; filename="firstfile"
Content-Type: application/octet-stream

{unreadable symbols thing here}

.
.
. 2 more files like above

我如何着手实际读取数据?我已经尝试了很多东西,但无法正常工作。我尝试使用 httpRequest.getParameterMap()httpRequest.getParameterNames() 并打印所有内容,但没有打印任何内容。我似乎甚至无法访问 post body 数据。我唯一可以访问的是 headers 使用 httpRequest.getHeaderNames().

我想做的是将每个文件存储在一个文件 object(或类似的文件)中,然后下载它。我会怎么做呢?我看过一些关于这个主题的 post,但是当我尝试实施这些解决方案时,它们似乎不起作用。

编辑:这是我的 pom.xml

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.serverproject</groupId>
    <artifactId>serverProject</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>serverProject</name>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <junit.version>5.6.2</junit.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>8.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
            <version>2.31</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
            <version>2.31</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>2.31</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>2.31</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.0</version>
            </plugin>
        </plugins>
    </build>
</project>

好的,您拥有的是多部分数据,它最常用于发送多个文件和元数据(或其他数据)以及这些文件。没有对多部分的标准 JAX-RS 支持,但您正在使用 Jersey(JAX-RS 实现)并且 Jersey 确实 支持多部分。您需要做的第一件事是向 pom.xml 文件

添加一个新的依赖项
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
    <version>2.31</version>
</dependency>

接下来您需要做的是在您的应用程序中注册此功能。由于您使用的是 Application subclass,因此您需要将 MultiPartFeature class 添加到 getClasses()

中的 classes
@ApplicationPath("/")
public class MyApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> h = new HashSet<>();
        h.add(HelloWorld.class);
        h.add(MultiPartFeature.class);
        return h;
    }
}

现在我们可以使用这个功能了。在您的资源方法中,我们要做的是使用 Jersey 的 @FormDataParam 注释声明每个部分。正如您在多部分实体中看到的那样,每个部分都有一个名称

Content-Disposition: form-data; name="somedata1"

这里这部分的名称是somedata1。所以我们将为这部分添加一个参数。另请注意,我将添加 @Consumes(MediaType.MULTIPART_FORM_DATA),因为这是我们的方法将消耗的 content-type。

@POST
@Path("upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response fileUpload(@FormDataParam("somedata1") String someData1) {
    ...
}

根据预期的数据类型,这就是您确定参数类型的方式。 somedata1 的类型只是纯文本,这就是我使用字符串的原因。但是 numeroffiles 部分是一个数字,所以我可以为该部分使用 int

public Response fileUpload(@FormDataParam("somedata1") String someData1,
                           @FormDataParam("numberoffiles") int numberOfFiles) {
    ...
}

firtfile 部分将是一个二进制文件。那么我们可以使用 InputStreamFilebyte[] 参数。我们还可以添加一个类型为 FormDataContentDisposition 的附加参数,这将为我们提供有关此文件的一些信息。对于这个例子,我将使用 InputStream。只是 google“如何将 InputStream 保存到 Java 中的文件”

public Response fileUpload(@FormDataParam("somedata1") String someData1,
                           @FormDataParam("numberoffiles") int numberOfFiles,
                           @FormDataParam("file1") InputStream firstFile,
                           @FormDataParam("file1") FormDataContentDisposition fdcd) {
    ...
    String fileName = fdcd.getFileName();
}

更新

如果文件数量未知,您可以做的只是声明一个类型为 FormDataMultiPart 的参数,然后以编程方式提取每个部分

,而不是对所有这些单独的部分进行贴标
public Response fileUpload(FormDataMultiPart multipart) {
    Map<String,List<FormDataBodyPart>> bodyParts = multipart.getFields();
    FormDataBodyPart someDataPart = multipart.getField("somedata1");
    String someData = someDataPart.getValueAs(String.class);
}

需要遍历所有FormDataBodyPart,使用bodyPart.getValueAs(Class)方法提取body部分的数据。对于文件,您将使用 getValueAs(InputStream.class).

差不多就这些了。这就是您在 Jersey 中使用 multipart 的方式。如需更多信息,您可以read the complete docs