Reading Time: 18 minutes

HTTP (Hypertext Transfer Protocol) là một giao thức ứng dụng đã trở thành tiêu chuẩn thực tế cho việc giao tiếp trên World Wide Web kể từ khi được phát minh vào năm 1989. Kể từ phiên bản HTTP/1.1 được phát hành năm 1997 cho đến gần đây, giao thức này hầu như không có nhiều thay đổi lớn.

HTTP/1.1 và HTTP/2

Tuy nhiên vào năm 2015, một phiên bản cải tiến có tên HTTP/2 đã ra đời, mang theo nhiều phương pháp giúp giảm độ trễ, đặc biệt hiệu quả trong bối cảnh thiết bị di động và các nội dung nặng như đồ họa hay video ngày càng phổ biến. HTTP/2 từ đó đã trở nên ngày càng phổ biến, với một số thống kê cho thấy khoảng một phần ba website trên toàn thế giới hiện đã hỗ trợ giao thức này.

Trong bối cảnh công nghệ đang thay đổi nhanh chóng, các lập trình viên web sẽ hưởng lợi đáng kể nếu hiểu được những khác biệt kỹ thuật giữa HTTP/1.1 và HTTP/2. Điều này giúp họ đưa ra quyết định đúng đắn, hiệu quả và cập nhật theo các thực hành tốt nhất trong phát triển web hiện đại.

Sau khi đọc bài viết này, bạn sẽ nắm được các khác biệt chính giữa HTTP/1.1 và HTTP/2, đặc biệt là những thay đổi kỹ thuật mà HTTP/2 áp dụng để tạo nên một giao thức web hiệu quả hơn.

Bối cảnh

Để hiểu rõ những thay đổi cụ thể mà HTTP/2 đã thực hiện so với HTTP/1.1, trước hết chúng ta cần điểm qua lịch sử phát triển và cách hoạt động cơ bản của từng phiên bản.

HTTP/1.1

HTTP được phát triển bởi Tim Berners-Lee vào năm 1989 như một tiêu chuẩn giao tiếp cho World Wide Web. Đây là một giao thức ứng dụng ở tầng cao, cho phép trao đổi thông tin giữa máy client và server web có thể là tại chỗ hoặc từ xa. Trong quá trình này, máy khách gửi một yêu cầu dạng văn bản đến máy chủ bằng cách gọi một phương thức như GET hoặc POST. Máy chủ sau đó sẽ phản hồi bằng cách gửi lại một tài nguyên, ví dụ như trang HTML, về cho máy khách.

Ví dụ, giả sử bạn truy cập một website có tên miền www.example.com. Khi bạn điều hướng đến địa chỉ URL này, trình duyệt web trên máy tính của bạn sẽ gửi một yêu cầu HTTP dưới dạng một thông điệp văn bản, tương tự như sau:

GET /index.html HTTP/1.1
Host: www.example.com

Yêu cầu này sử dụng phương thức GET, nghĩa là nó yêu cầu máy chủ cung cấp dữ liệu, được xác định bởi phần Host:trong nội dung yêu cầu. Đáp lại yêu cầu này, máy chủ của example.com sẽ gửi về cho máy khách một trang HTML, cùng với các tài nguyên khác như hình ảnh, stylesheet ****hoặc các tệp khác được tham chiếu trong trang HTML đó.

Lưu ý rằng không phải tất cả tài nguyên đều được trả về cho client ngay trong lần gọi đầu tiên để lấy dữ liệu. Các request và respone sẽ được gửi qua lại giữa server và client cho đến khi trình duyệt web nhận đủ tất cả các tài nguyên cần thiết để hiển thị nội dung của trang HTML trên màn hình của bạn.

Bạn có thể hình dung quá trình trao đổi request–response này như là một application layer trong internet protocol stack. Lớp này nằm trên lớp transfer layer thường sử dụng Transmission Control Protocol và trên các networking layers sử dụng Internet Protocol.

Protocol Stack

Có rất nhiều điều để nói về các tầng thấp hơn trong mô hình giao thức Internet, nhưng để hiểu tổng quan về HTTP/2, bạn chỉ cần nắm mô hình phân tầng trừu tượng này và vai trò của HTTP trong đó.

