Tạo ứng dụng Java RESTful Client với thư viện OkHttp

2803

Bài viết được sự cho phép của tác giả Giang Phan

Trong các bài viết trước chúng ta sử dụng thư viện Jersey client để gọi các RESTful API. Trong bài này, tôi sẽ giới thiệu với các bạn thư viện khác, rất mạnh mẽ để gọi các RESTful API là OkHttp.

Giới thiệu OkHttp

OkHttp là một thư viện Java mã nguồn mở, được thiết kế để trở thành một HTTP Client hiệu quả với tốc độ tải nhanh và giảm băng thông đường truyền.

Một số đặc điểm nỗi bật của OkHttp:

  • Hỗ trợ Http/2 protocol.
  • Connection pooling : giúp giảm độ trợ của các request.
  • GZIP compression : hỗ trợ nén dữ liệu khi truyền tải.
  • Cache : tránh lặp lại một request nhiều lần.
  • Hỗ trợ synchronous và asynchronous.
  10 lý do cho thấy tại sao bạn nên theo học ngôn ngữ lập trình Java
  10 tips để trở thành Java Developer xịn hơn

Xem thêm nhiều việc làm Java hấp dẫn trên TopDev

OkHttp được sử dụng để gửi và nhận kết quả từ các ứng dụng Restful web service.

Một số thành phần của OkHttp:

  • Request.Builder : hỗ trợ tạo request bao gồm : HTTP Method, header, cookie, media type, …
  • RestClient : chịu trách nhiệm giao tiếp với REST service bao gồm gửi Request và nhận Response.

Ví dụ CRUD Restful Client với OkHttp

Tạo project

Tạo maven project và khai báo dependency sau trong file pom.xml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.gpcoder</groupId>
    <artifactId>RestfulClientWithOkHttpExample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>RestfulClientWithOkHttpExample</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <lombok.version>1.16.20</lombok.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.14.2</version>
        </dependency>
    </dependencies>
</project>

Tạo CRUD Restful Client với OkHttp

Trong ví dụ này, chúng ta sẽ gọi lại các Restful API chúng ta đã tạo ở bài viết trước “JWT – Token-based Authentication trong Jersey 2.x“.

Đầu tiên, chúng ta cần gọi API /auth để lấy token và sau đó chúng ta sẽ attach token này vào mỗi request để truy cập resource.

AuthenticationClient.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.gpcoder.service;
import java.io.IOException;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
 * This is a first step in restful flow. We must authorize first and use the token for other request.
 */
public class AuthenticationClient {
    public static final String AUTH_URL = "http://localhost:8080/RestfulWebServiceExample/rest/auth";
    public static final String TOKEN = getToken();
    
    /**
     * Get Json Web Token (JWT)
     *
     */
    private static String getToken() {
        RequestBody requestBody = new FormBody.Builder()
                .add("username", "gpcoder")
                .add("password", "gpcoder")
                .build();
        Request request = new Request.Builder()
                .url(AUTH_URL)
                .post(requestBody)
                .build();
        
        OkHttpClient client = new OkHttpClient();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code: " + response);
            }
            String token = response.body().string();
            System.out.println("Token: " + token);
            return token;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

OkHttpClientExample.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package com.gpcoder;
import java.io.IOException;
import com.gpcoder.service.AuthenticationClient;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class OkHttpClientExample {
    public static final String API_URL = "http://localhost:8080/RestfulWebServiceExample/rest/orders";
    public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    private static OkHttpClient client;
    
    public static void main(String[] args) throws IOException {
        client = new OkHttpClient();
        String token = AuthenticationClient.TOKEN;
        createOrder(token);
        retrieveOrder(token);
        updateOrder(token);
        deleteOrder(token);
    }
    
    /**
     */
    private static void createOrder(String token) throws IOException {
        RequestBody requestBody = RequestBody.create(JSON, createJsonData());
        
        Request request = new Request.Builder()
                .header("Authorization", "Bearer " + token)
                .url(API_URL)
                .post(requestBody)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String result = response.body().string();
            System.out.println("createOrder: " + result);
        }
    }
    
