Table of Contents
Ở bài viết trước, chúng ta đã cùng tìm hiểu về Filter. Trong phần này, chúng ta sẽ tìm hiểu tiếp Interceptor và một số cấu hình khác của Jersey.
1. Jersey Interceptor
Interceptor có thể được sử dụng ở cả 2 phía Jersey Server và Jersey Client. Interceptor chủ yếu được sử dụng để chỉnh sửa data (entity) của input/ output stream. Một ví dụ về trường hợp sử dụng của Intercepter là zip/ unzip các data (entity) của input/ output stream.
Interceptor tham gia trong quá trình marshalling và unmarshalling HTTP message body có trong các request/ response.
Lưu ý: Intercepter được thực thi sau Filter và message body tồn tại.
Có 2 loại Interceptor:
- ReaderInterceptor : được sử dụng để xử lý inbound entity stream ở phía Server và response entity stream ở phía Client.
- WriterInterceptor : được sử dụng để xử lý outbound response entity ở phía Server và request entity stream ở phía Client.
1.1. Tạo WriterInterceptor
Tạo WriterInterceptor để wrap OutputStream bởi một GZIPOutputStream và gửi lên Server.
GZIPWriterInterceptor.java
package com.maixuanviet.interceptor; import java.io.IOException; import java.io.OutputStream; import java.util.zip.GZIPOutputStream; import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.WriterInterceptor; import javax.ws.rs.ext.WriterInterceptorContext; public class GZIPWriterInterceptor implements WriterInterceptor { @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException { final OutputStream outputStream = context.getOutputStream(); context.setOutputStream(new GZIPOutputStream(outputStream)); context.proceed(); } }
1.2. Tạo ReaderInterceptor
Tạo ReaderInterceptor để wrap InputStream bởi một GZIPInputStream để đọc data đã được compress từ Client gửi lên.
GZIPReaderInterceptor.java
package com.maixuanviet.interceptor; import java.io.IOException; import java.io.InputStream; import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.ReaderInterceptor; import javax.ws.rs.ext.ReaderInterceptorContext; import com.maixuanviet.util.ZipUtil; @Provider public class GZIPReaderInterceptor implements ReaderInterceptor { @Override public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException { final InputStream originalInputStream = context.getInputStream(); context.setInputStream(ZipUtil.decompressStream(originalInputStream)); return context.proceed(); } }
Lưu ý: Chúng ta sử dụng @Provider để đăng ký Interceptor này với Jersey.
Lớp ZipUtil.java hỗ trợ decompress data đã được gzip:
package com.maixuanviet.util; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; import java.io.RandomAccessFile; import java.util.zip.GZIPInputStream; public class ZipUtil { private ZipUtil() { super(); } /** * The method to decompress a input stream if a input stream is a gzip archive. * * @param in the input stream to be decompressed. * @return */ public static InputStream decompressStream(InputStream input) { try { // we need a pushbackstream to look ahead PushbackInputStream pb = new PushbackInputStream(input, 2); byte[] signature = new byte[2]; int len = pb.read(signature); // read the signature pb.unread(signature, 0, len); // push back the signature to the stream if (signature[0] == (byte) 0x1f && signature[1] == (byte) 0x8b) { // check if matches standard gzip magic number return new GZIPInputStream(pb); } return pb; } catch (Exception e) { return input; } } }
Test Interceptor sử dụng Jersey Client
InterceptorClient.java
package com.maixuanviet.client; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.logging.LoggingFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.maixuanviet.interceptor.GZIPWriterInterceptor; import com.maixuanviet.model.Order; public class InterceptorClient { public static final String API_URL = "http://localhost:8080/RestfulWebServiceExample/rest/orders"; public static void main(String[] args) { ClientConfig clientConfig = new ClientConfig(); // Config logging for client side clientConfig.register( // new LoggingFeature( // Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), // Level.INFO, // LoggingFeature.Verbosity.PAYLOAD_ANY, // 10000)); // Config filters clientConfig.register(new GZIPWriterInterceptor()); String payload = convertToJson(new Order(1, "maixuanviet")); Client client = ClientBuilder.newClient(clientConfig); Response response = client.target(API_URL).request(MediaType.APPLICATION_JSON_TYPE) .post(Entity.entity(payload, MediaType.APPLICATION_JSON)); System.out.println(response.readEntity(String.class)); } private static String convertToJson(Order order) { ObjectMapper mapper = new ObjectMapper(); try { return mapper.writeValueAsString(order); } catch (IOException e) { e.printStackTrace(); } return null; } }
Chạy chương trình trên chúng ta có kết quả như sau:
Console ở Client:
Console ở Server:
Như bạn thấy, dữ liệu ở Client gửi lên, và ở server nhận được đều đã được compress/ decompress với gzip.
Các bạn có thể, bỏ đoạn code đăng ký GZIPWriterInterceptor ở Client. Sau đó chạy lại, bạn sẽ sẽ thấy dữ liệu gửi và nhận ở dạng plain/text như sau:
2. Thứ tự thực thi Filter và Interceptor
Thứ tự thực thi các Filter và Interceptor được minh họa như hình sau:
3. Name binding
Mặc định, các Filter và Interceptor sẽ được áp dụng trên tất cả các resource method. Chúng ta, có thể chỉ định Filter chỉ áp dụng trên một vài resource method bằng cách sử dụng Name binding.
Để làm được điều này, chúng tạo một Annotation và đánh dấu với Annotation @NameBinding. Sau đó, sử dụng Annotation này với resource method cần áp dụng Filter được chỉ định.
Ví dụ:
Tạo một Annotation để đánh dấu binding cho Filter Compress data với các resource method trả về kích thước dữ liệu lớn:
Compress.java
package com.maixuanviet.annotation; 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 { }
Tạo Rest Web service:
package com.maixuanviet.api; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import com.maixuanviet.annotation.Compress; //URI: //http(s)://<domain>:(port)/<YourApplicationName>/<UrlPattern in web.xml>/<path> //http://localhost:8080/RestfulWebServiceExample/rest/compresses @Path("/compresses") public class HeavyService { @GET @Path("normal-data") public String getNormalData() { return "Normal data"; } @GET @Path("big-data") @Compress public String getBigData() { return "Big data"; } @GET @Path("dynamic-data") public String getDynamicData() { return "Dynamic data"; } }
Test ứng dụng:
- /normal-data : dữ liệu trả về dạng plain-text.
- /big-data : dữ liệu trả về đã được compress ở định dạng gzip.
- /dynamic-data : dữ liệu trả về dạng plain-text.
4. Dynamic binding
Ngoài sử dụng custom Annotation @NameBinding, chúng ta có thể áp dụng một Filter trên một số resource method nếu nó thõa mãn một số điều kiện nhất định.
Ví dụ:
Tạo DynamicFeature để kiểm tra nếu tên resource method có chứa “DynamicData” thì sẽ sử dụng GZIPWriterInterceptor.
package com.maixuanviet.interceptor; import javax.ws.rs.container.DynamicFeature; import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.core.FeatureContext; import javax.ws.rs.ext.Provider; import com.maixuanviet.api.HeavyService; //This dynamic binding provider registers GZIPWriterInterceptor //only for HelloWorldResource and methods that contain //"VeryLongString" in their name. It will be executed during //application initialization phase. @Provider public class CompressionDynamicBinding implements DynamicFeature { @Override public void configure(ResourceInfo resourceInfo, FeatureContext context) { if (HeavyService.class.equals(resourceInfo.getResourceClass()) && resourceInfo.getResourceMethod().getName().contains("DynamicData")) { context.register(GZIPWriterInterceptor.class); } } }
Chạy lại ứng dụng trên, chúng ta có kết quả như sau:
- /normal-data : dữ liệu trả về dạng plain-text.
- /big-data : dữ liệu trả về đã được compress ở định dạng gzip.
- /dynamic-data : dữ liệu trả về đã được compress ở định dạng gzip.
5. Priorities
Trong trường hợp đăng ký nhiều Filter và Interceptor, chúng ta mong muốn ứng dụng thực thi các Filter và Interceptor theo đúng thứ tự được chỉ định. Để làm được điều này, chúng ta sẽ sử dụng Annotation @Priority(number) trên các Fitler và Interceptor class.
- ContainerRequestFilter, ClientRequestFilter, ReaderInterceptor, WriterInterceptor : được sắp thứ tự ưu tiên tăng dần. Số nhỏ được thực thi trước.
- ContainerResponseFilter, ClientResponseFilter : được sắp thứ tự ưu tiên giảm dần. Số lớn được thực thi trước.
Ví dụ:
Tạo REST web service:
package com.maixuanviet.api; import javax.ws.rs.POST; import javax.ws.rs.Path; //URI: //http(s)://<domain>:(port)/<YourApplicationName>/<UrlPattern in web.xml>/<path> //http://localhost:8080/RestfulWebServiceExample/rest/priorities @Path("/priorities") public class PrioprityService { @POST public String create(String data) { return "Created " + data; } }
Chúng ta có các Filter sau:
package com.maixuanviet.filter; import java.io.IOException; import javax.annotation.Priority; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.ext.Provider; @Priority(10) @Provider public class Priority1RequestFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { System.out.println("#1 Priority1RequestFilter running ... "); } }
@Priority(20) @Provider public class Priority2RequestFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { System.out.println("#2 Priority2RequestFilter running ... "); } }
@Priority(10) @Provider public class Priority1ResponseFilter implements ContainerResponseFilter { @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { System.out.println("#1 Priority1ResponseFilter running ... "); } }
@Priority(20) @Provider public class Priority2ResponseFilter implements ContainerResponseFilter { @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { System.out.println("#2 Priority2ResponseFilter running ... "); } }
Và các Interceptor sau:
package com.maixuanviet.interceptor; import java.io.IOException; import javax.annotation.Priority; import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.WriterInterceptor; import javax.ws.rs.ext.WriterInterceptorContext; @Priority(10) @Provider public class Priority1WriterInterceptor implements WriterInterceptor { @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException { System.out.println("#1 Priority1WriterInterceptor running ... "); context.proceed(); } }
@Priority(20) @Provider public class Priority2WriterInterceptor implements WriterInterceptor { @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException { System.out.println("#2 Priority2WriterInterceptor running ... "); context.proceed(); } }
@Priority(10) @Provider public class Priority1ReaderInterceptor implements ReaderInterceptor { @Override public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException { System.out.println("#1 Priority1ReaderInterceptor running ... "); return context.proceed(); } }
@Priority(20) @Provider public class Priority2ReaderInterceptor implements ReaderInterceptor { @Override public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException { System.out.println("#2 Priority1ReaderInterceptor running ... "); return context.proceed(); } }
Test API trên, chúng ta có kết quả sau:
Trên đây là toàn bộ những lý thuyết và ví dụ cơ bản về sử dụng Filter và Interceptor với Jersey. Đây là phần rất là hay, nếu áp dụng tốt sẽ giúp chúng ta đỡ tốn công sức rất là nhiều. Mọi thứ đều được xử lý một cách tự động và thống nhất trên tất cả request/ response. Hy vọng bài viết giúp ích cho các bạn, hẹn gặp lại ở các bài viết tiếp theo!