Sau phần giới thiệu cơ bản về HTTP/1.1, giờ chúng ta sẽ tìm hiểu về sự phát triển ban đầu của HTTP/2.

HTTP/2

HTTP/2 bắt nguồn từ giao thức SPDY, được phát triển chủ yếu bởi Google với mục tiêu giảm độ trễ khi tải trang web, thông qua các kỹ thuật như nén dữ liệu, truyền song song và ưu tiên gói tin. SPDY chính là tiền thân cho HTTP/2 khi nhóm công tác httpbis của IETF chính thức chuẩn hóa giao thức này, dẫn đến việc HTTP/2 được công bố vào tháng 5 năm 2015.

Ngay từ đầu, chuẩn này đã được nhiều trình duyệt hỗ trợ, bao gồm: Chrome, Opera, Internet Explorer và Safari. Chính sự hỗ trợ này đã giúp HTTP/2 nhanh chóng được áp dụng rộng rãi, đặc biệt với các website mới.

Một trong những đặc điểm kỹ thuật nổi bật nhất giúp HTTP/2 khác biệt với HTTP/1.1 là lớp binary framing layer, được xem là một phần trong tầng ứng dụng của mô hình giao thức Internet. Khác với HTTP/1.1, vốn giữ mọi yêu cầu và phản hồi ở dạng văn bản thuần túy HTTP/2 sử dụng lớp đóng gói nhị phân này để gói tất cả thông điệp ở định dạng nhị phân, trong khi vẫn giữ nguyên cấu trúc HTTP truyền thống, như: phương thức verbs, methods và headers. Một application level API vẫn sẽ tạo ra các message theo định dạng HTTP thông thường, nhưng lớp bên dưới sẽ chuyển đổi các message này thành dạng binary. Việc này đảm bảo rằng các ứng dụng web được tạo ra trước khi có HTTP/2 vẫn có thể hoạt động bình thường khi tương tác với giao thức mới.

Việc chuyển đổi message sang dạng nhị phân cho phép HTTP/2 áp dụng những cách tiếp cận mới trong việc truyền dữ liệu, điều mà HTTP/1.1 không hỗ trợ. Sự khác biệt này chính là cốt lõi của những điểm khác nhau trong thực tiễn sử dụng giữa hai giao thức. Phần tiếp theo sẽ trình bày mô hình truyền tải của HTTP/1.1, sau đó là những mô hình mới mà HTTP/2 cho phép triển khai.

Mô hình truyền dữ liệu

Như đã đề cập, HTTP/1.1 và HTTP/2 sử dụng chung semantics, tức là cách chúng diễn đạt yêu cầu và phản hồi vẫn tuân theo các phương thức quen thuộc như GET, POST, với các phần headers và body.

Tuy nhiên, điểm khác biệt nằm ở cách thức truyền:

  • HTTP/1.1: Gửi yêu cầu/phản hồi dưới dạng văn bản thuần túy.
  • HTTP/2: Mã hóa thành nhị phân, mở ra khả năng truyền dữ liệu linh hoạt hơn rất nhiều.

HTTP/1.1 – Pipelining và Head-of-Line Blocking

Phản hồi đầu tiên từ máy chủ sau một lệnh GET thường không chứa toàn bộ nội dung trang web. Thay vào đó, nó chứa các liên kết đến các tài nguyên khác (ảnh, CSS, JavaScript, v.v). Trình duyệt chỉ phát hiện ra mình cần thêm những tài nguyên đó sau khi tải xong HTML ban đầu, và bắt đầu gửi thêm các yêu cầu mới.

Với HTTP/1.0, mỗi yêu cầu đều cần một kết nối TCP riêng, gây tốn thời gian và tài nguyên.

HTTP/1.1 đã cải tiến điều này bằng cách giới thiệu kết persistent connection và pipelining:

  • Persistent connection: Kết nối TCP sẽ được giữ mở cho nhiều yêu cầu, trừ khi máy chủ yêu cầu đóng.
  • Pipelining: Cho phép gửi nhiều yêu cầu mà không cần chờ phản hồi cho từng cái.

Tuy nhiên, HTTP/1.1 vẫn gặp vấn đề lớn:

Nếu một yêu cầu ở đầu hàng đợi bị chậm hoặc lỗi, thì mọi yêu cầu phía sau đều bị chặn. Đây được gọi là hiện tượng Head-of-Line (HOL) Blocking, làm giảm hiệu quả rõ rệt.

Có thể giảm vấn đề này bằng cách tạo nhiều kết nối TCP song song, nhưng điều này bị giới hạn bởi trình duyệt, máy chủ và tài nguyên hệ thống.

HTTP/2 – Ưu điểm của Binary Framing Layer

Trong HTTP/2, binary framing layer sẽ mã hóa các request/response và chia nhỏ chúng thành các gói dữ liệu nhỏ hơn, từ đó giúp việc truyền tải dữ liệu trở nên linh hoạt hơn rất nhiều.

Hãy cùng tìm hiểu kỹ hơn cách thức hoạt động của cơ chế này. Khác với HTTP/1.1, vốn phải sử dụng nhiều kết nối TCP để giảm bớt ảnh hưởng của hiện tượng HOL blocking, HTTP/2 chỉ thiết lập một kết nối duy nhất giữa hai máy. Bên trong kết nối này, có thể tồn tại nhiều luồng dữ liệu. Mỗi stream bao gồm nhiều message theo định dạng request/response quen thuộc. Cuối cùng, mỗi message sẽ được chia nhỏ thành các đơn vị nhỏ hơn gọi là frames:

Streams Frames

Ở cấp độ chi tiết nhất, kênh giao tiếp trong HTTP/2 bao gồm nhiều frame được mã hóa dưới dạng nhị phân, mỗi frame được gắn tag tương ứng với một stream cụ thể. Các nhãn này cho phép kết nối đan xen các frame trong quá trình truyền tải và tái tổ hợp chúng ở đầu bên kia. Nhờ đó, các request và response có thể được xử lý song song mà không chặn các message phía sau, quá trình này được gọi là multiplexing.

Multiplexing giúp giải quyết vấn đề “head-of-line blocking” trong HTTP/1.1 bằng cách đảm bảo rằng không có message nào phải chờ message khác hoàn thành. Điều này cũng có nghĩa là server và client có thể gửi nhiều request/response đồng thời, giúp kiểm soát tốt hơn và quản lý kết nối hiệu quả hơn.

Vì multiplexing cho phép client tạo nhiều stream song song, các stream này chỉ cần dùng chung một kết nối TCP duy nhất. Việc sử dụng một kết nối duy trì duy nhất cho mỗi origin là một cải tiến lớn so với HTTP/1.1, vì nó giảm mức tiêu thụ bộ nhớ và tài nguyên xử lý trên toàn mạng. Kết quả là tăng hiệu suất sử dụng băng thông và mạng, đồng thời giảm chi phí vận hành tổng thể.

Một kết nối TCP duy nhất cũng cải thiện hiệu suất của giao thức HTTPS, vì client và server có thể tái sử dụng cùng một phiên kết nối bảo mật cho nhiều request/response khác nhau. Trong HTTPS, trong quá trình bắt tay TLS hoặc SSL, hai bên sẽ thỏa thuận sử dụng một khóa mã hóa duy nhất cho toàn bộ phiên làm việc. Nếu kết nối bị gián đoạn, một phiên mới sẽ bắt đầu và cần tạo khóa mới cho quá trình giao tiếp tiếp theo. Do đó, việc duy trì một kết nối duy nhất giúp giảm đáng kể tài nguyên cần thiết để duy trì hiệu suất HTTPS.

Lưu ý rằng mặc dù HTTP/2 không bắt buộc phải dùng TLS, nhưng nhiều trình duyệt phổ biến hiện nay chỉ hỗ trợ HTTP/2 khi chạy qua HTTPS.