    /**
     */
    private static void retrieveOrder(String token) throws IOException {
        HttpUrl.Builder urlBuilder = HttpUrl.parse(API_URL).newBuilder();
        urlBuilder.addPathSegment("1");
        String url = urlBuilder.build().toString();
        
        Request request = new Request.Builder()
                .header("Authorization", "Bearer " + token)
                .url(url)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String result = response.body().string();
            System.out.println("retrieveOrder: " + result);
        }
    }
    
    /**
     */
    private static void updateOrder(String token) throws IOException {
        RequestBody requestBody = RequestBody.create(JSON, createJsonData());
        
        Request request = new Request.Builder()
                .header("Authorization", "Bearer " + token)
                .url(API_URL)
                .post(requestBody)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String result = response.body().string();
            System.out.println("updateOrder: " + result);
        }
    }
    
    /**
     */
    private static void deleteOrder(String token) throws IOException {
        HttpUrl.Builder urlBuilder = HttpUrl.parse(API_URL).newBuilder();
        urlBuilder.addPathSegment("1");
        String url = urlBuilder.build().toString();
        
        Request request = new Request.Builder()
                .header("Authorization", "Bearer " + token)
                .url(url)
                .delete()
                .build();
        try (Response response = client.newCall(request).execute()) {
            String result = response.body().string();
            System.out.println("deleteOrder: " + result);
        }
    }
    
    private static String createJsonData() {
        return "{"id": 1, "name": "gpcoder"}";
    }
}

Sử dụng Interceptor với OkHttp

Interceptor là một tính năng rất hữu ích nó giúp chúng ta có thể chỉnh sửa request/response, retry call, monitor ứng dụng.

OkHttp hỗ trợ 2 loại Interceptor:

  • Application Interceptor : loại interceptor này thường được sử dụng để thay đổi headers/query cho cả request/ response. Loại interceptor này chắc chắn được gọi 1 lần ngay cả khi nó được truy xuất từ bộ nhớ đệm (Cache).
  • Network Interceptor : được sử dụng để giám sát yêu cầu giống như được truyền qua mạng. Nó rất hữu ích để theo dõi chuyển hướng (redirect) , thử lại (retry) và cung cấp quyền truy cập vào các resource của request. Loại interceptor này sẽ KHÔNG được gọi nếu nó được truy xuất từ bộ nhớ đệm (Cache).

Bây giờ chúng ta sẽ sử dụng Interceptor để tự động thêm Token vào mỗi request, chúng ta không cần thêm nó một cách thủ công trong từng request. Ví dụ bên dưới sử dụng 2 Interceptor:

  • AuthInterceptor : thêm Token vào mỗi request.
  • LoggingInterceptor : log trước khi gửi request và sau khi nhận response.

AuthInterceptor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.gpcoder.interceptor;
import java.io.IOException;
import com.gpcoder.service.AuthenticationClient;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class AuthInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        /*
         * chain.request() returns original request that you can work with(modify,
         * rewrite)
         */
        Request originalRequest = chain.request();
        // Here we can rewrite the request
        // We add an Authorization header if the request is not an authorize request and already had a token
        Request authRequest = originalRequest;
        if (!originalRequest.url().toString().contains("/auth") && AuthenticationClient.TOKEN != null) {
            authRequest = originalRequest.newBuilder()
                    .header("Authorization", "Bearer " + AuthenticationClient.TOKEN)
                    .build();
        }
        
        /*
         * chain.proceed(request) is the call which will initiate the HTTP work. This
         * call invokes the request and returns the response as per the request.
         */
        Response response = chain.proceed(authRequest);
        
        // Here we can rewrite/modify the response
        
        return response;
    }
}

