JBoss.orgCommunity Documentation

第 21 章 Content Marshalling/Providers

21.1. 默认提供程序和默认 JAX-RS 内容编组
21.2. 使用 @Provider 类进行内容编排
21.3. Providers Utility Class
21.4. 配置文档编排
21.5. 文本媒体类型和字符集

RESTEasy 可以自动封送和解封几个不同的消息主体。


Note. 创建 java.io.File 时,如下所示

@Path("/test")
public class TempFileDeletionResource
{
   @POST
   @Path("post")
   public Response post(File file) throws Exception
   {
      return Response.ok(file.getPath()).build();
   }
}
      

在文件系统中创建一个临时文件。在服务器端,临时文件将在调用结束时被删除。但是,在客户端,用户有责任删除临时文件。

JAX-RS 规范允许您插入自己的请求/响应的reader和writers。为此,您可以使用@Provider 对类进行注解,并为 writer 指定 @Produces 类型,为 reader 指定@Consumes 类型。您还必须分别实现 MessageBodyReader/Writer 接口。下面是一个例子:

         @Provider
         @Produces("text/plain")
         @Consumes("text/plain")
         public class DefaultTextPlain implements MessageBodyReader, MessageBodyWriter {

            public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
               // StringTextStar should pick up strings
               return !String.class.equals(type) && TypeConverter.isConvertable(type);
            }

            public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
               InputStream delegate = NoContent.noContentCheck(httpHeaders, entityStream);
               String value = ProviderHelper.readString(delegate, mediaType);
               return TypeConverter.getType(type, value);
            }

            public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
               // StringTextStar should pick up strings
               return !String.class.equals(type) && !type.isArray();
            }

            public long getSize(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
               String charset = mediaType.getParameters().get("charset");
               if (charset != null)
                  try {
                     return o.toString().getBytes(charset).length;
                  } catch (UnsupportedEncodingException e) {
                     // Use default encoding.
                  }
               return o.toString().getBytes(StandardCharsets.UTF_8).length;
            }

            public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
               String charset = mediaType.getParameters().get("charset");
               if (charset == null) entityStream.write(o.toString().getBytes(StandardCharsets.UTF_8));
               else entityStream.write(o.toString().getBytes(charset));
            }
         }
      

注意,为了支持 Async IO ,你需要实现 AsyncMessageBodyWriter 接口,这需要你实现这个额外的方法:

         @Provider
         @Produces("text/plain")
         @Consumes("text/plain")
         public class DefaultTextPlain implements MessageBodyReader, AsyncMessageBodyWriter {
            // ...
            public CompletionStage<Void> asyncWriteTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, AsyncOutputStream entityStream) {
               String charset = mediaType.getParameters().get("charset");
               if (charset == null)
                  return entityStream.asyncWrite(o.toString().getBytes(StandardCharsets.UTF_8));
               else
                  return entityStream.asyncWrite(o.toString().getBytes(charset));
            }
         }
      

RESTEasy ServletContextLoader 将自动扫描 WEB-INF/lib 和 classes 目录,以查找带有 @Provider 注解的类,或者您可以在 web.xml 中手动配置它们。请参阅安装/配置。

javax.ws.rs.ext.Providers 是一个简单的可注入接口,允许您查找 MessageBodyReaders、 Writers、 ContextResolvers 和 ExceptionMappers。例如,它对于实现多部分提供程序非常有用。嵌入其他随机内容类型的内容类型。

public interface Providers
{

   /**
    * Get a message body reader that matches a set of criteria. The set of
    * readers is first filtered by comparing the supplied value of
    * {@code mediaType} with the value of each reader's
    * {@link javax.ws.rs.Consumes}, ensuring the supplied value of
    * {@code type} is assignable to the generic type of the reader, and
    * eliminating those that do not match.
    * The list of matching readers is then ordered with those with the best
    * matching values of {@link javax.ws.rs.Consumes} (x/y > x&#47;* > *&#47;*)
    * sorted first. Finally, the
    * {@link MessageBodyReader#isReadable}
    * method is called on each reader in order using the supplied criteria and
    * the first reader that returns {@code true} is selected and returned.
    *
    * @param type        the class of object that is to be written.
    * @param mediaType   the media type of the data that will be read.
    * @param genericType the type of object to be produced. E.g. if the
    *                    message body is to be converted into a method parameter, this will be
    *                    the formal type of the method parameter as returned by
    *                    <code>Class.getGenericParameterTypes</code>.
    * @param annotations an array of the annotations on the declaration of the
    *                    artifact that will be initialized with the produced instance. E.g. if the
    *                    message body is to be converted into a method parameter, this will be
    *                    the annotations on that parameter returned by
    *                    <code>Class.getParameterAnnotations</code>.
    * @return a MessageBodyReader that matches the supplied criteria or null
    *         if none is found.
    */
   <T> MessageBodyReader<T> getMessageBodyReader(Class<T> type,
                                                 Type genericType, Annotation annotations[], MediaType mediaType);

   /**
    * Get a message body writer that matches a set of criteria. The set of
    * writers is first filtered by comparing the supplied value of
    * {@code mediaType} with the value of each writer's
    * {@link javax.ws.rs.Produces}, ensuring the supplied value of
    * {@code type} is assignable to the generic type of the reader, and
    * eliminating those that do not match.
    * The list of matching writers is then ordered with those with the best
    * matching values of {@link javax.ws.rs.Produces} (x/y > x&#47;* > *&#47;*)
    * sorted first. Finally, the
    * {@link MessageBodyWriter#isWriteable}
    * method is called on each writer in order using the supplied criteria and
    * the first writer that returns {@code true} is selected and returned.
    *
    * @param mediaType   the media type of the data that will be written.
    * @param type        the class of object that is to be written.
    * @param genericType the type of object to be written. E.g. if the
    *                    message body is to be produced from a field, this will be
    *                    the declared type of the field as returned by
    *                    <code>Field.getGenericType</code>.
    * @param annotations an array of the annotations on the declaration of the
    *                    artifact that will be written. E.g. if the
    *                    message body is to be produced from a field, this will be
    *                    the annotations on that field returned by
    *                    <code>Field.getDeclaredAnnotations</code>.
    * @return a MessageBodyReader that matches the supplied criteria or null
    *         if none is found.
    */
   <T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> type,
                                                 Type genericType, Annotation annotations[], MediaType mediaType);

   /**
    * Get an exception mapping provider for a particular class of exception.
    * Returns the provider whose generic type is the nearest superclass of
    * {@code type}.
    *
    * @param type the class of exception
    * @return an {@link ExceptionMapper} for the supplied type or null if none
    *         is found.
    */
   <T extends Throwable> ExceptionMapper<T> getExceptionMapper(Class<T> type);

   /**
    * Get a context resolver for a particular type of context and media type.
    * The set of resolvers is first filtered by comparing the supplied value of
    * {@code mediaType} with the value of each resolver's
    * {@link javax.ws.rs.Produces}, ensuring the generic type of the context
    * resolver is assignable to the supplied value of {@code contextType}, and
    * eliminating those that do not match. If only one resolver matches the
    * criteria then it is returned. If more than one resolver matches then the
    * list of matching resolvers is ordered with those with the best
    * matching values of {@link javax.ws.rs.Produces} (x/y > x&#47;* > *&#47;*)
    * sorted first. A proxy is returned that delegates calls to
    * {@link ContextResolver#getContext(java.lang.Class)} to each matching context
    * resolver in order and returns the first non-null value it obtains or null
    * if all matching context resolvers return null.
    *
    * @param contextType the class of context desired
    * @param mediaType   the media type of data for which a context is required.
    * @return a matching context resolver instance or null if no matching
    *         context providers are found.
    */
   <T> ContextResolver<T> getContextResolver(Class<T> contextType,
                                             MediaType mediaType);
}

Providers 实例被注入到 MessageBodyReader 或 Writers 中:

@Provider
@Consumes("multipart/fixed")
public class MultipartProvider implements MessageBodyReader {

    private @Context Providers providers;

    ...

}

XML 文档解析器会受到一种称为 XXE (XML eXternal Entity) Attack ( http://www.securiteam.com/securitynews/6D0100A5PU.html )的攻击,在这种攻击中,展开外部实体会导致加载不安全的文件。例如,文件

<?xml version="1.0"?>
<!DOCTYPE foo
[<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<search>
    <user>bill</user>
    <file>&xxe;<file>
</search>

可能导致加载 passwd 文件。

默认情况下,RESTEasy 的用于 org.w3c.dom.Document 的内置 unmarshaller。文档不会展开外部实体,而是用空字符串替换它们。通过设置参数,可以将其配置为用 DTD 中定义的值替换外部实体

resteasy.document.expand.entity.references

设置为"true",如果在 web.xml 文件中配置为:

<context-param>
    <param-name>resteasy.document.expand.entity.references</param-name>
    <param-value>true</param-value>
</context-param>

有关应用程序配置的更多信息,请参见第 3.4 节 “配置”

处理这个问题的另一种方法是禁止 DTDs,RESTEasy 默认是这样做的。可以通过设置参数来更改此行为

resteasy.document.secure.disableDTDs

设置为 "false".

当缓冲区被大型实体或过多的属性占用时,文档也会受到分布式拒绝服务攻击攻击。例如,如果 DTD 定义了以下实体

<!ENTITY foo 'foo'>
<!ENTITY foo1 '&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;'>
<!ENTITY foo2 '&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;'>
<!ENTITY foo3 '&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;'>
<!ENTITY foo4 '&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;'>
<!ENTITY foo5 '&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;'>
<!ENTITY foo6 '&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;'>

那么 foo6 的扩张就会产生 1,000,000 个 foo。默认情况下,RESTEasy 将限制每个实体的扩展数目和属性数目。确切的行为取决于底层的解析器。可以通过设置参数来关闭限制

resteasy.document.secure.processing.feature

设置为 "false".

JAX-RS 规范说

When writing responses, implementations SHOULD respect application-supplied character
set metadata and SHOULD use UTF-8 if a character set is not specified by the application
or if the application specifies a character set that is unsupported.
在编写响应时,实现应尊重应用程序提供的字符
设置元数据,如果应用程序未指定字符集,则应使用UTF-8
或者应用程序指定了不受支持的字符集。

另一方面,HTTP 规范说

When no explicit charset parameter is provided by the sender, media subtypes of the
"text" type are defined to have a default charset value of "ISO-8859-1" when received
via HTTP. Data in character sets other than "ISO-8859-1" or its subsets MUST be labeled
with an appropriate charset value.
如果发件人未提供任何明确的charset参数,则
接收到的“文本”类型定义为默认字符集值“ ISO-8859-1”
通过HTTP。 除“ ISO-8859-1”以外的字符集中的数据或其子集必须被标记
具有适当的字符集值。

因此,在没有由resource或resource方法指定的字符集的情况下,RESTEasy 应该使用 UTF-8作为文本媒体类型的字符集,如果使用了,则必须向 Content-Type 响应头添加一个显式的 charset 参数。RESTEasy 开始在发行版3.1.2.Final 及3.0.22.Final 中添加显式 charset 参数。最后,新的行为可能会导致一些兼容性问题。为了指定以前的行为,其中 UTF-8用于文本媒体类型,但没有附加显式字符集,可以将参数"resteasy.add.charset"设置为"false"。默认为"true"。

Note. 我们所说的"text"媒体类型是指

  • 具有"text"类型和任何子类型的媒体类型;
  • 类型为"application",子类型以"xml"开头的媒体类型

后一组包括"application/xml-external-parsed-entity"和"application/xml-dtd"。