Tuy cơ chế multiplexing tích hợp trong lớp khung nhị phân đã giải quyết được nhiều vấn đề của HTTP/1.1, nhưng khi có nhiều stream cùng chờ một tài nguyên, hiệu suất vẫn có thể bị ảnh hưởng. Thiết kế của HTTP/2 đã tính đến điều này thông qua cơ chế ưu tiên stream, chủ đề sẽ được trình bày trong phần tiếp theo.

HTTP/2 – Ưu tiên luồng

Ưu tiên luồng không chỉ giúp giải quyết vấn đề tiềm ẩn khi nhiều request cùng tranh chấp một tài nguyên, mà còn cho phép lập trình viên tùy chỉnh mức độ ưu tiên tương đối giữa các request, từ đó tối ưu hóa hiệu suất ứng dụng một cách hiệu quả hơn. Trong phần này, chúng ta sẽ phân tích quá trình ưu tiên stream để giúp bạn hiểu rõ hơn về cách tận dụng tính năng này trong HTTP/2.

Như bạn đã biết, binary framing layer sẽ tổ chức các message thành các stream dữ liệu song song. Khi client gửi nhiều request đồng thời đến server, nó có thể ưu tiên các response mong muốn bằng cách gán một trọng số từ 1 đến 256 cho mỗi stream. Trọng số càng cao thì mức độ ưu tiên càng lớn.

Ngoài ra, client cũng có thể xác định mối quan hệ dependency giữa các stream bằng cách chỉ định ID của stream mà stream hiện tại phụ thuộc vào. Nếu không chỉ định parent, stream đó sẽ được xem là phụ thuộc vào root stream. Điều này được minh họa trong sơ đồ sau:

Stream Priority2

Trong hình minh họa, kênh kết nối chứa sáu stream, mỗi stream có một ID duy nhất và được gán một trọng số cụ thể. Stream 1 không có parent ID nên mặc định được xem là phụ thuộc vào root node. Các stream còn lại đều có một ID cha được chỉ định rõ ràng. Việc phân bổ tài nguyên cho từng stream sẽ dựa trên trọng số và mối quan hệ phụ thuộc của chúng.

Ví dụ, Stream 5 và Stream 6 trong hình minh họa được gán trọng số giống nhau và cùng phụ thuộc vào một stream cha, nên chúng sẽ có mức độ ưu tiên ngang nhau trong quá trình phân bổ tài nguyên.

Dựa trên thông tin này, server sẽ tạo ra một dependency tree. Cây này giúp server xác định thứ tự mà các request sẽ được xử lý và nhận dữ liệu. Dựa trên các stream trong hình trước đó, cấu trúc cây phụ thuộc sẽ như sau:

Dependency Tree

Trong dependency tree này, stream 1 phụ thuộc vào root stream và không có stream nào khác phụ thuộc trực tiếp vào root, nên toàn bộ tài nguyên sẵn có sẽ được cấp cho stream 1 trước các stream khác.

Vì cây chỉ ra rằng stream 2 phụ thuộc vào việc hoàn thành của stream 1, nên stream 2 sẽ không được xử lý cho đến khi stream 1 hoàn tất.

Tiếp theo, xét đến stream 3 và stream 4 – cả hai đều phụ thuộc vào stream 2. Tương tự như với stream 1, stream 2 sẽ nhận được toàn bộ tài nguyên trước stream 3 và 4. Sau khi stream 2 hoàn tất công việc, stream 3 và 4 sẽ bắt đầu nhận tài nguyên, được phân chia theo tỷ lệ trọng số 2:4 như đã được chỉ định, nghĩa là stream 4 sẽ nhận phần lớn hơn tài nguyên.

Cuối cùng, khi stream 3 kết thúc, stream 5 và 6 sẽ được cấp phát tài nguyên theo tỷ lệ bằng nhau. Điều này có thể xảy ra ngay cả khi stream 4 chưa hoàn thành, vì các stream ở cấp thấp hơn được phép bắt đầu ngay khi stream cha của chúng ở cấp cao hơn đã hoàn tất.

Là một lập trình viên ứng dụng, bạn có thể thiết lập trọng số cho các request dựa trên nhu cầu cụ thể.

