有许多情景当你的REST api提供的相应是非常长的,并且我们都知道传递速度和贷款在移动设备/网络上是多重要。当开发支持REST apis的移动app的时候,我认为首要的性能最优化的点就是需要解决。猜猜是什么?因为响应式文本,因此我们能压缩这些文本。而且随着当前的只能手机和平板的能力,在客户端解压文本应该不是个大问题...因此在这篇文章中,如果你使用java的Jersey构建它,我将介绍你怎么能有选择性的压缩REST API响应,这个Jersey事JAX-RS的映射实现(还有更多)...
1.Jersey过滤器和拦截器
啊,感谢Jersey的强大的过滤器和拦截器特性,这个实现是相当容易的。然后过滤器是主要打算来维护像HTTP headers,URIs和/或HTTP methods的request和response的参数,拦截器是维护实体,通过维护实体的输入/输出流。
但是对于压缩将使用一个GZip WriterInterceptor,一个写拦截器被用于这种情况,在那个类里,实体被写到"wire",当在这种情况中时,它在服务器这边,这就意味着输出一个响应实体。
1.1GZip Writer Interceptor
那让我们来看看我们的GZip Writer Interceptor吧:
GZip Writer Interceptor
package org.codingpedia.demo.rest.interceptors;

import java.io.IOException; 

import java.io.OutputStream; 

import java.util.zip.GZIPOutputStream; 



import javax.ws.rs.WebApplicationException; 

import javax.ws.rs.core.MultivaluedMap; 

import javax.ws.rs.ext.WriterInterceptor; 

import javax.ws.rs.ext.WriterInterceptorContext;



@Provider 

@Compress 

public class GZIPWriterInterceptor implements WriterInterceptor { 

  

   @Override 

   public void aroundWriteTo(WriterInterceptorContext context) 

                   throws IOException, WebApplicationException { 

    
  

    
 MultivaluedMap<String,Object> headers = context.getHeaders(); 

    
 headers.add("Content-Encoding", "gzip"); 

    
  

       final OutputStream outputStream = context.getOutputStream(); 

       context.setOutputStream(new GZIPOutputStream(outputStream)); 

       context.proceed(); 

   } 

}




注意:
它实现了WriterInterceptor,这是一个写拦截器的消息体的接口,这个接口包装调用javax.ws.rs.ext.MessageBodyWriter.writeTo
供应商实现WriterInterceptor协议必须要么以编程方式注册进一个JAX-RS运行环境,要么必须用@Provider注解来注解在一个提供商扫描语句期间自动的被JAX-RS运行环境发现。
@Compress是绑定注解的名称,在接下来的段落中我们将更详细的讨论它
“拦截器从WriterInterceptorContext中获得一个输出流并且设置一个新的用原始的GZIP包装器包装的输出流。在所有的拦截器被执行以后,输出流最终设置WriterInterceptorContext将用于序列化实体。在上面的例子中,实体字节将被写到GZIPOutputStream中,这个类将压缩流数据,然后把他们写到原始输出流。原始流总是把数据写到wire中。当拦截器被用在服务器上时,原始输出流会把数据写到底层服务器容器的流中,然后发送响应给客户端。”
“重载方法aroundWriteTo()获取WriterInterceptorContextz作为参数。这个上下文包括请求头参数getters和setters,请求属性,实体,实体流和其它属性;当你压缩你的响应时,你应当设置'Content-Encoding'头位gzip”
1.2 压缩注解
过滤器和拦截器能被绑定名字。名称绑定是一种概念,这种概念就是允许告诉一个JAX-RS的运行时,一个只为特定资源方法的特定的过滤器或者拦截器将被执行。当一个过滤器或者拦截器只对一些特定的资源方法限制,那我们就认为它是名称绑定。过滤器和拦截器没有这样的限制就被称作global。在我们的例子中我们已经构建了@Compress注解:
Compress annotation
package org.codingpedia.demo.rest.interceptors;


import java.lang.annotation.Retention; 

 import java.lang.annotation.RetentionPolicy; 



 import javax.ws.rs.NameBinding; 



 //@Compress annotation is the name binding annotation 

 @NameBinding 

 @Retention(RetentionPolicy.RUNTIME) 

 public @interface Compress {}




而且用它来标记在资源上的方法,这个方法应该是被压缩的(eg:当GET-ing的时候,所有的博客用PodcastsResource)


@Compress annotation在资源方法上的使用

@Component 

 @Path("/podcasts") 

 public class PodcastsResource { 



@Autowired 

private PodcastService podcastService; 



     ........................... 

 

/* 

* *********************************** READ *********************************** 

*/ 

/** 

* Returns all resources (podcasts) from the database 

*  

* @return 

* @throws IOException 

* @throws JsonMappingException 

* @throws JsonGenerationException 

* @throws AppException 

*/ 

@GET 

@Compress 

@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) 

public List<Podcast> getPodcasts( 

@QueryParam("orderByInsertionDate") String orderByInsertionDate, 

@QueryParam("numberDaysToLookBack") Integer numberDaysToLookBack) 

throws IOException, 
 AppException { 

List<Podcast> podcasts = podcastService.getPodcasts( 

orderByInsertionDate, numberDaysToLookBack); 

return podcasts; 

} 

 

     ........................... 

 }



2.测试
2.1SOAPui
好了,如果你正在用SOAPui测试,你能使用下面的请求违反PodcastsResource
Reqest:
请求例子:

GET http://localhost:8888/demo-rest-jersey-spring/podcasts/?orderByInsertionDate=DESC HTTP/1.1 

 Accept-Encoding: gzip,deflate 

 Accept: application/json, application/xml 

 Host: localhost:8888 

 Connection: Keep-Alive 

 User-Agent: Apache-HttpClient/4.1.1 (java 1.5)




Response:
被压缩的json响应,通过SOAPui自动的解压缩

HTTP/1.1 200 OK 

 Content-Type: application/json 

 Content-Encoding: gzip 

 Content-Length: 409 

 Server: Jetty(9.0.7.v20131107) 



 [ 

    { 

       "id": 2, 

       "title": "Quarks & Co - zum Mitnehmen", 

       "linkOnPodcastpedia": "http://www.podcastpedia.org/quarks", 

       "feed": "http://podcast.wdr.de/quarks.xml", 

       "description": "Quarks & Co: Das Wissenschaftsmagazin", 

       "insertionDate": "2014-10-29T10:46:13.00+0100" 

    }, 

     

    { 

       "id": 1, 

       "title": "- The Naked Scientists Podcast - Stripping Down Science", 

       "linkOnPodcastpedia": "http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science", 

       "feed": "feed_placeholder", 

       "description": "The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home.", 

       "insertionDate": "2014-10-29T10:46:02.00+0100" 

    } 

 ]




SOAPui接受Content-type:gzip头,我们在GZIPWriterIntercepter中添加了并且自动的解压了响应并且用人眼可读的方式展示出来。

好了,就这些了。你已经了解了Jersey如何让它直接压缩REST api响应了。



There may be cases when your REST api provides responses that are very long, and we all know how important transfer speed and bandwidth still are on mobile devices/networks. I think this is the first performance optimization point one needs to address, when developing REST apis that support mobile apps. Guess what? Because responses are text, we can compress them. And with today’s power of smartphones and tablets uncompressing them on the client side should not be a big deal… So in this post I will present how you can SELECTIVELY compress your REST API responses, if you’ve built it in Java with Jersey, which is  the JAX-RS Reference Implementation (and more)…
 
 

1. Jersey filters and interceptors

Well, thanks to Jersey’s powerful Filters and Interceptors features, the implementation is fairly easy.  Whereas filters are primarily intended to manipulate request and response parameters like HTTP headers, URIs and/or HTTP methods, interceptors are intended to manipulate entities, via manipulating entity input/output streams.

You’ve seen the power of filters in my posts:

GZip WriterInterceptor. A writer interceptor is used for cases where entity is written to the “wire”, which on the server side as in this case, means when writing out a response entity.

1.1. GZip Writer Interceptor

So let’s have a look at our GZip Writer Interceptor:

GZip Writer Interceptor

01
package org.codingpedia.demo.rest.interceptors;
02
 
03
import java.io.IOException;
04
import java.io.OutputStream;
05
import java.util.zip.GZIPOutputStream;
06
 
07
import javax.ws.rs.WebApplicationException;
08
import javax.ws.rs.core.MultivaluedMap;
09
import javax.ws.rs.ext.WriterInterceptor;
10
import javax.ws.rs.ext.WriterInterceptorContext;
11
 
12
@Provider
13
@Compress
14
public class GZIPWriterInterceptor implements WriterInterceptor {
15
      
16
    @Override
17
    public void aroundWriteTo(WriterInterceptorContext context)
18
                    throws IOException, WebApplicationException {
19
         
20
        MultivaluedMap<String,Object> headers = context.getHeaders();
21
        headers.add("Content-Encoding", "gzip");
22
         
23
        final OutputStream outputStream = context.getOutputStream();
24
        context.setOutputStream(new GZIPOutputStream(outputStream));
25
        context.proceed();
26
    }
27
}

Note:

  • it implements the 

WriterInterceptor

  • ,  which is an interface for message body writer interceptors that wrap around calls to

javax.ws.rs.ext.MessageBodyWriter.writeTo

  • providers implementing 

WriterInterceptor@Compress

  • “The interceptor gets a output stream from the WriterInterceptorContext and sets a new one which is a GZIP wrapper of the original output stream. After all interceptors are executed the output stream lastly set to the WriterInterceptorContext will be used for serialization of the entity. In the example above the entity bytes will be written to the GZIPOutputStream which will compress the stream data and write them to the original output stream. The original stream is always the stream which writes the data to the “wire”. When the interceptor is used on the server, the original output stream is the stream into which writes data to the underlying server container stream that sends the response to the client.” [2]
  • “The overridden method aroundWriteTo() gets WriterInterceptorContext as a parameter. This context contains getters and setters for header parameters, request properties, entity, entity stream and other properties.” [2]; when you compress your response you should set the “Content-Encoding” header to “gzip”

1.2. Compress annotation

Filters and interceptors can be name-bound. Name binding is a concept that allows to say to a JAX-RS runtime that a specific filter or interceptor will be executed only for a specific resource method. When a filter or an interceptor is limited only to a specific resource method we say that it is name-bound. Filters and interceptors that do not have such a limitation are called global. In our case we’ve built the @Compress annotation:

Compress annotation

01
package org.codingpedia.demo.rest.interceptors;
02
 
03
import java.lang.annotation.Retention;
04
import java.lang.annotation.RetentionPolicy;
05
 
06
import javax.ws.rs.NameBinding;
07
 
08
//@Compress annotation is the name binding annotation
09
@NameBinding
10
@Retention(RetentionPolicy.RUNTIME)
11
public @interface Compress {}

and used it to mark methods on resources which should be gzipped (e.g. when GET-ing all the podcasts with the PodcastsResource):

@Compress annotation usage on resource method


01
@Component
02
@Path("/podcasts")
03
public class PodcastsResource {
04
 
05
    @Autowired
06
    private PodcastService podcastService;
07
 
08
    ...........................
09
     
10
    /*
11
     * *********************************** READ ***********************************
12
     */
13
    /**
14
     * Returns all resources (podcasts) from the database
15
     *
16
     * @return
17
     * @throws IOException
18
     * @throws JsonMappingException
19
     * @throws JsonGenerationException
20
     * @throws AppException
21
     */
22
    @GET
23
    @Compress
24
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
25
    public List<Podcast> getPodcasts(
26
            @QueryParam("orderByInsertionDate") String orderByInsertionDate,
27
            @QueryParam("numberDaysToLookBack") Integer numberDaysToLookBack)
28
            throws IOException, AppException {
29
        List<Podcast> podcasts = podcastService.getPodcasts(
30
                orderByInsertionDate, numberDaysToLookBack);
31
        return podcasts;
32
    }
33
     
34
    ...........................
35
}

2. Testing

2.1. SOAPui

PodcastsResource.

Request:

Request example


1
GET http://localhost:8888/demo-rest-jersey-spring/podcasts/?orderByInsertionDate=DESC HTTP/1.1
2
Accept-Encoding: gzip,deflate
3
Accept: application/json, application/xml
4
Host: localhost:8888
5
Connection: Keep-Alive
6
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

Response:

GZipped json response, automatically unzipped by SOAPui

 HTTP/1.1  200  OK
02
 Content-Type: application/json
03
 Content-Encoding: gzip
04
 Content-Length: 409
05
 Server: Jetty(9.0.7.v20131107)
06
  
07
 [
08
    {
09
       "id": 2,
10
       "title": "Quarks & Co - zum Mitnehmen",
11
       "linkOnPodcastpedia": "http://www.podcastpedia.org/quarks",
12
       "feed": "http://podcast.wdr.de/quarks.xml",
13
       "description": "Quarks & Co: Das Wissenschaftsmagazin",
14
       "insertionDate": "2014-10-29T10:46:13.00+0100"
15
    },
16
     
17
    {
18
       "id": 1,
19
       "title": "- The Naked Scientists Podcast - Stripping Down Science",
20
       "linkOnPodcastpedia": "http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science",
21
       "feed": "feed_placeholder",
22
       "description": "The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home.",
23
       "insertionDate": "2014-10-29T10:46:02.00+0100"
24
    }
25
 ]

Content-Type: gzip header, we’ve added in the GZIPWriterInterceptor

Well, that’s it. You’ve learned how Jersey makes it straightforward to compress the REST api responses.