如何在java REST API中用GZip和Jersey壓縮相應

有許多情景當你的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響應了。


原文 http://www.javacodegeeks.com/2014/11/how-to-compress-responses-in-java-rest-api-with-gzip-and-jersey.html

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:

but for compressing will be using a 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 tojavax.ws.rs.ext.MessageBodyWriter.writeTo
  • providers implementing WriterInterceptor contract must be either programmatically registered in a JAX-RS runtime or must be annotated with @Provider annotation to be automatically discovered by the JAX-RS runtime during a provider scanning phase.
  • @Compress  is the name binding annotation, which we will discuss more detailed in the coming paragraph
  • “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

Well, if you are testing with SOAPui, you can issue the following request against the 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

01 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",
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 ]

SOAPui recognizes the Content-Type: gzip header, we’ve added in the GZIPWriterInterceptor and automatically uncompresses the response and displays it readable to the human eye.

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


發佈了108 篇原創文章 · 獲贊 22 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章