Rest Web service: Filter và Interceptor với Jersey 2.x (P1)

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.

1. 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ả.

1.1. Tạo REST Web service

OrderService.java

package com.maixuanviet.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.maixuanviet.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();
    }
}

1.2. 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.maixuanviet.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.maixuanviet.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.maixuanviet.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.maixuanviet.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");
        }
    }
}

1.3. 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.

1.4. 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.maixuanviet.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 maixuanviet-token");
        }
    }
}

Tạo ClientResponseFilter để thêm thông tin header sau khi nhận được response từ server.

CheckClientResponseFilter.java

package com.maixuanviet.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", "maixuanviet client filter");
        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy hh:mm:ss")));
    }
}

1.5. 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.maixuanviet.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.maixuanviet.filter.CheckClientRequestFilter;
import com.maixuanviet.filter.CheckClientResponseFilter;
 
public class OrderServiceClient {
 
    public static final String API_URL = "http://localhost:8080/RestfulWebServiceExample/rest/orders";
    public static final String API_TOKEN = "maixuanviet-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:

Related posts:

Converting a List to String in Java
Quick Guide on Loading Initial Data with Spring Boot
Hướng dẫn Java Design Pattern – Dependency Injection
Servlet 3 Async Support with Spring MVC and Spring Security
Guide to java.util.concurrent.Locks
Java Program to do a Depth First Search/Traversal on a graph non-recursively
Java Program to Implement Disjoint Sets
Quick Guide to java.lang.System
Java Program to Implement Word Wrap Problem
Java Program to Create a Balanced Binary Tree of the Incoming Data
Getting Started with Custom Deserialization in Jackson
How to Store Duplicate Keys in a Map in Java?
Number Formatting in Java
Java Program to Implement the linear congruential generator for Pseudo Random Number Generation
Java Program to Find Hamiltonian Cycle in an UnWeighted Graph
How to Kill a Java Thread
Mix plain text and HTML content in a mail
Hướng dẫn sử dụng luồng vào ra nhị phân trong Java
Java Program to Implement Aho-Corasick Algorithm for String Matching
Java Program to Check Cycle in a Graph using Topological Sort
Java Program to Implement Warshall Algorithm
Hướng dẫn Java Design Pattern – Facade
Java Program to Solve the 0-1 Knapsack Problem
Class Loaders in Java
Java Program to Perform the Unique Factorization of a Given Number
Câu lệnh điều khiển vòng lặp trong Java (break, continue)
Why String is Immutable in Java?
Spring MVC Tutorial
File Upload with Spring MVC
Apache Commons Collections BidiMap
So sánh ArrayList và Vector trong Java
Java Program to Perform Optimal Paranthesization Using Dynamic Programming