Ví dụ, bạn có thể gán mức ưu tiên thấp hơn cho việc tải ảnh độ phân giải cao, sau khi đã hiển thị thumbnail của ảnh đó trên trang web.

Nhờ vào khả năng gán trọng số, HTTP/2 giúp lập trình viên kiểm soát tốt hơn quá trình hiển thị nội dung trang web.

Giao thức này cũng cho phép client thay đổi mối quan hệ phụ thuộc và phân bổ lại trọng số ngay trong quá trình runtime, dựa trên tương tác của người dùng.

Tuy nhiên, cần lưu ý rằng server vẫn có thể tự thay đổi mức độ ưu tiên đã được gán nếu phát hiện một stream nào đó bị chặn không thể truy cập tài nguyên cần thiết.

Dưới đây là bản dịch tiếng Việt tự nhiên và dễ hiểu cho đoạn bạn cung cấp, giữ nguyên các thuật ngữ kỹ thuật cần thiết:

Tràn bộ đệm (Buffer Overflow)

Trong bất kỳ kết nối TCP nào giữa hai máy, cả client và server đều có một lượng bộ đệm (buffer) nhất định để chứa các request đến mà vẫn chưa được xử lý. Các bộ đệm này cung cấp sự linh hoạt để xử lý các trường hợp có nhiều request hoặc request có kích thước lớn, đồng thời giúp điều tiết sự không đồng đều về tốc độ tải lên và tải xuống giữa hai bên.

Tuy nhiên, trong một số tình huống, bộ đệm có thể không đủ.

Ví dụ, server có thể đang gửi một lượng lớn dữ liệu với tốc độ nhanh, vượt quá khả năng xử lý của ứng dụng client, do bộ đệm phía client bị giới hạn hoặc băng thông thấp. Tương tự, khi client tải lên một hình ảnh hoặc video có dung lượng lớn, bộ đệm của server có thể bị tràn, khiến một số gói dữ liệu bị mất.

Để tránh tình trạng tràn bộ đệm, cần có cơ chế kiểm soát luồng nhằm ngăn bên gửi truyền dữ liệu quá nhanh so với khả năng xử lý của bên nhận.

Phần tiếp theo sẽ trình bày tổng quan về cách HTTP/1.1 và HTTP/2 sử dụng các phiên bản khác nhau của cơ chế này, tùy thuộc vào mô hình truyền tải dữ liệu của từng giao thức.

HTTP/1.1

Trong HTTP/1.1, cơ chế kiểm soát luồng dựa vào kết nối TCP bên dưới. Khi kết nối TCP được thiết lập, cả client và server sẽ thiết lập kích thước bộ đệm dựa trên cấu hình mặc định của hệ thống.

Khi bộ đệm của bên nhận đã được sử dụng một phần, nó sẽ thông báo cho bên gửi về receive window tức là phần dung lượng còn trống trong bộ đệm. Thông tin này được gửi kèm trong một gói tin ACK tức là gói tin xác nhận rằng bên nhận đã nhận được tín hiệu mở đầu kết nối.

Nếu kích thước receive window được thông báo là bằng 0, điều đó có nghĩa là bộ đệm của bên nhận đã đầy. Khi đó, bên gửi sẽ tạm dừng việc gửi dữ liệu, chờ đến khi client giải phóng bộ đệm của mình và gửi yêu cầu tiếp tục truyền dữ liệu.

Điều quan trọng cần lưu ý là: việc sử dụng receive window dựa trên kết nối TCP chỉ cho phép kiểm soát luồng ở một trong hai đầu của kết nối hoặc bên gửi, hoặc bên nhận, chứ không phải từng phần dữ liệu riêng biệt.

Vì HTTP/1.1 dựa vào lớp truyền tải để ngăn hiện tượng tràn bộ đệm, nên mỗi kết nối TCP mới sẽ cần một cơ chế kiểm soát luồng riêng biệt.

Trong khi đó, HTTP/2 sử dụng cơ chế multiplexing, cho phép nhiều stream hoạt động song song trên cùng một kết nối TCP duy nhất, do đó phải thực hiện kiểm soát luồng theo một cách khác.

