---
description: https://viblo.asia/p/cross-domain-ajax-requests-l5y8Rr52vob3
---

# Cross-Domain Ajax Requests



## Cross-Domain Ajax Requests

[JavaScript](https://viblo.asia/tags/javascript)[Server](https://viblo.asia/tags/server)[Ruby on Rails](https://viblo.asia/tags/ruby-on-rails)

## Mở đầu <a href="#mo-dau-0" id="mo-dau-0"></a>

**Same-origin policy** là một khái niệm quan trọng trong mô hình bảo mật thông tin web. Đây là một chính sách dành cho các browser, browser chỉ được cho phép các đoạn script ở trang web thứ nhất truy cập vào dữ liệu ở trang web thứ hai khi mà hai trang đó có cùng nguồn (same-origin). Chính sách này ra đời nhằm mục đích ngăn cản các script độc hại ở một trang lấy các thông tin nhạy cảm ở một trang khác. Tuy nhiên nó cũng gây ra một số khó khăn trong quá trình phát triển web như không cho phép gửi AJAX request từ một trang đến trang khác không cùng domain để lấy dữ liệu. Việc gửi cross-domain AJAX request đôi khi là cần thiết, chẳng hạn khi bạn có một API server và muốn cung cấp cho các dịch vụ khác truy cập qua JavaScript. Ở bài viết mình xin giới thiệu một số cách để thực hiện AJAX request giữa các domain khác nhau, hi vọng sẽ giúp ích cho các bạn.

## 1. JSONP (JSON with padding) <a href="#id-1-jsonp-json-with-padding-1" id="id-1-jsonp-json-with-padding-1"></a>

Tuy chúng ta không thể gửi được AJAX request từ một domain này đến một domain khác, nhưng chúng ta có thể dùng thẻ `<script>` với thuộc tính `src` để load JavaScript từ bất kì domain nào. **JSONP** hoạt động trên nguyên tắc này, nó tự động tạo ra thẻ `<script>` để load code JavaScript từ phía server và thực thi đoạn code đó.

Dưới đây là một ví dụ đơn giản của việc dùng JSONP để thực thi cross-domain AJAX

```
<div id="results"></div>
```

```
$.ajax({
  url: 'http://other-domain.com/data',
  dataType: 'jsonp',
  jsonpCallback: 'handleResponse' // chỉ định tên của callback function
});

function handleResponse(response) {
  $('#results').html(response);
}
```

Lúc này cặp thẻ `<script>` với nội dung như dưới sẽ được tạo ra

```
<script src="http://other-domain.com/data?callback=handleResponse&_=1437907666366"></script>
```

Ở phía server sẽ cần phải thay đổi dạng của response, nếu như bình thường server trả về response là HTML với nội dung

```
<p>GET request handled at 2015-07-26 18:06:25</p>
```

thì để dùng **JSONP** server phải trả về JavaScript với nội dung

```
handleResponse('<p>GET request handled at 2015-07-26 18:06:25</p>')
```

Ở ví dụ trên nếu như không chỉ định tên của callback function thì jQuery sẽ tự sinh ngẫu nhiên tên function (ví dụ: `jQuery1111015991538995876908_1437907666364`), khi đó code JavaScript có thể được viết lại ngắn hơn 1 chút

```
$.ajax({
  url: 'http://other-domain.com/data',
  dataType: 'jsonp'
})
.done(function(response) {
  $('#results').html(response);
});
```

#### **Ưu điểm và nhược điểm** <a href="#uu-diem-va-nhuoc-diem-2" id="uu-diem-va-nhuoc-diem-2"></a>

Ưu điểm:

* Dễ dàng implement ở phía client
* Sử dụng được với tất cả các browser

Nhược điểm

* Cần sự hỗ trợ phía server
* Chỉ gửi được GET request
* Không thể gửi header kèm theo

## 2. CORS <a href="#id-2-cors-3" id="id-2-cors-3"></a>

**Cross-origin resource sharing (CORS)** là một cơ chế đặc biệt cho phép resource đặt tại một domain này có thể được request từ một domain khác với domain đó. **CORS** tận dụng HTTP headers để hỗ trợ browser quyết định xem trường hợp nào có thể cho phép thực hiện cross-domain request và trường hợp nào không. Về cơ bản, khi chúng ta thực hiện 1 cross-origin request, browser sẽ gửi kèm header `Origin` có giá trị là domain hiện tại. Ví dụ:

```
Origin: http://my-domain.com
```

Ở phía server khi tiếp nhận request sẽ kiểm tra xem domain này có được phép truy cập không, nếu được thì server trả về response với header `Access-Control-Allow-Origin`. Giá trị của header này cũng chính là domain nơi mà script gửi cross-origin request được thực thi, trong trường hợp này là

```
Access-Control-Allow-Origin: http://my-domain.com
```

Khi nhận được response từ server, browser kiểm tra xem header `Access-Control-Allow-Origin` có tồn tại hay không và có trùng với domain hiện tại hay không, nếu ok thì sẽ cho phép xử lí response.

Để có thể dùng được **CORS** thì chúng ta cần sự hỗ trợ từ cả 2 phía client và server.

*   Về phía client, CORS hiện nay đã được hỗ trợ bởi hầu hết các trình duyệt, chi tiết các bạn có thể xem ở hình dưới (nguồn: [http://caniuse.com/#search=cors](http://caniuse.com/#search=cors))

    ![CORS Support](https://viblo.asia/uploads/1785055f-8235-4624-a886-532e4d0706d9.png)

    Khi thực hiện **CORS** request, chúng ta dùng `XMLHttpRequest` trên Firefox 3.5+, Chrome, Safari 4+, IE 10+ và `XDomainRequest` trên IE 8, 9. Do jQuery đã hỗ trợ `XMLHttpRequest` một cách tự động nên chúng ta có thể tạo AJAX request như bình thường mà không phải thay đổi code JavaScript.

```
	$.ajax({
	  url: 'http://other-domain.com/data'
	})
```

Đối với `XDomainRequest` thì xử lí hơi phức tạp hơn một chút, do không được jQuery hỗ trợ nên chúng ta cần dùng plugin ngoài nếu như không muốn phải tạo `XDomainRequest` một cách thủ công. Có một thư viện hỗ trợ rất tốt việc này đó là [`jquery.xdomainrequest.js`](https://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest). Khi đã dùng thư viện này thì chúng ta cũng có thể tạo AJAX request một cách bình thường, tuy nhiên `XDomainRequest` có những hạn chế như sau:

* Protocol chỉ có thể là HTTP hoặc HTTPS
* Scheme của URL nơi gửi request và nơi nhận request phải giống nhau (Vd: script đặt ở [http://my-domain.com](http://my-domain.com/) sẽ không thể gửi request đến [https://other-domain.com](https://other-domain.com/) do hai URL khác scheme)
* HTTP method chỉ có thể là GET hoặc POST. Khi gửi POST request, `Content-Type` của header chỉ có thể là `text/plain`
* Không thể gửi kèm custom header
* Về phía server, cần phải tiến hành kiểm tra xem domain của request có hợp lệ hay không và gửi về header `Access-Control-Allow-Origin` kèm theo response. Nếu như server được phát triển bằng Rails thì chúng ta có thể dùng gem [`rack-cors`](https://github.com/cyu/rack-cors). Sau khi cài gem, chúng ta cần thêm đoạn code sau đây vào file `config/application.rb`

```
config.middleware.insert_before 0, 'Rack::Cors' do
      allow do
        origins 'http://my-domain.com' # cho phép request từ my-domain.com, để cho phép tất cả các domain dùng '*'
        resource '*',                  # cho phép request đến tất cả các resource
        headers: :any,                 # những header được phép sử dụng
        methods: [:get, :post, :delete, :put, :patch, :options, :head] # những HTTP method được phép sử dụng
      end
    end
```

#### **Ưu điểm và nhược điểm** <a href="#uu-diem-va-nhuoc-diem-4" id="uu-diem-va-nhuoc-diem-4"></a>

Ưu điểm:

* Dễ dàng implement ở phía client
* Sử dụng được với hầu hết các browser
* Gửi được request với tất cả các loại HTTP method (trừ IE 8, 9; tùy setting phía server)
* Gửi được custom header kèm theo (trừ IE 8, 9; tùy setting phía server)

Nhược điểm

* Cần sự hỗ trợ phía server
* Trên IE 8, 9 có nhiều hạn chế
* IE 7 trở xuống không hỗ trợ (who cares? (yaoming))

## 3. Proxy <a href="#id-3-proxy-5" id="id-3-proxy-5"></a>

Về mặt ý tưởng đây có lẽ là cách đơn giản nhất. Nếu như không được phép thực hiện cross-domain AJAX request thì chúng ta chuyển sang same-domain request (haha). Nguyên lý là chúng ta sẽ chuẩn bị một proxy server đặt tại domain giống với domain gửi AJAX request, proxy này có nhiệm vụ nhận request và chuyển hướng request sang domain bên ngoài. Do thực hiện request đến domain bên ngoài ở phía server nên chúng ta sẽ không gặp phải bất kì hạn chế nào liên quan đến same-origin policy.

![Proxy](https://viblo.asia/uploads/9f3efb29-81ee-47d4-94e5-f8f4d3d0d728.png)

Việc chuẩn bị một proxy nói chung là phức tạp nhưng để demo cách thức hoạt động của phương pháp này mình xin được đưa ra 1 ví dụ với Rails. Đây là 1 cách implement đơn giản, proxy thực chất là một controller đặt tại `http://my-domain.com/ajax`, có nhiệm vụ nhận và chuyển hướng request đến `http://other-domain.com`.

```
# routes.rb
match "ajax/:path", to: 'ajax#create', via: :all, constraints: {path: /.*/}
```

```
class AjaxController < ActionController::Base
  skip_before_action :verify_authenticity_token

  def create
    # get request url
    path = params[:path].split('/').map{|i| ERB::Util.url_encode(i)}.join('/')
    url = 'http://other-domain.com' + '/' + path

    # remove some unnecessary parameters
    data = params.except(:controller, :action, :path)

    # get custom headers
    request_headers = {
      'CustomHeader' => request.headers['CustomHeader']
    }

    # send cross-domain request
    case env['REQUEST_METHOD']
    when 'GET'
      response = RestClient.get(url + '?' + data.to_query, request_headers)
    when 'POST'
      response = RestClient.post(url, data, request_headers)
    when 'PUT', 'PATCH'
      response = RestClient.put(url, data, request_headers)
    when 'DELETE'
      response = RestClient.delete(url + '?' + data.to_query, request_headers)
    end

    # return response
    render html: response.body.html_safe
  end
end
```

Khi gửi AJAX request thì chúng ta chỉ cần thay đổi url là được, lúc này request đã trở thành same-domain request (yeah)

```
$.ajax({
  url: 'http://my-domain.com/ajax/data'
})
```

#### **Ưu điểm và nhược điểm** <a href="#uu-diem-va-nhuoc-diem-6" id="uu-diem-va-nhuoc-diem-6"></a>

Ưu điểm:

* Sử dụng được với tất cả các browser
* Gửi được request với tất cả các loại HTTP method
* Gửi được custom header kèm theo

Nhược điểm

* Implement ở phía server phức tạp
* Tốc độ chậm do phải request 2 lần

## Tổng kết <a href="#tong-ket-7" id="tong-ket-7"></a>

### `tl;dr` <a href="#tldr-8" id="tldr-8"></a>

Bài viết cũng đã khá dài nên có lẽ xin được kết thúc tại đây. Thay cho lời kết, mình xin tổng kết lại các đặc điểm của từng phương pháp trong bảng dưới để các bạn tiện tham khảo.

|                        | JSONP     | CORS (XMLHttpRequest) | CORS (XDomainRequest) | Proxy     |
| ---------------------- | --------- | --------------------- | --------------------- | --------- |
| Implement phía client  | Đơn giản  | Đơn giản              | Đơn giản              | Không cần |
| Implement phía server  | Đơn giản  | Đơn giản              | Đơn giản              | Phức tạp  |
| Các trình duyệt hỗ trợ | Tất cả    | Hầu hết               | IE 8, 9               | Tất cả    |
| HTTP method            | GET       | Tất cả                | GET, POST             | Tất cả    |
| Gửi kèm header         | Không thể | Có thể                | Có thể (\*)           | Có thể    |

(\*) Không thể gửi custom header

Cảm ơn các bạn đã theo dõi bài viết. (thanks)

#### Tài liệu tham khảo <a href="#tai-lieu-tham-khao-9" id="tai-lieu-tham-khao-9"></a>

1. [4 jQuery Cross-Domain AJAX Request methods](http://jquery-howto.blogspot.com/2013/09/jquery-cross-domain-ajax-request.html)
2. [IE8 and CORS](http://mcgivery.com/ie8-and-cors)
3. [How to use CORS requests in Internet Explorer 9 and below](http://blog.gauffin.org/2014/04/how-to-use-cors-requests-in-internet-explorer-9-and-below)
4. [XDomainRequest - Restrictions, Limitations and Workarounds](http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx)
