Home Blog Page 72

3PL Là Gì? Top Các Công Ty 3PL Ở Việt Nam

các công ty 3pl ở việt nam
3PL Là Gì? Top Các Công Ty 3PL Ở Việt Nam

Thị trường logistics ngày càng phát triển mang đến cho các công ty nhiều cơ hội và dư địa cho một thị trường mới. Để đáp ứng nhu cầu của các công ty từ trong đến ngoài nước, các dịch vụ 3PL đã sớm được triển khai để phục vụ thị trường. Vậy 3PL thực chất là gì? Các công ty 3PL ở Việt Nam đang phát triển gồm những thương hiệu nào?

các công ty 3pl ở việt nam

3PL là gì?

3PL – Third Party Logistics: hậu cần bên thứ 3, có thể hiểu một cách đơn giản là một đơn vị chuyên cung cấp các dịch vụ logistics sẽ được công ty có nhu cầu thuê để đảm nhận các nhiệm vụ liên quan đến việc vận hành một mảng nhất định trong chuỗi cung ứng của doanh nghiệp đó. Việc thuê ngoài các đơn vị 3PL giúp doanh nghiệp có thể tiết kiệm tối đa nhân lực của mình, sử dụng các dịch vụ bên ngoài với chuyên môn cao trong lĩnh vực đó sẽ giúp doanh nghiệp đạt hiệu quả công việc cao hơn.

Các dịch vụ do 3PL cung cấp rất đa dạng liên quan đến các vấn đề về logistics và chuỗi cung ứng. Có thể kể đến như lưu kho và vận chuyển hàng hóa, soạn hàng, đóng gói, giao hàng, thực hiện đơn hàng,… Tùy theo nhu cầu cụ thể của doanh nghiệp mà họ có thể thuê nhiều 3PL cùng lúc để thực hiện mỗi chức năng khác nhau.

Xem thêm Tổng Hợp Các Công Ty Outsourcing Lớn Nhất Việt Nam

Những lợi ích khi sử dụng 3PL đối với các doanh nghiệp

  • Giảm chi phí cho doanh nghiệp: Thực tế việc thuê một công ty ngoài để thực hiện 3PL sẽ tiết kiệm khá nhiều chi phí so với việc một công ty chưa có chuyên môn trong mảng này triển khai thực hiện. Dựa trên khối lượng công việc, 3PL có thể thương lượng với doanh nghiệp và đưa ra mức giá tốt nhất để hoàn thành những công việc đó.
  • Giúp doanh nghiệp tập trung phát triển các khía cạnh chuyên môn của mình.
  • Cung cấp trải nghiệm tốt hơn cho người tiêu dùng: Các doanh nghiệp thực hiện 3PL có chuyên môn sâu rộng trong lĩnh vực của mình, mạng lưới vận chuyển rộng lớn, do vậy họ hoàn toàn có thể thực hiện việc này tốt hơn so với doanh nghiệp mới bắt đầu.
  • Linh hoạt trong việc tăng giảm quy mô hoạt động: Nhu cầu tiêu dùng tùy theo thời điểm khác nhau mà sẽ có những biến động nhất định. Vậy nên việc sử dụng 3PL cho phép các doanh nghiệp dựa vào tình hình thực tế để tăng giảm tần suất hoạt động, mà không cần cam kết vốn khi không cần thiết.
  • Chủ động trong các vấn đề liên quan đến logistics quốc tế: Đối với các công ty hoạt động buôn bán trên thị trường quốc tế, 3PL sẽ giúp giải quyết hiệu quả các vấn đề liên quan đến giấy tờ, thuế, hải quan hay các vấn đề phát sinh ở biên giới,… Và giúp các công ty tránh gặp phải các rắc rối liên quan đến vấn đề giữa các quốc gia mà 3PL nắm rất rõ.
dịch vụ 3pl
Sử dụng 3PL mang lại rất nhiều lợi ích cho các doanh nghiệp

Các công ty 3PL ở Việt Nam

1. Tập đoàn Deutsche Post DHL Group

DHL là một thương hiệu có tuổi đời lâu năm trên thế giới, chuyên về hoạt động hậu cần xuất hiện ở hơn 220 quốc gia và vùng lãnh thổ. Các hoạt động của DHL được đánh giá cao về chất lượng và tiết kiệm chi phí.

2. Tập đoàn A.P. Moller-Maersk

A.P. Mller-Maersk được đánh giá là công ty vận tải biển lớn nhất thế giới. Với hơn 100 năm kinh nghiệm trong ngành vận tải hàng hóa, Maersk Việt Nam đã trở thành công ty vận chuyển hàng đầu với các dịch vụ linh hoạt, chất lượng và đáng tin cậy.

3. Công ty Vận Tải và Logistics Viettel Post

Là doanh nghiệp Việt Nam, sau nhiều năm hoạt động, đến nay Viettel Post đã vươn lên trở thành một trong những thương hiệu chuyển phát hàng đầu cả nước, với mạng lưới bưu cục, trung tâm điều hành, mạng lưới mở rộng phủ khắp các tỉnh, thành.

4. Công Ty Vận Tải và Logistics Gemadept

Gemadept tự hào là doanh nghiệp tiên phong trong lĩnh vực khai thác cảng và hậu cần tại thị trường Việt Nam, với mạng lưới hoạt động rộng lớn trải dài ở khắp các tỉnh thành trên cả nước.

5. Công Ty Vận Tải – Logistics TRANSIMEX

TRANSIMEX cung cấp các giải pháp logistics tổng thể cho khách hàng với nhiệm vụ của một 3PL, bao gồm giao nhận hàng hóa quốc tế, kho bãi, vận tải nội địa và phân phối.

6. Dịch Vụ Vận Tải và Logistics của FedEX

Có trụ sở đặt tại Hoa Kỳ, FedEx Express đến nay đã tận dụng mạng lưới đường hàng không và đường bộ toàn cầu để xúc tiến vận chuyển nhanh chóng, hiệu quả và chất lượng các lô hàng nhận được.

7. Công Ty Vận Tải và Logistics – Indo Trans Logistics Corporation (ITL)

ITL là thương hiệu công ty 3PL nổi bật trong cả nước, nhiều nằm liền đều nằm trong top các doanh nghiệp 3PL hoạt động hiệu quả nhất.

Các công ty 3PL cung cấp những giải pháp hậu cần, vận chuyển và giao nhận hàng hóa đầy hiệu quả và tối ưu chi phí. Nếu tận dụng tốt điều này cũng như có thể tìm được công ty đáp ứng yêu cầu đề ra, các doanh nghiệp chắc chắn sẽ đạt kết quả tốt hơn trong hoạt động hậu cần. Mong rằng các công ty 3PL ở Việt Nam được đề cập trong bài viết này sẽ giúp ích cho người đọc.


Tuyển Dụng Nhân Tài IT Cùng TopDev
Đăng ký nhận ưu đãi & tư vấn về các giải pháp Tuyển dụng IT & Xây dựng Thương hiệu tuyển dụng ngay!
Hotline: 028.6273.3496 – Email: contact@topdev.vn
Dịch vụ: https://topdev.vn/page/products

Phần 2: Giới thiệu xung quanh Cơ Sở Dữ Liệu (CSDL)

cơ sở dữ liệu
Phần 2: Giới thiệu xung quanh Cơ Sở Dữ Liệu(CSDL)

Bài viết được sự cho phép của tác giả Lê Chí Dũng

Kiến trúc hệ quản trị cơ sở dữ liệu

  1. Mô hình kiến trúc hệ quản trị CSDL gồm 3 mức:

    • Mức ngoài (External Level) hoặc mức khung nhìn (View Level): xác định các giao diện như những ứng dụng, tương tác và hiển thị cho người sử dụng.
    • Mức quan niệm (Conceptual Level) hoặc mức logic: còn gọi là mô hình quan niệm của dữ liệu (MQD) hoặc mô hình logic của dữ liệu (MLD). Nó xác định cách sắp xếp thông tin bên trong CSDL;
    • Mức trong (Internal Level) hoặc mức vật lý (Physical Level): xác định cách thức lưu trữ dữ liệu và các phương pháp truy cập vào đó;
      Giữa ba mức này có hai ánh xạ(mapping):Ánh xạ giữa mức ngoài và mức quan niệmÁnh xạ giữa mức trong và mức quan niệmMô phỏng cho mô hình kiến trúc hệ quản trị CSDL là mô hình ANSI/SPARC ra đời năm 1975 đã xác định một kiến trúc trừu tượng phục vụ cho phân tích và thiết kế các hệ quản trị cơ sở dữ liệu (DBMS)Mô hình kiến trúc ANSI/SPARC cho phép tạo ra sự độc lập giữa bản thân dữ liệu và việc xử lý dữ liệu. Sơ đồ ở hình bên cho thấy sự triển khai kiến trúc vật lý của một hệ quản trị CSDL là như thế nào.
  Chuyển đổi hệ cơ số trong Python
  26 công cụ và kỹ thuật trong Big Data có thể bạn chưa biết

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

2. Điểm ưu việt của hệ quản trị CSDL

Nói chung một hệ quản trị CSDL có những đặc trưng ưu việt sau đây:

    • Tính độc lập vật lý: mức vật lý có thể thay đổi mà không bị phụ thuộc vào mức quan niệm. Điều đó có nghĩa rằng những người sử dụng không cần nhìn thấy các khía cạnh vật chất của CSDL. Nói cách khác, cấu trúc thể hiện thông tin là trong suốt đối với những người sử dụng;
    • Tính độc lập logic: mức quan niệm có thể được hiệu chỉnh mà không phụ thuộc vào mức vật lý, nghĩa là người quản trị CSDL có thể phát triển nó mà không làm phiền gì đến người sử dụng;
    • Có thể thao tác được: những người không rành về CSDL cũng có thể mô tả được các yêu cầu của họ mà không cần biết tới các thành tố kỹ thuật của CSDL;
    • Tốc độ truy cập nhanh: hệ thống phải có khả năng đáp ứng (trả lời) các yêu cầu một cách nhanh nhất có thể có và điều đó đòi hỏi áp dụng các giải thuật tìm kiếm nhanh;
    • Tính quản trị tập trung: hệ quản trị CSDL phải cho phép người quản trị có khả năng thao tác các dữ liệu, chèn vào các phần tử và xác minh tính toàn vẹn của dữ liệu theo một cách tập trung;
    • Hạn chế sự dư thừa: hệ quản trị CSDL phải có khả năng lưu trữ tối thiểu những thông tin dư thừa, vừa để chống lãng phí bộ nhớ vừa để tránh các lỗi;
    • Kiểm tra tính toàn vẹn: các dữ liệu phải nhất quán giữa chúng với nhau, hơn nữa khi các phần tử này tham chiếu đến các phần tử khác thì những phần tử khác phải có mặt;
    • Dữ liệu có thể chia sẻ: hệ quản trị CSDL phải cho phép nhiều người sử dụng truy cập đồng thời đến CSDL;
    • An ninh dữ liệu: hệ quản trị CSDL phải có các cơ chế cho phép quản lý quyền truy cập vào dữ liệu tùy theo từng người sử dụng.

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

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

Xem thêm công việc CNTT hấp dẫn trên TopDev

REST Web service: JWT – Token-based Authentication trong Jersey 2.x

rest web service
REST Web service: JWT – Token-based Authentication trong Jersey 2.x

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

Trong bài trước, chúng ta đã cùng tìm hiểu về xác thực và phân quyền ứng dụng sử dụng cơ chế Basic Authentication trong Jersey 2.x. Trong bài này, chúng ta cùng tìm hiểu về cơ chế Token-based Authentication sử dụng tiêu chuẩn JSON Web Token (JWT).

Giới thiệu Token-based Authentication trong Jersey REST API

JSON Web Token (JWT) là 1 tiêu chuẩn mở (RFC 7519), định nghĩa cách thức truyền tin an toàn giữa các ứng dụng bằng một đối tượng JSON. Dữ liệu truyền đi sẽ được mã hóa và chứng thực, có thể được giải mã để lấy lại thông tin và đánh dấu tin cậy nhờ vào “chữ ký” của nó. Phần chữ ký của JWT sẽ được mã hóa lại bằng HMAC hoặc RSA. Chi tiết các bạn xem lại bài viết “Giới thiệu Json Web Token (JWT)“.

Token-based Authentication là cơ chế xác thực người dùng dựa trên việc tạo ra token – một chuỗi ký tự (thường được mã hóa) mang thông tin xác định người dùng được server tạo ra và lưu ở client. Server sau đó có thể không lưu lại token này.

Giới thiệu Token-based Authentication trong Jersey REST API

Tương tự như cơ chế Basic Authentication, với Token-based Authentication chúng ta cũng sẽ tạo ContainerRequestFilter để verify access của một user.

Token-based Authentication trong Jersey Server

Tạo Jersey project

Chúng ta sẽ tạo project mới tương tự project Basic Authentication ở bài viết trước.

cung cấp các REST API

Khai báo thư viện hỗ trợ JWT

Có nhiều thư viện Java hỗ trợ tạo JWT và verify JWT. Chúng ta có thể sử dụng một trong hai thư viện phổ biến sau:

Trong bài này, chúng ta sẽ sử dụng thư viện io.jsonwebtoken để minh họa cho Token-based Authentication trong Jersey REST API.

Mở file pom.xml và thêm các dependency sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.10.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.10.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.10.5</version>
    <scope>runtime</scope>
</dependency>

Tạo các data model class

Order.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.gpcoder.model;
import javax.xml.bind.annotation.XmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement
public class Order {
    private Integer id;
    private String name;
}

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.gpcoder.model;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String username;
    private String password;
    private List<String> roles;
}

Role.java

1
2
3
4
5
6
7
package com.gpcoder.model;
public interface Role {
    String ROLE_ADMIN = "Admin";
    String ROLE_CUSTOMER = "Customer";
}

Tạo service để hỗ trợ lấy thông tin User.

UserService.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
package com.gpcoder.service;
import java.util.ArrayList;
import java.util.Arrays;
import com.gpcoder.model.Role;
import com.gpcoder.model.User;
/**
 * This is a dummy class to get the user information
 */
public class UserService {
    public User getUser(String username) {
        User user = new User();
        user.setUsername(username);
        user.setPassword("gpcoder");
        if ("admin".equals(username)) {
            user.setRoles(Arrays.asList(Role.ROLE_ADMIN));
        } else if ("customer".equals(username)) {
            user.setRoles(Arrays.asList(Role.ROLE_CUSTOMER));
        } else if ("gpcoder".equals(username)) {
            user.setRoles(Arrays.asList(Role.ROLE_ADMIN, Role.ROLE_CUSTOMER));
        } else {
            user.setRoles(new ArrayList<>());
        }
        return user;
    }
}

Tạo SecurityContext class

SecurityContext : chứa thông tin chứng thực của một request. Sau khi chứng thực thành công, chúng ta cần cung cấp thông tin chứng thực user, role cho context class này. Các phương thức của SecurityContext sẽ được triệu gọi trước khi các resource method đã đánh dấu với các Annotation @RolesAllowed hoặc @PermitAll hoặc @DenyAll được thực thi.

BasicSecurityConext.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
package com.gpcoder.model;
import java.security.Principal;
import javax.ws.rs.core.SecurityContext;
/**
 * The SecurityContext interface provides access to security related
 * information. An instance of SecurityContext can be injected into a JAX-RS
 * resource class field or method parameter using the @Context annotation.
 *
 * @see https://jersey.github.io/documentation/latest/security.html
 * @see https://docs.oracle.com/javaee/7/api/javax/ws/rs/core/SecurityContext.html
 */
public class BasicSecurityConext implements SecurityContext {
    private User user;
    private boolean secure;
    public BasicSecurityConext(User user, boolean secure) {
        this.user = user;
        this.secure = secure;
    }
    @Override
    public Principal getUserPrincipal() {
        return () -> user.getUsername();
    }
    @Override
    public boolean isUserInRole(String role) {
        return user.getRoles().contains(role);
    }
    @Override
    public boolean isSecure() {
        return secure;
    }
    @Override
    public String getAuthenticationScheme() {
        return SecurityContext.BASIC_AUTH;
    }
}

Tạo REST API

Tạo rest api để chứng thực user và cung cấp web token.

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
package com.gpcoder.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.gpcoder.helper.JwTokenHelper;
import com.gpcoder.model.User;
import com.gpcoder.service.UserService;
@Path("/auth")
public class AuthService {
    /**
     * Authenticating a user with their username/ password and issuing a token
     *
     * @param username
     * @param password
     * @return JSON Web Token (JWT)
     */
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response authenticateUser(@FormParam("username") String username, @FormParam("password") String password) {
        // Authenticate the user using the credentials provided
        UserService userService = new UserService();
        User user = userService.getUser(username);
        if (user == null || !user.getPassword().equals(password)) {
            return Response.status(Response.Status.FORBIDDEN) // 403 Forbidden
                    .entity("Wrong username or password") // the response entity
                    .build();
        }
        // Issue a token for the user
        String token = JwTokenHelper.createJWT(user);
        // Return the token on the response
        return Response.ok(token).build();
    }
}

Chúng ta cung cấp các REST API như sau:

cung cấp các REST API

OrderService.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
package com.gpcoder.api;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
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.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import com.gpcoder.model.Order;
import com.gpcoder.model.Role;
// URI:
// http(s)://<domain>:(port)/<YourApplicationName>/<UrlPattern in web.xml>/<path>
// http://localhost:8080/RestfulWebServiceExample/rest/orders
@Path("/orders")
@PermitAll
public class OrderService {
    @GET
    @Path("/{id}")
    public Response get(@PathParam("id") int id) {
        System.out.println("OrderService->get()");
        return Response.ok("OrderService->get()").build();
    }
    @RolesAllowed(Role.ROLE_CUSTOMER)
    @POST
    public Response insert(Order order, @Context SecurityContext securityContext) {
        System.out.println("User: " + securityContext.getUserPrincipal().getName());
        System.out.println("OrderService->insert()");
        return Response.ok("OrderService->insert()").build();
    }
    @RolesAllowed({ Role.ROLE_ADMIN, Role.ROLE_CUSTOMER })
    @PUT
    public Response update(Order order) {
        System.out.println("OrderService->update()");
        return Response.ok("OrderService->update()").build();
    }
    @RolesAllowed(Role.ROLE_ADMIN)
    @DELETE
    @Path("/{id}")
    public Response delete(@PathParam("id") int id) {
        System.out.println("OrderService->delete()");
        return Response.ok("OrderService->delete()").build();
    }
}

Tạo class helper để hỗ trợ tạo Json Web Token (JWT), kiểm tra tính hợp lệ của token và extract thông tin user cần thiết.

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
package com.gpcoder.helper;
import java.security.Key;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import com.gpcoder.model.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class JwTokenHelper {
    // The privateKey is only valid for the given minutes
    private static final long EXPIRATION_LIMIT_IN_MINUTES = 30;
    // The JWT signature algorithm we will be using to sign the token
    private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
    // Keys used with HS256 MUST have a size >= 256 bits
    private static final String SECRET_KEY = "gpcoderdotcom-token-base-authentication-with-jwt-example";
    private static final String ISSUER = "https://gpcoder.com";
    private JwTokenHelper() {
        super();
    }
    public static String createJWT(User user) {
        // Get the current time
        long currentTimeInMillis = System.currentTimeMillis();
        Date now = new Date(currentTimeInMillis);
        // The privateKey is only valid for the next EXPIRATION_LIMIT_IN_MINUTES
        long expirationTimeInMilliSeconds = TimeUnit.MINUTES.toMillis(EXPIRATION_LIMIT_IN_MINUTES);
        Date expirationDate = new Date(currentTimeInMillis + expirationTimeInMilliSeconds);
        // Will sign our JWT with our ApiKey secret
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, SIGNATURE_ALGORITHM.getJcaName());
        // Sets the JWT Claims sub (subject) value
        Claims claims = Jwts.claims().setSubject(user.getUsername());
        claims.put("roles", user.getRoles());
        // Let's set the JWT Claims
        JwtBuilder builder = Jwts.builder() // Configured and then used to create JWT compact serialized strings
                .setClaims(claims).setId(UUID.randomUUID().toString()) // Sets the JWT Claims jti (JWT ID) value
                .setIssuedAt(now) // Sets the JWT Claims iat (issued at) value
                .setIssuer(ISSUER) // Sets the JWT Claims iss (issuer) value
                .setExpiration(expirationDate) // Sets the JWT Claims exp (expiration) value
                .signWith(signingKey, SIGNATURE_ALGORITHM);
        // Builds the JWT and serializes it to a compact, URL-safe string
        return builder.compact();
    }
    /**
     * Get User from the given token
     */
    public static User getUserFromToken(String token) {
        final Claims claims = decodeJWT(token);
        User user = new User();
        user.setUsername(claims.getSubject());
        user.setRoles((List<String>) claims.get("roles"));
        return user;
    }
    /**
     * Check if the token was issued by the server and if it's not expired
     */
    public static Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }
    private static Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }
    private static <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = decodeJWT(token);
        return claimsResolver.apply(claims);
    }
    private static Claims decodeJWT(String jwt) {
        // This line will throw an exception if it is not a signed JWS (as expected)
        return Jwts.parser() // Configured and then used to parse JWT strings
                .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET_KEY)) // Sets the signing key used to verify
                                                                                // any discovered JWS digital signature
                .parseClaimsJws(jwt) // Parses the specified compact serialized JWS string based
                .getBody();
    }
}

Tạo ContainerRequestFilter để chứng thực user

Chúng ta sử dụng ContainerRequestFilter để thực hiện chứng thực user trước khi truy cập resource method.

  • Lấy thông tin Authorization từ header ở phía client gửi lên.
  • Tách lấy token từ Authentication header.
  • Kiểm tra tính hợp lệ của token.
  • Thực hiện chứng thực user.
  • Lấy thông tin user từ token.
  • Lưu thông tin chứng thực lại để các request sau có thể kiểm tra chứng thực mà không cần truy xuất database, chẳng hạn @RolesAllowed.
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
package com.gpcoder.filter;
import java.io.IOException;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
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.core.SecurityContext;
import javax.ws.rs.ext.Provider;
import com.gpcoder.helper.JwTokenHelper;
import com.gpcoder.model.BasicSecurityConext;
import com.gpcoder.model.User;
@Provider
@Priority(Priorities.AUTHENTICATION) // needs to happen before authorization
public class AuthFilter implements ContainerRequestFilter {
    private static final String REALM = "gpcoder";
    private static final String AUTHENTICATION_SCHEME = "Bearer";
    /**
     * Extracting the token from the request and validating it
     *
     * The client should send the token in the standard HTTP Authorization header of
     * the request. For example: Authorization: Bearer <token-goes-here>
     *
     * Finally, set the security context of the current request
     */
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        // (1) Get Token Authorization from the header
        String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
        // (2) Validate the Authorization header
        if (!isTokenBasedAuthentication(authorizationHeader)) {
            return;
        }
        // (3) Extract the token from the Authorization header
        String token = authorizationHeader.substring(AUTHENTICATION_SCHEME.length()).trim();
        try {
            // (4) Validate the token
            if (JwTokenHelper.isTokenExpired(token)) {
                abortWithUnauthorized(requestContext);
                return;
            }
            // (5) Getting the User information from token
            User user = JwTokenHelper.getUserFromToken(token);
            // (6) Overriding the security context of the current request
            SecurityContext oldContext = requestContext.getSecurityContext();
            requestContext.setSecurityContext(new BasicSecurityConext(user, oldContext.isSecure()));
        } catch (Exception e) {
            abortWithUnauthorized(requestContext);
        }
    }
    private boolean isTokenBasedAuthentication(String authorizationHeader) {
        // Check if the Authorization header is valid
        // It must not be null and must be prefixed with "Bearer" plus a whitespace
        // The authentication scheme comparison must be case-insensitive
        return authorizationHeader != null
                && authorizationHeader.toLowerCase().startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
    }
    private void abortWithUnauthorized(ContainerRequestContext requestContext) {
        // Abort the filter chain with a 401 status code response
        // The WWW-Authenticate header is sent along with the response
        Response respone = Response.status(Response.Status.UNAUTHORIZED) // 401 Unauthorized
                .header(HttpHeaders.WWW_AUTHENTICATE, AUTHENTICATION_SCHEME + " realm=\"" + REALM + "\"")
                .entity("You cannot access this resource") // the response entity
                .build();
        requestContext.abortWith(respone);
    }
}

Chi tiết về Filter các bạn xem lại bài viết “Filter và Interceptor với Jersey 2.x“.

Đăng ký RolesAllowedDynamicFeature

Để có thể áp dụng các Annotation @RolesAllowed, @PermitAll hoặc @DenyAll, chúng ta cần đăng ký sử dụng tính năng này với Jersey.

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
package com.gpcoder.config;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.logging.LoggingFeature;
//Deployment of a JAX-RS application using @ApplicationPath with Servlet 3.0
//Descriptor-less deployment
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
public class JerseyServletContainerConfig extends ResourceConfig {
    public JerseyServletContainerConfig() {
        // if there are more than two packages then separate them with semicolon
        packages("com.gpcoder");
        register(new LoggingFeature(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), Level.INFO,
                LoggingFeature.Verbosity.PAYLOAD_ANY, 10000));
        register(JacksonFeature.class);
        
        // This authorization feature is not automatically turned on.
        // We need to turn it on by ourself.
        register(RolesAllowedDynamicFeature.class);
    }
}

Tạo custom exception để xử lý kết quả trả về Client khi có ngoại lệ xảy ra

GenericExceptionMapper.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
package com.gpcoder.exception.mapper;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
    @Override
    public Response toResponse(Throwable ex) {
        return Response.status(getStatusType(ex))
                .entity(ex.getMessage())
                .type(MediaType.TEXT_PLAIN) // "text/plain"
                .build();
    }
    
    private Response.StatusType getStatusType(Throwable ex) {
        if (ex instanceof WebApplicationException) {
            return((WebApplicationException)ex).getResponse().getStatusInfo();
        } else {
            // 500, "Internal Server Error"
            return Response.Status.INTERNAL_SERVER_ERROR;
        }
    }
}

Chi tiết về xử lý ngoại lệ với Jersey, các bạn xem lại bài viết “HTTP Status Code và xử lý ngoại lệ RESTful web service với Jersey 2.x“.

Test ứng dụng với Postman

Test @GET http://localhost:8080/RestfulWebServiceExample/rest/orders/1

sử dụng token này để truy cập resource.vvvvvv

Như bạn thấy, chúng ta vẫn truy xuất được resource method với @PermitAll.

Test @DELETE http://localhost:8080/RestfulWebServiceExample/rest/orders/1

sử dụng token này để truy cập resource.

Chúng ta nhận được thông báo lỗi 403, do chưa được chứng thực với user có role Admin.

Bây giờ, chúng ta đăng nhập với user Admin để lấy token:

@POST http://localhost:8080/RestfulWebServiceExample/rest/auth

sử dụng token này để truy cập resource.

Tiếp theo, chúng ta sẽ sử dụng token này để truy cập resource.

sử dụng token này để truy cập resource.

Chúng ta đã truy xuất thành công resource với user có role Admin.

Tương tự các bạn test với @POST, @PUT lần lượt với các username là customer, admin, test và password là gpcoder để xem kết quả.

JWT – Token-based Authentication trong Jersey Client

Ví dụ: tạo ứng dụng REST Client truy cập @DELETE resource.

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
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.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.logging.LoggingFeature;
public class OrderServiceClient {
    public static final String AUTH_URL = "http://localhost:8080/RestfulWebServiceExample/rest/auth";
    public static final String API_URL = "http://localhost:8080/RestfulWebServiceExample/rest/orders";
    public static void main(String[] args) {
        // (1) Authenticate and get token
        final String token = getToken();
        // (2) Call @DELETE API
        Client client = createJerseyRestClient();
        WebTarget target = client.target(API_URL).path("1");
        Invocation.Builder invocationBuilder = target.request(MediaType.APPLICATION_JSON_TYPE);
        invocationBuilder.header("Authorization", "Bearer " + token);
        final Response response = invocationBuilder.delete();
        // (3) Handle result
        System.out.println("Call delete() successful with the result: " + response.readEntity(String.class));
    }
    private static String getToken() {
        Form formData = new Form();
        formData.param("username", "admin");
        formData.param("password", "gpcoder");
        Client client = createJerseyRestClient();
        WebTarget target = client.target(AUTH_URL);
        Response response = target.request(MediaType.APPLICATION_JSON_TYPE).post(Entity.form(formData));
        return response.readEntity(String.class);
    }
    private static Client createJerseyRestClient() {
        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));
        return ClientBuilder.newClient(clientConfig);
    }
}

Lưu ý: ngoài cách thêm thông tin header thủ công trong mỗi request, chúng ta có thể tạo một ClientRequestFilter để tự động thêm thông tin header như đã giới thiệu trong bài viết “Filter và Interceptor với Jersey 2.x“.

Các bước tiến hành Kiểm Thử Tự Động

kiểm thử tự động
Các bước tiến hành Kiểm Thử Tự Động

Bài viết được sự cho phép của vntesters.com

Với sự phát triển không ngừng của ngành công nghệ phần mềm và ứng dụng chu trình phát triển Agile, kiểm thử tự động (Automated Testing) đã trở thành một đề tài nóng bỏng trong giới kiểm thử và quản trị chất lượng phần mềm. Nhưng, làm thế nào để có thể tự động hoá việc kiểm thử cho một sản phẩm phần mềm thì không phải ai cũng có thể áp dụng hiệu quả. Đây chỉ là các bước mà mình, qua quá trình học tập và làm việc với kiểm thử tự động đúc kết được. Hy vọng sẽ giúp ích được phần nào cho các bạn trên con đường kiểm thử tự động này…

Cac buoc

Bước 1: Phân tích khả năng áp dụng kiểm thử tự động.

Hiển nhiên, chúng ta không thể tự động hoá mọi việc trong kiểm thử phần mềm được. Có những phần mềm mới hay công nghệ viết ra phần mềm mà những công cụ kiểm thử tự động hiện tại chưa hỗ trợ hoặc chỉ hỗ trợ một phần. Ví dụ rõ ràng nhất là khi chúng ta kiểm thử một trang Web trên một trình duyệt mới, và lúc đó, công cụ kiểm thử tự động chưa có phiên bản mới hỗ trợ trên trình duyệt đó. Hay, một ví dụ khác về chương trình SAP, các nhà phát triển SAP đã đưa ra một lựa chọn ngăn chặn việc chạy script tự động trên nó, và để có thể kiểm thử tự động trên SAP, chúng ta cần yêu cầu các nhà phát triển chương trình SAP gỡ bỏ lựa chọn này.

  10 lý do kiểm thử phần mềm trở thành một nghề thời thượng
  5 điều cần lưu ý trước khi bắt đầu kiểm thử di động

Xem thêm việc làm tester hấp dẫn trên TopDev

Bước 2: Lựa chọn công cụ kiểm thử tự động thích hợp.

Sau khi xác định được sản phẩm hiện tại có thể làm Kiểm Thử Tự Động hay không, bước kế tiếp, chúng ta cần xác định nên sử dụng công cụ kiểm thử tự động nào. Công cụ nào hỗ trợ kiểm thử tự động cho công nghệ mà sản phẩm sử dụng? Ưu nhược điểm của từng công cụ? Ngôn ngữ kịch bản nào mà công cụ kiểm thử sử dụng? Nhân sự hiện tại có quen thuộc với công cụ đó hay không?

Bước 3: Xây dựng môi trường làm việc.

Môi trường làm việc bao gồm các khái niệm, chu trình, thủ tục và môi trường mà kịch bản kiểm thử tự động được thiết kế và viết ra. Bên cạnh đó, nó cũng nên bao gồm luôn cấu trúc thư mục, lưu trữ các kịch bản kiểm thử cũng như các mối quan hệ logic giữa các thành phần.

Bước 4: Viết kịch bản kiểm thử, thực thi và phân tích kết quả.

Dựa trên các kịch bản kiểm thử đã được tạo ra bằng kiểm thử thủ công, dựa vào ngôn ngữ kịch bản mà công cụ kiểm thử tự động hỗ trợ, chúng ta viết các đoạn mã tương tác với sản phẩm phần mềm trên các môi trường và thực thi nó. Sau khi thực thi các đoạn mã, chúng ta cần phân tích các kết quả đạt được và ghi lại các vấn đề của sản phẩm, nếu có.

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

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

Đừng đánh nhau với borrow checker

borrow checker

Bài viết được sự cho phép của tác giả Huy Trần

TL;DR: Đừng bao giờ đánh nhau với borrow checker, nó được sinh ra để bạn phải phục tùng nó 

Một trong những cơn ác mộng của lập trình viên khi làm việc với Rust đó là chuyện đập nhau với borrow checker, ví dụ như những lúc bị compiler chửi vào mặt như thế này.

fn main() {
    let a = vec![5];
    let b = a;
    println!("{:?}", a);
}
error[E0382]: use of moved value: `a`
 --> borrowchecker.rs:4:22
  |
3 |     let b = a;
  |         - value moved here
4 |     println!("{:?}", a);
  |                      ^ value used here after move
  |
  = note: move occurs because `a` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait

Chỉ với một câu lệnh gán thông thường, chúng ta đã bị ăn lỗi.

Và trong hầu hết mọi trường hợp thì kẻ có lỗi chính là các lập trình viên  Borrow checker cũng chưa bao giờ đập ai (nó chỉ đứng đó và chửi vô mặt người ta thôi). Lỗi ở đây chính là vì chúng ta không hiểu về mô hình quản lý bộ nhớ của Rust, và các quy tắc mà ngôn ngữ lập trình này đặt ra.

  CSS Box Model và box-sizing: border-box là gì vậy?
  Mediator Design Pattern – Collaborate via me

Xem thêm việc làm IoT lương cao trên TopDev

Vậy Rust kiểm soát bộ nhớ như thế nào? Và làm sao để chúng ta có thể làm vừa lòng Rust compiler trong những trường hợp như thế này?

Bài viết này sẽ giúp các bạn hiểu được chuyện đó thông qua hai vấn đề chính: Ownership (quyền sở hữu của một biến) và Borrowing (vay mượn/tham chiếu giữa các biến).

Ownership

Trong máy tính, bộ nhớ (memory) là từ dùng để chỉ các ô nằm liền kề nhau để lưu trữ thông tin.

Khi chúng ta khai báo một biến mới trong Rust, một vùng nhớ sẽ được cấp phát trong bộ nhớ, và nó sẽ mọc thêm mắt mũi chân càng như thế này:

Just kididng  một biến a khi được khai báo, máy tính sẽ cấp phát một vùng nhớ trên stack, với giá trị mặc định là giá trị được truyền vào khi khai báo. Địa chỉ của vùng nhớ này sẽ được gán cho biến a.

Khi đó ta có thể coi là: vùng nhớ này thuộc về biến a, và a có quyền sở hữu (ownership) đối với vùng nhớ đó.

Một vùng nhớ tại một thời điểm chỉ có thể thuộc về duy nhất một biến. Và điều gì xảy ra nếu ta gán một biến vào một biến khác?

Quyền sở hữu vùng nhớ của biến này sẽ được chuyển sang biến khác, ở ví dụ trên ta khai báo biến a là một vector, sau đó gán biến a cho b, lúc này vùng nhớ mà biến a sở hữu đã được chuyển (move) sang cho b.

Nếu ngay sau đó chúng ta tìm cách đọc biến a thì sẽ gặp lỗi, vì không còn mang giá trị nào để có thể đọc được nữa.

error[E0382]: use of moved value: `a`
  |
3 |     let b = a;
  |         - value moved here
4 |     println!("{:?}", a);
  |                      ^ value used here after move
  |

Để tránh việc giá trị của một biến bị moved sau khi thực hiện phép gán, ta có thể implement trait Copy cho kiểu dữ liệu của biến đó.

#[derive(Copy, Clone)]
struct Point {
  x: i32,
  y: i32
}

Chức năng của Copy trait đúng với tên gọi của nó, khi phép gán xảy ra, thì thay vì move giá trị của biến này sang biến khác, Rust runtime sẽ copy giá trị đó. Và sau phép gán, cả 2 biến vẫn có thể được sử dụng một cách bình thường.

Một số kiểu dữ liệu như i32 thường được implement sẵn trait này. Để biết kiểu dữ liệu mình tính xài được implement sẵn những trait nào thì bạn có thể xem trong phần Trait Implementations của kiểu dữ liệu đó.

Borrowing

Sao Rust compiler khó tánh dữ vậy? Giá trị của một biến thì thuộc quyền sở hữu của biến đó à? Vậy làm sao để truyền dữ liệu qua lại giữa các biến? Lỡ kiểu dữ liệu tui xài hổng có implement trait Copy thì làm sao?

Đến đây thì chúng ta bắt gặp một vấn đề rất là đời thường, xin nhắc lại, mặc dù Rust là một ngôn ngữ lập trình nhưng nó luôn phản ánh đúng với thực trạng của xã hội, đây là vấn đề thường ngày trong chuyện giao tiếp giữa người với người, vâng, rất nhân văn: Muốn xài đồ của người khác thì tất nhiên phải đi mượn (borrow).

Để mượn một giá trị trong Rust rất dễ, chúng ta chỉ cần đặt dấu & vào trước biến cần mượn. Ví dụ:

let b = &a;

Và tất nhiên khi đem cho mượn, giá trị của một biến vẫn thuộc quyền sở hữu của biến đó, Rust chỉ tạo một tham chiếu (references) đến vùng nhớ chứa giá trị này, chứ không move nó đi chỗ khác. Chính vì vậy, một biến có thể cho mượn bao nhiêu lần tùy ý. Nhưng với một điều kiện, đó là các tham chiếu đến giá trị của biến đó là readonly. Tức là không ai có thể thay đổi được giá trị của biến, trừ chính biến đó.

Cũng giống như khi bạn cho ai mượn ví tiền của bạn chỉ để xem cái ví như thế nào thôi, thì người ta không có quyền lấy tiền từ ví của bạn. Nhưng với vợ bạn thì sẽ khác… 

Vợ bạn có thể dùng &mut để vừa mượn vừa thay đổi được số tiền trong ví của bạn.

Lưu ý: Có thể bạn biết rồi, trong Rust, mọi giá trị được khai báo đều là immutable, nghĩa là không thể thay đổi được. Vì thế một biến được khai báo theo cách thông thường thì cũng immutable nốt. Nếu ta mượn một biến immutable để ghi (mutate nó) thì sẽ bị ăn lỗi:

error[E0596]: cannot borrow immutable local variable `a` as mutable
 --> borrowchecker.rs:3:18
  |
2 |     let a = 10;
  |         - consider changing this to `mut a`
3 |     let b = &mut a;
  |                  ^ cannot borrow mutably

Để không phải đau đầu với chuyện mượn/trả, moving của các biến, chúng ta có các quy tắc cần nhớ sau:

  • Một biến sau khi được gán thì không dùng được nữa (bị moved)
  • Muốn dùng một biến sau khi gán thì có thể borrow nó, hoặc implement Copy trait cho kiểu dữ liệu của nó
  • Một biến có thể được mượn để đọc (immutable borrow) không giới hạn số lần
  • Chỉ có duy nhất một lần mượn để ghi (mutable borrow) tại một thời điểm

Tạm thời viết chừng này đã, bài này nằm trong thư mục draft lâu quá rồi . Ở phần tiếp theo chúng ta sẽ tiếp tục tìm hiểu sang khái niệm còn lại, đó là Lifetime.

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

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

Xem thêm công việc IT hấp dẫn trên TopDev

Tạo ứng dụng Java RESTful Client không sử dụng 3rd party libraries

java restful client
Tạo ứng dụng Java RESTful Client không sử dụng 3rd party libraries

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

Trong bài này tôi sẽ giới thiệu với các bạn cách gọi Restful web service sử dụng thư viện chuẩn java.net của Java, không sử dụng bất kỳ 3rd party libraries nào khác.

Các bước thực hiện

Để gọi restful web service thông qua lớp java.net chúng ta lần lượt thực hiện các bước sau:

  • Tạo 1 java.net.URL object.
  • Mở HttpURLConnection từ URL trên.
  • Set các Request property cần thiết.
  • Gửi Request data lên server (nếu có).
  • Nhận Response từ server gửi về (nếu có).
  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 chương trình tuyển dụng Java lương cao trên TopDev

Ví dụ tạo ứng dụng Java RESTful Client sử dụng java.net

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.

package com.gpcoder;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpClientExample {

public static final String BASE_URL = "http://localhost:8080/RestfulWebServiceExample/rest";
private static String token;

public static void main(String[] args) throws IOException {
token = getToken();
System.out.println("token: " + token);

createOrder();
retrieveOrder();
updateOrder();
deleteOrder();
}

/**
* @POST http://localhost:8080/RestfulWebServiceExample/rest/auth
*/
private static String getToken() throws IOException {
// Create A URL Object
URL url = new URL(BASE_URL + "/auth");

// Open a Connection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Set the Request Content-Type Header Parameter
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

// Set Response Format Type
connection.setRequestProperty("Accept", "application/json");

// Set the Request Method
connection.setRequestMethod("POST");

// Create the Request Body and Send post request
String urlParameters = "username=gpcoder&password=gpcoder";
sendRequest(connection, urlParameters);

// Read the Response from Input Stream
return getResponse(connection);
}

private static void sendRequest(HttpURLConnection connection, String data) throws IOException {
// Ensure the Connection Will Be Used to Send Content
connection.setDoOutput(true);

// Create the Request Body and Send post request
try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
wr.writeBytes(data);
wr.flush();
}
}

private static String getResponse(HttpURLConnection connection) throws IOException {
int responseCode = connection.getResponseCode();
System.out.println("Response Code : " + responseCode);

StringBuilder response = new StringBuilder();
try (InputStream is = connection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));) {
String line;
while ((line = rd.readLine()) != null) {
response.append(line);
}
}
return response.toString();
}

/**
* @POST http://localhost:8080/RestfulWebServiceExample/rest/orders
*/
private static void createOrder() throws IOException {
// Create A URL Object
URL url = new URL(BASE_URL + "/orders");

// Open a Connection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Set Authorization header
connection.setRequestProperty("Authorization", "Bearer " + token);

// Set the Request Content-Type Header Parameter
connection.setRequestProperty("Content-Type", "application/json");

// Set the Request Method
connection.setRequestMethod("POST");

// Create the Request Body and Send post request
String data = "{\"id\" : 1, \"name\": \"gpcoder\"}";
sendRequest(connection, data);

// Read the Response from Input Stream
String response = getResponse(connection);
System.out.println("createOrder: " + response);
}

/**
* @GET http://localhost:8080/RestfulWebServiceExample/rest/orders/1
*/
private static void retrieveOrder() throws IOException {
// Create A URL Object
URL url = new URL(BASE_URL + "/orders/1");

// Open a Connection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Set Authorization header
connection.setRequestProperty("Authorization", "Bearer " + token);

// Set the Request Method
connection.setRequestMethod("GET");

// Read the Response from Input Stream
String response = getResponse(connection);
System.out.println("retrieveOrder: " + response);
}

/**
* @PUT http://localhost:8080/RestfulWebServiceExample/rest/orders
*/
private static void updateOrder() throws IOException {
// Create A URL Object
URL url = new URL(BASE_URL + "/orders");

// Open a Connection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Set Authentication header
connection.setRequestProperty("Authorization", "Bearer " + token);

// Set the Request Content-Type Header Parameter
connection.setRequestProperty("Content-Type", "application/json");

// Set the Request Method
connection.setRequestMethod("PUT");

// Create the Request Body and Send post request
String data = "{\"id\" : 1, \"name\": \"gpcoder\"}";
sendRequest(connection, data);

// Read the Response from Input Stream
String response = getResponse(connection);
System.out.println("updateOrder: " + response.toString());
}

/**
* @DELETE http://localhost:8080/RestfulWebServiceExample/rest/orders/1
*/
private static void deleteOrder() throws IOException {
// Create A URL Object
URL url = new URL(BASE_URL + "/orders/1");

// Open a Connection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Set Authorization header
connection.setRequestProperty("Authorization", "Bearer " + token);

// Set the Request Method
connection.setRequestMethod("DELETE");

// Read the Response from Input Stream
String response = getResponse(connection);
System.out.println("deleteOrder: " + response);
}
}

Chạy chương trình trên, chúng ta có kết quả như sau:

Response Code : 200
token: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJncGNvZGVyIiwicm9sZXMiOlsiQWRtaW4iLCJDdXN0b21lciJdLCJqdGkiOiIwNWQwZTE5ZS1lODYzLTQ5MGQtOGE0Yi1mZWE4NWQ0OTZhYzIiLCJpYXQiOjE1NjE4MTgxMTgsImlzcyI6Imh0dHBzOi8vZ3Bjb2Rlci5jb20iLCJleHAiOjE1NjE4MTk5MTh9.fhhogziLVLMGz_nZjpFy9N0-MG2t4fOYKl6LSU7tLxo
Response Code : 200
createOrder: OrderService->insert()
Response Code : 200
retrieveOrder: OrderService->get()
Response Code : 200
updateOrder: OrderService->update()
Response Code : 200
deleteOrder: OrderService->delete()

Lưu ý: toàn bộ các API trả về kết quả là plain/text nên tôi không cần xử lý gì thêm. Nếu API các bạn nhận được là một chuỗi json, các bạn có thể sử dụng các thư viện như Gson hay Jackson để convert json về java object.

Trên đây là ví dụ đơn giản để gọi các Restful API sử dụng thư viện chuẩn java.net. Trong các dự án người ta thường ít sử dụng cách này do viết khá dài dòng và phức tạp. Trong các bài viết tiếp theo, tôi sẽ giới thiệu với các bạn các thư viện rất đơn giản và hiệu quả để tạo ứng dụng Java RESTful Client như Jersey client, OkHttp, Retrofit, Feign.

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

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

Xem thêm công việc IT hấp dẫn trên TopDev

Làm việc với Apache Kafka Topic sử dụng CLI

apache kafka topic
Làm việc với Apache Kafka Topic sử dụng CLI

Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh

Sau khi cài đặt Apache Kafka, các bạn có thể sử dụng công cụ Apache Kafka CLI để làm việc với topic trong Apache Kafka server.

  Apache Kafka là gì?
  Cài đặt Apache Kafka sử dụng Docker Compose

Xem thêm việc làm AngularJS hấp dẫn trên TopDev

Tạo mới topic

Chúng ta sẽ sử dụng tập tin kafka-topics.sh của Apache Kafka CLI để làm việc với topic trong Apache Kafka server.

Để tạo mới một topic, các bạn có thể sử dụng câu lệnh với cú pháp như sau:

kafka-topics.sh --create --topic <topic_name> --bootstrap-server <kafka_server> --partitions <partition_number> --replication-factor <replication_number>

Trong đó:

  • topic_name là tên topic mà chúng ta cần tạo
  • kafka_server là địa chỉ Apache Kafka server với định dạng host:port
  • partition_number là số lượng partitions mà chúng ta cần tạo
  • replication_number là số lượng replicate mà chúng ta muốn cho mỗi partition. Giá trị của tham số này sẽ phụ thuộc vào số lượng Apache Kafka server mà chúng ta đang có các bạn nhé!

Ví dụ, mình chạy câu lệnh sau:

kafka-topics.sh --create --topic huongdanjava --bootstrap-server localhost:9092 --partitions 2 --replication-factor 1

Kết quả sẽ như sau:

Để kiểm tra topic mà chúng ta vừa tạo ở trên trong Apache Kafka server, các bạn có thể chạy câu lệnh sau:

kafka-topics.sh --describe --topic <topic_name> --bootstrap-server <kafka_server>

Ý nghĩa của các tham số như mình đã đề cập ở trên.
Ví dụ của mình như sau:

kafka-topics.sh --describe --topic huongdanjava --bootstrap-server localhost:9092

Kết quả:

Như các bạn thấy, topic huongdanjava của mình có 2 partitions và số lượng ReplicationFactor là 1 như câu lệnh mình đã chạy ở trên.

Xem tất cả các topic

Để kiểm tra tất cả các topic có trong một Apache Kafka server, các bạn có thể chạy câu lệnh sau:

kafka-topics.sh --list --bootstrap-server localhost:9092

Các bạn sẽ thấy topic mà chúng ta vừa tạo ở trên như sau:

Xoá một topic

Để xoá một topic trong Apache Kafka server, chúng ta sẽ sử dụng câu lệnh sau:

kafka-topics.sh --list --bootstrap-server localhost:9092

Ví dụ của mình như sau:

kafka-topics.sh --delete --topic huongdanjava --bootstrap-server localhost:9092

Kết quả:

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

FPT Techday 2021 – nơi quy tụ các xu hướng công nghệ mới

Ngày 3/12, Diễn đàn Công nghệ FPT Techday 2021 diễn ra theo hình thức trực tuyến với chủ đề “Tái thiết toàn diện, bứt phá trong bình thường xanh”.

Diễn đàn đã thu hút hơn 100.000 người tham dự trực tiếp trên nhiều nền tảng khác nhau. Sự kiện một lần nữa giúp FPT khẳng định vai trò tiên phong chuyển đổi số hướng đến mục tiêu giúp các tổ chức, doanh nghiệp tái thiết toàn diện, bứt phá trong bình thường xanh, đồng thời tạo sân chơi trí tuệ cho các tài năng công nghệ trẻ.

Chia sẻ tại Diễn đàn Công nghệ FPT Techday 2021, ông Trương Gia Bình, Chủ tịch HĐQT FPT đã đặt lại bài toán chung về sứ mệnh của công nghệ trong bình thường xanh.

“COVID-19 ập đến như một tai họa. Doanh nghiệp đóng cửa, người lao động mất việc, cuộc sống đảo lộn. Chính trong thời điểm Covid-19 này chúng ta nhận ra rằng công nghệ đang mang một sứ mạng mới, công nghệ vị nhân sinh. Công nghệ vì cuộc sống của con người. Bằng công nghệ, chúng ta có thể chiến thắng COVID-19. Bằng công nghệ, các doanh nghiệp tái thiết mọi hoạt động quản trị, vận hành, kinh doanh để tăng tốc, bứt phá. Bằng công nghệ, người dân có thể thích ứng an toàn với dịch bệnh. Bằng công nghệ Việt Nam sẽ trở thành một điểm sáng phát triển kinh tế của thế giới, sẽ là một địa điểm hấp dẫn đầu tư nước ngoài và trở thành một quốc gia cường thịnh vào năm 2045” – ông Trương Gia Bình nhấn mạnh.

FPT Techday 2021 - nơi quy tụ các xu hướng công nghệ mới - Ảnh 1.

Tại sự kiện FPT Techday 2021, FPT đã ra mắt bộ giải pháp công nghệ gồm 6 sản phẩm giúp doanh nghiệp đảm bảo nguồn lực, vận hành linh hoạt, tăng tốc kinh doanh.

FPT eCovax nhân lực giúp doanh nghiệp đảm bảo nguồn lực dựa trên công nghệ AI và Big Data, thông qua việc số hóa toàn bộ quy trình tuyển dụng, hỗ trợ phỏng vấn sàng lọc, định hướng công việc phù hợp bằng Bot AI, lưu trữ – phân tích – dự báo nhu cầu của ứng viên.

FPT.AI Advisory Virtual Assistant – trợ lý ảo ứng dụng AI thế hệ thứ hai và giải pháp ký điện tử FPT.eContract và FPT.CA giúp doanh nghiệp tăng tốc kinh doanh ngay trong đại dịch. Còn với FPT AI Reader Flex và Ubot, mọi tài liệu văn bản với cấu trúc thông tin đa dạng, mọi nghiệp vụ trong doanh nghiệp đều được số hóa một cách dễ dàng và tự động hóa một cách trơn tru, giúp doanh nghiệp vận hành linh hoạt.

Cũng tại sự kiện, lần đầu tiên FPT trình diễn mô hình triển lãm thành phố xanh thông minh (Green Smart City) gồm 6 cấu phần chính: chính quyền xanh, doanh nghiệp xanh, giáo dục xanh, y tế xanh, cuộc sống xanh, lưu chuyển xanh. Triển lãm mang đến trải nghiệm đầy đủ về một thế giới bình thường xanh được tái thiết, vận hành, kết nối linh hoạt dựa trên nền tảng cốt lõi là công nghệ với gần 50 giải pháp, sản phẩm công nghệ của FPT, giúp vừa đảm bảo an toàn trong mọi tình huống vừa linh hoạt phát triển kinh tế.

FPT Techday 2021 - nơi quy tụ các xu hướng công nghệ mới - Ảnh 2.

Trong đó, chính quyền xanh – trái tim của thành phố xanh thông minh, được vận hành dựa trên các giải pháp công nghệ tiên tiến nhất giúp các nhà chức trách có được dữ liệu cập nhật theo thời gian thực về mọi hoạt động, từ đó ra quyết định kịp thời, chặt chẽ và ngay lập tức thấy được kết quả của sự thay đổi. Đặc biệt, trong bối cảnh COVID-19 diễn biến phức tạp trung tâm chỉ đạo xanh sẽ là nơi trao đổi và kết nối liên thông với các bộ chỉ huy để cùng bàn thảo và đưa ra lời giải cho các bài toán lớn mà không cần phải gặp trực tiếp.

Doanh nghiệp xanh, với sự hỗ trợ của công nghệ, có thể chủ động kiểm soát được tỷ lệ nhân sự xanh; quản trị, vận hành, làm việc từ xa. Hay khu vực y tế xanh mô phỏng một nền y tế không giấy tờ, với sự kết hợp nhịp nhàng của y bác sĩ với các hệ thống bệnh án điện tử, sổ khám bệnh điện tử… giúp công việc được thông suốt, giảm thời gian chờ đợi. Giáo dục xanh mang tới trải nghiệm học tập, giảng dạy giữa thầy và trò được kết nối thông suốt dựa trên công nghệ trí tuệ nhân tạo, phương pháp đào tạo kiến tạo xã hội giúp cho người học tích cực, chủ động và thích thú tham gia, gia tăng hiệu quả học tập.

Ông Nguyễn Văn Khoa, Tổng Giám đốc FPT chia sẻ: “COVID-19 đã khiến các doanh nghiệp nhìn nhận lại vai trò của công nghệ. Rất nhiều các doanh nghiệp đã chi hàng triệu USD để hoàn thiện khả năng quản trị và vận hành như Kim Tín, Boston Pharma, Tân Hoàng Minh, AceCook… Với hệ thống quản trị này, họ xác định rằng không để vuột mất cơ hội và bước vào kỷ nguyên số một cách vững chắc. Với năng lực về công nghệ, sự thấu hiểu và kinh nghiệm triển khai thành công rất nhiều dự án cho các chính phủ, doanh nghiệp trên thế giới và tại Việt Nam, chúng tôi tự tin và cam kết sẽ là một đối tác cùng đồng hành với các doanh nghiệp để tái thiết an toàn – linh hoạt – thần tốc trong bình thường xanh”.

Cùng với việc ra mắt các giải pháp công nghệ mới, ngay tại sự kiện, Giám đốc Công nghệ của FPT, ông Vũ Anh Tú cam kết: “Tập đoàn sẽ tiếp tục nghiên cứu và ứng dụng những công nghệ mới để giúp doanh nghiệp giải những bài toán một cách nhanh hơn, chính xác hơn và ưu việt hơn. Đồng thời tập trung vào việc kiến trúc lại các sản phẩm sẵn có để tạo ra một nền tảng mở để cộng đồng cùng tham gia phát triển​, có tính tin cậy và linh hoạt, có khả năng thấu hiểu và tự học hỏi để thông minh hơn, có tính bảo mật cao”.

Trong thời gian tới, FPT sẽ tập trung và phát triển 4 nền tảng công nghệ lõi: Hyper Automation; AI; Cloud và Blockchain.​

Với công nghệ Hyper Automation, các giải pháp của FPT sẽ giúp doanh nghiệp tự động hóa thông minh cho nhiều quy trình có giá trị lớn nhưng dữ liệu phi cấu trúc, phân mảnh, chẳn hạn như: phân tích tình trạng nợ công ty vay vốn; đối soát hóa đơn; tổng đài ảo gọi điện nhắc nhở tự động về khoản vay, tình trạng hồ sơ…​

Với công nghệ trí tuệ nhân tạo (AI), FPT sẽ tạo ra những trợ lý ảo thế hệ mới có khả năng suy luận gần như con người trên cơ sở những thông tin tiếp nhận từ các giác quan và xây dựng hệ sinh thái giải pháp ứng dụng AI phục vụ nhiều ngành công nghiệp khác nhau.

Trong lĩnh vực Cloud, FPT sẽ đẩy mạnh mô hình cung cấp dịch vụ PaaS (cung cấp nền tảng như một dịch vụ) và đặc biệt là SaaS (cung cấp phần mềm như một dịch vụ). Qua đó, công nghệ và sản phẩm công nghệ Made by FPT không chỉ phục vụ cho việc phát triển nội tại của FPT mà còn phụng sự cho bền vững, đột phá của khách hàng.​

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

Screencast cho các thiết bị iOS

screencast trên ios
Screencast cho các thiết bị iOS

Bài viết được sự cho phép của vntesters.com

Làm sao để cho Dev thấy màn hình của phone mình đang gặp 1 con bug? Cách đơn giản nhất là mang cái phone tới bàn làm việc của Dev. Nhưng nếu Dev không làm việc chung tòa nhà với Tester hoặc ở một quốc gia hay châu lục khác thì sao? Có một cách khá đơn giản đó là hiển thị màn hình của thiết bị trên PC và share màn hình PC với họ.

Đối với Android, việc này khá đơn giản vì có hằng tá các công cụ hỗ trợ, đặc biệt là AndroidScreenCast (hàng chính hãng & miễn phí luôn nhá.)

  5 bài học quí giá về việc phát triển ứng dụng iOS
  Build một ứng dụng Chat cho Android & iOS bằng Contus Fly như thế nào?

Xem thêm nhiều việc làm iOS lương cao trên TopDev

Với iOS thì hơi khó khăn hơn một chút nhưng không phải là không làm được. Dưới đây là vài bước cấu hình để bạn đạt được mục đích

  1. Đầu tiên các bạn cần cài iTunes
  2. Tiếp theo các bạn cần download và cài đặt iTool
  3. Coi như các bước chuẩn bị đã xong, các bạn gắn thiết bị iOS như iPhone, iPad vào PC thông qua cable
  4. Mở iTools & chọn Desktop, tiếp theo chọn “Live Desktop” và thế là bạn có thể thấy màn hình iOS trên PC.

iTools Desktop

iTools Live Desktop

Bạn biết những cách khác hoặc tool khác, hãy chia sẻ tiếng nói của mình :). Mình rất mong nhận được những đóng góp, gợi ý cũng như những lời đề nghị để nội dung của các bài viết trên VNTesters ngày càng phong phú và hữu ích cho mọi người, đặc biệt là các bạn Tester (kỹ sư kiểm thử phần mềm) chuyên nghiệp.

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

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

Paper Review: Why Functional Programming Matters

paper review

Bài viết được sự cho phép của tác giả Huy Trần

Như đã có lần mình đề cập, việc đọc paper cũng khá là quan trọng, vì bên cạnh việc được đọc từ những nguồn kiến thức “sạch”, và chất lượng, chúng ta còn sẽ xây dựng được cho mình một kĩ năng khác, là kĩ năng đọc sâu và kĩ năng nghiên cứu, khá là quan trọng trong thời đại thông tin tràn lan trên Internet, khi mà chúng ta tiếp xúc với một lượng lớn thông tin nhưng không có thời gian để lãnh hội hết, dẫn tới một thói quen hết sức tai hại đó là đọc một cách hời hợt (shallow reading).

Nằm trong hoạt động nghiên cứu của nhóm rbvn/algorithm, tuần này mình đọc một paper khá thú vị đó là Why Functional Programming Matters của John Hughes. Dưới đây là một vài ghi chép của mình trong quá trình đọc, share lên để giúp cho các bạn có cái nhìn toàn cảnh về nội dung của paper, giúp cho việc đọc trở nên dễ dàng hơn, và hoàn toàn không có mục đích tổng hợp, tóm tắt.

  5 điều NÊN và KHÔNG NÊN khi review tăng lương mà lập trình viên nào cũng nên biết!
  Những gì tôi học được từ 1000 code reviews

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

■  Trong paper này cũng có vài đoạn đọc vào khá là thú vị, mình sẽ không ghi ra đây mà để cho các bạn tự đọc và tự thấy. Ông tác giả cũng khá là hài hước mà không kém phần nghiêm túc.

Các bạn có thể tìm đọc paper này tại: https://academic.oup.com/comjnl/article/32/2/98/543535

Đây là một trong số những paper mà bất cứ ai quan tâm đến computer science đều phải đọc ít nhất một lần trong đời, và khá là dễ đọc, trừ vụ code minh họa được viết bằng Miranda (a.k.a daed lang ), trông khá giống với Haskell.

Quan điểm mà tác giả John Hughes đưa ra trong bài là: Một dự án phần mềm cần phải được tổ chức một cách tốt nhất có thể, để dễ viết hơn, dễ debug hơn, reuse code tốt hơn. Và yếu tố quan trọng nhất để có một cấu trúc project tốt là Modularity (chia project thành nhiều module nhỏ).

Tác giả viết:

Modular design brings with it great productivity improvements. First of all, small modules can be coded quickly and easily. Secondly, general purpose modules can be re-used, leading to faster development of subsequent programs. Thirdly, the modules of a program can be tested independently, helping to reduce the time spent debugging.

Vậy modularize là làm gì? Là chia nhỏ nó ra thành nhiều bài toán con, giải từng bài con đó rồi tổng hợp chúng lại:

When writing a modular program to solve a problem, one first divides the problem into sub- problems, then solves the sub-problems and combines the solutions.

Và các functional programming languages cung cấp cho chúng ta 2 features quan trọng để phục vụ cho việc modularize, đó là:

  • Higher-order Function
  • Lazy Evaluation

Đối với higher-order function, tác giả đưa ra một loạt ví dụ minh họa, như là xây dựng một hàm reduce, nhận vào một hàm bất kỳ (thể hiện đặc tính của higher-order function) để thực hiện tính toán trên một list, từ đó thực hiện việc tính toán các phép toán bất kỳ chỉ bằng việc kết hợp (compose) các chương trình nhỏ hơn (module) với hàm reduce:

reduce :: (a -> a -> a) -> a -> [a] -> a
reduce f a [] = a
reduce f a (x:xs) = f x (reduce f a xs)

-- tính tổng một list
add a b = a + b
sum = reduce add 0
sum [1, 2, 3, 4] == 10

-- tính tích một list
multiply a b = a * b
product = reduce multiply 1
product [1, 2, 3, 4] == 24

Về lazy evaluation, tác giả giải thích bằng cách đưa ra một ví dụ hết sức đơn giản nhưng mà đọc lâu thiệt lâu mới hiểu: Giả sử có hai chương trình con (hoặc là hàm) f và g, chúng ta có thể compose chúng như sau:

(g . f) input
-- tương đương với
g (f input)

Có nghĩa là, ta sẽ gọi hàm f với tham số là input, đồng thời lấy kết quả trả về để làm tham số cho câu lệnh gọi hàm g.

Đặc tính laziness của Haskell và các functional programming languages nằm ở chỗ, việc thực thi f và g diễn ra một cách đồng bộ và chặt chẽ (ai dịch chữ strict synchronisation cho tui với), f chỉ được thực thi và trả về giá trị khi g thực hiện đọc tham số đầu vào của nó, và f cũng sẽ bị dừng khi g dừng lại.

The two programs f and g are run together in strict synchronisation. F is only started once g tries to read some input, and only runs for long enough to deliver the output g is trying to read. Then f is suspended and g is run until it tries to read another input. As an added bonus, if g terminates without reading all of f’s output then f is aborted.

Và trong trường hợp f là một hàm sinh ra một tập giá trị có số lượng cực kì lớn, nếu implement trên các ngôn ngữ thông thường (thực thi xong f, lấy kết quả, truyền vào g) thì có thể sẽ cần một lượng bộ nhớ cực kì lớn, mà có khi là không đủ, ví dụ:

-- trả về danh sách các số từ input đến +∞
f input = [input..]

Như thế, nhờ vào đặc tính lazy evaluation, chúng ta có thể dễ dàng phân tách chương trình thành nhiều module nhỏ mà không cần quá bận tâm đến time và space complexity. Có thể thấy rõ qua ví dụ tính căn bậc 2 dùng phương pháp Newton-Raphson.

■  Trước khi đọc tiếp, các bạn nên dừng lại và tìm đọc về thuật toán tìm căn bậc hai theo phương pháp Newton-Raphson trước nhé.
import Prelude hiding (sqrt)

next N x = (x + N/x)/2

within eps (a:b:rest) =
  if abs (a - b) <= eps then
    b
  else
    within eps (b:rest)

sqrt N = within 0.0001 (iterate (next N) 1.0)
■  Đoạn code đã được convert sang Haskell để dễ chạy, không kiếm ra cái Miranda compiler nào cả, lệnh repeat chính là iterate trong Haskell.

Ở đây, output của câu lệnh iterate (next N) 1.0 là một list dài vô hạn, nhưng trong câu lệnh within 0.0001 (iterate (next N) 1.0), nó sẽ dừng lại ngay khi chúng ta tìm được hai giá trị xấp xỉ nhau. Thế mới thấy được ưu điểm của lazy evaluation.

Tác giả kết thúc phần kĩ thuật bằng ví dụ ở mục 5: thuật toán Alpha-beta để implement trò chơi tic-tac-toe, minh họa một cách đầy đủ cả hai ứng dụng của higher-order function và lazy evaluation để định nghĩa gametree và sinh ra các nước đi cho cây trò chơi (vốn là một thao tác cực kì tốn kém).

Thông qua paper này, tác giả đã khẳng định được tầm quan trọng của functional programming, cũng như trả lời được câu hỏi mà có lẽ nhiều người cũng hay hỏi: “Why Functional Programming?”, thông qua việc làm rõ 3 vấn đề sau:

  • Modularity là yếu tố quan trọng để phát triển một phần mềm tốt.
  • Higher-order function và lazy evaluation là hai feature quan trọng của các functional programming languages giúp cho việc modularize hiệu quả hơn, giúp cho việc phát triển được productive hơn.
  • Suy ra Functional Programming đóng vai trò quan trọng trong việc bảo đảm chất lượng và productivity của các dự án phần mềm.

Hy vọng những ghi chép trên sẽ có ích cho các bạn nào đang có ý định tự mình đọc paper này. Hoặc giúp các bạn phần nào trả lời cho câu hỏi liệu mình có nên dấn thân vào tìm hiểu FP hay không.

Còn bây giờ thì mình phải tạm gác khoa học sang một bên, vợ bắt đi ngủ rồi, hẹn gặp lại các bạn trong các bài review paper sắp tới.

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

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

