Debug và khắc phục lỗi hiển thị ký tự Unicode của ứng dụng Web

Bài viết được sự cho phép của BBT Tạp chí Lập trình

Tác giả: Nguyễn Bình Sơn

Bài viết này ngầm định rằng bạn biết “bảng mã” nghĩa là gì, và bạn quen thuộc với các thành phần của mô hình ứng dụng web cũng như mô hình trình diễn MVC. Mặc dù mã ở đây được trình bày dưới dạng thức của ngôn ngữ Java, framework Spring MVC và database MySQL, các vấn đề được nhắc đến là chung, các khái niệm được nhắc đến là phổ biến, và các cách giải quyết là tổng quát. Hãy cùng tìm hiểu về Debug và khắc phục lỗi hiển thị ký tự Unicode của ứng dụng Web nhé!

  Debug và khắc phục lỗi hiển thị ký tự Unicode của ứng dụng Web
”]

  Một số tip debug trong Laravel

Model mang dữ liệu Unicode nhưng View thì không

Bạn có model như thế này.

Nhưng bạn nhận được kết quả như thế này:

Là vì View của bạn có nội dung như thế này:

CREATE DATABASE IF NOT EXISTS cms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Bạn cần làm cho view engine render cho bạn một view mà trong đó các ký tự unicode của template và model được bảo toàn. Ví dụ sau đây là cấu hình cho view engine Thymeleaf.

@Bean
public ViewResolver viewResolver() {
    // ...
    viewResolver.setCharacterEncoding("UTF-8");
    return viewResolver;
}

Template mang ký tự Unicode nhưng View thì không

Bạn có view template như thế này:

<tr>
  <td>Tên</td>
  <td>
    <input type="text" name="name" th:value="${customer.name}">
  </td>
</tr>
<tr>
  <td>Email</td>
  <td>
    <input type="text" name="email" th:value="${customer.email}">
  </td>
</tr>
<tr>
  <td>Địa chỉ</td>
  <td>
    <input type="text" name="address" th:value="${customer.address}">
  </td>
</tr>

Nhưng bạn nhận được view như thế này:

Nếu bạn đã xử lý vấn đề encoding trong lúc render như ở trên rồi, thì có thể vấn đề là do template resorver đã dùng một encoding khác để đọc tài liệu template. Hãy cấu hình lại cho cả template resolver nữa.

@Bean
public ITemplateResolver templateResolver() {
    // ...
    templateResolver.setCharacterEncoding("UTF-8");
    return templateResolver;
}

Không thể chuyển tải ký tự Unicode qua tầng giao vận

Giả sử cần submit form sau:

Tầng giao vận TCP/IP không quan tâm với bảng mã, nó đơn giản và vận chuyển gói tin, từng byte một. Phần lớn web server, trừ khi là web server do bạn tự viết, decode các bytes này để có các parametter theo lối như thể rằng các bytes đó trước kia là ký tự của bảng mã ISO-8859–1. Nếu bạn nhìn các ký tự đã được decode ra dưới con mắt của bảng mã UTF-8 (trớ trêu rằng, phần lớn các ngôn ngữ lập trình làm như thế), bạn sẽ nhận được kết quả không mong muốn.

Cách xử lý luôn luôn là encode chuỗi ký tự ngược lại thành dòng bytes (theo bảng mã ISO-8859–1, tất nhiên), và sau đó decode lại theo bảng mã UTF-8. Cho dù là thủ công như sau:

public String createCustomer(Customer customer) {
    try {
        byte[] bytes = customer.getName().getBytes("ISO-8859-1");
        String decodedName = new String(bytes, "UTF-8");
        customer.setName(decodedName);
        customerService.save(customer);
        return "redirect:/customers";
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
        return "500";
    }
}

… hay tự động, bằng cách sử dụng filter, như sau chẳng hạn:

public class AppInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    // ...    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        FilterRegistration.Dynamic filterRegistration =
            servletContext.addFilter("endcoding-filter", new CharacterEncodingFilter());
        filterRegistration.setInitParameter("encoding", "UTF-8");
        filterRegistration.setInitParameter("forceEncoding", "true");
        
        //make sure encodingFilter is matched most first, by "false" arg
        filterRegistration.addMappingForUrlPatterns(null, false, "/*");
        
        super.onStartup(servletContext);
    }
}

Entity mang thông tin Unicode, vào đến Database thì mất dấu

Bạn có form như thế này:

Entity ngon nghẻ như thế này:

Vào tới database thì loạn cào cào hết cả:

Hầu hết các client của các dbms, khi kết nối tới dbms server, đều chọn mặc định một bảng mã để làm việc với nhau. “Mặc định” này đôi khi là một giá trị cố định, đôi khi là lấy dynamic. Dù là trường hợp nào đi chăng nữa, đôi khi charset đó hoàn toàn khác với charset được dùng cho Schema/Table/Column. Nghĩa là “anh nói tiếng của anh, nhưng tôi hiểu theo tiếng của tôi”.

Cách xử lý luôn là cố định lại bảng mã dùng trong cuộc nói chuyện giữa client và dbms server:

CREATE DATABASE IF NOT EXISTS cms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Và đồng thời (điều này rất quan trọng), sử dụng cùng một bảng mã đó cho Schema/Table/Column:

CREATE DATABASE IF NOT EXISTS cms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Nếu database cùng với dữ liệu đã tồn tại, theo một bảng mã khác, cách xử lý luôn là sử dụng một kỹ thuật nào đó được dbms hỗ trợ để convert dữ liệu từ bảng mã cũ sang bảng mã mong muốn.

Bài viết gốc được đăng tải tại Tạp Chí Lập Trình

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

Xem thêm các  IT Jobs for Developer hấp dẫn tại TopDev