Đục khoét Javascript (Phần 12): Bên trong lớp Network + Làm sao để tối ưu hóa hiệu năng và bảo mật
Javascript
314
White

vncafecode viết ngày 24/11/2018

Bài này có rất nhiều kiến thức chuyên sâu về công nghệ mạng (Networking), mà mình lại không chuyên về mạng nên dịch thuật có gì sai sót mong mọi người comment giúp đỡ. Cảm ơn nhé.

Bài viết được dịch từ series How JS Works của team SessionStack với sự đồng ý của Co-founder & CEO Alexander Zlatkov

Đục khoét Javascript (Phần 1): Khái quát về engine, runtime và callstack

Đục khoét Javascript (Phần 2): Bên trong engine V8 & 5 mẹo để tối ưu hóa code

Đục khoét Javascript (Phần 3): Quản lý bộ nhớ & 4 trường hợp rò rỉ phổ biến

Đục khoét Javascript (Phần 4): Event loop, lập trình bất đồng bộ & 5 mẹo cải thiện Async/Await

Đục khoét Javascript (Phần 5): Đào sâu WebSocket & HTTP/2 với SSE + Hãy chọn giá đúng!

Đục khoét Javascript (Phần 6): So sánh với WebAssembly + Khi nào dùng nó tốt hơn dùng JS

Đục khoét Javascript (Phần 7): Thành phần của WebWorker + 5 trường hợp sử dụng

Đục khoét Javascript (Phần 8): Service Workers, vòng đời và các trường hợp sử dụng

Đục khoét Javascript (Phần 9): Cấu tạo của Web Push Notifications

Đục khoét Javascript (Phần 10): Quan sát thay đổi trên DOM bằng MutationObserver

Đục khoét Javascript (Phần 11): Render engine & mẹo tối ưu hóa hiệu năng render

Chào các bạn đến với bài thứ 12 trong series đục khoét và khám phá Javascript cũng như các thành phần của nó. Trong quá trình xác định và tìm hiểu các thành phần cốt lõi, tác giả cũng chia sẻ một số nguyên tắc mà họ đang dùng để xây dựng SessionStack, một ứng dụng Javascript hướng đến sự mạnh mẽ, hiệu năng cao và ổn định.

Như đã nói trong bài trước về render engine, tác giả bài viết tin rằng sự khác biệt giữa một Javascript developer tốt (good) và tuyệt vời (great) là dev tuyệt vời không những hiểu về các thành phần cơ bản của một ngôn ngữ mà còn cả phần cốt lõi cũng như môi trường xung quanh nó.

Nhắc sơ qua về lịch sử một chút

49 năm trước, một thứ gọi là ARPAnet được tạo ra. Nó chính là một mạng chuyển đổi gói tin sớm và cũng là mạng đầu tiên triển khai bộ TCP/IP. Mạng này cài đặt một liên kết giữa trường đại học California và Học viện nghiên cứu Stanford. 20 năm sau, Tim Berners-Lee phát hành một lời đề nghị cho "Mesh" - thứ mà sau này được biết đến là World Wide Web. Trong 49 năm đó, internet đã đi được một quãng đường dài, bắt đầu chỉ với 2 máy tính trao đổi các gói dữ liệu và giờ đạt tới hơn 75 triệu server, 3.8 tỉ người dùng internet và 1.3 tỉ websites.

internet

Trong bài này, chúng ta sẽ thử phân tích những kỹ thuật nào trình duyệt hiện đại sử dụng để tự động đẩy mạnh hiệu năng (thậm chí bạn không biết đến điều đó), và chúng ta sẽ đặc biệt soi kỹ vào lớp networking của trình duyệt. Ở cuối bài, tác giả sẽ cung cấp một số ý tưởng làm thế nào để giúp trình duyệt đẩy mạnh hơn nữa hiệu năng của webapp của bạn.

Khái quát

Trình duyệt web hiện đại được thiết kế đặc trị cho việc truyền tải webapp/website một cách nhanh chóng, hiệu quả và an toàn bảo mật. Với hàng trăm component cùng hoạt động trên nhiều layer khác nhau, từ quản lý tiến trình và bảo mật sandbox đến các GPU pipeline, audio và video, và còn nhiều thứ khác nữa, trình duyệt trông giống như một hệ điều hành hơn là một phần mềm bình thường.