Xem thêm công việc IT hấp dẫn trên TopDev

TOP Các Công Ty Phần Mềm Nước Ngoài Tại Việt Nam

các công ty phần mềm nước ngoài tại việt nam
TOP Các Công Ty Phần Mềm Nước Ngoài Tại Việt Nam

Các công ty phần mềm nước ngoài tại Việt Nam đóng góp rất nhiều cho việc tạo ra những sản phẩm công nghệ hiện đại và đáp ứng nhu cầu sử dụng của người dùng. Tìm hiểu thêm top các công ty IT đang phát triển tại Việt Nam sẽ giúp bạn đọc có thêm cái nhìn tổng quan về ngành công nghệ thông tin ở nước ta hiện nay.

các công ty phần mềm nước ngoài tại việt nam

*Số thứ tự trong bài viết chỉ nhằm liệt kê các công ty, không nhằm mục đích xếp hạng hay đánh giá

1. FPT Software

Trực thuộc tập đoàn FPT, FPT Software hiện là công ty xuất khẩu dịch vụ phần mềm lớn nhất nhì ở Việt Nam và đang hiện diện tại 11 quốc gia trên thế giới và lớn nhất Đông Nam Á.

2. KMS Technology

KMS Technology Việt Nam là nhà cung cấp hàng đầu về dịch vụ kiểm thử và tư vấn, dịch vụ thiết kế app cũng như các dịch vụ outsourcing khác.

3. Vinova

Trụ sở đặt tại Singapore, Vinova chuyên cung cấp các dịch vụ phát triển ứng dụng web và thiết bị di động sáng tạo, đẳng cấp.

4. Harvey Nash

Harvey Nash được đánh giá rất cao trong dịch vụ thuê ngoài công nghệ thông tin/ dịch vụ nghiệp vụ doanh nghiệp, lĩnh vực tìm kiếm nhân sự cấp cao.

5. Global CyberSoft

Công ty lập trình app GCS được đánh giá là công ty hàng đầu về thiết kế app tại Việt Nam sau hơn 13 năm chính thức gia nhập thị trường. Chất lượng sản phẩm và hoạt động kinh doanh hiệu quả của công ty đã được ghi nhận qua thời gian.

6. Fujinet Systems JSC

Công ty hoạt động chủ yếu cho các khách hàng đến từ Nhật Bản và đã nhiều năm liên tiếp giành được các giải thưởng là công ty hàng đầu trong lĩnh vực IT outsourcing.

7. Netcompany

Với các chi nhánh đặt tại 10 quốc gia, Netcompany cung cấp các giải pháp CNTT quan trọng đối với doanh nghiệp để hỗ trợ phát triển một xã hội bền vững, các doanh nghiệp thành công và cạnh tranh tốt hơn trong thời đại chuyển đổi công nghệ số hiện nay.

8. E.V.O.L.A.B.L.E Asia (Evolable Châu Á tại Việt Nam)

Công ty hoạt động với hình thức “các phòng Lab” – một hình thức mới mẻ của ngành các công ty IT. Evolable định hướng công ty trở thành nhà cung cấp dịch vụ hàng đầu Châu Á.

9. ASUS Việt Nam

Là một trong những thương hiệu IT luôn đạt doanh thu hàng đầu tại Đài Loạn, ở Việt Nam, ASUS khá phổ biến với các dòng sản phẩm là các thiết bị thông minh, máy tính bảng, điện thoại, máy tính…

10. NashTech Vietnam

NashTech Vietnam là công ty thành viên của tập đoàn Harvey Nash đến từ vương quốc Anh. Đây được xem là nơi tập hợp những chuyên gia trong lĩnh vực công nghệ, cung cấp giải pháp thông minh nhằm tạo dựng giá trị thực tiễn cho khách hàng.

11. HANSEN TECHNOLOGIES

Hansen là nhà cung cấp phần mềm và dịch vụ toàn cầu cho ngành năng lượng, nước và truyền thông. Đến nay đã tiếp cận được hơn 600 khách hàng đến từ 80 quốc gia khác nhau.

12. Techbase Vietnam

Công ty Techbase Việt Nam có 100% vốn đầu tư từ Tập đoàn Yahoo Nhật Bản. Hoạt động chính của công ty là phát triển phần mềm cho các sản phẩm của Tập đoàn Yahoo Nhật Bản (hơn 20 dịch vụ).

13. Bosch Việt Nam

Bosch là một trong những công ty hàng đầu trong mảng IT – Consultant trên thế giới. Bosch Việt Nam bắt đầu hoạt động tại Việt Nam từ năm 1994, từ đó đến nay liên tục là công ty đi đầu trong các hoạt động về tăng trưởng doanh thu và môi trường làm việc phát triển.

14. Axon Active Vietnam

Axon Active Việt Nam là một công ty phát triển phần mềm quốc tế có trụ sở chính tại Thụy Sĩ. Tại Việt Nam, Axon Active không chỉ chú trọng đến chất lượng sản phẩm mà cũng rất để tâm đến phát triển môi trường làm việc cho nhân viên.

15. Absolute Software (Vietnam) Ltd

Có trụ sở đặt tại Vancouver, Canada, hoạt động chính của Absolute Software là cung cấp các giải pháp quản lý rủi ro dữ liệu và bảo mật bền vững cho hàng nghìn khách hàng trên toàn cầu.

16. Nexus Frontier Tech

Với các văn phòng đặt tại London, Tokyo, Singapore, Boston và Hà Nội, Nexus Frontier Tech tập trung vào việc phát triển các giải pháp AI cụ thể, hiệu quả nhất cho khách hàng.

Các công ty phần mềm nước ngoài tại Việt Nam sẽ liên tục được cập nhật để người đọc có thêm các thông tin hữu ích. Đón đọc thêm nhiều bài viết khác tại topdev.vn/blog


Tuyển Dụng Nhân Tài IT Cùng TopDev
Đăng ký nhận ưu đãi & tư vấn về các giải pháp Tuyển dụng IT & Xây dựng Thương hiệu tuyển dụng ngay!
Hotline: 028.6273.3496 – Email: contact@topdev.vn
Dịch vụ: https://topdev.vn/page/products

Cấu trúc chương trình C/C++, file .c, .cpp

cấu trúc c/c++
Cấu trúc chương trình C/C++, Cấu trúc file c, cpp.

Bài viết được sự cho phép của tác giả Trần Hữu Cương

Cấu trúc chương trình C/C++, Cấu trúc file c, cpp.

(Xem thêm: Tự học lập trình C, C++ qua code ví dụ)

File lập trình ngôn ngữ C có đuôi mở rộng là .c, với ngôn ngữ C++ thì đuôi mở rộng là .cpp.

Cấu trúc chương trình C/C++, file .c, .cpp

Một chương trình C cơ bản gồm các thành phần sau:

  • Phần mô tả
  • Các lệnh tiền xử lý (Preprocessor Commands) hay còn gọi là phần liên kết: như khai báo thư viện, hằng số
  • Các hàm (Functions)
  • Các biến (Variables)
  • Các khai báo, biểu thức (Statements & Expressions)
  • Các bình luận, chú thích (Comments)
  Bảng băm trong C++
  C Token là gì? Cú pháp trong lập trình C/C++

Xem thêm tuyển dụng C++ lương hấp dẫn trên TopDev

Xét ví dụ chương trình Hello World (in ra dòng chữ Hello World) sau:

Hello.c

/*
* Hello.c
* Created on: Mar 24, 2019
* Author: stackjava
* Description: Writes the words "Hello World!" on the screen
*/
#include 
int main() {
// print 'Hello World' to console
printf("Hello World");
return 0;
}
  • /* hello.c ...*/ phần mô tả file. Cho biết tác giả, ngày tạo và mục đích của file. Phần này cũng có thể coi là một chú thích.
  • #include <stdio.h> là lệnh tiền xử lý thực hiện khai báo thư viện stdio.h. Thư viện này cho phép chúng ta sử dụng hàm printf()
  • int main() {...} là một hàm với tên là main, có kiểu giá trị trả về là int
  • // print 'Hello Worl' to console là một chú thích, dùng để giải thích code cho người đọc, nó sẽ được trình biên dịch bỏ qua khi biên dịch
  • printf("Hello World \n"); Thực hiện in ra dòng chữ ‘Hello World‘. Chi tiết việc in ra màn hình của hàm printf() được định nghĩa trong file stdio.h
  • return 0; kết thúc hàm main và trả về kết quả là 0

Phần mô tả file và phần chút thích không gây ảnh hưởng tới chương trình (có cũng được mà không có cũng không sao)

Biên dịch và chạy thử file Hello.c ở trên:

Cấu trúc chương trình C/C++, Cấu trúc file c, cpp

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

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

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

Viết Reminder Parser dùng Rust

viết reminder parser
Viết Reminder Parser dùng Rust

Bài viết được sự cho phép của tác giả Huy Trần 

Khi dùng Google Calendar hoặc Reminder.app của macOS, mình rất thích chức năng tạo nhanh một event bằng cách nhập vào nội dung một cách tự nhiên như khi đang nói, ví dụ:

get hair cut at 10am every Sunday

hoặc là

doctor appointment at 1pm on Monday

Khi nhận được input như này, một event mới sẽ được tạo ra với ngày và giờ tương ứng, còn nội dung của event sẽ là phần text mở đầu, ví dụ “get hair cut”, hoặc “doctor appointment”.

Thường thì cái gì mình thích, mình sẽ tìm cách clone lại, chức năng này cũng ko ngoại lệ.

Vì bài viết này là phần tiếp theo của bài viết trước, chúng ta sẽ tiếp tục dùng Rust và Nom. Recommend các bạn đọc kĩ phần trước, và chuẩn bị những kiến thức cơ bản của Rust, nhất là về ResultOptioncargo test, trước khi đọc tiếp bài này.

Các bạn cũng có thể tham khảo code implementation hoàn chỉnh tại Github trước khi bắt đầu: https://github.com/huytd/reminder-parser

  Tại sao team Discord chuyển từ Go sang Rust?
  Dynamic typing trong Rust với std::any::Any

Xem thêm việc làm SSIS hấp dẫn trên TopDev

Phân tích cú pháp

Đây không phải là cú pháp mà Google Calendar hay Reminder.app sử dụng, nhưng đây sẽ là cú pháp chúng ta sẽ dùng trong bài viết này, đơn giản là vì nó… đơn giản 

Cấu trúc của một event input là một tổ hợp của nhiều token như sau:

Trong đó:

  • <event-text> là nội dung của event cần tạo.
  • <time> là thời gian diễn ra event, có thể nhập thời gian ở 2 dạng: at 10pm hoặc 11:32. Từ khóa “at” có thể được bỏ đi, phần meridiem indicator (“am” hoặc “pm”) cũng là optional.Một token <time> sẽ có dạng:
    time = ?"at" + hour + ?(":" + minutes) + ?("am"|"pm")
    

    Nếu chỉ có giá trị giờ (hour) mà không có phút (minutes) thì mặc định sẽ là 00 phút.

  • <repeat> và <date>: Hai token này đi chung với nhau vì đôi lúc, những event sẽ lặp lại định kỳ (ví dụ nhắc trả bill hàng tháng, ví dụ every 15th), cũng có thể chỉ diễn ra một lần (on Monday), giá trị on hoặc every sẽ được dùng để xác định token <repeat>.
    date = ?("on"|"every") + date
    

Để cho đơn giản thì chúng ta sẽ tạm bỏ qua việc xác thực nội dung nhập vào, nên những case như thế này vẫn sẽ được chấp nhận:

32:42 pm
hoc
24:59

Trên thực tế, validation cũng không phải là nhiệm vụ của parser.

Thuật toán parse event

Vậy ta sẽ parse nội dung như thế nào sau khi đã xác định được 3 thành phần trên?

Thuật toán parse của chúng ta sẽ duyệt từng kí tự từ đầu đến cuối string, và tìm cách parse ra từng token.

Chạy từ đầu input cho đến trước khi ta gặp phần tử đầu tiên của một <time> token, lưu tất cả kí tự đã gặp lại:

Tiếp theo, khi bắt gặp phần tử đầu tiên của <time> token (là chữ “at”), cho đến cuối (là khi đọc được phần meridiem indicator “am” hoặc “pm”) ta sẽ bắt đầu quá tình parse <time> token.

Sau khi xác định xong <time> token, đi tiếp và nếu gặp “on” hoặc “every”, ta sẽ bắt đầu parse <date> token.

Sau khi đã xác định được <time> token và <date> token, thì phần nội dung chúng ta đã lưu lại từ đầu chính là <event-text>. Đến lúc này ta hoàn tất quá trình parse.

Implementation

Xác định xong cú pháp và thuật toán là coi như giải quyết xong 75% bài toán rồi, 95% còn lại nằm ở việc đánh nhau với Rust bây giờ ta implement thôi.

Việc đầu tiên là define ra cấu trúc dữ liệu cho kết quả sau khi parse, ta sẽ gọi nó là ReminderEvent:

struct ReminderEvent<'a> {
    text: String,
    date: ReminderDate<'a>,
    time: ReminderTime<'a>
}

Trường text là nội dung của event, kiểu dữ liệu cho date và time lần lượt sẽ là:

struct ReminderDate<'a> {
    content: &'a str,
    repeated: bool
}

struct ReminderTime<'a> {
    hour: &'a str,
    minute: &'a str,
    meridiem: bool
}

Một ReminderDate sẽ chứa content là một chuỗi thô chưa qua xử lý, ví dụ như “Tuesday”“14/12”. Bao giờ cần xài thì parse tiếp sau, trong bài viết này mình sẽ lưu raw. Nếu đây là một event diễn ra định kì (có chữ “every” khi parse date), thì trường repeated sẽ là true.

Một ReminderTime chứa các giá trị giờ / phút dưới dạng chuỗi thô, giá trị meridiem indicator sẽ được lưu trong trường meridiem, vì nó có thể có, có thể không tồn tại trong input, mặc định sẽ là true cho “am”, và false cho “pm”.

Vì các giá trị raw được lưu dưới dạng string slice &str, tham chiếu trực tiếp từ input, bản thân Rust compiler không tự suy ra được lifetime cho các giá trị này, nên ta phải tự annotate bằng <'a>.

Tiếp theo, kế hoạch thực hiện sẽ là:

  1. Viết hàm parse ReminderTime
  2. Viết hàm parse ReminderDate
  3. Sau đó kết hợp 2 hàm này để viết hàm parse ReminderEvent

Hàm parse ReminderTime

Ở bài trước ta đã biết, một parser dùng Nom sẽ có dạng fn(&str) -> IResult, hàm parse_time của chúng ta cũng sẽ có dạng như thế:

fn parse_time(input: &str) -> IResult<&str, ReminderTime> {
    // tbd
}

Trước khi bắt tay vào code, mình có thói quen viết trước một vài test case, để khi implement chúng ta sẽ biết được mình có đi đúng hướng hay không. “Má, chém gió vl! ” — Bình luận từ đồng nghiệp của tác giảĐây là một thói quen tốt, các bạn cũng nên làm như vậy, tốn time chút nhưng hiệu quả.

#[test]
fn test_parse_time() {
    let test_times = [
        ("at 11:00", Ok(("11", "00", true))),
        ("at 10pm", Ok(("10", "00", false))),
        ("at 12:13 am", Ok(("12", "13", true))),
        ("13:42pm", Ok(("13", "42", false))),
        ("15:30", Ok(("15", "30", true))),
        ("at 5", Ok(("5", "00", true))),
        ("32:412", Ok(("32", "412", true))),
        ("at 32:281am", Ok(("32", "281", true))),
        ("at 32pm", Ok(("32", "00", false))),
        ("night time", Err(())),
        ("at night", Err(())),
    ];

    for test_case in test_times {
        let result = parse_time(test_case.0);
        if test_case.1.is_ok() {
            let (_, actual) = result.unwrap();
            let expected = test_case.1.unwrap();
            assert_eq!(actual.hour, expected.0);
            assert_eq!(actual.minute, expected.1);
            assert_eq!(actual.meridiem, expected.2);
        } else {
            assert!(result.is_err());
        }
    }
}

Trong hàm test_parse_time() ở trên, ta tạo một mảng test_times chứa tập các tuple dạng (input, expected_result). Trong đó, các input là các string mà trên thực tế sẽ được nhập vào từ phía user, expected_result là một giá trị kiểu Result, nếu nó là giá trị Ok(...) có nghĩa là input này parse được, còn giá trị Err() là trường hợp lỗi. Mỗi giá trị Ok(...) sẽ có dạng (hour, minute, meridiem). Trong vòng lặp tiếp theo của hàm test, ta duyệt qua từng test case, lấy ra giá trị thực tế actual từ parser (kiểu ReminderTime) và so sánh nó với từng giá trị trong tuple test.

Phương pháp này không có gì mới, bên Golang gọi là table driven testing.

Có test rồi thì ta có thể implement được rồi, quay lại hàm parse_time, việc đầu tiên là dùng các combinator của nom để đọc input, nhắc lại cú pháp của <time> token:

time = ?"at" + hour + ?(":" + minutes) + ?("am"|"pm")

Trước khi đi vào giải thích, đây là hàm parse_time hoàn chỉnh:

fn parse_time(input: &str) -> IResult<&str, ReminderTime> {
    let (remain, (_, _, hour, opt_min, _, am)) = tuple((
        opt(tag("at")),
        multispace0,
        digit1,
        opt(tuple((tag(":"), digit1))),
        multispace0,
        opt(alt((tag("am"), tag("pm")))),
    ))
    .parse(input)?;

    let (_, minute) = opt_min.unwrap_or(("", "00"));
    let meridiem = am == Some("am") || am == None;

    Ok((remain, ReminderTime { hour, minute, meridiem }))
}

Để đọc chuỗi “at”, ta có thể dùng hàm tag() của nom, vì “at” có thể có hoặc không xuất hiện trong input, ta dùng hàm opt() để cho nom biết nó là một giá trị optional.

opt(tag("at"))

Tiếp sau “at” là một hoặc nhiều kí tự space, ta dùng multispace0 để xác định nó. Sau đó là hour, cấu thành từ một hoặc nhiều kí tự số, ta dùng digit1 để parse.

Phần giá trị phút minute, thường sẽ đi sau hour, cách nhau bằng một kí tự ':', nó cũng là optional.

opt(tuple(tag(":"), digit1))

Tiếp theo lại là một mớ khoảng trắng nếu có, tiếp tục dùng multispace0.

Cuối cùng là phần meridiem indicator, có thể có hoặc không, ta dùng hàm alt() để parse lấy một trong 2 giá trị tag("am") và tag("pm").

opt(alt((tag("am"), tag("pm"))))

Các giá trị sau khi parse được lưu vào một tuple, mỗi phần tử của tuple sẽ được gán với một biến, lần lượt là remainhouropt_minam. Trong đó remain là phần chuỗi còn sót lại sau quá trình parse.

Vì phần minute là một giá trị optional, kiểu Option, ta cần một bước hậu xử lý để nếu nó là None thì mặc định nó thành giá trị "00".

let (_, minute) = opt_min.unwrap_or(("", "00"));

Tương tự, giá trị am cũng là optional, nhưng ta sẽ có 2 trường hợp: Hoặc nó là Some("am"), hoặc nó là None, nếu đúng thì giá trị meridiem của kết quả trả về sẽ là true, ngược lại nó là false.

let meridiem = am == Some("am") || am == None;

Cuối cùng, nếu mọi thứ ok hết, ta tạo ra một object ReminderTime từ các giá trị đã được xử lý ở trên, và trả về kết quả.

Chạy thử hàm test, ta sẽ thấy mọi thứ đều pass hết, lý do không phải vì test lụi, mà là vì chúng ta code xịn, chả có mấy khi, cứ tự tin lên.

running 1 test
test test_parse_time ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Hàm parse ReminderDate

Tiếp theo là hàm parse parse_date, như trên, ta cũng bắt đầu bằng:

fn parse_date(input: &str) -> IResult<&str, ReminderDate> {
}

Và tiếp theo là viết hàm test_parse_date():

#[test]
fn test_parse_date() {
    let test_cases: [(&str, Result<(&str, bool), ()>); 8] = [
        (" every Sunday", Ok(("Sunday", true))),
        ("every Monday", Ok(("Monday", true))),
        ("on Tuesday ", Ok(("Tuesday", false))),
        ("tomorrow", Ok(("tomorrow", false))),
        ("today", Ok(("today", false))),
        ("on 08/25", Ok(("08/25", false))),
        ("every 3rd", Ok(("3rd", true))),
        ("", Ok(("today", false)))
    ];

    for test_case in test_cases {
        let result = parse_date(test_case.0);
        if test_case.1.is_ok() {
            let (_, actual) = result.unwrap();
            let expected = test_case.1.unwrap();
            assert_eq!(actual.content, expected.0);
            assert_eq!(actual.repeated, expected.1);
        } else {
            assert!(result.is_err());
        }
    }
}

Khỏi cần giải thích dài dòng, cơ bản vì mình lười viết, nên để phần này cho các bạn tự suy luận 

Implement đầy đủ của hàm parse_date như sau:

fn parse_date(input: &str) -> IResult<&str, ReminderDate> {
    let (remain, (_, opt_repeat, _, date)) = tuple((
        multispace0,
        opt(alt((value(true, tag("every")), value(false, tag("on"))))),
        multispace0,
        rest
    ))
    .parse(input)?;

    let repeated = opt_repeat.unwrap_or(false);
    let content = if date.trim().is_empty() { "today" } else { date.trim() };

    Ok((remain, ReminderDate { content, repeated }))
}

Như đã viết ở trên, cú pháp của <date> token là:

date = ?("on"|"every") + date

Khá đơn giản, vì phần <date> nằm sau phần <time> token, hai phần này ngăn cách với nhau bằng một hoặc nhiều khoảng trắng, ta dùng multispace0 để parse các khoảng trắng này.

Tiếp theo, để vì nội dung mở đầu của <date> là “on” hoặc “every”, thay vì lấy luôn giá trị raw của chúng dưới dạng string, ta có thể map các giá trị này thành một giá trị kiểu boolean, và dùng luôn giá trị đó để xác định thuộc tính repeated của <date> token. Ta dùng hàm value() để map giá trị “on” thành false, và “every” thành true. Vì phần “on” || “every” là optional, ta wrap biểu thức trên bằng opt().

opt(alt((value(true, tag("every")), value(false, tag("on")))))

Sau đó là bước hậu xử lý, giá trị repeat sau khi parse sẽ có 2 trường hợp: Some(true|false) hoặc None, nếu giá trị trả về là None, ta có thể mặc định nó thành false.

Phần còn lại của input sẽ là phần ReminderDate.content, chúng ta chỉ việc lấy ra dưới dạng raw string, xài rest combinator để lấy toàn bộ ra. Nếu phần content là một chuỗi rỗng (empty), thì ta sẽ mặc định nó thành “today”.

Cuối cùng là khởi tạo object ReminderDate rồi trả về kết quả.

Lại tiếp tục chạy test để thấy khả năng code thần sầu của tác giả:

running 1 test
test test_parse_date ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

Như vậy, chúng ta đã hoàn thành 2 building block quan trọng đó là parse_time() và parse_date() để xác định được <time> và <date> token. Bước cuối cùng là kết hợp 2 parser này và parse nội dung input hoàn chỉnh.

Hàm parse ReminderEvent

Xem lại cú pháp của một input event hoàn chỉnh:

<event-text> <time> <repeat> <date>

Đến đây hẳn các bạn cũng đoán được logic của hàm parse_event(), nhưng mà khoan đã, việc đầu tiên là define ra cái hàm:

fn parse_event(input: &str) -> IResult<&str, ReminderEvent> {
}

Và viết test:

#[test]
fn test_parse_event() {
    let test_events: [(&str, Result<(&str, &str, &str, bool, &str, bool), ()>); 10] = [
        ("go feed the fish at 10am", Ok(("go feed the fish", "10", "00", true, "today", false))),
        ("feed the fish at 10:00am", Ok(("feed the fish", "10", "00", true, "today", false))),
        ("walk the dog 10:00am today", Ok(("walk the dog", "10", "00", true, "today", false))),
        ("feed the cat at 4 tomorrow", Ok(("feed the cat", "4", "00", true, "tomorrow", false))),
        ("get haircut at 14:24 pm", Ok(("get haircut", "14", "24", false, "today", false))),
        ("credit card pay at 8am", Ok(("credit card pay", "8", "00", true, "today", false))),
        ("credit card pay at 8:00 every 20th", Ok(("credit card pay", "8", "00", true, "20th", true))),
        ("cafe with Justin at Ginza at 6 on 08/23", Ok(("cafe with Justin at Ginza", "6", "00", true, "08/23", false))),
        ("pick up books at library at 10am every Sunday", Ok(("pick up books at library", "10", "00", true, "Sunday", true))),
        ("lorem ipsum doro tata", Err(()))
    ];

    for test_case in test_events {
        let result = parse_event(test_case.0);
        if test_case.1.is_ok() {
            let (_, actual) = result.unwrap();
            let expected = test_case.1.unwrap();
            assert_eq!(actual.text, expected.0);
            assert_eq!(actual.time.hour, expected.1);
            assert_eq!(actual.time.minute, expected.2);
            assert_eq!(actual.time.meridiem, expected.3);
            assert_eq!(actual.date.content, expected.4);
            assert_eq!(actual.date.repeated, expected.5);
        } else {
            assert!(result.is_err());
        }
    }
}

Mảng test_events chứa các tuple có cấu trúc dạng:

(input, Result<(event_text, hour, minute, meridiem, date, repeated)>)

Trong đó input là nội dung event input sẽ được nhập vào từ user, và các field trong Result sẽ là các field tương ứng của object ReminderEventReminderTime và ReminderDate.

Bằng việc kết hợp các hàm parse_time() và parse_date(), ta có thể implement hàm parse_event() một cách rất đơn giản:

fn parse_event(input: &str) -> IResult<&str, ReminderEvent> {
    let (input, (vtask, (time, date))) =
        many_till(anychar, pair(parse_time, parse_date)).parse(input)?;
    let text = vtask
        .iter()
        .map(|c| c.to_string())
        .collect::<Vec<String>>()
        .join("")
        .trim()
        .to_string();
    Ok((input, ReminderEvent { text, time, date }))
}

Ta dùng combinator tên là many_till(anychar, ...) để lấy tất cả các kí tự xuất hiện trong input, cho đến khi parse được <time> hay <date> token.

Giá trị trả về của many_till là một vector chứa nhiều kí tự Vec<char>, việc còn lại chỉ là join() chúng lại để thu về một giá trị kiểu String.

Cuối cùng, chạy lại toàn bộ test:

running 3 tests
test test_parse_date ... ok
test test_parse_time ... ok
test test_parse_event ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Như vậy chúng ta đã hoàn thành việc build một parser hết sức đơn giản, để parse một input event dưới dạng ngôn ngữ tự nhiên thành một data struct hoàn chỉnh. Từ đây ta có thể xây dựng được những ứng dụng triệu đô, dư sức cạnh tranh với Google Calendar hay Reminder.app (tự tin không ai đánh thuế mà).

Input:

"write new blog post at 9am every 14th Dec"

Output:

ReminderEvent{
   text: "write new blog post",
   date: ReminderDate{
      content: "14th Dec",
      repeated: true
   },
   time: ReminderTime{
      hour: "9",
      minute: "00",
      meridiem: true
   }
}

Hy vọng vẫn có bạn đọc xuống được đến tận đây mà không drop giữa chừng  và hy vọng bài viết này giúp các bạn hiểu rõ hơn về Nom Parser, cũng như kĩ thuật Parser Combination. Lần tới, khi cần parse một nội dung gì đó, thay vì đâm đầu vào sử dụng RegEx, hãy thử chậm lại một tí và sử dụng Nom hay một thư viện Parser Combination nào đó để build, mình nghĩ ngoài đồng nghiệp ra, thì chính bạn trong tương lai sẽ rất cảm kích cái quyết định đó của bản thân. Chúc các bạn may mắn 

Hẹn gặp lại các bạn trong các bài viết tiếp theo.

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

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

Xem thêm công việc IT hấp dẫn trên TopDev

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

thư viện okhttp

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

Dynamic typing trong Rust với std::any::Any

dynamic typing
Dynamic typing trong Rust với std::any::Any

Bài viết được sự cho phép của tác giả Huy Trần

Xét một tình huống thường gặp trong lập trình: Giả sử ta có một interface kiểu Shape, và 2 class là RectangleCircle cùng implement interface Shape, sau đó ta tạo một danh sách tên là shapes để chứa tất cả các đối tượng có implement Shape.

  Tại sao team Discord chuyển từ Go sang Rust?
  Hướng dẫn và sử dụng jquery plugin typing để giả hiệu ứng gõ văn bản

Xem thêm tuyển dụng C# lương cao trên TopDev

Trong Rust code sẽ nhìn như thế này:

pub trait Shape {}

pub struct Rectangle {}
impl Shape for Rectangle {}

pub struct Circle {}
impl Shape for Circle {}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Rectangle {}),
        Box::new(Circle {}),
    ];
}

Trong quá trình làm việc với mảng shapes, có lúc chúng ta muốn lấy một giá trị ra và cast nó về kiểu Rectangle hoặc Circle để sử dụng, thường thì chúng ta sẽ làm như này:

let rect: &Rectangle = shapes.get(0).unwrap().as_ref();

Xong rồi sẽ bị Rust chửi vào mặt:

error[E0308]: mismatched types
  --> src/main.rs:15:28
   |
