---
description: https://viblo.asia/p/http-caching-6BAMYknzvnjz
---

# 5. HTTP Caching (ok)

> Xem thêm bài viết [https://viblo.asia/p/tim-hieu-ve-http-caching-djeZ1BRJlWz](https://viblo.asia/p/tim-hieu-ve-http-caching-djeZ1BRJlWz)



Trong các ứng dụng web hiện nay chắc hẳn các bạn đã khá quen thuộc với từ khóa `Cache`. Chúng ta có cache lại kết quả của các câu truy vấn (`queries`) để trả về kết quả nhanh hơn, ngoài ra chúng ta có thể cache lại các views để render nhanh hơn,...

Bài viết hôm nay tôi muốn giới thiệu với các bạn về `HTTP Caching`, về định nghĩa, cơ chế hoạt động, tại sao nên sử dụng và cách config server để thực hiện việc cache này.

Đối với một ứng dụng web nào đó thì vấn đề nhức nhối luôn được quan tâm hàng đầu đó là `tốc độ`. Bất kỳ ai trong chúng ta đều ghét phải chờ đợi ![😛](https://twemoji.maxcdn.com/2/72x72/1f61b.png) thử tượng tượng các bạn truy cập vào một ứng dụng web nào đó, ngồi chờ cái `loading indicator` quay vòng tròn trước mắt, ngắm từng dòng chữ, từng hình ảnh dần dần xuất hiện, rồi một lúc lâu sau mới được chiêm ngưỡng những thông tin cần thiết. Nếu là một người không có tính kiên nhẫn, tôi đảm bảo chỉ sau một vài cú click chuột, bạn đó sẽ không bao giờ dám quay lại ứng dụng của bạn lần thứ hai.

Vậy chúng ta phải làm gì trong trường hợp này? Dù chúng ta đã rất cố gắng refactor queries phía `Server` để trả về kết quả nhanh nhất tuy nhiên để tốc độ ngon hơn nữa thì đây chính là lúc cần sự trợ giúp thêm từ phía `Client`. Các file tĩnh như các file hình ảnh (**image**), các file stylesheet (**CSS**), các file javascript (**JS**), các đoạn clip,... dù đã lưu trên `Server` nhưng chúng ta đã có sự trợ giúp của `HTTP Caching` như một cầu nối giao tiếp để `Client` cũng có thể lưu lại những file tĩnh đó trên máy của người dùng. Làm được như vậy sẽ giảm tải khá nhiều cho `Server` trong việc xử lý các request đến các file tĩnh này và trả về cho `Client`.

### HTTP Caching là gì? <a href="#http-caching-la-gi-0" id="http-caching-la-gi-0"></a>

Trong nội dung bài viết này tôi xin phép không giới thiệu về giao thức `HTTP` cũng như các tham số của `HTTP Header`.

Kỹ thuật `HTTP Caching` chính là việc bạn chuyển một bản copy các tài nguyên tĩnh phía **Server** xuống lưu ở dưới **Client**. Về cơ bản, người dùng sẽ cảm nhận thấy một độ trễ rất thấp khi yêu cầu các tài nguyên tĩnh này từ phía **Server**, lưu lượng truyền đi ít hơn, số request đến **Server** ít hơn, do vậy **Server** sẽ nhàn hơn để dùng sức của mình vào những việc khác.

Chúng ta hãy cùng xem qua một request ví dụ như sau:![HTTP\_request.png](https://viblo.asia/uploads/30d0bb9e-bc5d-4fc3-8d80-779b41cb1e31.png)

1. **Client** yêu cầu file index.html
2. **Server** làm công việc đi tìm kiếm xem file index.html ở đâu
3. **Server** tìm thấy và trả về cho **Client**
4. **Client** download file và hiển thị cho người dùng.

Giả sử index.html là một file tĩnh rất ít khi thay đổi thì điều bất cập xảy ra ở đây đó là mỗi lần **Client** yêu cầu truy cập file này, **Server** đều phải lục lọi tìm kiếm rồi bắt buộc **Client** phải download thì mới sử dụng được. Việc làm này đang làm lãng phí thời gian của **Server** và thời gian của người sử dụng.

### Tại sao cần sử dụng HTTP Caching? <a href="#tai-sao-can-su-dung-http-caching-1" id="tai-sao-can-su-dung-http-caching-1"></a>

Các lợi ích chính mà `HTTP Caching` mang lại có thể kể ra như sau:

1. Giúp cho ứng dụng Web load nhanh hơn (giảm thời gian trễ).
2. Giảm băng thông sử dụng.
3. Giảm số lần truy cập lên Server.

### HTTP Caching hoạt động như thế nào? <a href="#http-caching-hoat-dong-nhu-the-nao-2" id="http-caching-hoat-dong-nhu-the-nao-2"></a>

Tất cả các hệ thống cache đều sử dụng chung các nguyên tắc để xác định thời điểm cung cấp đối tượng (tất nhiên là các đối tượng đó đã được lưu trong Cache). Trường hợp đối tượng chưa được lưu trong Cache thì sẽ phát sinh yêu cầu đến **Server** để lấy thông tin của đối tượng đó.

Một số nguyên tắc được thiết lập trong giao thức `HTTP` (1.0, 1.1) và một số nguyên tắc khác sẽ được thiết lập bởi những người quản trị cache (hiểu nôm na có thể là người dùng Browser có thể đặt các setting cho Cache thông qua các trình duyệt như Safari, Firefox, Chrome,...)

Một số nguyên tắc chung có thể được liệt kê ra như sau:

1. Nếu trong `Response Header` từ **Server** có thông tin không cho phép lưu cache, **Browser** sẽ không được quyền lưu.
2. Nếu đối tượng trong request có yêu cầu xác thực hay bảo mật (VD: HTTPS), nó sẽ không được phép lưu trong `shared cache`.
3. Nếu một đối tượng được gọi là "mới" được phép lưu cache ở **Browser** mà không cần xác thực với **Server** thì nó cần phải có thông tin về khoảng thời gian tồn tại.
4. Nếu một đối tượng được gọi là "cũ", **Server** sẽ nhận được yêu cầu xác thực đối tượng và báo cho **Client** biết nó còn giá trị sử dụng hay không. Trong trường hợp này **Client** và **Server** chỉ trao đổi với nhau thông qua việc truyền và gửi `message` để confirm nên lưu lượng sẽ giảm đi rất nhiều so với việc gửi cả một đối tượng có dung lượng xác định.

Như vậy các bạn có thể thấy thay bằng việc Client cứ phải download những file hàng trăm KB hay đơn vị MB với mỗi request lên Server thì giờ đây chỉ bằng việc trao đổi với nhau vài `message` với dung lượng cực kỳ nhỏ, Client đã có thông tin mà mình cần, đồng thời Server cũng đỡ cực hơn trong việc xử lý request từ Client. `Message` mà tôi nói đến chính là những tham số mà `HTTP Header` hỗ trợ. Bây giờ chúng ta sẽ cùng xem chi tiết đó là những tham số gì và nó hoạt động ra sao.

### Các giải pháp HTTP Caching <a href="#cac-giai-phap-http-caching-3" id="cac-giai-phap-http-caching-3"></a>

Sau đây tôi xin giới thiệu với các bạn một số giải pháp thực hiện `HTTP Caching` sử dụng các tham số của `HTTP Header`. Các bạn nên lưu ý rằng các tham số này chỉ mang tính chất truyền thông điệp giữa Client và Server, đồng thời cũng là thông tin đối sánh để Client biết lấy dữ liệu từ `Cache` hay lại phải request lên Server để lấy dữ liệu mà mình cần. `Cache` mà tôi nói đến ở đây chính là một vùng lưu trữ nào đó trên ổ cứng của máy bạn mà Client (hay chính xác là Browser) sử dụng để lưu trữ các file tĩnh từ phía Server trả về.

#### Tham số `Last-Modified` <a href="#tham-so-last-modified-4" id="tham-so-last-modified-4"></a>

Khi lần đầu tiên Client request một file (VD: logo.png) từ Server, file này chưa hề được lưu trong `Cache` ở Client, Server sẽ trả về cho Client file đó kèm theo tham số `Last-Modified` trên `Response Headers` để xác định thời điểm cuối cùng file đó được sửa đổi.

![header\_last\_modified.png](https://viblo.asia/uploads/75c0c1ae-768f-4a7d-a09c-825d4bfb490b.png)

Nhìn vào hình ảnh trên ta có thể thấy file được chỉnh sửa lần cuối cùng vào `Thứ 2, ngày 1/6/2015, lúc 2 giờ 57 phút`, `Status Code` là **200 OK**. Sau đó Client sẽ lưu lại file này vào Cache và đồng thời lưu luôn thông tin ngày thay đổi cuối cùng của file.

Tiếp theo, Client lại thực hiện request một lần nữa lên Server. Trước hết hãy cùng xem qua sơ đồ hoạt động ví dụ sau:

![HTTP-caching-last-modified\_1.png](https://viblo.asia/uploads/a147a1fe-7ab4-4350-ad00-5eef4635e83e.png)(Thông số trong ảnh chỉ mang tính chất minh họa)

1. Client gửi yêu cầu lấy file logo kèm theo điều kiện ngày thay đổi của file đó phải chính xác là `Thứ 2, ngày 1/6/2015, lúc 2 giờ 57 phút` (tham số `If-Modified-Since` trong `Request Headers`).
2. Server đi tìm file logo và kiểm tra xem ngày thay đổi có đúng với thông tin Client gửi lên không.
3. Nếu Server check OK, Client sẽ nhận được `Status Code` là **304 Not Modified**.
4. Client không phải download file logo đó một lần nữa từ Server mà chui thẳng vào Cache để lấy file đó ra dùng.

Hình ảnh thực tế của request này như sau:![header\_last\_modified\_1.png](https://viblo.asia/uploads/69099e9b-6379-4a7b-80fa-c7439c2938c8.png)

Như vậy câu hỏi đặt ra là: Nếu `If-Modified-Since` trong **Request Headers** và `Last-Modified` trong **Response Headers** trước đó không trùng nhau thì sao?

Câu trả lời hết sức đơn giản, Client sẽ download file mới, lưu Cache đồng thời lưu thông tin ngày thay đổi mới. Quy trình sẽ lại lặp lại từ bước đầu tiên tôi nói phía trên.

* **Ưu điểm:**
  * Nếu file không có sự thay đổi, Server chỉ phải gửi về status **304** với thông báo **Not Modified** mà không cần phải gửi lại toàn bộ nội dung file cho Client.
* **Nhược điểm:**
  * Vẫn phải kết nối đến Server để nhận thông điệp.
  * Server vẫn phải xử lý kiểm tra thời gian thay đổi file.
  * Nếu có phát sinh sai lệch thời gian trên Server thì nội dung file vẫn được gửi xuống Client download mặc dù file đó không hề thay đổi.

#### Tham số `Etag` <a href="#tham-so-etag-5" id="tham-so-etag-5"></a>

Cơ chế hoạt động của tham số `Etag` cũng gần gần tương tự với `Last-Modified` chỉ khác ở điểm `Etag` có giá trị là một chuỗi ký tự đại diện cho file.

`Etag` có thể là một chuỗi **Hash** hay **Footprint**. Trong nội dung bài viết này tôi xin phép không đi sâu vào việc **Footprint** được sinh ra thế nào hay theo thuật toán nào, ta chỉ hiểu đơn giản là với mỗi file, Server sẽ sinh ra 1 chuỗi ký tự riêng và duy nhất để đại diện cho file đó (mà chúng ta gọi là tham số `Etag`). Nếu bạn thay đổi nội dung file thì `Etag` chắc chắn sẽ thay đổi giá trị.

Chúng ta hãy cùng phân tích sơ đồ giả định sau:![HTTP\_caching\_if\_none\_match.png](https://viblo.asia/uploads/b410ce20-fb40-4af7-85f8-cde9a0ffa417.png)(Thông tin trong ảnh chỉ mang tính chất minh họa)

1. Client yêu cầu file logo từ Server, và gửi kèm theo tham số `If-None-Match` (chính là giá trị của **Etag**) trong `Request Headers` đã lưu trước đó (có thể hiểu là từ lần request đầu tiên).
2. Server tìm kiếm file logo và kiểm tra mã **Etag**.
3. Nếu Server check OK, Client sẽ nhận được `Status Code` là **304 Not Modified**.
4. Client không phải download file logo đó một lần nữa từ Server mà chui thẳng vào Cache để lấy file đó ra dùng.

Hình ảnh thực tế với `Request Headers` và `Response Headers` như sau: ![header\_etag.png](https://viblo.asia/uploads/5a0e0bfb-8254-4b79-b5b0-08ae8aa242e1.png)

Ta có thể nhận thấy ưu và nhược điểm của tham số `Etag` này cũng gần tương tự với tham số `Last-Modified`, ta vẫn cứ phải chui lên Server để kiểm tra xem file có sự thay đổi hay không.

#### Tham số `Expires` <a href="#tham-so-expires-6" id="tham-so-expires-6"></a>

Để khắc phục nhược điểm của 2 tham số trên, chúng ta hãy cùng tiếp tục tìm hiểu về tham số `Expires`. Để tránh việc request một file nào đó từ lần thứ 2 trở đi cứ phải chui lên Server hỏi và kiểm tra, với lần đầu tiên Client request, Server sẽ trả về nội dung file kèm theo thông tin `Expires` trong `Response Headers`.

Thông tin trong tham số `Expires` chính là thời điểm hết hạn của file đó, sau thời điểm này nếu Client vẫn muốn sử dụng file thì phải lên Server để download lại cũng như cập nhật thông tin `Expires` mới, còn nếu chưa hết hạn thì Client sẽ chỉ việc lấy thông tin từ Cache để sử dụng mà không cần hỏi lại Server.

Sơ đồ giả định như sau:![HTTP\_caching\_expires.png](https://viblo.asia/uploads/ba3cfb69-5271-4dca-bc5d-c36e64bb736e.png)

1. Client cần request file logo, tuy nhiên kiểm tra thấy thời hạn hết hiệu lực chưa đến.
2. Client tự lấy file đó từ Cache mà không cần lên Server hỏi.

* **Ưu điểm** của cách làm này đó là chúng ta hạn chế được việc Client sẽ phải chui lên để hỏi Server, khi đã làm được như vậy thì Server sẽ rảnh hơn để dành sức và tài nguyên phục vụ những thao tác khác. Ngoài ra thì tham số `Expires` cũng đã được hỗ trợ từ chuẩn `HTTP 1.0` nên nó được sử dụng khá rộng rãi hiện nay.
* **Nhược điểm** hay câu hỏi đặt ra cho cách làm này đó là nhỡ đâu trong thời điểm file đó vẫn chưa hết hạn (tức là Client vẫn đương nhiên lấy ở Cache) thì phía Server đã thực hiện thay đổi file đó. Như vậy trong trường hợp này Client sẽ không nhận được thông tin file mới ít nhất cho đến khi qua thời điểm hết hạn của file.

Có một giải pháp đã được đưa ra để giải quyết nhược điểm của cách làm này đó là đánh version cho file. Các bạn có thể thấy trong một số ứng dụng họ có đánh **version** cho các assets (js, css) dựa trên sự thay đổi của các file đó. Version có thể là một thành phần trong tên file (VD: A.123456.js) hoặc có thể ở dạng `query string` (VD: a.js?v=123456).

Cách đánh version như vậy sẽ giải quyết được nhược điểm của phương pháp này ở chỗ khi Server cập nhật file mới với version mới, Client sẽ download file mới kèm theo `Expires` của file mới và từ đó làm việc với file mới chứ không phải file cũ đã được Cache.

#### Tham số `Cache-Control` <a href="#tham-so-cache-control-7" id="tham-so-cache-control-7"></a>

Cách dùng tham số `Cache-Control` về cơ bản lại giống với `Expires` tuy nhiên cách cấu hình uyển chuyển và linh hoạt hơn. Thay vì việc đặt ra thời gian cụ thể hết hạn thì tham số `Cache-Control` này có thể khai báo thay cho một thông báo kiểu như: "File này sẽ hết hạn sử dụng trong vòng 1 tháng tính từ thời điểm hiện tại". Cụ thể thì tham số này hỗ trợ cấu hình các thông tin như sau:

* `max-age=[seconds]`: định nghĩa khoảng thời gian tối đa mà file được cache (tính từ thời điểm request file), tính bằng đơn vị giây, một số thông tin chuyển đổi cơ bản như sau:
  * 1 ngày = 86400 giây
  * 1 tuần = 604800 giây
  * 1 tháng = 2629000 giây
  * 1 năm = 31636000 giây
* `s-maxage=[seconds]`: tương tự như `max-age` nhưng thông số này chỉ được sử dụng cho `shared caches` (VD: **Proxy caches**).
* `public`: cho phép file được cache bởi các **Proxy** và các server trung gian.
* `private`: file có giá trị khác nhau cho các Client khác nhau, **Browser** có thể cache nhưng **Proxy** thì không.
* `no-cache`: không được phép cache file này, thường được sử dụng cho những trang kết quả tìm kiếm, mặc dù URL là giống nhau nhưng nội dung có thể thay đổi khác nhau.
* `no-store`: chỉ thị cho Client biết không được phép tạo một bản copy của file và lưu trữ dưới bất kỳ hình thức nào.
* `must-revalidate`: chỉ thị bắt buộc phải validate lại với Server khi sử dụng file đã được cache.
* `proxy-revalidate`: tương tự như `must-revalidate` nhưng chỉ áp dụng cho các **Proxy caches**.

Ví dụ sử dụng:

```
Cache-Control: max-age=3600, must-revalidate
```

### Cách config server hoạt động với HTTP Caching <a href="#cach-config-server-hoat-dong-voi-http-caching-8" id="cach-config-server-hoat-dong-voi-http-caching-8"></a>

Phần này tôi xin giới thiệu cách config `HTTP Caching` hoạt động với Server `Apache` (hệ điều hành UBUNTU). Cách config với `Nginx` về cơ bản cũng tương tự (các bạn vui lòng Google để biết thêm chi tiết).

Điều kiện cần đầu tiên đó là 2 module: `mod_headers` và `mod_expires` của Apache phải được enable. Các bạn cần làm theo các bước như sau:

* **Bước 1**: kiểm tra xem 2 mod đó đã được enable chưa

```
	apachectl -t -D DUMP_MODULES
```

* **Bước 2**: enable 2 mod nói ở trên (nếu kiểm tra fail ở bước 1), sau đó nhớ restart server

```
    sudo a2enmod headers
	sudo a2enmod expires
```

Sau khi đã có 2 mod cần thiết chúng ta tiến hành cấu hình cho `HTTP Caching` hoạt động như sau:

* Sử dụng `Expires Header`:

```
    <IfModule mod_expires.c>
        ExpiresActive On
        ExpiresByType image/jpg "access 1 year"
        ExpiresByType image/jpeg "access 1 year"
        ExpiresByType image/gif "access 1 year"
        ExpiresByType image/png "access 1 year"
        ExpiresByType text/css "access 1 month"
        ExpiresByType text/html "access 1 month"
        ExpiresByType application/x-javascript "access 1 month"
        ExpiresByType application/javascript "access 1 month"
        ExpiresByType text/javascript "access 1 month"
        ExpiresByType image/x-icon "access 1 year"
        ExpiresDefault "access 1 month"
    </IfModule>
```

* Sử dụng `Cache-Control Header`:

```
    <IfModule mod_headers.c>
        <FilesMatch "\.(ico|jpg|jpeg|png|gif|css)$">
        	Header set Cache-Control "max-age=2678400, public"
        </FilesMatch>
        <FilesMatch "\.(html|htm)$">
        	Header set Cache-Control "max-age=7200, private, must-revalidate"
        </FilesMatch>
        <FilesMatch "\.(pdf)$">
        	Header set Cache-Control "max-age=86400, public"
        </FilesMatch>
        <FilesMatch "\.js$">
        	Header set Cache-Control "max-age=2678400, private"
        </FilesMatch>
    </IfModule>
```

Có một số chú ý như sau:

* Những cấu hình như tôi nói ở bên trên các bạn có thể đặt vào trong config của `Virtualhost` hoặc trong file `.htaccess` của ứng dụng của bạn.
* Tùy vào mục đích sử dụng và xử lý các bạn có thể đưa ra các cách cấu hình khác dựa trên 2 module đã nói ở trên của **Apache**.
* Nếu bạn muốn làm việc với tham số `Etag` mà đang sử dụng **Apache 2** thì hãy cẩn thận vì hình như có lỗi với version apache này. Có thể tham khảo thêm tại:
  * [http://stackoverflow.com/questions/841209/why-isnt-my-javascript-css-caching](http://stackoverflow.com/questions/841209/why-isnt-my-javascript-css-caching)
  * [https://developer.yahoo.com/blogs/ydn/high-performance-sites-rule-13-configure-etags-7211.html](https://developer.yahoo.com/blogs/ydn/high-performance-sites-rule-13-configure-etags-7211.html)

### Kết luận <a href="#ket-luan-9" id="ket-luan-9"></a>

* Trong các cách sử dụng tham số cho `HTTP Headers` để thực hiện `HTTP Caching` có thể chia làm 2 loại:
  * **Strong Caching Header**: `Expires` và `Cache-Control`.
  * **Weak Caching Header**: `Etag` và `Last-Modified`.
* Ứng dụng của bạn chỉ nên sử dụng một **Strong Caching Header** và một **Weak Caching Header** vì cơ chế tương tự nhau, nếu sử dụng cả hai cùng một lúc sẽ dư thừa.
* Nếu đồng thời sử dụng `Expires` và `Cache-Control` thì `Cache-Control` sẽ được ưu tiên hơn.
* Nên sử dụng `Cache-Control` hơn là `Expires` vì tính linh động của nó.
* Nên sử dụng `Last-Modified` hơn là `Etag` vì có thể `Etag` sẽ gặp vấn đề khi config với Apache2.
* Với các file tĩnh ít khi thay đổi thì nên đặt thời gian còn hiệu lực là khoảng 1 tháng đến 1 năm.
* Nên sử dụng thêm việc đánh version cho các file tĩnh để khắc phục nhược điểm của `Expires` cũng như `Cache-Control`.

Hy vọng các bạn tìm thấy thông tin hữu ích qua bài viết này (bow)

### Tham khảo <a href="#tham-khao-10" id="tham-khao-10"></a>

1. [Caching Tutorial](https://www.mnot.net/cache_docs/)
2. [Web cache](https://en.wikipedia.org/wiki/Web_cache)
3. [List of HTTP header fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields)
4. [A Beginner's Guide to HTTP Cache Headers](http://www.mobify.com/blog/beginners-guide-to-http-cache-headers/)
5. [Increasing Application Performance with HTTP Cache Headers](https://devcenter.heroku.com/articles/increasing-application-performance-with-http-cache-headers)
6. [HTTP caching](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en)
7. [How To Optimize Your Site With HTTP Caching](http://betterexplained.com/articles/how-to-optimize-your-site-with-http-caching/)

## Các Web Developer Đã Sử Dụng "Cache-Control" Như Thế Nào

Hầu hết các web developer có kinh nghiệm đều nắm được các chỉ thị phổ biến trong HTTP header như Status Code, Content Type, Cookie. Tuy nhiên nếu được hỏi về **Cache-Control** thì không nhiều có thể trả lời ngay được chỉ thị này dùng để làm gì. Nếu bạn là một trong số này thì bài viết này sẽ giúp bạn có được câu trả lời cho lần tiếp theo nếu như được hỏi cũng như áp dụng vào công việc thực tế.

### `Cache-Control` Directive Dùng Làm Gì

Đầu tiên đối với những bạn dev nào ít kiên nhẫn thì câu trả lời nhanh gọn cho câu hỏi trên đó là sử dụng chỉ thị `Cache-Control` trong header phục vụ hai mục đích chính:

* Tối ưu tốc độ tải trang
* Tăng tính bảo mật

Với các bạn dev nào có tính tò mò, ham tìm tòi học hỏi (rất tốt tôi tin tưởng mãnh liệt rằng các bạn sẽ có một sự nghiệp đầy triển vọng!) thì hãy kiên nhẫn kéo xuống đọc thêm một xíu nữa để có thể hiểu được chi tiết hơn về câu trả lời ở trên đây.

### Tối Ưu Tốc Độ Tải Trang

Tốc dộ tải trang là một trong những điểm quan trọng khi phát triển ứng dụng website. Theo một khảo sát gần đây thì một nửa số người dùng internet hy vọng nội dung trang web được tải về trong vòng 2 giây hoặc ít hơn. Điều này có nghĩa nếu như website của bạn không quá phổ biến, thì việc yêu cầu người dùng đợi hơn 2 giây để nội dung trang được tải về có thể khiến website gặp nguy cơ mất đi một nửa lượng truy cập.

Vậy thì câu hỏi đặt ra tiếp theo ở đây đó là `Cache-Control` giúp cải thiện tốc độ tải trang web như thế nào?

Đầu tiên chúng ta lưu ý `Cache-Control` thường được sử dụng trong **response** header (hay header trả về từ server) và chức năng của nó là để kiểm soát việc lưu trữ dữ liệu của trang trong bộ nhớ đệm của trình duyệt (hay **cache**).

Ví dụ trong một trang web bạn có một ảnh banner với kích thước khá lớn và bạn muốn trình duyệt lưu trữ ảnh này vào bộ nhớ cache của nó để lần sau khi người dùng truy cập trang thì trình duyệt chỉ cần lấy ảnh từ cache thay vì phải tải về ảnh này từ server thêm một lần nữa.

Để làm điều này bạn sẽ cần gán giá trị `Cache-Control` trong response header trả về cho các yêu cầu tải hình ảnh gửi tới server:

```
Cache-Control: max-age=31536000
```

31536000 là thời gian (với đơn vị là tính _giây_) mà dữ liệu được sẽ được lưu trữ trong bộ nhớ đệm của trình duyệt. Trong trường hợp bạn gán giá trị `max-age` cho ảnh banner trên là 1 năm, tuy nhiên vài ngày sau đó thì ảnh này được cập nhật và bạn muốn trình duyệt không sử dụng ảnh đã lưu trong cache thì bạn có thể chọn cách làm như sau:

* Sử dụng một URL khác cho banner. Ví dụ banner cũ là `http://my-site.vn/banner.jpg` thì bạn có thể đổi lại thành `http://my-site.vn/banner-new.jpg`
* Trường hợp bạn không muốn đổi URL thì bạn có thể gắn thêm query string cho URL ví dụ: `http://my-site.vn/banner.jpg?time=11112017`
* Và một số cách khác ...

Bạn cũng cần lưu ý thêm rằng một khi đã gán giá trị cho `max-age` thì trình duyệt sẽ không gửi yêu cầu tới server để lấy lại dữ liệu nữa cho tới khi dữ liệu này có tuổi thọ bằng thời gian quy định bởi `max-age`.

![Sử Dụng Cache Control Header](https://img.codehub.vn/post/75230f5ca103ab4935e268521032f702.png)

Trên đây chúng ta đã sử dụng `Cache-control` để tối ưu tốc độ tải trang cho các lần truy cập thứ 2 trở đi, vậy thì lần truy cập đầu tiên thì có thể dùng chỉ thị này để tăng tốc độ tải trang nhanh hơn được không? Câu trả lời là có!

Thông thường khi phát triển web thì chúng ta sẽ cần sử dụng các thử viện bên ngoài ví dụ như AngularJS, jQuery, Twitter Bootstrap... Nếu như bạn sử dụng các thư viện này từ [máy chủ CDN](http://www.codehub.vn/Ky-Thuat-Toi-Uu-Hoa-Toc-Do-Tai-Cua-Trang-Web) thay vì từ máy chủ của bạn sẽ giúp tăng tốc độ của trang. Lý do giải thích ở đây là bởi vì các thư viện này được dùng phổ biến bởi nhiều trang khác nhau và do đó có khả năng cao là trình duyệt đã lưu lại chúng trong bộ nhớ cache khi người dùng truy cập các trang web khác trước khi đến với trang của bạn. Và các server CDN này cũng sử dụng `Cache-Control` để lưu trữ dữ liệu trong cache của trình duyệt.

### Tăng Tính Bảo Mật Của Trang

Một công dụng khác của `Cache-Control` đó là giúp tăng tính bảo mật của trang web. Ví dụ trường hợp bạn xây dựng một ứng dụng web có tính năng thanh toán trực tuyến và người dùng cần nhập thông tin về phương thức thanh toán của họ (ví dụ như thẻ ngân hàng) để có thể thực hiện việc mua hàng và thanh toán online.

Trong trường hợp người dùng sử dụng một máy tính ở nơi công cộng và truy cập trang chứa thông tin phương thức thanh toán của họ. Xem xong rồi thì người dùng này bấm vào logout để đăng xuất. Lúc này nếu như có một người khác tiếp theo sử dụng máy tính trên và họ bấm nút quay lại (biểu tượng dấu mũi tên quay lại ở phía góc trái thanh công cụ của trình duyệt) thì bạn cần đảm bảo rằng trình duyệt không hiển thị thông tin thanh toán của người dùng đã logout ngay trước đó.

Để làm việc này thì trong response header trả về cho truy cập tới trang chứa thông tin phương thức thanh toán của người dùng, bạn sẽ gán giá trị cho `Cache-Control` như sau:

```
Cache-Control: no-cache, no-store, must-revalidate
```

Khi đó trình duyệt sẽ không lưu dữ liệu của trang này trong cache và khi người kế tiếp bấn vào nút quay lại để về trang trước đó thì trình duỵệt sẽ gửi lại yêu cầu tới server để lấy thông tin. Tất nhiên do người dùng trước đã đăng xuất server của bạn sẽ yêu cầu người dùng tiếp theo đó nhập thông tin tài khoản của họ (nếu có) để vào được trang.

### Kết Luận

Mặc dù ít được biến đến hơn so với các chỉ thị header phổ biến khác tuy nhiên `Cache-Control` lại đóng một vài trò vô cùng quan trọng trong lập trình ứng dụng web đặc biệt là vấn đề bảo mật. Ở bài viết này chúng ta đã tìm hiểu về hai trong số các công dụng phổ biến của chỉ thị `Cache-Control` trong giao thức HTTP. Hy vọng bạn sẽ có thể sử dụng những kiến thức này để cải thiện tốt hơn các ứng dụng đã có hoặc áp dụng chúng một cách triệt trong quá trình phát triển các ứng dụng web các lần tới.

## Tìm hiểu về http caching

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

Trong các ứng dụng web, caching là một vấn đề cực kỳ quan trọng để tối ưu hóa performance. Có rất nhiều cách để caching -Page Caching -Action Caching -Fragment Caching -Russian Doll Caching -Managing dependencies -Low-Level Caching -SQL Caching

Nhưng hôm nay mình xin giới thiệu về `http caching`

Thực ra nếu làm web, với client là `browser` (chrome, firefox)... thì các bạn có thể bỏ qua bài viết này, hoặc đọc để hiểu rõ hơn. Vì browser đã tự có cơ chế caching này rồi, còn nếu làm ứng dụng APP (Android, IOS), có thể sẽ có bạn không để ý về `http caching`, dẫn tới việc mất thời gian đi tìm các giải pháp phức tạp.

### 1. Tại sao dùng `http caching` <a href="#id-1-tai-sao-dung-http-caching-1" id="id-1-tai-sao-dung-http-caching-1"></a>

Các trình duyệt xây dựng trong nó hệ thống cache. Mỗi khi ta nhấn nút Back trên trình duyệt thì nó đọc lại cho ta trang ta vừa xem trong cache của nó. Đối tượng cache có thể là các file tạm thời trên đĩa hoặc có thể là trong bộ nhớ trong.

Tư tưởng là khi request 1 file từ host, trước hết ta sẽ load về `HTTP header` của file. Nếu là lần đầu hoặc, `HTTP header` thay đổi so với lần trước thì sẽ lấy file từ host về.

Ngược lại, sẽ lấy luôn file trên local.

Dưới đây là một gói HTTP header thông thường

```
HTTP/1.1 200 OK

Date: Fri, 08 Nov 2013 04:41:40 GMT

Server: Apache/2.2.15 (CentOS)

Last-Modified: Sun, 11 Aug 2013 02:41:48 GMT

Etag: "1c253d-6c2-4e3a2f30b418d"

Accept-Ranges: bytes

Content-Length: 1730

Cache-Control: max-age=1209600

Expires: Fri, 22 Nov 2013 04:41:40 GMT

Keep-Alive: timeout=15, max=100

Connection: Keep-Alive

Content-Type: text/javascript
```

### 2. Last-Modified <a href="#id-2-last-modified-2" id="id-2-last-modified-2"></a>

Ta sẽ so sánh thời gian sửa đổi cuối cùng để xác định xem file này đã cập nhật nội dung nào mới chưa, nếu có thì tải về:

B1: Client —-Last-modified——–> Server

B2: Server: Lục file đó, kiểm tra Last-modified có khớp vs Last-modified của client không, nếu khớp, tới B4.

B3: Nếu không khớp (tức là đã có cập nhật mới), gửi HTTP response chứa file yêu cầu về cho client. Tới B5.

B4: Gửi HTTP response chứa Status Code: 304 Not Modified.

Bước 5: Kết thúc.

### 3. Etag (Entity Tags) <a href="#id-3-etag-entity-tags-3" id="id-3-etag-entity-tags-3"></a>

Khuyết điểm khi dùng Last-Modified:

* Múi giờ và thời gian của Server phải trùng với của client.
* Vẫn yêu cầu xử lý trên server.

Do đó, có một cách khác là dùng Etag, nó là một chuỗi duy nhất được sinh ra bằng hash hoặc footprint, tức là mỗi lần có thay đổi là Etag cũng sẽ bị đổi theo.

B1: Client —–Etag—-> Server

B2: Server kiểm tra xem Etag có khớp không, nếu khớp chuyển tới B4.

B3: Nếu không khớp (có cập nhật mới), gửi HTTP response chứa file yêu cầu về cho client. Tới B5.

B4: Gửi HTTP response chứa Status Code: 304 Not Modified.

Bước 5: Kết thúc.

### 4. Expires <a href="#id-4-expires-4" id="id-4-expires-4"></a>

Khuyết điểm của Etag: Vẫn phải kết nối tới server để kiểm tra trong mỗi request.

Ta cần tìm một cách nào đó để browser tự biết khi nào cần tải về. Expires trong HTTP Header cũng giống như khi ta đặt expire time cho session. Browser sẽ tự biết khi nào file đó hết hạn để tài về.

B1: Browser: check Experies, nếu còn trong Expiration thì chuyển tới bước 4.

B2: Nếu đã hết hạn (expired) thì gửi request tới server yêu cầu gửi file về.

B3: Server —–file requested —–> Client. Tới bước 4.

Bước 4: Kết thúc.

Expires headers sử dụng tốt nhất cho caching các hình ảnh tĩnh (như Navigation bar và các button). Do đó ta có thể đặt một khoảng thời gian expired lớn.

### 5. Cache-Control <a href="#id-5-cache-control-5" id="id-5-cache-control-5"></a>

Rõ ràng việc sử dụng Expires khiến ta giảm được một lượng đáng kể công việc cho server nhưng thay vào đó, Browser vẫn phải thực hiện những phép tính thời gian và vẫn phải đòi hỏi sự đồng bộ giữa đồng hồ của Web server và của cache.

Mục đích của Cach-Control là để các nhà phát triển có thể kiểm soát tốt hơn nội dung trang web và giới hạn địa chỉ cho Expires.

Cache-Control header bao gồm:

* max-age=\[seconds]: gán thời gian tối đa trước khi file hết hạn. Giá trị này cũng tương tự như Expires và được tính bằng giây.
* s-maxage=\[seconds] : cũng tương tự như max-age nhưng chỉ áp dụng cho những cache được chia sẻ (ví dụ như proxy).
* public: Đánh dấu rằng gói response này có thể cached. Thông thường người ta set thuộc tính public cho những thông tin không bảo mật và báo cho trình duyệt biết rằng có thể cache. Thông thường, nếu yêu cầu xác thực HTTP thì response sẽ tự động private.
* private: Chỉ cho phép cache thông tin của một user nào đó vào trình duyệt của họ. Và những shared cache (như proxy) thì không được phép.

### 6. Các lưu ý để xây dựng một web site cache hiệu quả: <a href="#id-6-cac-luu-y-de-xay-dung-mot-web-site-cache-hieu-qua-6" id="id-6-cac-luu-y-de-xay-dung-mot-web-site-cache-hieu-qua-6"></a>

* Sử dụng những URL duy nhất: Đây là nguyên tắc vàng trong caching. Nếu ta sử dụng một nội dung giống nhau cho trang khác nhau, những người dùng khác nhau hoặc những website khác nhau. Ta nên sử dụng các URL giống nhau. Đây là cách dễ dàng và hiệu quả nhất để trang web của ta có thể dễ dàng caching. Ví dụ, nếu ta sử dụng /index.html để chỉ tới một nội dung thì phải luôn luôn sử dụng nó.
* Sử dụng một thư viện ảnh chung: như vậy những phần tử khác nhau sẽ dẫn đến những thư viện ảnh đó từ những vị trí khác nhau.
* Cache lại những hình ảnh và những trang không thay đổi thường xuyên: Sử dụng header Cache-Control: max-age và set cho nó một giá trị thật rộng.
* Nếu một resource (đặc biệt là một file để download) bị thay đổi, hãy đổi tên của nó: Với cách này, ta có thể giữ nó lâu hơn mà vẫn đảm bảo được phiên bản chính xác đang được cung cấp bằng cách mỗi lần cập nhật phiên bản mới, ta chỉ cần sửa đường dẫn tới nó một chút. Và trang web dẫn tới nó phải là duy nhất và thời gian quá hạn của trang web này phải ngắn.
* Không thay đổi những file không cần thiết: Khi ta update web site, chỉ thay thế những file được sửa đổi, không nên update toàn bộ trang web vì những file không bị sửa đổi thì thuộc tính Last-Modified của nó sẽ thay đổi và trình duyệt sẽ phải cache lại nó.
* Chỉ sử dụng cookies khi cần thiết: Cookies rất khó cache và không cần thiết trong nhiều trường hợp. Nếu bạn bắt buộc phải sử dụng cookie thì nên hạn chế dùng nó cho những trang web động.
* Hạn chế sử dụng SSL: Vì những trang mã hóa không được cache lại, do đó ta chỉ sử dụng chúng trong những trường hợp bắt buộc và không nên sử dụng nhiều hình ảnh cho những trang SSL.
* Kiểm tra trang web của bạn với: [http://redbot.org/](http://redbot.org/) Nó sẽ giúp bạn hiểu hơn những nội dung được trình bày bên trên để optimize trang web của bạn.

### 6. Kết luận <a href="#id-6-ket-luan-7" id="id-6-ket-luan-7"></a>

Với framework `Ruby on rails` mọi thứ đã sẵn có, chúng ta cũng không cần quan tâm đến điều này lắm. Tuy nhiên, nếu bạn làm APP và gặp phải vấn đề performance khi tải đi tải lại 1 resource (file pdf, img, video...) hãy nghĩ tới `http caching`.

Tài liệu tham khảo [https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching)

Hi vọng bài viết này hữu ích.&#x20;