Hiệu năng tổng quát của trình duyệt được xác định bằng một cơ số các component lớn: parsing (phân giải), layout, tính toán style, quá trình thực thi Javascript & WebAssembly, rendering và dĩ nhiên là cả networking stack (ngăn xếp mạng).

Các kỹ sư thường nghĩ rằng networking stack là một nút cổ chai. Điều này xảy ra thường xuyên vì tất cả các tài nguyên đều cần phải được lấy về từ internet trước khi các bước còn lại được thực hiện. Với networking layer, để hoạt động hiệu quả nó cần phải đóng vai trò nhiều hơn là một bộ quản lý socket đơn giản. Với chúng ta, nó như một thứ núp dưới dạng một cơ chế rất đơn giản để kéo tài nguyên về nhưng đó thực sự là một nền tảng (platform) đầy đủ với các tiêu chí tối ưu hóa, APIs và service của riêng nó.

network

Là web developer, chúng ta không cần phải lo nghĩ về từng gói tin TCP hay UDP, định dạng request, caching và tất cả những thứ liên quan. Toàn bộ sự phức tạp này được trình duyệt gánh dùm nên ta chỉ cần tập trung vào ứng dụng mà chúng ta đang tạo ra. Tuy nhiên, hiểu rõ điều gì thực sự đang diễn ra bên trong có thể giúp chúng ta tạo ra app nhanh hơn và bảo mật tốt hơn.

Về bản chất thì dưới đây là những gì xảy ra khi user bắt đầu tương tác với trình duyệt:

  • User nhập một URL vào thanh địa chỉ trên trình duyệt
  • Giả sử URL đó chỉ đến 1 tài nguyên trên mạng, trình duyệt sẽ bắt đầu kiểm tra local cache và cache của ứng dụng và cố thử sử dụng một phải copy có sẵn ở local để đáp ứng request.
  • Nếu cache không dùng được, tình duyệt sẽ lấy tên miền từ URL và yêu cầu địa chỉ IP của server từ một DNS. Nếu tên miền đã được cache sẵn thì không cần truy vấn đến DNS.
  • Trình duyệt tạo ra một gói tin HTTP nói rằng nó yêu cầu một trang web đang cư trú tại một server từ xa.
  • Gói tin được gửi đến TCP layer, layer này sẽ thêm thông tin của chính nó vào vị trí trên cùng của gói tin HTTP. Thông tin này cần thiết để duy trì phiên khởi động.
  • Gói tin sau đó được trao cho IP layer với công việc chính là tìm hiểu một cách để gửi gói tin từ user đến server từ xa. Thông tin này cũng được lưu vào vị trí trên cùng của gói tin.
  • Gói tin được gửi đến server từ xa.
  • Khi đã nhận gói tin, một phản hồi được gửi ngược lại theo cách thức tương tự.

Đặc tính kỹ thuật của Navigation Timing từ W3C cung cấp một API trình duyệt cũng như khả năng hiển thị dữ liệu về thời gian và hiệu năng đằng sau mỗi request trên trình duyệt. Giờ thì cùng quan sát các component, mỗi phần sẽ đóng một vai trò quan trọng trong việc cung cấp các trải nghiệm người dùng (UX) tối ưu:

navigation timing

Toàn bộ tiến trình networking rất phức tạp và có nhiều layer khác nhau có thể trở thành một nút cổ chai. Đây là lý do các trình duyệt cố gắng phấn đấu để cải thiện hiệu năng bản thân bằng cách sử dụng rất nhiều kỹ thuật đa dạng để giảm thiểu tối đa sự ảnh hưởng của toàn bộ giao tiếp network.

Quản lý socket

Cùng khởi động với một số thuật ngữ nào:

  • Origin: một bộ 3 chứa các giao thức ứng dụng, tên miền và số port (ví dụ: https, www.example.com, 443)
  • Socket pool: một nhóm các socket thuộc về cùng origin (tất cả các trình duyệt lớn đều giới hạn pool size lớn nhất là 6 socket).

Javascript và WebAssembly không cho phép chúng ta quản lý vòng đời của các network socket riêng tư, và dĩ nhiên như vậy là tốt! Điều này không những giúp chúng ta dễ dàng hơn mà còn cho phép trình duyệt tự động thực hiện rất nhiều tối ưu hóa hiệu năng, một trong số đó bao gồm: sử dụng lại socket, yêu cầu sự ưu tiên và ràng buộc muộn (late binding), giao dịch giao thức, ép buộc giới hạn kết nối, vân vân.

Thật ra các trình duyệt hiện đại đã làm tốt trong việc chia tách vòng quản lý request khỏi phần quản lý socket. Các socket được tổ chức trong các pool và được nhóm lại theo origin, mỗi pool bắt buộc phải giới hạn kết nối và các ràng buộc bảo mật. Các request chờ được xếp vào trong hàng đợi, đánh thứ tự ưu tiên và sau đó gắn kết với những socket riêng tư trong pool. Trừ khi server có ý định đóng kết nối thì cùng một socket có thể được sử dụng lại một cách tự động xuyên suốt nhiều request.

socket

Bởi vì mở mới 1 kết nối TCP thường kèm theo chi phí tốn kém cho nên tái sử dụng lại các kết nối cũ sẽ đảm bảo hiệu năng tốt hơn nhiều. Mặc định thì trình duyệt sử dụng cơ chế gọi là "keepalive" (giữ cho sống) để tiết kiệm thời gian từ việc mở mới kết nối đến server khi có request được tạo ra. Thời gian trung bình để mở 1 kết nối TCP là:

  • Request đến máy local: 23ms
  • Request trong nội bộ châu lục: 120ms
  • Request giữa các châu lục với nhau: 225ms

Kiểu kiến trúc này mở ra cánh cửa đến với rất nhiều cơ hội để tối ưu hóa. Request có thể được thực thi với những thứ tự khác nhau tùy thuộc vào độ ưu tiên của nó. Trình duyệt có thể tối ưu hóa sự phân chia băng thông giữa toàn bộ các socket hoặc là nó có thể mở socket khi dự đoán trước về một request.

Như đã nói trước đó, toàn bộ đều được quản lý bởi trình duyệt và không yêu cầu chúng ta giúp bất cứ thứ gì. Nhưng nó không nhất thiết nghĩa là chúng ta không thể làm gì. Chọn lựa đúng pattern về giao tiếp mạng, loại và tần suất transfer, lựa chọn các giao thức và tinh chỉnh/tối ưu hóa server stack có thể đóng vai trò rất lớn trong việc cải thiện hiệu năng tổng thể của ứng dụng.

Một vài trình duyệt thậm chí còn đi xa hơn. Ví dụ, Chrome có thể tự dạy cho chính nó hoạt động nhanh hơn khi bạn sử dụng nó. Nó học hỏi dựa trên những trang bạn đã ghé thăm và kiểu duyệt web điển hình cho nên nó có thể dự đoán hành vi người dùng có khả năng và thực hiện hành động trước khi user làm gì đó. Ví dụ đơn giản nhất là tự động render trước nội dung của trang khi user rê chuột lên 1 link. Nếu bạn thấy hứng thú về chủ đề tối ưu hóa của Chrome thì có thể đọc thêm chương này https://www.igvita.com/posa/high-performance-networking-in-google-chrome/ nằm trong quyển sách High-Performance Browser Networking

Bảo mật mạng và đóng gói sandbox

Cho phép trình duyệt quản lý các socket riêng biệt có một ý nghĩa rất quan trọng: bằng cách này trình duyệt cho phép áp đặt một hệ thống đồng nhất các ràng buộc về chính sách và bảo mật lên những nguồn tài nguyên ứng dụng không đáng tin cậy. Ví dụ như trình duyệt sẽ không chấp nhận API truy xuất trực tiếp vào network socket thô vì như vậy có thể cho phép các ứng dụng độc hại tạo kết nối tùy tiện đến bất cứ host nào. Trình duyệt cũng áp đặt các giới hạn kết nối để bảo vệ server cũng như client khỏi cạn kiệt tài nguyên.