HTTP/2

HTTP/2 cho phép truyền nhiều stream dữ liệu đồng thời trên một kết nối TCP duy nhất. Do đó, việc dùng receive window ở cấp độ TCP không đủ để kiểm soát quá trình truyền tải của từng stream riêng lẻ.

HTTP/2 giải quyết vấn đề này bằng cách cho phép client và server tự triển khai cơ chế kiểm soát luồng riêng, thay vì phụ thuộc vào tầng truyền tải. Tầng ứng dụng sẽ thông báo dung lượng bộ đệm còn trống, từ đó client và server có thể thiết lập receive window riêng cho từng stream trong kết nối multiplexed.

Cơ chế kiểm soát luồng chi tiết này có thể được điều chỉnh hoặc duy trì sau khi kết nối được thiết lập, thông qua một loại frame gọi là WINDOW_UPDATE.

Vì kiểm soát luồng diễn ra ở tầng ứng dụng, nên không cần phải chờ tín hiệu đến đích cuối cùng mới điều chỉnh receive window. Các node trung gian cũng có thể sử dụng thông tin kiểm soát luồng để tự phân bổ tài nguyên và điều chỉnh linh hoạt.

Nhờ vậy, mỗi máy chủ trung gian có thể thực hiện chiến lược tài nguyên riêng, giúp cải thiện hiệu quả kết nối tổng thể.

Sự linh hoạt này rất hữu ích trong việc xây dựng các chiến lược sử dụng tài nguyên phù hợp.

Ví dụ, client có thể tải phần đầu tiên của một hình ảnh, hiển thị trước cho người dùng xem, trong khi ưu tiên tải các tài nguyên quan trọng hơn. Sau khi hoàn tất phần quan trọng, trình duyệt sẽ tiếp tục tải phần còn lại của ảnh.

Việc chuyển quyền kiểm soát luồng cho client và server có thể giúp cải thiện hiệu suất cảm nhận của ứng dụng web.

Xét về cơ chế kiểm soát luồng và ưu tiên stream đã đề cập trước đó, HTTP/2 cung cấp mức độ kiểm soát chi tiết hơn, tạo điều kiện để tối ưu hóa hiệu suất cao hơn.

Phần tiếp theo sẽ giới thiệu một kỹ thuật đặc trưng khác của HTTP/2, giúp cải thiện kết nối theo cách tương tự: dự đoán yêu cầu tài nguyên bằng cơ chế server push.

Dự đoán các yêu cầu tài nguyên

Trong một ứng dụng web thông thường, client sẽ gửi một yêu cầu GET và nhận về một trang HTML, thường là trang index của website. Khi phân tích nội dung của trang index, client có thể phát hiện ra rằng nó cần phải tải thêm các tài nguyên khác, như các tệp CSS và JavaScript, để có thể hiển thị đầy đủ trang web. Client chỉ xác định được rằng nó cần những tài nguyên bổ sung này sau khi đã nhận phản hồi từ yêu cầu GET ban đầu, do đó phải gửi thêm các yêu cầu khác để lấy về những tài nguyên đó và hoàn tất quá trình dựng trang. Những yêu cầu bổ sung này làm tăng thời gian tải kết nối tổng thể.

Tuy nhiên, có những giải pháp cho vấn đề này: vì server biết trước rằng client sẽ cần thêm các tệp bổ sung, nên server có thể tiết kiệm thời gian cho client bằng cách gửi trước các tài nguyên này mà không đợi client phải yêu cầu. HTTP/1.1 và HTTP/2 có những cách tiếp cận khác nhau để thực hiện điều này, và mỗi cách sẽ được trình bày trong phần tiếp theo.

HTTP/1.1 – Nhúng tài nguyên

Trong HTTP/1.1, nếu lập trình viên biết trước những tài nguyên bổ sung nào mà client sẽ cần để hiển thị trang, họ có thể sử dụng kỹ thuật gọi là resource inlining để đưa trực tiếp các tài nguyên cần thiết vào trong tài liệu HTML mà server gửi lại để phản hồi yêu cầu GET ban đầu. Ví dụ, nếu client cần một tệp CSS cụ thể để hiển thị trang, việc nhúng tệp CSS đó vào HTML sẽ giúp client có được tài nguyên cần thiết mà không cần phải gửi thêm yêu cầu, từ đó giảm tổng số request mà client phải gửi đi.

Tuy nhiên, kỹ thuật resource inlining cũng có một số vấn đề. Việc đưa tài nguyên vào tài liệu HTML là giải pháp khả thi cho những tài nguyên nhỏ, dạng văn bản. Nhưng đối với các tệp lớn hơn hoặc ở định dạng không phải văn bản, điều này có thể khiến kích thước tài liệu HTML tăng đáng kể, từ đó làm chậm tốc độ kết nối và làm mất đi lợi ích ban đầu mà kỹ thuật này mang lại. Ngoài ra, vì tài nguyên được nhúng không còn tách biệt khỏi tài liệu HTML, client không có cách nào từ chối nhận tài nguyên mà nó đã có sẵn, cũng như không thể lưu riêng tài nguyên đó vào bộ nhớ cache. Nếu nhiều trang khác nhau đều cần tài nguyên đó, thì mỗi tài liệu HTML mới sẽ lại chứa bản sao của tài nguyên được nhúng, dẫn đến kích thước HTML lớn hơn và thời gian tải trang lâu hơn so với trường hợp tài nguyên đó được cache từ đầu.

Vì vậy, một nhược điểm lớn của resource inlining là client không thể tách biệt tài nguyên và tài liệu HTML. Để tối ưu hóa kết nối, cần một mức độ kiểm soát tinh vi hơn, điều mà HTTP/2 hướng tới thông qua tính năng server push.

HTTP/2 – Đẩy tài nguyên từ phía server

Vì HTTP/2 cho phép nhiều phản hồi đồng thời đối với một yêu cầu GET ban đầu từ client, server có thể gửi một tài nguyên đến client cùng lúc với trang HTML được yêu cầu, tức là cung cấp tài nguyên trước khi client cần yêu cầu đến nó. Quá trình này được gọi là server push.

Bằng cách này, kết nối HTTP/2 có thể đạt được mục tiêu tương tự như resource inlining, nhưng vẫn giữ nguyên sự tách biệt giữa tài nguyên được đẩy và tài liệu HTML. Điều này có nghĩa là client có thể quyết định cache hoặc từ chối tài nguyên được đẩy một cách độc lập với tài liệu HTML chính ,khắc phục nhược điểm lớn nhất của kỹ thuật inlining.

Trong HTTP/2, quá trình server push bắt đầu khi server gửi một frame có tên là PUSH_PROMISE để thông báo cho client rằng nó sắp đẩy một tài nguyên. Khung này chỉ bao gồm phần header của thông điệp, và giúp client biết trước tài nguyên nào sẽ được server gửi tới. Nếu client đã có tài nguyên đó trong cache, nó có thể từ chối nhận tài nguyên bằng cách gửi lại một khung RST_STREAM. PUSH_PROMISE cũng giúp tránh việc client gửi một yêu cầu trùng lặp đến server, vì nó đã biết trước tài nguyên nào sẽ được gửi.

Một điểm quan trọng cần lưu ý là server push luôn đặt sự kiểm soát trong tay client. Nếu client muốn thay đổi mức độ ưu tiên của việc đẩy tài nguyên, hoặc thậm chí vô hiệu hóa tính năng này, nó hoàn toàn có thể gửi một khung SETTINGS để điều chỉnh cấu hình liên quan đến tính năng này trong HTTP/2.

Dù tính năng server push mang lại nhiều tiềm năng, nó không phải lúc nào cũng là giải pháp tối ưu cho việc cải thiện hiệu suất ứng dụng web. Ví dụ, một số trình duyệt web không thể hủy các yêu cầu được đẩy ngay cả khi client đã có sẵn tài nguyên đó trong bộ nhớ cache.

