首页 > 代码库 > 如何在java REST API中用GZip和Jersey压缩相应
如何在java REST API中用GZip和Jersey压缩相应
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:
- How to add CORS support on the server side in Java with Jersey, where I’ve shown how to CORS-enable a REST API
and - How to log in Spring with SLF4J and Logback, where I’ve shown how to log requests and responses from the REST API
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" , |
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 | ] |
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.
如何在java REST API中用GZip和Jersey压缩相应