Trình duyệt định dạng tất cả các request đi ra để áp đặt sự đồng nhất và các ngữ nghĩa giao thức tốt để bảo vệ server. Tương tự, giải mã response được thực hiện một cách tự động để bảo vệ user từ những server độc hại.

Trao đổi TLS

Transport Layer Security (TLS) là một giao thức mật mã cung cấp giao tiếp bảo mật trong mạng máy tính. Nó được sử dụng rộng rãi trong nhiều ứng dụng, một trong số đó là trình duyệt web. Website có thể dùng TLS để bảo đảm an ninh cho tất cả các giao tiếp giữa server và trình duyệt web.

Toàn bộ quá trình bắt tay TLS bao gồm các bước sau:

  1. Client gửi một lời nhắn "Client hello" đến server, cùng với một giá trị ngẫu nhiên của client và bộ mã hóa được hỗ trợ.
  2. Server trả lời bằng cách gửi lời nhắn "Server hello" về cho client, cùng với giá trị ngẫu nhiên của server.
  3. Server gửi chứng chỉ xác thực của nó về cho client và có thể yêu cầu một chứng chỉ tương tự từ phía client. Server gửi lời nhắn "Server hello done".
  4. Nếu server đã yêu cầu một chứng chỉ từ client thì client phải gửi nó.
  5. Client tạo ra một Pre-Master Secret ngẫu nhiên và mã hóa nó với public key từ chứng chỉ của server, gửi Pre-Master Secret đã được mã hóa về cho server.
  6. Server nhận Pre-Master Secret. Server và client mỗi bên sẽ sinh ra Master Secret và session key (chìa khóa phiên) dựa trên Pre-Master Secret.
  7. Client gửi thông báo "Change cipher spec" đến server để xác định rằng client sẽ bắt đầu sử dụng session key mới để băm và mã hóa message. Client cũng đồng thời gửi tin nhắn "Client finished".
  8. Server nhận "Change cipher spec" và chuyển đổi trạng thái bảo mật của record layer của nó sang trạng thái bảo mật mã hóa đối xứng bằng session key. Server gửi lời nhắn "server finished" về cho client.
  9. Client và server giờ có thể trao đổi dữ liệu ứng dụng thông qua kênh bảo mật mà chúng đã thiết lập. Tất cả message được gửi từ client đến server và ngược lại đều được mã hóa bằng session key.

User được cảnh báo trong trường hợp một trong số xác thực nào đó bị sai, ví dụ: server đang dùng một chứng chỉ tự cấp.

Chính sách cùng origin

Hai trang có cùng origin nếu như giao thức, cổng (nếu được chỉ định) và host đều giống nhau giữa 2 trang

Dưới đây là một vài ví dụ về các tài nguyên có thể được nhúng cross-origin (xuyên origin):

  • Javascript với code <script src="…"></script>. Thông báo lỗi cú pháp chỉ tồn tại cho những đoạn script cùng origin.
  • CSS với <link rel="stylesheet" href="…">. Do quy tắc cú pháp thoải mái của CSS nên CSS cross-origin cần một header Content-Type đúng loại. Sự hạn chế thì tùy thuộc vào trình duyệt.
  • Hình ảnh với thẻ <img />
  • File đa phương tiện với <video><audio>
  • Plug in với <object>, <embed> and <applet>
  • Fonts với @font-face. Vài trình duyệt cho phép các font cross-origin, một số khác thì yêu cầu fonts trong cùng origin.
  • Bất cứ thứ gì với <frame><iframe>. Một trang có thể sử dụng header X-Frame-Options để ngăn chặn trường hợp tương tác cross-origin này.

Danh sách trên vẫn còn thiếu sót nhiều, mục đích của nó là làm nổi bật nguyên tắc "quyền hạn tối thiểu" (least privilege). Trình duyệt chỉ phô ra những API và tài nguyên cần thiết cho code của chương trình: ứng dụng hỗ trợ dữ liệu và URL, trình duyệt định dạng các request và xử lý toàn bộ vòng đời của mỗi kết nối.

