Làm thế nào để sử dụng Servlets và Ajax?


334

Tôi rất mới với các ứng dụng web và Servlets và tôi có câu hỏi sau:

Bất cứ khi nào tôi in một cái gì đó bên trong servlet và gọi nó bằng webbrowser, nó sẽ trả về một trang mới chứa văn bản đó. Có cách nào để in văn bản trong trang hiện tại bằng Ajax không?

Câu trả lời:


560

Thật vậy, từ khóa là "ajax": JavaScript và XML không đồng bộ . Tuy nhiên, năm ngoái, nó thường xuyên hơn JavaScript và JSON không đồng bộ . Về cơ bản, bạn để cho JS thực hiện một yêu cầu HTTP không đồng bộ và cập nhật cây DOM HTML dựa trên dữ liệu phản hồi.

Vì đây là một công việc khá tẻ nhạt để làm cho nó hoạt động trên tất cả các trình duyệt (đặc biệt là Internet Explorer so với các trình duyệt khác), có rất nhiều thư viện JavaScript đơn giản hóa điều này trong các chức năng đơn lẻ và bao gồm nhiều lỗi / quirks cụ thể của trình duyệt trong các trình duyệt , chẳng hạn như jQuery , Prototype , Mootools . Vì jQuery phổ biến nhất hiện nay, tôi sẽ sử dụng nó trong các ví dụ dưới đây.

Ví dụ khởi động trở lại Stringdưới dạng văn bản thuần túy

Tạo một cái /some.jspgiống như bên dưới (lưu ý: mã không mong đợi tệp JSP được đặt trong thư mục con, nếu bạn làm như vậy, hãy thay đổi URL servlet tương ứng):

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>SO question 4112686</title>
        <script src="http://code.jquery.com/jquery-latest.min.js"></script>
        <script>
            $(document).on("click", "#somebutton", function() { // When HTML DOM "click" event is invoked on element with ID "somebutton", execute the following function...
                $.get("someservlet", function(responseText) {   // Execute Ajax GET request on URL of "someservlet" and execute the following function with Ajax response text...
                    $("#somediv").text(responseText);           // Locate HTML DOM element with ID "somediv" and set its text content with the response text.
                });
            });
        </script>
    </head>
    <body>
        <button id="somebutton">press here</button>
        <div id="somediv"></div>
    </body>
</html>

Tạo một servlet với một doGet()phương thức giống như thế này:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String text = "some text";

    response.setContentType("text/plain");  // Set content type of the response so that jQuery knows what it can expect.
    response.setCharacterEncoding("UTF-8"); // You want world domination, huh?
    response.getWriter().write(text);       // Write response body.
}

Ánh xạ servlet này trên một mẫu URL /someservlethoặc /someservlet/*như bên dưới (rõ ràng, mẫu URL là miễn phí cho sự lựa chọn của bạn, nhưng bạn cần thay đổi someservletURL trong các ví dụ về mã JS trên tất cả các vị trí tương ứng):

@WebServlet("/someservlet/*")
public class SomeServlet extends HttpServlet {
    // ...
}

Hoặc, khi bạn chưa sử dụng bộ chứa tương thích Servlet 3.0 (Tomcat 7, Glassfish 3, JBoss AS 6, v.v. hoặc mới hơn), sau đó ánh xạ nó web.xmltheo cách thức cũ (xem thêm trang wiki Servlets của chúng tôi ):

<servlet>
    <servlet-name>someservlet</servlet-name>
    <servlet-class>com.example.SomeServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>someservlet</servlet-name>
    <url-pattern>/someservlet/*</url-pattern>
</servlet-mapping>

Bây giờ hãy mở http: // localhost: 8080 / bối cảnh / test.jsp trong trình duyệt và nhấn nút. Bạn sẽ thấy rằng nội dung của div được cập nhật với phản hồi của servlet.

Trở lại List<String>như JSON

Với JSON thay vì văn bản gốc như định dạng phản hồi, bạn thậm chí có thể có thêm một số bước nữa. Nó cho phép nhiều động lực hơn. Trước tiên, bạn muốn có một công cụ để chuyển đổi giữa các đối tượng Java và chuỗi JSON. Có rất nhiều trong số họ là tốt (xem dưới cùng của trang này để biết tổng quan). Yêu thích cá nhân của tôi là Google Gson . Tải xuống và đặt tệp JAR của nó trong /WEB-INF/libthư mục ứng dụng web của bạn.

Đây là một ví dụ hiển thị List<String>như <ul><li>. Các servlet:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    List<String> list = new ArrayList<>();
    list.add("item1");
    list.add("item2");
    list.add("item3");
    String json = new Gson().toJson(list);

    response.setContentType("application/json");
    response.setCharacterEncoding("UTF-8");
    response.getWriter().write(json);
}

Mã JS:

$(document).on("click", "#somebutton", function() {  // When HTML DOM "click" event is invoked on element with ID "somebutton", execute the following function...
    $.get("someservlet", function(responseJson) {    // Execute Ajax GET request on URL of "someservlet" and execute the following function with Ajax response JSON...
        var $ul = $("<ul>").appendTo($("#somediv")); // Create HTML <ul> element and append it to HTML DOM element with ID "somediv".
        $.each(responseJson, function(index, item) { // Iterate over the JSON array.
            $("<li>").text(item).appendTo($ul);      // Create HTML <li> element, set its text content with currently iterated item and append it to the <ul>.
        });
    });
});

Xin lưu ý rằng jQuery tự động phân tích cú pháp phản hồi dưới dạng JSON và cung cấp cho bạn trực tiếp một đối tượng JSON ( responseJson) làm đối số hàm khi bạn đặt loại nội dung phản hồi thành application/json. Nếu bạn quên đặt nó hoặc dựa vào mặc định text/plainhoặc text/html, thì responseJsonđối số sẽ không cung cấp cho bạn một đối tượng JSON, nhưng một chuỗi vanilla đơn giản và bạn cần phải tự tìm hiểu xung quanh JSON.parse(), điều này hoàn toàn không cần thiết nếu bạn đặt loại nội dung ngay ở vị trí đầu tiên.

Trở lại Map<String, String>như JSON

Đây là một ví dụ khác hiển thị Map<String, String>như <option>:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Map<String, String> options = new LinkedHashMap<>();
    options.put("value1", "label1");
    options.put("value2", "label2");
    options.put("value3", "label3");
    String json = new Gson().toJson(options);

    response.setContentType("application/json");
    response.setCharacterEncoding("UTF-8");
    response.getWriter().write(json);
}

Và bản tin:

$(document).on("click", "#somebutton", function() {               // When HTML DOM "click" event is invoked on element with ID "somebutton", execute the following function...
    $.get("someservlet", function(responseJson) {                 // Execute Ajax GET request on URL of "someservlet" and execute the following function with Ajax response JSON...
        var $select = $("#someselect");                           // Locate HTML DOM element with ID "someselect".
        $select.find("option").remove();                          // Find all child elements with tag name "option" and remove them (just to prevent duplicate options when button is pressed again).
        $.each(responseJson, function(key, value) {               // Iterate over the JSON object.
            $("<option>").val(key).text(value).appendTo($select); // Create HTML <option> element, set its value with currently iterated key and its text content with currently iterated item and finally append it to the <select>.
        });
    });
});

với

<select id="someselect"></select>

Trở lại List<Entity>như JSON

Dưới đây là một ví dụ mà hiển thị List<Product>trong một <table>nơi Productlớp có các thuộc tính Long id, String nameBigDecimal price. Các servlet:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    List<Product> products = someProductService.list();
    String json = new Gson().toJson(products);

    response.setContentType("application/json");
    response.setCharacterEncoding("UTF-8");
    response.getWriter().write(json);
}

Mã JS:

$(document).on("click", "#somebutton", function() {        // When HTML DOM "click" event is invoked on element with ID "somebutton", execute the following function...
    $.get("someservlet", function(responseJson) {          // Execute Ajax GET request on URL of "someservlet" and execute the following function with Ajax response JSON...
        var $table = $("<table>").appendTo($("#somediv")); // Create HTML <table> element and append it to HTML DOM element with ID "somediv".
        $.each(responseJson, function(index, product) {    // Iterate over the JSON array.
            $("<tr>").appendTo($table)                     // Create HTML <tr> element, set its text content with currently iterated item and append it to the <table>.
                .append($("<td>").text(product.id))        // Create HTML <td> element, set its text content with id of currently iterated product and append it to the <tr>.
                .append($("<td>").text(product.name))      // Create HTML <td> element, set its text content with name of currently iterated product and append it to the <tr>.
                .append($("<td>").text(product.price));    // Create HTML <td> element, set its text content with price of currently iterated product and append it to the <tr>.
        });
    });
});

Trở lại List<Entity>dưới dạng XML

Đây là một ví dụ có hiệu quả tương tự như ví dụ trước, nhưng sau đó với XML thay vì JSON. Khi sử dụng JSP làm trình tạo đầu ra XML, bạn sẽ thấy rằng việc mã hóa bảng và tất cả sẽ ít tẻ nhạt hơn. JSTL theo cách này hữu ích hơn nhiều khi bạn thực sự có thể sử dụng nó để lặp lại kết quả và thực hiện định dạng dữ liệu phía máy chủ. Các servlet:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    List<Product> products = someProductService.list();

    request.setAttribute("products", products);
    request.getRequestDispatcher("/WEB-INF/xml/products.jsp").forward(request, response);
}

Mã JSP (lưu ý: nếu bạn đặt mã <table>a <jsp:include>, nó có thể được sử dụng lại ở nơi khác trong phản hồi không phải là ajax):

<?xml version="1.0" encoding="UTF-8"?>
<%@page contentType="application/xml" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<data>
    <table>
        <c:forEach items="${products}" var="product">
            <tr>
                <td>${product.id}</td>
                <td><c:out value="${product.name}" /></td>
                <td><fmt:formatNumber value="${product.price}" type="currency" currencyCode="USD" /></td>
            </tr>
        </c:forEach>
    </table>
</data>

Mã JS:

$(document).on("click", "#somebutton", function() {             // When HTML DOM "click" event is invoked on element with ID "somebutton", execute the following function...
    $.get("someservlet", function(responseXml) {                // Execute Ajax GET request on URL of "someservlet" and execute the following function with Ajax response XML...
        $("#somediv").html($(responseXml).find("data").html()); // Parse XML, find <data> element and append its HTML to HTML DOM element with ID "somediv".
    });
});

Bây giờ bạn có thể nhận ra tại sao XML mạnh hơn JSON nhiều cho mục đích cụ thể là cập nhật tài liệu HTML bằng Ajax. JSON là buồn cười, nhưng nói chung chỉ hữu ích cho cái gọi là "dịch vụ web công cộng". Các khung công tác MVC như JSF sử dụng XML dưới vỏ bọc cho phép thuật ajax của chúng.

Xác định một hình thức hiện có

Bạn có thể sử dụng jQuery $.serialize()để dễ dàng xác định các biểu mẫu POST hiện có mà không phải loay hoay với việc thu thập và chuyển các tham số đầu vào của biểu mẫu riêng lẻ. Giả sử một biểu mẫu hiện có hoạt động hoàn toàn tốt mà không cần JavaScript / jQuery (và do đó làm giảm một cách duyên dáng khi enduser đã tắt JavaScript):

<form id="someform" action="someservlet" method="post">
    <input type="text" name="foo" />
    <input type="text" name="bar" />
    <input type="text" name="baz" />
    <input type="submit" name="submit" value="Submit" />
</form>

Bạn có thể tăng cường dần dần với ajax như sau:

$(document).on("submit", "#someform", function(event) {
    var $form = $(this);

    $.post($form.attr("action"), $form.serialize(), function(response) {
        // ...
    });

    event.preventDefault(); // Important! Prevents submitting the form.
});

Bạn có thể trong servlet phân biệt giữa các yêu cầu thông thường và yêu cầu ajax như dưới đây:

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String foo = request.getParameter("foo");
    String bar = request.getParameter("bar");
    String baz = request.getParameter("baz");

    boolean ajax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));

    // ...

    if (ajax) {
        // Handle ajax (JSON or XML) response.
    } else {
        // Handle regular (JSP) response.
    }
}

Các plugin jQuery Mẫu làm ít hoặc nhiều tương tự như trên jQuery ví dụ, nhưng nó đã hỗ trợ trong suốt bổ sung cho multipart/form-datacác hình thức theo yêu cầu của tập tin tải lên.

Gửi thủ công các tham số yêu cầu đến servlet

Nếu bạn hoàn toàn không có biểu mẫu, nhưng chỉ muốn tương tác với servlet "trong nền", theo đó bạn muốn POST một số dữ liệu, thì bạn có thể sử dụng jQuery $.param()để dễ dàng chuyển đổi một đối tượng JSON thành mã hóa URL chuỗi truy vấn.

var params = {
    foo: "fooValue",
    bar: "barValue",
    baz: "bazValue"
};

$.post("someservlet", $.param(params), function(response) {
    // ...
});

doPost()Phương pháp tương tự như được hiển thị ở trên có thể được sử dụng lại. Hãy lưu ý rằng cú pháp trên cũng hoạt động với $.get()trong jQuery và doGet()trong servlet.

Gửi thủ công đối tượng JSON đến servlet

Tuy nhiên, nếu bạn có ý định gửi toàn bộ đối tượng JSON thay vì tham số yêu cầu riêng lẻ vì một số lý do, thì bạn cần tuần tự hóa nó thành một chuỗi bằng cách sử dụng JSON.stringify()(không phải là một phần của jQuery) và hướng dẫn jQuery đặt loại nội dung yêu cầu application/jsonthay thế của (mặc định) application/x-www-form-urlencoded. Điều này không thể được thực hiện thông qua $.post()chức năng tiện lợi, nhưng cần phải được thực hiện thông qua $.ajax()như dưới đây.

var data = {
    foo: "fooValue",
    bar: "barValue",
    baz: "bazValue"
};

$.ajax({
    type: "POST",
    url: "someservlet",
    contentType: "application/json", // NOT dataType!
    data: JSON.stringify(data),
    success: function(response) {
        // ...
    }
});

Hãy lưu ý rằng rất nhiều người mới bắt đầu trộn contentTypevới dataType. Các contentTypeđại diện cho loại cơ thể yêu cầu . Kiểu dataTypeđại diện (dự kiến) của phần phản hồi , thường không cần thiết vì jQuery đã tự động phát hiện nó dựa trên Content-Typetiêu đề của phản hồi .

Sau đó, để xử lý đối tượng JSON trong servlet không được gửi dưới dạng tham số yêu cầu riêng lẻ mà là toàn bộ chuỗi JSON theo cách trên, bạn chỉ cần phân tích thủ công phần thân yêu cầu bằng cách sử dụng công cụ JSON thay vì sử dụng getParameter()thông thường đường. Cụ thể, các servlet không hỗ trợ application/jsoncác yêu cầu được định dạng, mà chỉ application/x-www-form-urlencodedhoặc multipart/form-datacác yêu cầu được định dạng. Gson cũng hỗ trợ phân tích chuỗi JSON thành đối tượng JSON.

JsonObject data = new Gson().fromJson(request.getReader(), JsonObject.class);
String foo = data.get("foo").getAsString();
String bar = data.get("bar").getAsString();
String baz = data.get("baz").getAsString();
// ...

Hãy lưu ý rằng tất cả điều này là vụng về hơn là chỉ sử dụng $.param(). Thông thường, bạn chỉ muốn sử dụng JSON.stringify()nếu dịch vụ đích là dịch vụ JAX-RS (RESTful) vì một số lý do chỉ có khả năng tiêu thụ chuỗi JSON và không phải là tham số yêu cầu thông thường.

Gửi một chuyển hướng từ servlet

Điều quan trọng cần nhận ra và hiểu là bất kỳ sendRedirect()forward()gọi bởi servlet theo yêu cầu ajax sẽ chỉ chuyển tiếp hoặc chuyển hướng yêu cầu ajax chứ không phải tài liệu / cửa sổ chính nơi yêu cầu ajax bắt nguồn. Trong trường hợp đó, JavaScript / jQuery chỉ lấy ra phản hồi được chuyển hướng / chuyển tiếp dưới dạng responseTextbiến trong hàm gọi lại. Nếu nó đại diện cho toàn bộ trang HTML và không phải là phản hồi XML hoặc JSON dành riêng cho ajax, thì tất cả những gì bạn có thể làm là thay thế tài liệu hiện tại bằng nó.

document.open();
document.write(responseText);
document.close();

Lưu ý rằng điều này không thay đổi URL như enduser nhìn thấy trên thanh địa chỉ của trình duyệt. Vì vậy, có vấn đề với bookmarkability. Do đó, tốt hơn hết là bạn chỉ cần trả lại "hướng dẫn" cho JavaScript / jQuery để thực hiện chuyển hướng thay vì trả lại toàn bộ nội dung của trang được chuyển hướng. Ví dụ: bằng cách trả về boolean hoặc URL.

String redirectURL = "http://example.com";

Map<String, String> data = new HashMap<>();
data.put("redirect", redirectURL);
String json = new Gson().toJson(data);

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);

function(responseJson) {
    if (responseJson.redirect) {
        window.location = responseJson.redirect;
        return;
    }

    // ...
}

Xem thêm:


cần phân tích json trên ví dụ cuối cùng.
shinzou

4
@kuhaku: không. Nếu bạn đọc bài viết từ trên xuống dưới, bạn sẽ tìm hiểu lý do tại sao.
BalusC

1
Câu trả lời này đã là nguồn sống của tôi trong tháng vừa qua lol. Học một bó từ nó. Tôi yêu ví dụ XML. Cám ơn đã đưa này lại với nhau! Một câu hỏi noob mặc dù nếu bạn có thời gian. Có lý do để đặt thư mục xml trong WEB-INF không?
Jonathan Laliberte

1
@JonathanLaliberte: Vì vậy, người dùng không thể tải chúng xuống.
BalusC

@BalusC, ví dụ XML của bạn rất tuyệt, cảm ơn bạn. Nhưng tôi nhận được "Không thể lấy thuộc tính 'thay thế' của tham chiếu không xác định hoặc null" cho $("#somediv").html($(responseXml).find("data").html())dòng này. Nó cũng nói "Số lượng đối số sai hoặc gán thuộc tính không hợp lệ". Tôi cũng có thể thấy rằng XML của tôi được điền dữ liệu khi tôi gỡ lỗi nó. Có ý kiến ​​gì không?
629

14

Cách đúng để cập nhật trang hiện đang hiển thị trong trình duyệt của người dùng (không tải lại) là để một số mã thực thi trong trình duyệt cập nhật DOM của trang.

Mã đó thường là javascript được nhúng hoặc liên kết từ trang HTML, do đó gợi ý AJAX. (Trên thực tế, nếu chúng tôi giả định rằng văn bản cập nhật đến từ máy chủ thông qua yêu cầu HTTP, thì đây là AJAX cổ điển.)

Cũng có thể triển khai loại điều này bằng cách sử dụng một số plugin hoặc tiện ích bổ sung cho trình duyệt, mặc dù có thể rất khó để plugin tiếp cận cấu trúc dữ liệu của trình duyệt để cập nhật DOM. (Các plugin mã gốc thường ghi vào một số khung đồ họa được nhúng trong trang.)


13

Tôi sẽ cho bạn thấy một ví dụ toàn bộ về servlet và cách gọi ajax.

Ở đây, chúng ta sẽ tạo ví dụ đơn giản để tạo biểu mẫu đăng nhập bằng cách sử dụng servlet.

index.html

<form>  
   Name:<input type="text" name="username"/><br/><br/>  
   Password:<input type="password" name="userpass"/><br/><br/>  
   <input type="button" value="login"/>  
</form>  

Đây là mẫu ajax

       $.ajax
        ({
            type: "POST",           
            data: 'LoginServlet='+name+'&name='+type+'&pass='+password,
            url: url,
        success:function(content)
        {
                $('#center').html(content);           
            }           
        });

Mã dịch vụ đăng nhậpServlet: -

    package abc.servlet;

import java.io.File;


public class AuthenticationServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {   
        doPost(request, response);
    }

    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

        try{
        HttpSession session = request.getSession();
        String username = request.getParameter("name");
        String password = request.getParameter("pass");

                /// Your Code
out.println("sucess / failer")
        } catch (Exception ex) {
            // System.err.println("Initial SessionFactory creation failed.");
            ex.printStackTrace();
            System.exit(0);
        } 
    }
}

8
$.ajax({
type: "POST",
url: "url to hit on servelet",
data:   JSON.stringify(json),
dataType: "json",
success: function(response){
    // we have the response
    if(response.status == "SUCCESS"){
        $('#info').html("Info  has been added to the list successfully.<br>"+
        "The  Details are as follws : <br> Name : ");

    }else{
        $('#info').html("Sorry, there is some thing wrong with the data provided.");
    }
},
 error: function(e){
   alert('Error: ' + e);
 }
});

7

Ajax (cũng là AJAX) một từ viết tắt của JavaScript không đồng bộ và XML) là một nhóm các kỹ thuật phát triển web có liên quan đến nhau được sử dụng ở phía máy khách để tạo các ứng dụng web không đồng bộ. Với Ajax, các ứng dụng web có thể gửi dữ liệu đến và truy xuất dữ liệu từ một máy chủ không đồng bộ Dưới đây là mã ví dụ:

Chức năng tập lệnh java của trang Jsp để gửi dữ liệu tới servlet với hai biến FirstName và lastName:

function onChangeSubmitCallWebServiceAJAX()
    {
      createXmlHttpRequest();
      var firstName=document.getElementById("firstName").value;
      var lastName=document.getElementById("lastName").value;
      xmlHttp.open("GET","/AJAXServletCallSample/AjaxServlet?firstName="
      +firstName+"&lastName="+lastName,true)
      xmlHttp.onreadystatechange=handleStateChange;
      xmlHttp.send(null);

    }

Servlet để đọc dữ liệu gửi lại cho jsp ở định dạng xml (Bạn cũng có thể sử dụng văn bản. Chỉ cần bạn thay đổi nội dung phản hồi thành văn bản và hiển thị dữ liệu trên chức năng javascript.)

/**
 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
 */
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    String firstName = request.getParameter("firstName");
    String lastName = request.getParameter("lastName");

    response.setContentType("text/xml");
    response.setHeader("Cache-Control", "no-cache");
    response.getWriter().write("<details>");
    response.getWriter().write("<firstName>"+firstName+"</firstName>");
    response.getWriter().write("<lastName>"+lastName+"</lastName>");
    response.getWriter().write("</details>");
}

5

Thông thường bạn không thể cập nhật một trang từ một servlet. Khách hàng (trình duyệt) phải yêu cầu cập nhật. Máy khách Eiter tải một trang hoàn toàn mới hoặc nó yêu cầu cập nhật lên một phần của trang hiện có. Kỹ thuật này được gọi là Ajax.


4

Sử dụng bootstrap multi select

Ajax

function() { $.ajax({
    type : "get",
    url : "OperatorController",
    data : "input=" + $('#province').val(),
    success : function(msg) {
    var arrayOfObjects = eval(msg); 
    $("#operators").multiselect('dataprovider',
    arrayOfObjects);
    // $('#output').append(obj);
    },
    dataType : 'text'
    });}
}

Trong Servlet

request.getParameter("input")
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.