<chapter id="gzip">
   <title>Content encoding</title>
   
<sect1>
   <title>GZIP Compression/Decompression</title>
   <para>
    RESTEasy supports (though not by default - see below)
    GZIP decompression.  If properly configured,
    the client framework or a &REST-API; service, upon receiving
    a message body with a Content-Encoding of "gzip",
    will automatically decompress it.  The client framework can
    (though not by default - see below) automatically
    set the Accept-Encoding header to be "gzip, deflate" so you do not
    have to set this header yourself.
    </para>
    <para>RESTEasy also supports (though not by default - see below) automatic
    compression.  If the client framework is sending a request
    or the server is sending a response with the Content-Encoding header
    set to "gzip", RESTEasy will (if properly configured)
    do the compression. So that you do not
    have to set the Content-Encoding header directly, you can use the
    @org.jboss.resteasy.annotation.GZIP annotation.
   </para>
<programlisting>
@Path("/")
public interface MyProxy {

   @Consumes("application/xml")
   @PUT
   public void put(@GZIP Order order);
}
</programlisting>
   <para>In the above example, we tag the outgoing message body, order, to
   be gzip compressed.  The same annotation can be used to tag server responses
   </para>
<programlisting>
@Path("/")
public class MyService {

   @GET
   @Produces("application/xml")
   @GZIP
   public String getData() {...}
}
</programlisting>

<sect2 id="configuring">
   <title>Configuring GZIP compression / decompression</title>
   
   <para><emphasis role="bold">Note.</emphasis> Decompression carries a risk of attack
   from a bad actor that can package an entity that will expand greatly. Consequently,
   RESTEasy disables GZIP compression / decompression by default.
   </para>
   
   <para>
   There are three interceptors that are relevant to GZIP compression / decompression:
   </para>
   
   <orderedlist>
      <listitem>org.jboss.resteasy.plugins.interceptors.GZIPDecodingInterceptor:
      If the Content-Encoding header is present and has the value "gzip",
      <classname>GZIPDecodingInterceptor</classname> will install an <classname>InputStream</classname>
      that decompresses the message body.
      </listitem>
      
      <listitem>org.jboss.resteasy.plugins.interceptors.GZIPEncodingInterceptor:
      If the Content-Encoding header is present and has the value "gzip",
      <classname>GZIPEncodingInterceptor</classname> will install an <classname>OutputStream</classname>
      that compresses the message body.
      </listitem>
      
      <listitem>org.jboss.resteasy.plugins.interceptors.AcceptEncodingGZIPFilter: 
      If the Accept-Encoding header does not exist, <classname>AcceptEncodingGZIPFilter</classname>
      will add Accept-Encoding with the value "gzip, deflate". If the Accept-Encoding header exists but
      does not contain "gzip", <classname>AcceptEncodingGZIPFilter</classname> will append ", gzip".
      Note that enabling GZIP compression / decompression does not depend on the presence of this
      interceptor.
      </listitem>
   </orderedlist>
   
   <para>
   If GZIP decompression is enabled, an upper limit is imposed on the number of bytes
   <classname>GZIPDecodingInterceptor</classname> will extract from a compressed message body.
   The default limit is 10,000,000, but a different value can be configured. See below.
   </para>
   
   <sect3>
   <title>Server side configuration</title>
   
   <para>
   The interceptors may be enabled by including their classnames in a
   META-INF/services/jakarta.ws.rs.ext.Providers file on the classpath. The upper limit on deflated files may
   be configured by setting the parameter "resteasy.gzip.max.input".
   [See <xref linkend="microprofile_config"/> for more information about application configuration.]
   If the limit is exceeded on the server side, GZIPDecodingInterceptor will return a 
   <classname>Response</classname> with status 413 ("Request Entity Too Large") and
   a message specifying the upper limit.
   </para>
   
   <para>
   <emphasis role="bold">Note.</emphasis>
   As of release 3.1.0.Final, the GZIP interceptors have moved from package 
   <classname>org.jboss.resteasy.plugins.interceptors.encoding</classname> to
   <classname>org.jboss.resteasy.plugins.interceptors</classname>. and they should be
   named accordingly in jakarta.ws.rs.ext.Providers.
   </para>

   </sect3>
   
   <sect3>
   <title>Client side configuration</title>
   
   <para>
   The interceptors may be enabled by registering them with a
   <classname>Client</classname> or <classname>WebTarget</classname>. For example,
   </para>
   
<programlisting>
Client client = ClientBuilder.newBuilder() // Activate gzip compression on client:
                    .register(AcceptEncodingGZIPFilter.class)
                    .register(GZIPDecodingInterceptor.class)
                    .register(GZIPEncodingInterceptor.class)
                    .build();
</programlisting>

   <para>
   The upper limit on deflated files may be configured by creating an instance of
   <classname>GZIPDecodingInterceptor</classname> with a specific value:
   </para>
   
<programlisting>
Client client = ClientBuilder.newBuilder() // Activate gzip compression on client:
                    .register(AcceptEncodingGZIPFilter.class)
                    .register(new GZIPDecodingInterceptor(256))
                    .register(GZIPEncodingInterceptor.class)
                    .build();
</programlisting>
   <para>
   If the limit is exceeded on the client side, GZIPDecodingInterceptor will throw a
   <classname>ProcessingException</classname> with a message specifying the upper limit.
   </para>
   
   </sect3>
</sect2>
</sect1>

<sect1>
   <title>General content encoding</title>
   
   <para>
   The designation of a compressible entity by the use of the <classname>@GZIP</classname> annotation is a
   built-in, specific instance of a more general facility supported by RESTEasy.  There are three components
   to this facility.
   </para>
   
   <orderedlist>
      <listitem>
      The annotation <classname>org.jboss.resteasy.annotations.ContentEncoding</classname> is a "meta-annotation"
      used on other annotations to indicate that they represent a Content-Encoding. For example, <classname>@GZIP</classname>
      is defined
      
      <programlisting>
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@ContentEncoding("gzip")
public @interface GZIP
{
}
      </programlisting>
      The value of <classname>@ContentEncoding</classname> indicates the represented Content-Encoding. For <classname>@GZIP</classname>
      it is "gzip".
      </listitem>
      <listitem>
      <classname>ClientContentEncodingAnnotationFeature</classname> and
      <classname>ServerContentEncodingAnnotationFeature</classname>,
      two <classname>DynamicFeature</classname>s 
      in package <classname>org.jboss.resteasy.plugins.interceptors</classname>,
      examine resource methods for annotations decorated with <classname>@ContentEncoding</classname>.
      </listitem>
      <listitem>
      For each value found in a <classname>@ContentEncoding</classname> decorated annotation on a resource method, an instance of
      <classname>ClientContentEncodingAnnotationFilter</classname> or
      <classname>ServerContentEncodingAnnotationFilter</classname>,
      <classname>jakarta.ws.rs.ext.WriterInterceptor</classname>s in package
      <classname>org.jboss.resteasy.plugins.interceptors</classname>, is registered.
      They are responsible for adding an appropriate Content-Encoding header. For example,
      <classname>ClientContentEncodingAnnotationFilter</classname> is defined
      <programlisting>
@ConstrainedTo(RuntimeType.CLIENT)
@Priority(Priorities.HEADER_DECORATOR)
public class ClientContentEncodingAnnotationFilter implements WriterInterceptor
{
   protected String encoding;

   public ClientContentEncodingAnnotationFilter(String encoding)
   {
      this.encoding = encoding;
   }

   @Override
   public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException
   {
      context.getHeaders().putSingle(HttpHeaders.CONTENT_ENCODING, encoding);
      context.proceed();
   }
}
      </programlisting>
      When it is created, <classname>ClientContentEncodingAnnotationFeature</classname> passes in the value
      to be used for Content-Encoding headers.
      </listitem>
   </orderedlist>
   
   <para>
      The annotation <classname>@GZIP</classname> is built into RESTEasy, but 
      <classname>ClientContentEncodingAnnotationFeature</classname> and <classname>ServerContentEncodingAnnotationFeature</classname>
      will also recognize application defined annotations. For example,
   </para>
   
   <programlisting>
   @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
   @Retention(RetentionPolicy.RUNTIME)
   @ContentEncoding("compress")
   public @interface Compress
   {
   }
   
   @Path("")
   public static class TestResource {
      
      @GET
      @Path("a")
      @Compress
      public String a() {
         return "a";
      }
   }
   </programlisting>
   
   <para>
      If <methodname>TestResource.a()</methodname> is invoked as follows
   </para>
   
   <programlisting>
   @Test
   public void testCompress() throws Exception
   {
      Client client = ClientBuilder.newClient();
      Invocation.Builder request = client.target("http://localhost:8081/a").request();
      request.acceptEncoding("gzip,compress");
      Response response = request.get();
      System.out.println("content-encoding: "+ response.getHeaderString("Content-Encoding"));
      client.close();
   }
   </programlisting>
   
   <para>
      the output will be
   </para>
   
   <programlisting>
content-encoding: compress
   </programlisting>
</sect1>
   
</chapter>