15 |     let rect: &Rectangle = shapes.get(0).unwrap().as_ref();
   |               ----------   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Rectangle`, found trait object `dyn Shape`
   |               |
   |               expected due to this
   |
   = note: expected reference `&Rectangle`
              found reference `&dyn Shape`

Lỗi vì shapes là một vector chứa các object kiểu dyn Shape, nên khi dùng hàm get() để lấy một phần tử ra, phần tử đó sẽ mang kiểu dyn Shape.

Để có thể chuyển một object kiểu dyn Shape thành Rectangle, chúng ta có thể implement trait std::any::Any cho kiểu Rectangle.

pub trait Shape {
    fn as_any(&self) -> &dyn std::any::Any;
}

pub struct Rectangle {}
impl Shape for Rectangle {
    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

Từ bây giờ, chúng ta có thể gọi hàm .as_any() để chuyển một Shape về kiểu Any, rồi dùng hàm downcast_ref của Any để cast nó về kiểu mong muốn:

let shape: &dyn Shape = shapes.get(0).unwrap().as_ref();
let rect: &Rectangle = shape.as_any().downcast_ref().unwrap();
Bài viết gốc được đăng tải tại thefullsnack.com
Có thể bạn quan tâm:
Xem thêm công việc IT hấp dẫn trên TopDev

5 sự ngộ nhận phổ biến về kiểm thử phần mềm

ngộ nhận về kiểm thử phần mềm
5 sự ngộ nhận phổ biến về kiểm thử phần mềm

Bài viết được sự cho phép của vntesters.com

Kiểm thử phần mềm đang ngày càng phát triển ở Việt Nam và được nhiều người quan tâm biết đến. Trong quá trình tìm hiểu và phát triển nghề nghiệp, mình thấy có những ngộ nhận về kiểm thử phần mềm mà nhiều người (bao gồm kỹ sư kiểm thử phần mềm, lập trình viên, nhà quản lý) thường mắc phải. Dưới đây là 5 ngộ nhận phổ biến đúc kết từ việc nghiên cứu tìm hiểu và kinh nghiệm cá nhân của một kỹ sư kiểm thử phần mềm được coi như “có chút kinh nghiệm” (7 năm).

Ngộ nhận 1: “Không làm lập trình được thì đi làm kiểm thử”

Đây là một trong những ngộ nhận phổ biến nhất về công việc kiểm thử phần mềm. Rất nhiều ứng viên đã chia sẻ như vậy trong những buổi phỏng vấn vào vị trí kỹ sư kiểm thử phần mềm. Họ chia sẻ rằng họ được đào tạo để trở thành lập trình viên nhưng vẫn chưa tìm được việc cho nên họ muốn chuyển qua công việc kiểm thử. Hoặc một dạng câu trả lời khác là tạm thời làm kiểm thử sao đó để chuyển qua làm lập trình viên. Sở dĩ có sự ngộ nhận này là do mọi người ngầm hiểu công việc kiểm thử là rất dễ làm và không cần kiến thức lập trình hoặc kiểm thử là những bước đầu tiên của lập trình.

Thực ra kiểm thử và lập trình là 2 công việc khác nhau và đòi hỏi những kỹ năng chuyên môn khác nhau. Không có gì đảm bảo một lập trình viên giỏi sẽ làm công việc kiểm thử tốt hơn một lập trình viên bình thường hoặc không phải là lập trình viên

  04 Điều Cần Chú Ý Cho Người Mới Làm Automation Test
  AB testing là gì? Tại sao phải làm AB testing?

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

Ngộ nhận 2: “Kiểm thử phần mềm ai làm chẳng được…”

Nhiều người quan niệm làm kiểm thử phần mềm rất dễ, chẳng cần biết lập trình, chỉ cần biết sơ sơ về kiến thức tin học và chút tỉ mỉ. Nếu ai đó có quan niệm như vậy thì cần phải nhìn nhận lại. Không có công việc nào là dễ dàng nếu như mình không có đủ kỹ năng cần thiết để làm nó hoặc chưa bao giờ làm nó. Nhiều người sau một thời gian làm công việc kiểm thử cũng nhận định công việc kiểm thử “cũng dễ thiệt”. Tuy nhiên, họ không nhận ra rằng làm được việc và làm xuất sắc là khác nhau. Kiểm thử phần mềm đòi hỏi những kỹ năng chuyên môn mà không phải ai cũng có sẵn như sự đam mê, tò mò, khả năng sáng tạo, khả năng quan sát, phân tích, trình bày, tranh luận, vv…và cả kiến thức lập trình. Kiểm thử phần mềm là một nghệ thuật mà đã là nghệ thuật thì không phải ai cũng có thể đam mê và làm được.

Vision

Ngộ nhận 3: “Cái gì…thời buổi này còn kiểm thử thủ công?”

Trong vài năm trở lại đây, mọi người đã được biết đến nhiều hơn về kiểm thử tự động. “Người người nhà nhà” làm kiểm thử tự động. Đi một vòng mấy website về tuyển dụng, số lượng tuyển dụng cho vị trí kiểm thử tự động tăng cao. Điều đó tạo nên một trào lưu làm kiểm thử tự động khiến cho nhiều người ngộ nhận là đây là thời đại của kiểm thử tự động và kiểm thử thủ công được gọi là “kiểm thử chân tay” mang hàm ý mỉa mai.

Kiểm thử tự động và kiểm thử thủ công thực ra là 2 cách tiếp cận việc kiểm thử hoàn toàn khác nhau. Kiểm thử tự động có thể thích hợp cho việc kiểm thử đệ quy, unit test nhưng kiểm thử tự động không thể giúp tìm được nhiều lỗi sản phẩm hơn. Một điều mà nhiều người vẫn hay quên rằng ngay cả công cụ kiểm thử tự động cũng có lỗi (dĩ nhiên là có rồi, con người phát triển mà) và chắc chắn là ít ra vẫn sẽ cần kỹ sư kiểm thử phần mềm để tìm lỗi cho nó.

Ngộ nhận 4: “Kiểm thử làm chi cho tốn kém”

Rất nhiều người nghĩ kiểm thử là công việc tốn kém không cần thiết. Tại sao phải kiểm thử khi mình đã có những lập trình viên “pro”/”xuất sắc”/“siêu”. Thứ nhất, “pro”, “xuất sắc”, “siêu” hay những mỹ từ nào để nói về năng lực của lập trình viên là những từ ngữ mang tính tương đối. Thứ hai, đã là người là có sai sót.

Những người có quan niệm “kiểm thử là tốn kém” thì họ sẽ nhận ra thế nào tốn kém đúng nghĩa khi sản phảm đưa ra thị trường và bị phàn nàn về chất lượng sản phẩm. Chi phí để vá lỗi thì có thể cân đo đong đếm được còn chi phí để lấy lại niềm tin đã mất của khách hàng thì không biết đo làm sao. Lúc đó nhiều người sẽ nói “ Giá như, giá như có đội kiểm thử thì tốt biết bao…”

Một quan niệm khác cũng nguy hiểm không kém là để lập trình viên kiểm thử luôn sản phẩm. Tại sao không chứ? Không ai hiểu sản phẩm tốt hơn người đã làm ra nó. Có thể đúng nhưng không phải ai cũng có thể nhận ra lỗi của mình hoặc đủ dũng cảm và sự khách quan để thừa nhận lỗi của mình (Ở đây mình đang giả định là lập trình viên có đầy đủ kỹ năng của một kỹ sư kiểm thử phần mềm chuyên nghiệp, có thể làm 16 tiếng/1 ngày và chỉ nhận lương bằng với một lập trình viên chuyên code và làm 8 tiếng/1 ngày).

Ngộ nhận 5: “Kiểm thử rồi mà sao ứng dụng vẫn còn lỗi vậy?”

Oh, mấy sếp hay hỏi câu này. Bất cứ sản phẩm nào cũng sẽ có lỗi cho dù đội kiểm thử có chuyên nghiệp bao nhiêu. Muốn kiểm thử và tìm ra tất cả lỗi thì phải có điều kiện cần và đủ. Cần là kiểm thử hết tất cả mọi thứ liên quan đến công việc kiểm thử như các loại kiểm thử, kỹ thuật kiểm thử, phương pháp tiếp cận, cấp độ kiểm thử (xem thêm https://en.wikipedia.org/wiki/Software_testing). Cho dù có được điều kiện cần thì vẫn phải cần điều kiện đủ là có đủ thời gian để kiểm thử. Ngày nay, dưới áp lực của việc đưa sản phẩm ra thị trường càng sớm càng tốt để tăng tính cạnh tranh cho sản phẩm cho nên nhiều người đã chọn giải pháp là rút ngắn giai đoạn kiểm thử hoặc chấp nhận sản phẩm ra thị trường với những lỗi “chấp nhận được”. Dĩ nhiên, nói như thế không có nghĩa là kỹ sư kiểm thử phần mềm có quyền thở phào nhẹ nhõm và thờ ơ với công việc kiểm thử của mình. Trách nhiệm vẫn sẽ thuộc về kỹ sư kiểm thử phần mềm nếu như những lỗi đó nằm trong phạm vi kiểm thử.

Mình vừa chia sẻ 5 ngộ nhận phổ biến về công việc kiểm thử phần mềm. Những ngộ nhận trên cũng chính là những quan sát nhận định của chính mình trong thời gian làm công việc kiểm thử ở vị trí kỹ sư kiểm thử phần mềm hoặc trưởng nhóm kiểm thử. Những nhận định trên là mang tính chủ quan và có thể chỉ có giá trị tại thời điểm khi viết bài này và không loại trừ khả năng chính mình cũng đang ngộ nhận. Sự ngộ nhận tuy không xấu nhưng đừng để sự ngộ nhận cản trở bạn trở thành một kỹ sư kiểm thử phần mềm chuyên nghiệp hoặc con đường phát triển của công việc kiểm thử phần mềm. Một công việc đầy thử thách nhưng cũng không kém phần thú vị.

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

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

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

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

java restful client
Tạo ứng dụng Java RESTful Client với thư viện Retrofit

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 clientOkHttp để 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 là Retrofit.

Giới thiệu Retrofit

Retrofit là một type-safe HTTP client cho Java và Android được phát triển bởi Square. Retrofit giúp dễ dàng kết nối đến một dịch vụ REST trên web bằng cách chuyển đổi API thành Java Interface.

Tương tự với các thư viện khác, Retrofit giúp bạn dễ dàng xử lý dữ liệu JSON hoặc XML sau đó phân tích cú pháp thành Plain Old Java Objects (POJOs). Tất cả các yêu cầu GETPOSTPUT, và DELETE đều có thể được thực thi.

Retrofit được xây dựng dựa trên một số thư viện mạnh mẽ và công cụ khác. Đằng sau nó, Retrofit làm cho việc sử dụng OkHttp để xử lý các request/ response trên mạng. Ngoài ra, từ Retrofit2 không tích hợp bất kỳ một bộ chuyển đổi JSON nào để phân tích từ JSON thành các đối tượng Java. Thay vào đó nó đi kèm với các thư viện chuyển đổi JSON sau đây:

Sử dụng retrofit

Để sử dụng Retrofit chúng ta thực hiện các bước sau:

  • Một class object tương ứng với JSON/ XML data.
  • Một interface dùng để định nghĩa các các phương thức request đến API.
  • Sử dụng Annotations để mô tả yêu cầu HTTP.
  • Tạo một Retrofit.Builder để khởi tạo các phương thức trong interface đã được định nghĩa.
  8 thủ thuật khi làm việc với Object sử dụng resting và spreading
  API là gì? Các nguyên tắc xây dựng Rest API

Xem thêm tuyển dụng Java lương cao trên TopDev

Các Annotations để mô tả yêu cầu HTTP

Request method

Mỗi phương thức phải có Annotation HTTP cung cấp request method và URL. Có 5 Annotation được tích hợp sẵn: @GET@POST@PUT@DELETE và @HEAD.

@GET("/api/v1/users?sort=desc")
Call<List> getUsers();

Header manipulation

Chúng ta có thể set thông tin static header bằng cách sử dụng annotation @Header ở mức method.

@Headers({
"Cache-Control: max-age=640000",
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("/api/v1/users?sort=desc")
Call<List> getUsers();

Trong trường hợp thông tin header có thể thay đổi, chúng có thể sử dụng @Header ở mức parameter.

@GET("/api/v1/users?sort=desc")
Call<List> getUsers(@Header("Authorization") String authorization);

Đối với các kết hợp tham số truy vấn phức tạp, có thể sử dụng @HeaderMap.

@GET("/api/v1/users?sort=desc")
Call<List> getUsers(@HeaderMap Map<String, String> headers);

Url manipulation

URL request có thể được cập nhật tự động bằng cách sử dụng các khối thay thế và tham số trên phương thức.

Chúng ta có thể sử dụng URL 1 cách động dựa vào biến truyền vào, bằng cách sử dụng anotation @Path.

@GET("/api/v1/users/{id}")
Call getUser(@Path("id") int userId);

Chúng ta có thể nối thêm paramater vào sau URL bằng cách sử dụng @Query.

@GET("/api/v1/users?page=1&limit=10")

Call<List> getUsers(@Query("page") int page, @Query("limit") int limit);

Đối với các kết hợp tham số truy vấn phức tạp, có thể sử dụng @QueryMap.

@GET("/api/v1/users?page=1&limit=10&sortBy=createdAt&order=desc")

Call<List> getUsers(@QueryMap Map<String, String> options);

Request body

Một đối tượng có thể được chỉ định để sử dụng làm phần thân yêu cầu HTTP với Annotation @Body.

@POST("/api/v1/users")
Call createUser(@Body User user);

Form encoded and Multipart

Các phương thức cũng có thể được khai báo để gửi dữ liệu được mã hóa và dữ liệu multipart. Dữ liệu được mã hóa theo form được gửi khi @FormUrlEncoded được chỉ định trên phương thức. Mỗi cặp key-value được chú thích bằng @Field chứa tên và đối tượng cung cấp giá trị.

@FormUrlEncoded
@POST("/api/v1/auth")
Call getToken(@Field("username") String username, @Field("password") String password);

Các yêu cầu multipart được sử dụng khi @Multipart xuất hiện trên phương thức. Các phần được khai báo bằng cách sử dụng @Part. @Multipart thường được sử dụng để truyền tải file.

@Multipart
@POST("/api/v1/files/upload")
Call uploadFile(@Part("uploadFile") RequestBody uploadFile, @Part("description") RequestBody description);

Ví dụ CRUD Restful Client với Retrofit

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.gpcoder</groupId>
    <artifactId>RestfulClientWithRetrofitExample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>RestfulClientWithRetrofitExample</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.retrofit2</groupId>
            <artifactId>retrofit</artifactId>
            <version>2.6.0</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>converter-jackson</artifactId>
            <version>2.6.0</version>
        </dependency>
        <!-- <dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>converter-gson</artifactId>
            <version>2.6.0</version>
        </dependency> -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

Trong project này, tôi sử dụng thư viện jackson để convert request/ response data giữa client và server.

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.

AuthService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.gpcoder.service;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.Headers;
import retrofit2.http.POST;
public interface AuthService {
    @Headers("Accept: application/json; charset=utf-8")
    @FormUrlEncoded
    @POST("auth")
    Call<ResponseBody> getToken(@Field("username") String username, @Field("password") String password);
}

OrderService.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
package com.gpcoder.service;
import com.gpcoder.model.Order;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
public interface OrderService {
    @GET("orders/{id}")
    Call<ResponseBody> getOrder(@Path("id") int id, @Header("Authorization") String authorization);
    @POST("orders")
    Call<ResponseBody> createOrder(@Body Order order, @Header("Authorization") String authorization);
    @PUT("orders")
    Call<ResponseBody> updateOrder(@Body Order order, @Header("Authorization") String authorization);
    @DELETE("orders/{id}")
    Call<ResponseBody> deleteOrder(@Path("id") int id, @Header("Authorization") String authorization);
}

Các method của chúng ta sử dụng Call để nhận kết quả trả về.

  • Call : là một invocation của các method trong Retrofit được sử gửi request lên server và nhận kết quả trả về.
  • ResponseBody : là kiểu dữ liệu của response về từ server. Do tất cả API của Server trả về là String nên chúng ta sẻ sử dụng ResponseBody. Nếu response trả về là json Order, các bạn có thể sử dụng trực tiếp Call<User>.

Để sử dụng các method này, chúng ta sẽ tạo một Retrofit client:

RetrofitClientCreator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.gpcoder.helper;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.jackson.JacksonConverterFactory;
public class RetrofitClientCreator {
    public static final String BASE_URL = "http://localhost:8080/RestfulWebServiceExample/rest/";
    
    public static Retrofit getClient() {       
        return new Retrofit.Builder()
                .baseUrl(BASE_URL) //This is the onlt mandatory call on Builder object
                .addConverterFactory(JacksonConverterFactory.create()) // Convertor library used to convert response into POJO
                // .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
}

Tiếp theo chúng ta sẽ sử dụng Retrofit để call các API.

RetrofitClientExample.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
package com.gpcoder;
import java.io.IOException;
import com.gpcoder.helper.RetrofitClientCreator;
import com.gpcoder.model.Order;
import com.gpcoder.service.AuthService;
import com.gpcoder.service.OrderService;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
public class RetrofitClientExample {
    private static Retrofit retrofit;
    public static void main(String[] args) throws IOException {
        retrofit = RetrofitClientCreator.getClient();
        String token = getToken();
        String authentication = "Bearer " + token;
        createOrder(authentication);
        retrieveOrder(authentication);
        updateOrder(authentication);
        deleteOrder(authentication);
    }
    private static String getToken() throws IOException {
        // Create an implementation of the API endpoints defined by the service interface
        AuthService authService = retrofit.create(AuthService.class);
        // Create an invocation of a Retrofit method that sends a request to a webserver
        // and returns a response.
        Call<ResponseBody> call = authService.getToken("gpcoder", "gpcoder");
        // Synchronously send the request and return its response
        Response<ResponseBody> response = call.execute();
        String token = response.body().string();
        System.out.println("getToken: " + token);
        return token;
    }
    /**
     */
    private static void createOrder(String authentication) throws IOException {
        OrderService orderService = retrofit.create(OrderService.class);
        Call<ResponseBody> call = orderService.getOrder(1, authentication);
        Response<ResponseBody> response = call.execute();
        System.out.println("createOrder: " + response.body().string());
    }
    /**
     */
    private static void retrieveOrder(String authentication) throws IOException {
        OrderService orderService = retrofit.create(OrderService.class);
        Call<ResponseBody> call = orderService.createOrder(new Order(), authentication);
        Response<ResponseBody> response = call.execute();
        System.out.println("retrieveOrder: " + response.body().string());
    }
    /**
     */
    private static void updateOrder(String authentication) throws IOException {
        OrderService orderService = retrofit.create(OrderService.class);
        Call<ResponseBody> call = orderService.updateOrder(new Order(), authentication);
        Response<ResponseBody> response = call.execute();
        System.out.println("updateOrder: " + response.body().string());
    }
    /**
     */
    private static void deleteOrder(String authentication) throws IOException {
        OrderService orderService = retrofit.create(OrderService.class);
        Call<ResponseBody> call = orderService.deleteOrder(1, authentication);
        Response<ResponseBody> response = call.execute();
        System.out.println("deleteOrder: " + response.body().string());
    }
}

Trong ví dụ này, tôi sử dụng phương thức call.execute() để gửi request lên server và nhận kết quả trả về. Phương thức này được thực thi đồng bộ (Synchronous). Nếu bạn muốn gửi request bất đồng bộ, có thể sử dụng phương thức call.enqueue(callback).

Sử dụng Interceptor với OkHttp

Trong ví dụ trên, ở mỗi phương thức chúng ta đều phải thêm một tham số authentication để gửi lên server. Cách làm này khá là phiền phức. Để giải quyết vấn đề này, chúng ta có thể sử dụng Interceptor, một tính năng đã được hỗ trợ trong OkHttp. Với Retrofit chúng ta cũng có thể sử dụng Interceptor, do nó sử dụng toàn bộ OkHttp như một phần implement của nó.

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
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
package com.gpcoder.interceptor;
import java.io.IOException;
import com.gpcoder.helper.RetrofitClientCreator;
import com.gpcoder.service.AuthService;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Retrofit;
public class AuthInterceptor implements Interceptor {
    
    private static String token = null;
    @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") && getToken() != null) {
            authRequest = originalRequest.newBuilder()
                    .header("Authorization", "Bearer " + getToken())
                    .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;
    }
    private String getToken() throws IOException {
        if (token != null) {
            return token;
        }
        
        Retrofit retrofit = RetrofitClientCreator.getClientWithInterceptor();
        
        // Create an implementation of the API endpoints defined by the service interface
        AuthService authService = retrofit.create(AuthService.class);
        // Create an invocation of a Retrofit method that sends a request to a webserver
        // and returns a response.
        Call<ResponseBody> call = authService.getToken("gpcoder", "gpcoder");
        // Synchronously send the request and return its response
        retrofit2.Response<ResponseBody> response = call.execute();
        String token = response.body().string();
        System.out.println("getToken: " + token);
        return token;
    }
}

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.

RetrofitClientCreator.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
package com.gpcoder.helper;
import com.gpcoder.interceptor.AuthInterceptor;
import com.gpcoder.interceptor.LoggingInterceptor;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
public class RetrofitClientCreator {
    public static final String BASE_URL = "http://localhost:8080/RestfulWebServiceExample/rest/";
    
    public static Retrofit getClientWithInterceptor() {    
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .addInterceptor(new LoggingInterceptor())
                .addInterceptor(new AuthInterceptor())
                .addNetworkInterceptor(new LoggingInterceptor())
                .build();
        
        return new Retrofit.Builder()
                .baseUrl(BASE_URL) //This is the onlt mandatory call on Builder object.
                .client(okHttpClient) //The Htttp client to be used for requests
                .addConverterFactory(JacksonConverterFactory.create()) // Convertor library used to convert response into POJO
                .build();
    }
}

Bây giờ chương trình client của chúng ta, có thể sửa bỏ tham số authentication.

OrderServiceV2.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
package com.gpcoder.service;
import com.gpcoder.model.Order;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
public interface OrderServiceV2 {
    @GET("orders/{id}")
    Call<ResponseBody> getOrder(@Path("id") int id);
    @POST("orders")
    Call<ResponseBody> createOrder(@Body Order order);
    @PUT("orders")
    Call<ResponseBody> updateOrder(@Body Order order);
    @DELETE("orders/{id}")
    Call<ResponseBody> deleteOrder(@Path("id") int id);
}

Class Client đơn giản sửa lại như sau:

RetrofitClientWithInterceptorExample.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
package com.gpcoder;
import java.io.IOException;
import com.gpcoder.helper.RetrofitClientCreator;
import com.gpcoder.model.Order;
import com.gpcoder.service.OrderServiceV2;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
public class RetrofitClientWithInterceptorExample {
    private static Retrofit retrofit;
    public static void main(String[] args) throws IOException {
        retrofit = RetrofitClientCreator.getClientWithInterceptor();
        createOrder();
        retrieveOrder();
        updateOrder();
        deleteOrder();
    }
    /**
     */
    private static void createOrder() throws IOException {
        OrderServiceV2 orderService = retrofit.create(OrderServiceV2.class);
        Call<ResponseBody> call = orderService.getOrder(1);
        Response<ResponseBody> response = call.execute();
        System.out.println("createOrder: " + response.body().string());
    }
    /**
     */
    private static void retrieveOrder() throws IOException {
        OrderServiceV2 orderService = retrofit.create(OrderServiceV2.class);
        Call<ResponseBody> call = orderService.createOrder(new Order());
        Response<ResponseBody> response = call.execute();
        System.out.println("retrieveOrder: " + response.body().string());
    }
    /**
     */
    private static void updateOrder() throws IOException {
        OrderServiceV2 orderService = retrofit.create(OrderServiceV2.class);
        Call<ResponseBody> call = orderService.updateOrder(new Order());
        Response<ResponseBody> response = call.execute();
        System.out.println("updateOrder: " + response.body().string());
    }
    /**
     */
    private static void deleteOrder() throws IOException {
        OrderServiceV2 orderService = retrofit.create(OrderServiceV2.class);
        Call<ResponseBody> call = orderService.deleteOrder(1);
        Response<ResponseBody> response = call.execute();
        System.out.println("deleteOrder: " + response.body().string());
    }
}

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ề Retrofit, ngoài ra chúng ta có thể upload, download file khi sử dụng retrofit. Tương tự OkHttp, Retrofit được sử dụng chủ yếu trong các ứng dụng Android để thao tác từ client đến server. Tuy nhiên, chúng ta hoàn toàn có thể sử dụng nó một cách dễ dàng với Rest Client trong ứng dụng java web.

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

Bảng tra cứu lệnh cơ bản Unix/Linux

lệnh trong unix linux
Bảng tra cứu lệnh cơ bản Unix/Linux

Bài viết được sự cho phép của tác giả Lê Chí Dũng

Các lệnh xử lý tập tin

ls – liệt kê nội dung thư mục hiện tại

ls -al – liệt kê có định dạng và cả tập tin ẩn

cd dir – chuyển từ thư mục hiện tại sang

dir cd – chuyển từ thư mục hiện tại về thư mục riêng

pwd – hiện thư mục hiện tại

mkdir dir – tạo thư mục

dir rm file – xóa tập tin file

rm -r dir – xóa thư mục dir

rm -f file – ép xóa tập tin file

rm -rf dir – ép xóa thư mục

dir * cp file1 file2 – chép tập tin file1 sang file2

cp -r dir1 dir2 – chép thư mục dir1 sang dir2; tạo dir2 nếu chưa tồn tại

mv file1 file2 – đổi tên hoặc di chuyển tập tin file1 thành file2; nếu file2 là một thư mục có sẵn, di chuyển file1 vào thư mục file2

ln -s file link – tạo liên kết biểu tượng link đến tập tin file

touch file – tạo hoặc cập nhật tập tin file

cat > file – Nhập từ bàn phím (đầu vào chuẩn – standard input) vào tập tin file mới

more file – hiện nội dung tập tin file

head file – hiện 10 dòng đầu của tập tin file

tail file – hiện 10 dòng cuối của tập tin file

tail -f file – hiện nội dung của tập tin file và cập nhật liên tục, khởi đầu với 10 dòng cuối

  10 điều bạn có thể làm với Linux mà bạn không thể làm với Windows
  5 lý do lập trình viên nên sử dụng hệ điều hành Linux

Xem thêm việc làm Linux lương cao trên TopDev

Quản lý tiến trình

ps – hiện những tiến trình đang hoạt động tích cực

top – hiện tất cả các tiến trình đang hoạt động

kill pid – ép thoát tiến trình có mã pid

killall proc – ép thoát các tiến trình tên proc *

bg – hiện các công việc đã kết thúc hoặc đang chạy nền; tiếp tục một công việc đã tạm ngừng

fg – ngừng chạy nền (chuyển sang foreground) với công việc gần đây nhất

fg n – ngừng chạy nền với công việc n

Quyền sử dụng tập tin

chmod octal file – thay đổi quyền sử dụng của tập tin file thành octal. Mỗi chữ số ứng với từng tài khoản có được bằng cách cộng các số sau:

  •  4 – đọc (r)
  •  2 – ghi (w)
  •  1 – thực thi (x)

Ví dụ:

  • chmod 777 – tất cả đều có đủ 3 quyền
  • chmod 755 – rwx cho người sở hữu, rx cho nhóm sở hữu và các tài khoản khác

Xem man chmod để biết thêm chi tiết.

SSH

ssh user@host – kết nối đến máy host với tài khoản user

ssh -p port user@host – kết nối đến máy host qua cổng port với tài khoản user

ssh-copy-id user@host – thêm khóa công cộng của tài khoản user vào máy host để thiết lập đăng nhập không cần mật khẩu (đăng nhập có khóa) Tìm kiếm

grep pattern files – tìm mẫu lặp pattern trong các tập tin files grep -r pattern dir – tìm mẫu lặp pattern trong thư mục dir và tất cả các thư mục con (recursive)

command | grep pattern – tìm mẫu lặp pattern trong đầu ra của lệnh command

locate file – tìm tất cả các tập tin có tên file

Thông tin hệ thống

date – hiện ngày giờ hiện tại

cal – hiện lịch tháng này

uptime – hiện thời gian từ lúc bật máy

w – hiện những người đang đăng nhập

whoami – hiện tên tài khoản của bạn

finger user – hiện thông tin về tài khoản user

uname -a – hiện thông tin về nhân HĐH

cat /proc/cpuinfo – hiện thông tin về CPU

cat /proc/meminfo – hiện thông tin về bộ nhớ

man command – hiện hướng dẫn cho lệnh command

df – hiện mức sử dụng đĩa

du – hiện dung lượng thư mục

* Hiện top 10 file dung lượng lớn trong thư mục /var: du -a /var | sort -n -r | head -n 10

free – hiện dung lượng bộ nhớ trống và lượng bộ nhớ tráo đổi (swap) đã dùng

whereis app – hiện đường dẫn của ứng dụng

app which app – cho biết lệnh nào sẽ được chạy mặc định thay cho app

Nén

tar cf file.tar files – tạo một tập tar có tên file.tar chứa các tập tin khác

tar xf file.tar – giải phóng các tập tin từ file.tar

tar czf file.tar.gz files – tạo một tập tar có nén bằng Gzip

tar xzf file.tar.gz – giải nén một tập tar bằng Gzip

tar cjf file.tar.bz2 – tạo một tập tar có nén bằng Bzip2

tar xjf file.tar.bz2 – giải nén một tập tar bằng Bzip2

gzip file – nén file và đổi tên thành file.gz

gzip -d file.gz – giải nén file.gz

Mạng

ping host – gửi lệnh ping đến máy host và hiện kết quả

whois domain – kiểm tra thông tin whois của tên miền domain

dig domain – kiểm tra thông tin DNS của tên miền domain

dig -x host – tìm ngược tên miền của máy host

wget file – tải tập tin file

wget -c file – tiếp tục tải tập tin đang dở

Cài đặt

Cài đặt từ mã nguồn:

./configure

make

make install

dpkg -i pkg.deb – cài gói phần mềm (Debian)

rpm -Uvh pkg.rpm – cài gói phần mềm (RPM)

Phím tắt

Ctrl+C – dừng hoàn toàn lệnh đang chạy

Ctrl+Z – tạm dừng lệnh hiện tại, tiếp tục chạy nền bằng lệnh bg hoặc chạy chính với lệnh fg

Ctrl+D – thoát khỏi phiên làm việc hiện tại, giống với exit

Ctrl+W – xóa một từ trong dòng hiện tại

Ctrl+U – xóa cả dòng

Ctrl+R – hiện danh sách các lệnh gần đây !! – lặp lại lệnh gần đây nhất exit – thoát khỏi phiên làm việc hiện tại

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

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

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

Cách làm việc với Storyboard trong Xcode

storyboard
Cách làm việc với Storyboard trong Xcode

Bài viết được sự cho phép của tác giả Trần Hữu Cương

I.Giới thiệu

StoryBoard  là nơi chứa một hoặc nhiều màn hình.

Là nơi để ta có thể kéo thả những đối tượng (label, image, button,…) một cách dễ dàng và thuận tiện hơn.

Storyboard là thành phần thể hiện và quản lý giao diện người dùng của ứng dụng iOS bên trong XCode.

  Bazel 4.1.0: Bài 4 – Sử dụng tulsi generate Xcode project
  Cách sử dụng lệnh Xcopy trong CMD (Command Prompt)

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

Storyboard đồng thời đảm nhiệm việc kết nối các màn hình với nhau.

  • Mỗi màn hình được quản lý bởi một View Controller và chứa các View để thể hiện giao diện.
  • Các màn hình được kết nối với nhau bởi các đối tượng segue. Segue giúp bạn tạo hiệu ứng chuyển cảnh và truyền dữ liệu qua lại giữa các màn hình.

Storyboard sẽ thể hiện cho bạn một bức tranh toàn cảnh về các màn hình và mối quan hệ giữa các chúng. Do đó bạn có thể sử dụng Storyboard để thiết kế và quản lý giao diện các màn hình cho ứng dụng của bạn.

II.Cách làm việc với Storyboard

Cách tạo storyboard mới các bạn click vào File –> New –>File –>Storyboard

Và giờ các bạn chọn nơi lưu

Một project chúng ta có thể tạo một hoặc nhiều storyboard để quản lý các màn hình.

Khi làm việc với các tập tin storyboard thì Xcode sẽ sử dụng một công cụ đặc biệt là Interface Builder.

Interface builder cung cấp một môi trường kéo thả giúp lập trình viên có thể dễ dàng thêm, chỉnh sửa và xoá các thành phần UI tham gia vào ứng dụng.

Mỗi một màn hình trong ứng dụng sẽ tương ứng với một scene  trong storyboard.

Khi tạo 1 project mới chúng ta chỉ có 1 scene tương ứng với 1 màn hình trống trong ứng dụng.

Mũi tên ở phía tay trái trong cửa sổ interface builder chỉ vào scene này được gọi là điểm bắt đầu  của storyboard.

Scene ứng với điểm đầu tiên sẽ được chạy  đầu tiên khi ứng dụng bắt đầu chạy.

Hy vọng bài hôm nay sẽ giúp các bạn hiểu rõ về storyboard trong Xcode

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

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

Xem thêm tuyển dụng việc làm IT hấp dẫn trên TopDev

[Update] TOP Những Công Ty Trả Lương Cao Nhất Việt Nam 2024

top những
TOP Những Công Ty Trả Lương Cao Nhất Việt Nam

Lương là một trong những thước đo quan trọng được nhiều người dùng để lựa chọn công việc cho bản thân. Một công ty có thu nhập tốt luôn là yếu tố thu hút đông đảo các ứng viên ứng tuyển. Vậy ở Việt Nam hiện tại có những công ty nào đang trả lương cao nhất? Bài viết dưới đây sẽ tổng hợp top những công ty trả lương cao nhất Việt Nam cho người đọc có cái nhìn tổng quan hơn về thị trường lao động.

top những công ty trả lương cao nhất việt nam
Top các công ty trả lương cao nhất Việt Nam

*Số thứ tự trong bài viết chỉ nhằm liệt kê các công ty, không nhằm mục đích xếp hạng hay đánh giá

1. Hãng hàng không Quốc gia Việt Nam – Vietnam Airlines

Thu nhập bình quân của các nhân viên đang làm việc tại Vietnam Airlines hiện nằm trong khoảng từ 25 – 40 triệu đồng/tháng.

2. Ngân hàng Vietcombank

Thu nhập bình quân của nhân viên ngân hàng Vietcombank trong năm 2018 là 35,47 triệu đồng/người/tháng. Ngoài ra, chế độ đãi ngộ hấp dẫn mới tiền thưởng cao là một trong những lý do giúp Vietcombank luôn thu hút nhiều nhân tài.

3. Tổng công ty đầu tư và kinh doanh vốn nhà nước SCIC

Thu nhập trung bình của người lao động tại SCIC là 30,4 triệu đồng/tháng.

4. Tập đoàn Viễn thông Quân đội Viettel

Mức lương của nhân viên tại Viettel phân hóa theo vị trí làm việc. Theo đó, mức lương tại Viettel hoàn toàn có thể lên đến 30 triệu đồng/tháng.

Xem ngay lương thực nhận với công cụ đổi lương gross sang net

5. Ngân hàng ViettinBank

Mức chi trả cho nhân viên tại ViettinBank trung bình trên 20 triệu đồng/ tháng chưa tính các khoản phụ cấp.

6. Công ty thực phẩm Vinafood

Thu nhập bình quân của người lao động tại Vinafood có thể lên đến trên 28 triệu đồng/ tháng.

7. Samsung Electronics Việt Nam

Mức lương Samsung trả cho người mới vào làm, không có kinh nghiệm thường ở mức khoảng 15 triệu đồng/tháng. Số năm và chất lượng công việc tăng thì tiền lương cũng theo đó tăng lên.

8. Công ty Tân Cảng Sài Gòn SNP

Thu nhập bình quân của người lao động tại SNP rơi vào khoảng 23 triệu đồng/tháng.

9. Ngân hàng Quân đội MBBank

Thu nhập bình quân của người lao động tại MB Bank cũng nằm trong top lương cao của các ngân hàng khi nhân viên nhận trên 18 triệu đồng/tháng.

10. Tập đoàn dầu khí Việt Nam PVN

Thu nhập bình quân của người lao động trên 16 triệu đồng/tháng.

11. Công Ty Cổ Phần Ô Tô Trường Hải

Mức lương cứng nhân viên tại Ô Tô Trường Hải nhận được hàng tháng rơi vào khoảng 10 – 12 triệu đồng/tháng chưa kể hoa hồng.

12. Công Ty Cổ Phần Sữa Việt Nam Vinamilk

Mức lương tại Vinamilk luôn là con số hấp dẫn với các ứng viên, tùy thuộc vào vị trí cũng như năng lực làm việc mà nhân viên tại đây sẽ nhận những mức lương khác nhau, trung bình đều trên 10 triệu đồng/tháng.

13. FPT Software Việt Nam

Thu nhập trung bình tại FPT Software là trên 20 triệu đồng/tháng, khá cao so với mặt bằng chung mức lương trong ngành IT.

(Tiếp tục update)


Tuyển Dụng Nhân Tài IT Cùng TopDev
Đăng ký nhận ưu đãi & tư vấn về các giải pháp Tuyển dụng IT & Xây dựng Thương hiệu tuyển dụng ngay!
Hotline: 028.6273.3496 – Email: contact@topdev.vn
Dịch vụ: https://topdev.vn/page/products