9.3. Multipart

9.3.1. Overview 概述

在 JAX-RS 运行环境,这个模块中的类提供了 multipart/* 请求和响应体的集成。注册提供者的集合是为杠杆,在这样的一个消息体部分的内容类型重用同一 MessageBodyReader/MessageBodyWriter 实现将用于该内容类型作为一个独立的实体。

下面列出的是目前支持常见的 MIME MultiPart :

  • MIME-Version: 1.0 HTTP header 包含在生成的响应中。这是可以接受的,但在处理请求中不是必需的。
  • MessageBodyReader 实现,用于消耗 MIME MultiPart 实体。
  • MessageBodyWriter<T> 实现用于产生 MIME MultiPart 实体。适当的 @Provider 是基于媒体类型,用于序列化响应体的每个部分。
  • 如果不是已经存在,在平常的 Content-Type header 创建一个可选的适当的边界参数 。

更多信息,见 Multi Part

9.3.1.1. Dependency 依赖

添加 jersey-media-multipart 到 pom.xml

  1. <dependency>
  2. <groupId>org.glassfish.jersey.media</groupId>
  3. <artifactId>jersey-media-multipart</artifactId>
  4. <version>2.16</version>
  5. </dependency>

如果你不使用Maven,确保有所有需要的依赖(见jersey-media-multipart)在类路径

9.3.1.2. Registration 注册

为了在客户端/服务端代码 使用 jersey-media-multipart 模块的功能,先注册MultiPartFeature

Example 9.42. Building client with MultiPart feature enabled.

  1. final Client client = ClientBuilder.newBuilder()
  2. .register(MultiPartFeature.class)
  3. .build();

Example 9.43. Creating JAX-RS application with MultiPart feature enabled.

  1. // Create JAX-RS application.
  2. final Application application = new ResourceConfig()
  3. .packages("org.glassfish.jersey.examples.multipart")
  4. .register(MultiPartFeature.class)

9.3.1.3. Examples 实例

Multipart Web Application Example

9.3.2. Client 客户端

MultiPart 类(或子类)可以当做实体指向使用 jersey-media-multipart 的模块在客户端。这个类 表现为 MIME multipart 消息 并且能够容纳任意数量的BodyPart。 MultiPart 实体默认的媒体类型 multipart/mixed,而 BodyPart 是 text/plain 。

Example 9.44. MultiPart entity

  1. final MultiPart multiPartEntity = new MultiPart()
  2. .bodyPart(new BodyPart().entity("hello"))
  3. .bodyPart(new BodyPart(new JaxbBean("xml"), MediaType.APPLICATION_XML_TYPE))
  4. .bodyPart(new BodyPart(new JaxbBean("json"), MediaType.APPLICATION_JSON_TYPE));
  5. final WebTarget target = // Create WebTarget.
  6. final Response response = target
  7. .request()
  8. .post(Entity.entity(multiPartEntity, multiPartEntity.getMediaType()));

如果发送 multiPartEntity 到服务端,实体的 Content-Type header 在 HTTP message 就像下面那样:(别忘了注册 JSON 提供者)

Example 9.45. MultiPart entity in HTTP message.

  1. Content-Type: multipart/mixed; boundary=Boundary_1_829077776_1369128119878
  2. --Boundary_1_829077776_1369128119878
  3. Content-Type: text/plain
  4. hello
  5. --Boundary_1_829077776_1369128119878
  6. Content-Type: application/xml
  7. <?xml version="1.0" encoding="UTF-8" standalone="yes"?><jaxbBean><value>xml</value></jaxbBean>
  8. --Boundary_1_829077776_1369128119878
  9. Content-Type: application/json
  10. {"value":"json"}
  11. --Boundary_1_829077776_1369128119878--

当涉及到 form 表单时,(例如媒体类型 multipart/form-data)且有多个字段,有一个更方便使用的类- FormDataMultiPart。它会自动设置为FormDataMultiPart 实体 的媒体类型为 multipart/form-data 及Content-Disposition 报头 为 FormDataBodyPart 。

Example 9.46. FormDataMultiPart entity

  1. final FormDataMultiPart multipart = new FormDataMultiPart()
  2. .field("hello", "hello")
  3. .field("xml", new JaxbBean("xml"))
  4. .field("json", new JaxbBean("json"), MediaType.APPLICATION_JSON_TYPE);
  5. final WebTarget target = // Create WebTarget.
  6. final Response response = target.request().post(Entity.entity(multipart, multipart.getMediaType()));

为了说明 使用 FormDataMultiPart 替换 FormDataBodyPart 不同点,可以看下 FormDataMultiPart 的 HTML 消息中的 实体:

Example 9.47. FormDataMultiPart entity in HTTP message.

  1. Content-Type: multipart/form-data; boundary=Boundary_1_511262261_1369143433608
  2. --Boundary_1_511262261_1369143433608
  3. Content-Type: text/plain
  4. Content-Disposition: form-data; name="hello"
  5. hello
  6. --Boundary_1_511262261_1369143433608
  7. Content-Type: application/xml
  8. Content-Disposition: form-data; name="xml"
  9. <?xml version="1.0" encoding="UTF-8" standalone="yes"?><jaxbBean><value>xml</value></jaxbBean>
  10. --Boundary_1_511262261_1369143433608
  11. Content-Type: application/json
  12. Content-Disposition: form-data; name="json"
  13. {"value":"json"}
  14. --Boundary_1_511262261_1369143433608--

对于许多用户来说常见的情况是从客户端向服务器发送文件。为了这个目的,你可以使用来自org.glassfish.jersey.jersey.media.multipart 包类,如 FileDataBodyPartStreamDataBodyPart

Example 9.48. Multipart - sending files.

  1. // MediaType of the body part will be derived from the file.
  2. final FileDataBodyPart filePart = new FileDataBodyPart("my_pom", new File("pom.xml"));
  3. final FormDataMultiPart multipart = new FormDataMultiPart()
  4. .field("foo", "bar")
  5. .bodyPart(filePart);
  6. final WebTarget target = // Create WebTarget.
  7. final Response response = target.request()
  8. .post(Entity.entity(multipart, multipart.getMediaType()));

警告

不要使用 ApacheConnectorProvider 、 GrizzlyConnectorProvider 或者 JettyConnectorProvider 连接器实现 Jersey Multipart features。见 Header modification issue

9.3.3. Server

从服务器返回一个 multipart 响应到 客户端,跟客户端描述的美誉太大不同。为获得 客户端发送的多个实体的应用中,你可以使用两种方法:

9.3.3.1. Injecting and returning the MultiPart entity

注入和返回 MultiPart 实体

MultiPart 类型的工作方式 与注入/返回其他实体类型不同。Jersey 提供 MessageBodyReader<T> 用来读取请求实体,并且注入 这个实体到资源方法的参数中,而 MessageBodyWriter<T> 用于实体的输出。 你可以预计,多部分或FormDataMultiPart(多部分/格式数据媒体类型)对象注入资源的方法。你可以预期MultiPart 或 FormDataMultiPart (multipart/form-data 媒体类型) 对象用来注入到资源方法中。

Example 9.49. Resource method using MultiPart as input parameter / return value.

  1. @POST
  2. @Produces("multipart/mixed")
  3. public MultiPart post(final FormDataMultiPart multiPart) {
  4. return multiPart;
  5. }

9.3.3.2. Injecting with @FormDataParam 通过 @FormDataParam 注入

如果你只是需要 multipart/form-data 请求实体 到资源的 方法中,可以使用 @FormDataParam 注解。

这个注解结合使用的媒体类型 multipart/form-data 应该包含文件、非 ASCII 数据, 和编译数据的提交和消费形式。

注解的类型参数可以是下列之一(更多详细描述见javadoc @FormDataParam):

  • FormDataBodyPart - 参数的值将会是第一个命名的 body 部分或 null 如果这样的 body 部分不存在
  • FormDataBodyPart 的集合-参数的值将会是一个或多个具有相同名称的命名的 body 部位或 null 如果这样的 body 部位不存在。
  • FormDataContentDisposition - 参数的值将被会是第一个命名的 body 部分的内容处理部分或 null 如果这样的 body 部分不存在。
  • FormDataContentDisposition 集合。参数的值将一个或多个内容处理指定的 body 部分使用相同的名称或null如果这样命名的 body 部分是不存在的。
  • 一种类型的消息体的读者可以给出第一个命名为主体的媒体类型。参数的值将使用给定类型的消息体读者阅读的结果,对指定的媒体类型,以及指定的 body 的一部分作为输入字节。

如果没有指定部分存在,有一个默认值存在用 @DefaultValue 声明,那么媒体类型将被设置为 text/plain 。参数的值将被阅读的结果使用消息体的读者类型 T,媒体类型 text/plain,UTF-8 编码的字节的默认值作为输入。

如果没有消息体读者可用, 那么类型 T 符合类型 @FormParam 然后通过@FormParam特定处理,在形式参数的值是由读取字节的字符串实例指定的 body 部分使用字符串类型的消息体的读者和媒体类型的 text/plain。

如果没有指定部分表现那么处理执行规定的 @FormParam .

Example 9.50. Use of @FormDataParam annotation

  1. @POST
  2. @Consumes(MediaType.MULTIPART_FORM_DATA_TYPE)
  3. public String postForm(
  4. @DefaultValue("true") @FormDataParam("enabled") boolean enabled,
  5. @FormDataParam("data") FileData bean,
  6. @FormDataParam("file") InputStream file,
  7. @FormDataParam("file") FormDataContentDisposition fileDisposition) {
  8. // ...
  9. }

示例中,服务器消耗 multipart/form-data 请求实体 body ,包含了一个可选的指定的 body 部分,和两个必须的指定的 body 部分数据和文件。

可选部分启动是当做一个 布尔值 处理,如果这部分不再那么值是 true。

数据部分当做 JAXB bean 处理,包含了下面部分的 元数据。

文件部分是上次的文件,处理成 InputStream。从 Content-Disposition header 看到附加信息关于文件 可以通过参数 fileDisposition 访问。

提示

@FormDataParam 注解同样适用于字段