Rất đáng để lưu tâm rằng hoàn toàn không có một concept cụ thể nào của "chính sách cùng origin" (same-origin policy). Thay vào đó, chỉ có 1 bộ cơ chế liên quan áp đặt các ràng buộc lên việc truy xuất DOM, cookie và quản lý trạng thái của session, mạng và các thành phần khác của trình duyệt.

Lưu đệm tài nguyên và trạng thái của client

Request nhanh nhất và tốt nhất chính là không gọi request nào cả. Trước khi điều phối một request, trình duyệt tự động kiểm tra bộ đệm tài nguyên của nó, thực hiện các kiểm tra xác nhận cần thiết và trả về một bản copy local của tài nguyên đó nếu phù hợp với những điều kiện cụ thể. Nếu tài nguyên ở local không tồn tại trong cache thì request lên mạng được gọi và response sẽ được chèn tự động vào trong cache để cho lần truy cập tiếp theo nếu được phép.

  • Trình duyệt tự động đánh giá các chỉ thị lưu đệm (cache directives) cho mỗi tài nguyên.
  • Trình duyệt tự động xác nhận lại các tài nguyên hết hạn khi nó có thể.
  • Trình duyệt tự động quản lý kích cỡ của bộ đệm và thu hồi tài nguyên.

Quản lý bộ đệm tài nguyên một cách hiệu quả và tối ưu là rất khó. Ơn trời trình duyệt đã xử lý toàn bộ những thứ phức tạp ấy giúp chúng ta rồi, tất cả những gì ta cần làm là đảm bảo server của mình trả về cache directive phù hợp, để hiểu rõ hơn thì bạn có thể đọc bài Cache Resources on the Client. Bạn cung cấp các response headers như Cache-Control, ETagLast-Modified cho tất cả nguồn tài nguyên trên trang của bạn, phải không?

Cuối cùng, một chức năng thường bị bỏ quả nhưng khá quan trọng của trình duyệt chính là nhiệm vụ cung cấp xác thực, session và quản lý cookie. Trình duyệt duy trì các gói cookie (cookie jars - tác giả chơi chữ "cookie - bánh quy") riêng biệt cho mỗi origin, cung cấp các ứng dụng cần thiết và server APIs để đọc/ghi cookie, session và dữ liệu xác thực mới, tự động nối & xử lý các header HTTP phù hợp để tự động hóa toàn bộ quá trình thay cho chúng ta.

Ví dụ:
Một ví dụ đơn giản nhưng dễ minh họa nhất về sự tiện dụng của việc hoãn quản lý trạng thái session với trình duyệt: một session đã được xác thực có thể chia sẻ giữa nhiều tab với nhau hoặc nhiều cửa sổ trình duyệt và ngược lại; một hành động đăng xuất (sign-out) ở 1 tab sẽ vô hiệu hóa các session đang mở ở toàn bộ các cửa sổ đang mở khác.

Các API ứng dụng và giao thức

Càng đi sâu tìm hiểu về các dịch vụ network sẵn có thì cuối cùng chúng ta cũng đã tiến đến các API ứng dụng và giao thức (Application APIs & Protocols). Như ta đã biết, những layer thấp thì cung cấp một mảng rộng các dịch vụ quan trọng: quản lý socket & kết nối, xử lý request & response, áp đặt nhiều chính sách bảo mật, lưu đệm & còn nhiều nữa. Mỗi khi chúng ta khởi tạo HTTP hay XMLHttpRequest, sự kiện long-lived Server-Sent hay WebSocket session, hoặc mở kết nối WebRTC... chúng ta đang tương tác với một hoặc nhiều các dịch vụ đó.

Không có giao thức hay API nào tốt nhất. Mỗi ứng dụng phức tạp sẽ cần một tổ hợp các giao vận (transports) khác nhau dựa trên sự đa dạng của các yêu cầu: giao tiếp với bộ đệm trình duyệt, protocol overhead (metadata hoặc thông tin điều hướng mạng được gửi bởi ứng dụng), độ trễ của message, độ tin cậy, kiểu truyền tải dữ liệu, vân vân. Một số giao thức có thể đáp ứng với độ trễ thấp (ví dụ: Server-Sent Events, WebSocket), nhưng không yêu cầu các tiêu chí quan trọng khác, chẳng hạn như khả năng tận dụng bộ đệm trình duyệt hoặc hỗ trợ truyền tải nhị phân hiệu quả trong mọi trường hợp.