Tương tự chúng ta sẽ tạo LoggingInterceptor
LoggingInterceptor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.gpcoder.interceptor;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class LoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();
        long t1 = System.nanoTime();
        System.out.println(
                String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
        Response response = chain.proceed(request);
        long t2 = System.nanoTime();
        System.out.println(String.format("Received response for %s in %.1fms%n%s", response.request().url(),
                (t2 - t1) / 1e6d, response.headers()));
        return response;
    }
}

Để sử dụng Interceptor, chúng ta cần đăng ký với Client thông qua phương thức addInterceptor() hoặc addNetworkInterceptor(). Chương trình bên dưới, tôi đăng ký AuthInterceptor ở mức Application và LoggingInterceptor cho cả 2 mức Application và Network.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.gpcoder;
import java.io.IOException;
import com.gpcoder.interceptor.AuthInterceptor;
import com.gpcoder.interceptor.LoggingInterceptor;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class OkHttpClientWithInterceptorExample {
    public static final String API_URL = "http://localhost:8080/RestfulWebServiceExample/rest/orders";
    public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    private static OkHttpClient client;
    
    public static void main(String[] args) throws IOException {
        client = new OkHttpClient.Builder()
                .addInterceptor(new LoggingInterceptor())
                .addInterceptor(new AuthInterceptor())
                .addNetworkInterceptor(new LoggingInterceptor())
                .build();
        
        createOrder();
        retrieveOrder();
        updateOrder();
        deleteOrder();
    }
    
    /**
     */
    private static void createOrder() throws IOException {
        RequestBody requestBody = RequestBody.create(JSON, createJsonData());
        
        Request request = new Request.Builder()
                .url(API_URL)
                .post(requestBody)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String result = response.body().string();
            System.out.println("createOrder: " + result);
        }
    }
    
    /**
     */
    private static void retrieveOrder() throws IOException {
        HttpUrl.Builder urlBuilder = HttpUrl.parse(API_URL).newBuilder();
        urlBuilder.addPathSegment("1");
        String url = urlBuilder.build().toString();
        
        Request request = new Request.Builder()
                .url(url)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String result = response.body().string();
            System.out.println("retrieveOrder: " + result);
        }
    }
    
    /**
     */
    private static void updateOrder() throws IOException {
        RequestBody requestBody = RequestBody.create(JSON, createJsonData());
        
        Request request = new Request.Builder()
                .url(API_URL)
                .post(requestBody)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String result = response.body().string();
            System.out.println("updateOrder: " + result);
        }
    }
    
    /**
     */
    private static void deleteOrder() throws IOException {
        HttpUrl.Builder urlBuilder = HttpUrl.parse(API_URL).newBuilder();
        urlBuilder.addPathSegment("1");
        String url = urlBuilder.build().toString();
        
        Request request = new Request.Builder()
                .url(url)
                .delete()
                .build();
        try (Response response = client.newCall(request).execute()) {
            String result = response.body().string();
            System.out.println("deleteOrder: " + result);
        }
    }
    
    private static String createJsonData() {
        return "{"id": 1, "name": "gpcoder"}";
    }
}

Chạy ứng dụng, các bạn sẽ thấy nó có cùng kết quả với ví dụ đầu tiên. Tuy nhiên, chúng ta không cần quan tâm đến token nữa. Mọi thứ đã được handle trong AuthInterceptor.

Trên đây là những thông tin cơ bản về OkHttp, ngoài ra chúng ta có thể upload, download file khi sử dụng retrofit. Trong java thường nếu xử lý trên web thì ít khi dùng đến thư viện OkHttp, thư viện này được sử dụng chủ yếu trong các ứng dụng Android để thao tác từ client đến server. 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.

Tài liệu tham khảo:

Bài viết gốc được đăng tải tại gpcoder.com

Có thể bạn quan tâm:

Xem thêm tuyển dụng IT hấp dẫn trên TopDev