Nếu client vô tình cho phép server gửi lại một tài nguyên trùng lặp, server push có thể gây lãng phí băng thông và tài nguyên kết nối. Do đó, việc sử dụng server push nên được cân nhắc kỹ lưỡng bởi lập trình viên. Để hiểu rõ hơn cách tận dụng server push một cách chiến lược và tối ưu hóa ứng dụng web, bạn có thể tham khảo mô hình PRPL do Google phát triển.

Nén dữ liệu

Một phương pháp phổ biến để tối ưu hóa các ứng dụng web là sử dụng các thuật toán nén nhằm giảm kích thước của các thông điệp HTTP được truyền giữa client và server. Cả HTTP/1.1 và HTTP/2 đều áp dụng chiến lược này, nhưng trong HTTP/1.1 vẫn tồn tại những vấn đề trong cách triển khai khiến không thể nén toàn bộ thông điệp. Phần tiếp theo sẽ giải thích lý do vì sao lại như vậy và cách HTTP/2 đưa ra giải pháp cho vấn đề này.

HTTP/1.1

Các chương trình như gzip đã được sử dụng từ lâu để nén dữ liệu trong các thông điệp HTTP, đặc biệt nhằm giảm kích thước của các tệp CSS và JavaScript. Tuy nhiên, phần header của thông điệp luôn được gửi dưới dạng văn bản thuần.

Dù mỗi header riêng lẻ thường rất nhỏ, nhưng khi số lượng request tăng lên thì tổng dung lượng của các header không nén này cũng gây áp lực ngày càng lớn lên kết nối, đặc biệt là với các ứng dụng web phức tạp, sử dụng nhiều API và yêu cầu nhiều tài nguyên khác nhau. Thêm vào đó, việc sử dụng cookie có thể khiến header trở nên rất lớn, từ đó làm tăng nhu cầu cần có cơ chế nén.

Để giải quyết nút thắt này, HTTP/2 sử dụng cơ chế nén HPACK để giảm kích thước của phần header đây, là nội dung sẽ được trình bày chi tiết hơn ở phần tiếp theo.

HTTP/2

Một chủ đề được lặp lại nhiều lần trong HTTP/2 là khả năng kiểm soát chi tiết tốt hơn thông qua lớp binary framing layer . Điều này cũng đúng trong việc nén header. HTTP/2 cho phép tách riêng phần header ra khỏi dữ liệu, tạo thành một header frame và một data frame. Sau đó, chương trình nén chuyên biệt của HTTP/2 là HPACK sẽ nén phần header frame này.

Thuật toán HPACK có thể mã hóa metadata trong header bằng cách sử dụng mã hóa Huffman, giúp giảm đáng kể kích thước của chúng. Bên cạnh đó, HPACK còn có thể ghi nhớ các trường metadata đã truyền trước đó và nén chúng hiệu quả hơn thông qua một bảng chỉ mục động được chia sẻ giữa client và server. Ví dụ, hãy xét hai yêu cầu sau đây:

Header Frame for Request #1
method:		GET
scheme:		https
host:		example.com
path:		/academy
accept:		/image/jpeg
user-agent:	Mozilla/5.0 ...
Header Frame for Request #2
path:		/academy/images

Sử dụng HPACK và các phương pháp nén khác, HTTP/2 cung cấp thêm một tính năng giúp giảm độ trễ giữa client và server.

Kết luận

Qua phần phân tích từng điểm ở trên, có thể thấy HTTP/2 khác biệt với HTTP/1.1 ở nhiều khía cạnh. Một số tính năng của HTTP/2 mang lại khả năng kiểm soát chi tiết hơn, từ đó giúp tối ưu hiệu suất của ứng dụng web tốt hơn; trong khi một số tính năng khác đơn giản là cải tiến các điểm hạn chế của giao thức cũ.

Giờ đây, khi bạn đã có cái nhìn tổng quan về sự khác biệt giữa hai giao thức, bạn có thể cân nhắc xem các yếu tố như multiplexing, stream prioritization, flow control, server push, và compression trong HTTP/2 sẽ ảnh hưởng như thế nào đến xu hướng phát triển ứng dụng web trong tương lai.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất