在 jersey servlet 的方法中使用 MULTIPART_FORM_DATA 和 application/x-www-form-urlencoded 媒体类型

Consuming MULTIPART_FORM_DATA and application/x-www-form-urlencoded Media Types in a method of jersey servlet

我在 jersey servlet 中有一个方法,它同时使用 MULTIPART_FORM_DATA 和 application/x-www-form-urlencoded 媒体类型,在我的请求中,我在文件输入流中发送一些参数和一个文件。

这是我的方法

@POST
@Path("/upload")
@Consumes({MediaType.MULTIPART_FORM_DATA,MediaType.APPLICATION_FORM_URLENCODED})
@Produces(MediaType.TEXT_PLAIN)
public String uploadFile(MultivaluedMap<String,String> requestParamsPost,@FormDataParam("file") InputStream fis,
@FormDataParam("file") FormDataContentDisposition fdcd){

//some code goes here

}

但我的问题是当我在 web.xml 中完成 servlet 映射后启动我的服务器时,出现了一些严重的异常

SEVERE: Missing dependency for method public javax.ws.rs.core.Response com.package.ImportService.uploadFile(java.lang.String,java.lang.String,java.lang.String) at parameter at index 0
SEVERE: Missing dependency for method public javax.ws.rs.core.Response com.package.ImportService.uploadFile(java.lang.String,java.lang.String,java.lang.String) at parameter at index 1
SEVERE: Missing dependency for method public javax.ws.rs.core.Response com.package.ImportService.uploadFile(java.lang.String,java.lang.String,java.lang.String) at parameter at index 2

是否有可能在单个端点以一种方法使用两种媒体类型? 每个请求都需要发送一个文件参数?

错误原因是MultivaluedMap参数。泽西岛不知道如何处理它。每个方法只能有一个实体类型。在您的方法中,您试图在请求中接受两种不同的正文类型。你不能那样做。我什至不知道你打算如何从客户端发送。

application/x-www-form-urlencoded 数据需要是多部分正文的一部分。所以你可以做

@Consumes({MediaType.MULTIPART_FORM_DATA})
public String uploadFile(@FormDataParam("form-data") MultivaluedMap<String,String> form,
                         @FormDataParam("file") InputStream fis,
                         @FormDataParam("file") FormDataContentDisposition fdcd){

那行得通。唯一的问题是,您需要确保客户端将 form-data 部分的 Content-Type 设置为 application/x-www-form-urlencoded。如果他们不这样做,那么该部分的默认 Content-Type 将是 text/plain,Jersey 将无法将其解析为 MultivaluedMap

您可以做的只是使用 FormDataBodyPart 作为方法参数,然后显式设置媒体类型。然后你可以将它提取到MultivaluedMap。这样,不需要期望客户端为该部分设置 Content-Type。有些客户甚至不允许设置单独的零件类型。

这是一个使用 Jersey Test Framework

的例子
import java.util.logging.Logger;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;

import org.junit.Test;
import static junit.framework.Assert.assertEquals;

public class MultipartTest extends JerseyTest {

    @Path("test")
    public static class MultiPartResource {

        @POST
        @Consumes(MediaType.MULTIPART_FORM_DATA)
        public Response post(@FormDataParam("form-data") FormDataBodyPart bodyPart, 
                             @FormDataParam("data") String data) {
            bodyPart.setMediaType(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
            MultivaluedMap<String, String> formData = bodyPart.getEntityAs(MultivaluedMap.class);
            StringBuilder sb = new StringBuilder();
            sb.append(data).append(";").append(formData.getFirst("key"));

            return Response.ok(sb.toString()).build();
        }
    }

    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(MultiPartResource.class)
                .register(MultiPartFeature.class)
                .register(new LoggingFilter(Logger.getAnonymousLogger(), true));
    }

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

    @Test
    public void doit() {
        FormDataMultiPart multiPart = new FormDataMultiPart();
        multiPart.field("data", "hello");
        multiPart.field("form-data", "key=world");
        final Response response = target("test")
                .request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA));
        assertEquals("hello;world", response.readEntity(String.class));
    }
}

如果您查看日志记录,您会看到请求为

--Boundary_1_323823279_1458137333706
Content-Type: text/plain
Content-Disposition: form-data; name="data"

hello
--Boundary_1_323823279_1458137333706
Content-Type: text/plain
Content-Disposition: form-data; name="form-data"

key=world
--Boundary_1_323823279_1458137333706--

你可以看到 form-data 正文部分的 Content-Typetext/plain,这是默认值,但在服务器端,我们在 Jersey 解析它之前明确设置它

public Response post(@FormDataParam("form-data") FormDataBodyPart bodyPart, 
                     @FormDataParam("data") String data) {
    bodyPart.setMediaType(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
    MultivaluedMap<String, String> formData = bodyPart.getEntityAs(MultivaluedMap.class);