Một vài thứ bạn có thể làm để cải thiện hiệu năng và bảo mật của Webapp

  • Luôn luôn sử dụng header Connection: Keep-Alive trong các request. Trình duyệt đã mặc định sẵn rồi. Đảm bảo server sử dụng cơ chế tương tự.
  • Sử dụng header Cache-Control, EtagLast-Modified phù hợp để tiết kiệm thời gian download cho trình duyệt.
  • Dành thời gian để tinh chỉnh và tối ưu hóa web server, phép màu sẽ xảy ra! Nhớ rằng quá trình này rất cụ thể cho từng loại webapp và kiểu dữ liệu mà bạn trao đổi.
  • Luôn luôn dùng TLS! Đặc biệt nếu như bạn có bất kỳ xác thực nào trong ứng dụng của bạn.
  • Nghiên cứu trình duyệt cung cấp các chính sách bảo mật nào và áp đặt chúng vào trong ứng dụng của bạn.

Hiệu năng và bảo mật là ưu tiên hàng đầu trong SessionStack. Lý do tại sao team tác giả không thể nghiêng về một bên nào hơn là bởi vì một khi đã tích hợp SessionStack vào webapp, nó bắt đầu giám sát mọi thứ từ thay đổi trên DOM, tương tác người dùng đến request mạng, biệt lệ và thông báo debug. Tất cả thông tin này được truyền về server theo thời gian thực và cho phép user có thể chạy lại các vấn đề đã xảy ra dưới dạng video & xem mọi thứ xảy ra với người dùng của bạn. Tất cả hoạt động này được thực hiện với độ trễ tối thiểu và không ảnh hưởng tới hiệu năng của app của bạn.

Đó là lý do tại sao team tác giả đã cố gắng sử dụng tất cả những mẹo ở trên và một số kỹ thuật khác mà chúng ta sẽ cùng tìm hiểu trong các bài tiếp theo.

bản dùng thử miễn phí nếu bạn muốn thử SessionStack.

session stack

Nguồn: How JavaScript Works: Inside the Networking Layer + How to Optimize Its Performance and Security

Xem tiếp Phần 13

vncafecode

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

vncafecode

19 bài viết.
177 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
56 6
Bài viết được dịch từ series (Link) của team (Link) với sự đồng ý của Cofounder & CEO (Link) Javascript càng ngày càng phổ biến, có nhiều nhóm các...
vncafecode viết gần 2 năm trước
56 6
White
30 4
Bài viết được dịch từ series (Link) của team (Link) với sự đồng ý của Cofounder & CEO (Link) (Link) (Link) (Link) Chào các bạn đến với bài thứ ...
vncafecode viết gần 2 năm trước
30 4
White
19 2
Bài viết được dịch từ series (Link) của team (Link) với sự đồng ý của Cofounder & CEO (Link) (Link) Hôm trước chúng ta đã có bài bắt đầu một chuỗ...
vncafecode viết gần 2 năm trước
19 2
Bài viết liên quan
White
70 8
Tăng sức mạnh cho javascript với lodash Lần này mình sẽ giới thiệu 1 thư viện javascript vô cùng bá đạo có tên là "lodash]1]", có thể nói nó là LI...
Huy Hoàng Phạm viết gần 5 năm trước
70 8
White
10 1
_Có mấy chia sẻ nhỏ, mình muốn đưa ra để mọi người cùng thảo luận góp ý. Thread này không tập trung vào Technical nữa mà discuss về Coding Style & ...
Hùng Phong viết gần 2 năm trước
10 1
White
38 8
Lâu không post gì muốn viết một bài dài dài về js cơ mà đau đầu quá viết mãi không xong, thôi post bài ngắn vậy :smiley: Lấy screen size ở đây tôi...
Hà Phạm viết gần 5 năm trước
38 8
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
19 bài viết.
177 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!