Trong bài này, chúng ta sẽ cùng tìm hiểu về Filter, Interceptor và một số cấu hình của Jersey 2.x. Filter và Interceptor có thể được sử dụng ở cả 2 phía Jersey Server và Jersey Client. Filter có thể được sử dụng để chỉnh sửa các dữ liệu đầu vào/ đầu ra từ các request và response, bao gồm: header, data (entity), parameter. 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.
Jersey Filter
Như đã giới thiệu, Filter có thể được sử dụng ở cả 2 phía Jersey Server và Jersey Client. Nó có thể được sử dụng để chỉnh sửa các dữ liệu đầu vào/ đầu ra (inbound/ outbound) từ các request và response, bao gồm: header, data (entity), parameter.
Jersey Server filter:
- ContainerRequestFilter : sử dụng filter này khi cần thực hiện một số hành động trước khi resource method được thực thi. Chẳng hạn, chứng thực user trước khi truy cập resource.
- ContainerResponseFilter: sử dụng filter này khi cần thực hiện một số hành động trước khi trả kết quả về client. Chẳng hạn, thêm một số thông header gửi về Client.
- ContainerRequestFilter với Annotation @PreMatching:
- ContainerRequestFilter mặc định là một post-matching filter, nghĩa là filter được áp dụng chỉ sau khi đã tìm thấy một resource method phù hợp để xử lý request, tức là sau request matching thực thi. Request matching là quá trình tìm resource method để thực thi dựa trên Path, HTTP Method và một và parameter khác. Khi post-matching request filter được gọi, các filter khác sẽ không làm ảnh hưởng đến xử lý resource method matching.
- @PreMatching : là một request filter được thực thi trước khi request matching bắt đầu. Vì vậy nó có thể làm ảnh hưởng đến kết quả tìm kiếm một resource method thực thi. Chẳng hạn, chúng ta có thể thay đổi request method của Client từ PUT sang POST.
Jersey Client Filter:
- ClientRequestFilter : sử dụng filter này khi cần thực hiện một số hành động trước khi gửi request lên server. Chẳng hạn, thêm token đã chứng thực lên server.
- ClientResponseFilter : sử dụng filter này khi cần thực hiện một số hành động sau khi đã nhận response từ server trước khi phương thức ở Client nhận kết quả.
Tạo REST Web service
OrderService.java
package com.gpcoder.api; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Response; import com.gpcoder.model.Order; // URI: // http(s)://<domain>:(port)/<YourApplicationName>/<UrlPattern in web.xml>/<path> // http://localhost:8080/RestfulWebServiceExample/rest/orders @Path("/orders") public class OrderService { @GET @Path("/{id}") public Response get(@PathParam("id") int id) { System.out.println("OrderService#get()"); return Response.ok("OrderService#get()").build(); } @POST public Response insert(Order order) { System.out.println("OrderService#insert()"); return Response.ok("OrderService#insert()").build(); } @PUT public Response update(Order order) { System.out.println("OrderService#update()"); return Response.ok("OrderService#update()").build(); } @DELETE @Path("/{id}") public Response delete(@PathParam("id") int id) { System.out.println("OrderService#delete()"); return Response.ok("OrderService#delete()").build(); } }
Tạo Server Filter
Tạo ContainerRequestFilter chứng thực user: nếu HTTP request là DELETE thì cần được chứng thực. Nếu chưa được chứng thực thì sẽ trả về Client status Response.Status.UNAUTHORIZED.
AuthorizationRequestFilter.java
package com.gpcoder.filter; import java.io.IOException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider; @Provider public class AuthorizationRequestFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { System.out.println("AuthorizationRequestFilter running ... "); // Must be logged-in to perform the delete action if ("DELETE".equals(requestContext.getMethod()) && !hasToken(requestContext)) { Response response = Response.status(Response.Status.UNAUTHORIZED) // .entity("User cannot access the resource.") // .build(); requestContext.abortWith(response); } } private boolean hasToken(ContainerRequestContext requestContext) { // Extract Authorization header details String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer")) { return false; } // Extract the token String token = authorizationHeader.substring("Bearer".length()).trim(); System.out.println("token: " + token); return token != null && token.trim().length() > 0; } }
Lưu ý: chúng ta cần sử dụng @Provider hoặc gọi phương thức register() trong ResourceConfig để đăng ký với Jersey sử dụng Filter này.
Tạo ContainerResponseFilter thêm một vài thông tin header trước khi trả kết quả về Client:
PoweredByResponseFilter.java
package com.gpcoder.filter; import java.io.IOException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.ext.Provider; @Provider public class PoweredByResponseFilter implements ContainerResponseFilter { @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { System.out.println("PoweredByResponseFilter running ... "); responseContext.getHeaders().add("X-Api-Version", "2.x"); responseContext.getHeaders().add("X-Powered-By", "api.gpcoder.com"); } }
Tạo @PreMatching ContainerRequestFilter để tự động chuyển hướng request của Client từ HTTP PUT sang HTTP POST.
PreMatchingFilter.java
package com.gpcoder.filter; import java.io.IOException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.PreMatching; import javax.ws.rs.ext.Provider; @Provider @PreMatching public class PreMatchingFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { System.out.println("PreMatchingFilter running ... "); if (requestContext.getMethod().equals("PUT")) { System.out.println("Change PUT methods to POST"); requestContext.setMethod("POST"); } } }
Test Server Filter
Test @GET http://localhost:8080/RestfulWebServiceExample/rest/orders/1
Mở browser và truy cập địa chỉ: http://localhost:8080/RestfulWebServiceExample/rest/orders/1
Mở cửa sổ console của server, bạn sẽ thấy thứ tự thực thi của các filter và request/ response như sau:
Test @PUT http://localhost:8080/RestfulWebServiceExample/rest/orders
Như bạn thấy, request từ Client là PUT, nhưng được filter chuyển sang POST.
Test @DELETE http://localhost:8080/RestfulWebServiceExample/rest/orders/1
Như bạn thấy, request đã không được chấp nhận và response trả về với status Status.UNAUTHORIZED.
Tạo Client Filter
Tạo ClientRequestFilter để tự động thêm Token đã được chứng thực trước khi gửi request lên server.
CheckClientRequestFilter.java
package com.gpcoder.filter; import java.io.IOException; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.core.HttpHeaders; public class CheckClientRequestFilter implements ClientRequestFilter { @Override public void filter(ClientRequestContext requestContext) throws IOException { System.out.println("CheckClientRequestFilter running ... "); String authorization = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); if (authorization == null || authorization.trim().isEmpty()) { requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer gpcoder-token"); } } }
Tạo ClientResponseFilter để thêm thông tin header sau khi nhận được response từ server.
CheckClientResponseFilter.java
package com.gpcoder.filter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientResponseContext; import javax.ws.rs.client.ClientResponseFilter; public class CheckClientResponseFilter implements ClientResponseFilter { @Override public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) { System.out.println("CheckClientResponseFilter running ... "); responseContext.getHeaders().add("X-Test-Client", "gpcoder client filter"); System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy hh:mm:ss"))); } }
Test Client Filter
Trong ví dụ này, tôi sẽ tạo request HTTP DELETE. API delete trên server yêu cầu xác thực mới được thực thi. Do đó, phía Client cần phải gửi thông tin token đã xác thực. Chúng ta sẽ xem 2 cách gửi thông tin xác thực như sau:
- Sử dụng Invocation.Builder để thêm header.
- Sử dụng Filter
package com.gpcoder.client; 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.Invocation; import javax.ws.rs.client.WebTarget; 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.gpcoder.filter.CheckClientRequestFilter; import com.gpcoder.filter.CheckClientResponseFilter; public class OrderServiceClient { public static final String API_URL = "http://localhost:8080/RestfulWebServiceExample/rest/orders"; public static final String API_TOKEN = "gpcoder-token"; public static void main(String[] args) { callApiWithoutFilter(); System.out.println("------"); callApiWithFilter(); } public static void callApiWithFilter() { 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 CheckClientRequestFilter()); clientConfig.register(new CheckClientResponseFilter()); Client client = ClientBuilder.newClient(clientConfig); WebTarget target = client.target(API_URL).path("1"); Invocation.Builder invocationBuilder = target.request(MediaType.APPLICATION_JSON_TYPE); // Don't need to add this line because it's already added automatically by CheckClientRequestFilter // invocationBuilder.header("Authorization", "Bearer " + API_TOKEN); final Response response = invocationBuilder.delete(); System.out.println("Header added by CheckClientResponseFilter: " + response.getHeaderString("X-Test-Client")); System.out.println("Call delete() successful with the result: " + response.readEntity(String.class)); } public static void callApiWithoutFilter() { 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)); Client client = ClientBuilder.newClient(clientConfig); WebTarget target = client.target(API_URL).path("1"); Invocation.Builder invocationBuilder = target.request(MediaType.APPLICATION_JSON_TYPE); invocationBuilder.header("Authorization", "Bearer " + API_TOKEN); String result = invocationBuilder.delete(String.class); System.out.println("Call delete() successful with the result: " + result); } }
Chạy ứng dụng Client, chúng ta có kết quả như sau:
Console ở Client:
Console ở Server:
Jersey Interceptor
Bài viết khá là dài nên mình sẽ tách phần Interceptor ra ở một bài viết khác. Các bạn xem tiếp tại đây.
Tài liệu tham khảo: