Giao thức đẩy web

Chúng ta đã thấy cách sử dụng thư viện để kích hoạt thông báo đẩy, nhưng chính xác thì các thư viện này đang làm gì?

Vâng, họ tạo các yêu cầu về mạng trong khi vẫn đảm bảo rằng các yêu cầu như vậy là định dạng phù hợp. Thông số kỹ thuật xác định yêu cầu mạng này là Giao thức đẩy web.

Sơ đồ gửi thông báo đẩy từ máy chủ đến dịch vụ đẩy

Phần này trình bày cách máy chủ có thể tự xác định bằng khoá máy chủ ứng dụng cũng như cách gửi tải trọng đã mã hoá và dữ liệu liên quan.

Đây không phải là một tính năng thú vị của tính năng đẩy trên web và tôi không phải chuyên gia về mã hoá, nhưng hãy xem qua từng phần vì sẽ rất hữu ích nếu bạn biết các thư viện này đang làm gì để nâng cao chất lượng.

Khoá máy chủ ứng dụng

Khi đăng ký cho người dùng, chúng ta sẽ truyền vào một applicationServerKey. Khoá này được truyền đến dịch vụ đẩy và dùng để kiểm tra nhằm đảm bảo rằng ứng dụng đã đăng ký người dùng cũng là ứng dụng đang kích hoạt thông báo đẩy.

Khi kích hoạt một thông báo đẩy, chúng ta sẽ gửi một nhóm tiêu đề cho phép dịch vụ đẩy xác thực ứng dụng. (Quy cách này được xác định theo thông số VAPID.)

Tất cả những điều này thực sự có nghĩa là gì và chính xác thì điều gì đang xảy ra? Sau đây là các bước được thực hiện để xác thực máy chủ ứng dụng:

  1. Máy chủ ứng dụng ký một số thông tin JSON bằng khoá ứng dụng riêng tư của máy chủ.
  2. Thông tin đã ký này được gửi tới dịch vụ đẩy dưới dạng tiêu đề trong yêu cầu POST.
  3. Dịch vụ đẩy sử dụng khoá công khai được lưu trữ đã nhận được từ pushManager.subscribe() để kiểm tra xem thông tin nhận được có được ký bằng khoá riêng tư liên quan đến khoá công khai hay không. Lưu ý: Khoá công khai là applicationServerKey được truyền vào lệnh gọi đăng ký.
  4. Nếu thông tin đã ký là hợp lệ, thì dịch vụ đẩy sẽ gửi thông báo đẩy đến người dùng.

Dưới đây là ví dụ về luồng thông tin này. (Lưu ý chú giải ở dưới cùng bên trái để cho biết khoá công khai và riêng tư.)

Hình minh hoạ cách sử dụng khoá máy chủ ứng dụng riêng tư khi gửi thông báo

"Thông tin đã ký" được thêm vào tiêu đề trong yêu cầu là một mã thông báo web JSON.

Mã thông báo web JSON

Mã thông báo web JSON (hay viết tắt là JWT) là một cách gửi thông báo đến bên thứ ba để trình nhận có thể xác thực ai đã gửi thông báo đó.

Khi một bên thứ ba nhận được thư, họ cần lấy khoá công khai của người gửi và sử dụng khoá đó để xác thực chữ ký của JWT. Nếu chữ ký hợp lệ thì JWT phải được ký bằng khoá riêng tư trùng khớp. Vì vậy, phải là từ người gửi dự kiến.

Có nhiều thư viện trên https://jwt.io/ có thể thực hiện việc ký cho bạn và bạn nên thực hiện việc đó nếu có thể. Để hoàn tất, hãy xem cách tạo một JWT đã ký theo cách thủ công.

Thông báo đẩy trên web và JWT đã ký

JWT đã ký chỉ là một chuỗi, mặc dù có thể coi nó là 3 chuỗi được nối với nhau bằng các dấu chấm.

Hình minh hoạ các chuỗi trong Mã thông báo web JSON

Chuỗi đầu tiên và thứ hai (Thông tin JWT và dữ liệu JWT) là các đoạn JSON được mã hoá base64, có nghĩa là tệp này có thể đọc được công khai.

Chuỗi đầu tiên là thông tin về chính JWT, cho biết thuật toán được dùng để tạo chữ ký.

Thông tin JWT cho thông báo đẩy trên web phải có các thông tin sau:

{
  "typ": "JWT",
  "alg": "ES256"
}

Chuỗi thứ hai là Dữ liệu JWT. Tệp này cung cấp thông tin về người gửi JWT, đối tượng dự kiến và thời gian có hiệu lực.

Đối với đẩy dữ liệu trên web, dữ liệu sẽ có định dạng sau:

{
  "aud": "https://some-push-service.org",
  "exp": "1469618703",
  "sub": "mailto:example@web-push-book.org"
}

Giá trị aud là "đối tượng", tức là JWT dành cho đối tượng nào. Đối với web, thì đối tượng đẩy là dịch vụ đẩy. Vì vậy, chúng tôi đặt đối tượng này thành nguồn gốc của dịch vụ đẩy.

Giá trị exp là thời hạn của JWT, điều này ngăn những kẻ theo dõi không thể sử dụng lại JWT nếu chúng chặn được. Thời gian hết hạn là dấu thời gian tính bằng giây và không được là 24 giờ nữa.

Trong Node.js, thời hạn được đặt bằng cách sử dụng:

Math.floor(Date.now() / 1000) + 12 * 60 * 60;

Đó là 12 giờ thay vì 24 giờ để tránh mọi vấn đề về sự khác biệt về xung nhịp giữa ứng dụng gửi và dịch vụ đẩy.

Cuối cùng, giá trị sub cần phải là URL hoặc địa chỉ email mailto. Việc này là để nếu một dịch vụ đẩy cần liên hệ với người gửi, dịch vụ đó có thể tìm thấy thông tin liên hệ từ JWT. (Đây là lý do thư viện đẩy web cần có địa chỉ email).

Giống như Thông tin JWT, Dữ liệu JWT được mã hoá dưới dạng chuỗi base64 an toàn với URL.

Chuỗi thứ ba, chữ ký, là kết quả của việc lấy hai chuỗi đầu tiên (Thông tin JWT và Dữ liệu JWT), kết hợp chúng bằng một ký tự dấu chấm mà chúng ta sẽ gọi là "mã thông báo chưa có chữ ký" rồi ký tên đó.

Quá trình ký yêu cầu mã hoá "mã thông báo chưa có chữ ký" bằng ES256. Theo thông số kỹ thuật JWT, ES256 là từ viết tắt của "ECDSA sử dụng đường cong P-256 và thuật toán băm SHA-256". Khi sử dụng web Crypto, bạn có thể tạo chữ ký như sau:

// Utility function for UTF-8 encoding a string to an ArrayBuffer.
const utf8Encoder = new TextEncoder('utf-8');

// The unsigned token is the concatenation of the URL-safe base64 encoded
// header and body.
const unsignedToken = .....;

// Sign the |unsignedToken| using ES256 (SHA-256 over ECDSA).
const key = {
  kty: 'EC',
  crv: 'P-256',
  x: window.uint8ArrayToBase64Url(
    applicationServerKeys.publicKey.subarray(1, 33)),
  y: window.uint8ArrayToBase64Url(
    applicationServerKeys.publicKey.subarray(33, 65)),
  d: window.uint8ArrayToBase64Url(applicationServerKeys.privateKey),
};

// Sign the |unsignedToken| with the server's private key to generate
// the signature.
return crypto.subtle.importKey('jwk', key, {
  name: 'ECDSA', namedCurve: 'P-256',
}, true, ['sign'])
.then((key) => {
  return crypto.subtle.sign({
    name: 'ECDSA',
    hash: {
      name: 'SHA-256',
    },
  }, key, utf8Encoder.encode(unsignedToken));
})
.then((signature) => {
  console.log('Signature: ', signature);
});

Dịch vụ đẩy có thể xác thực JWT bằng cách sử dụng khoá máy chủ ứng dụng công khai để giải mã chữ ký và đảm bảo chuỗi đã giải mã giống với "mã thông báo chưa được ký" (tức là 2 chuỗi đầu tiên trong JWT).

JWT đã ký (tức là cả 3 chuỗi được nối bằng dấu chấm), sẽ được gửi tới dịch vụ đẩy web dưới dạng tiêu đề Authorization có thêm WebPush vào trước, như sau:

Authorization: 'WebPush [JWT Info].[JWT Data].[Signature]';

Giao thức đẩy web cũng cho biết rằng khoá máy chủ ứng dụng công khai phải được gửi trong tiêu đề Crypto-Key dưới dạng chuỗi được mã hoá base64 an toàn cho URL có p256ecdsa= được thêm vào trước.

Crypto-Key: p256ecdsa=[URL Safe Base64 Public Application Server Key]

Mã hoá tải trọng

Tiếp theo, hãy xem cách chúng ta có thể gửi trọng tải bằng thông báo đẩy để khi ứng dụng web nhận được thông báo đẩy, ứng dụng đó có thể truy cập vào dữ liệu nhận được.

Một câu hỏi phổ biến của những ai đã sử dụng các dịch vụ đẩy khác là tại sao tải trọng đẩy web cần phải được mã hoá? Với các ứng dụng gốc, thông báo đẩy có thể gửi dữ liệu dưới dạng văn bản thuần tuý.

Ưu điểm của tính năng đẩy web là vì mọi dịch vụ đẩy đều sử dụng cùng một API (giao thức đẩy web), nên nhà phát triển không phải quan tâm dịch vụ đẩy là ai. Chúng tôi có thể đưa ra yêu cầu theo đúng định dạng và chờ một thông báo đẩy sẽ được gửi. Nhược điểm của việc này là các nhà phát triển có thể hình dung được là gửi tin nhắn tới một dịch vụ đẩy không đáng tin cậy. Bằng cách mã hoá tải trọng, dịch vụ đẩy không thể đọc dữ liệu được gửi. Chỉ trình duyệt mới có thể giải mã thông tin. Việc này giúp bảo vệ dữ liệu của người dùng.

Việc mã hoá tải trọng được xác định trong thông số kỹ thuật Mã hoá thư.

Trước khi xem xét các bước cụ thể để mã hoá tải trọng thông báo đẩy, chúng ta nên đề cập đến một số kỹ thuật sẽ được dùng trong quá trình mã hoá. (Mẹo về Mat Scales cho bài viết xuất sắc của anh về mã hoá bằng công nghệ đẩy).

ECDH và HKDF

Cả ECDH và HKDF đều được sử dụng trong suốt quá trình mã hoá và mang lại các lợi ích cho mục đích mã hoá thông tin.

ECDH: Trao đổi khoá Diffie-Hellman theo đường cong elip

Giả sử bạn có hai người muốn chia sẻ thông tin, Alice và Bob. Cả Alice và Bob đều có khoá công khai và riêng tư. Alice và Bob chia sẻ khoá công khai của mình với nhau.

Thuộc tính hữu ích của các khoá được tạo bằng ECDH là Alice có thể sử dụng khoá riêng tư của mình và khoá công khai của Bob để tạo giá trị bí mật "X". Bob có thể làm như vậy, lấy khoá riêng tư của mình và khoá công khai của Alice để tạo ra cùng một giá trị 'X' một cách độc lập. Điều này khiến 'X' trở thành bí mật dùng chung và Alice và Bob chỉ phải chia sẻ khoá công khai. Giờ đây, Bob và Alice có thể sử dụng "X" để mã hoá và giải mã thông điệp giữa họ.

Theo những gì tôi biết, ECDH xác định các thuộc tính của đường cong cho phép "tính năng" tạo bí mật "X" chung.

Đây là nội dung giải thích tổng quan về ECDH. Nếu bạn muốn tìm hiểu thêm, bạn nên xem video này.

Về mã, hầu hết ngôn ngữ / nền tảng đều có thư viện để giúp bạn dễ dàng tạo các khoá này.

Trong nút, chúng ta sẽ làm như sau:

const keyCurve = crypto.createECDH('prime256v1');
keyCurve.generateKeys();

const publicKey = keyCurve.getPublicKey();
const privateKey = keyCurve.getPrivateKey();

HKDF: Hàm dẫn xuất khoá dựa trên HMAC

Wikipedia có mô tả ngắn gọn về HKDF:

HKDF là một hàm dẫn xuất khoá dựa trên HMAC, chuyển đổi mọi tài liệu khoá yếu thành tài liệu khoá mạnh được mã hoá. Ví dụ: Hệ thống có thể dùng công cụ này để chuyển đổi các bí mật mà Diffie Hellman chia sẻ thành nội dung khoá phù hợp để sử dụng trong quá trình mã hoá, kiểm tra hoặc xác thực tính toàn vẹn.

Về cơ bản, HKDF sẽ sử dụng dữ liệu đầu vào không được bảo mật nhiều và giúp tăng cường bảo mật.

Thông số kỹ thuật xác định việc mã hoá này yêu cầu sử dụng SHA-256 làm thuật toán băm và các khoá kết quả cho HKDF trong quá trình đẩy web không được dài hơn 256 bit (32 byte).

Trong nút, bạn có thể triển khai như sau:

// Simplified HKDF, returning keys up to 32 bytes long
function hkdf(salt, ikm, info, length) {
  // Extract
  const keyHmac = crypto.createHmac('sha256', salt);
  keyHmac.update(ikm);
  const key = keyHmac.digest();

  // Expand
  const infoHmac = crypto.createHmac('sha256', key);
  infoHmac.update(info);

  // A one byte long buffer containing only 0x01
  const ONE_BUFFER = new Buffer(1).fill(1);
  infoHmac.update(ONE_BUFFER);

  return infoHmac.digest().slice(0, length);
}

Mẹo cho bài viết của Mat Scale cho mã ví dụ này.

Thư viện này chưa bao gồm hàm ECDHHKDF một cách thoải mái.

ECDH là một cách an toàn để chia sẻ khoá công khai và tạo khoá bí mật dùng chung. HKDF là một cách để lấy tài liệu không an toàn và đảm bảo an toàn cho chúng.

Dữ liệu này sẽ được sử dụng trong quá trình mã hoá tải trọng. Tiếp theo, hãy xem dữ liệu chúng tôi lấy làm dữ liệu đầu vào và cách mã hoá dữ liệu đó.

Thông tin đầu vào

Khi muốn gửi thông báo đẩy đến người dùng có tải trọng, chúng ta cần có 3 thông tin đầu vào:

  1. Chính tải trọng.
  2. Khoá bí mật auth của PushSubscription.
  3. Khoá p256dh qua PushSubscription.

Chúng ta đã thấy các giá trị authp256dh được truy xuất từ PushSubscription, nhưng xin lưu ý rằng với một gói thuê bao, chúng ta cần các giá trị sau:

subscription.toJSON().keys.auth;
subscription.toJSON().keys.p256dh;

subscription.getKey('auth');
subscription.getKey('p256dh');

Giá trị auth phải được coi là bí mật và không được chia sẻ ra bên ngoài ứng dụng của bạn.

Khoá p256dh là khoá công khai, đôi khi còn được gọi là khoá công khai của ứng dụng. Ở đây, chúng tôi sẽ gọi p256dh là khoá công khai của gói thuê bao. Khoá công khai của gói thuê bao sẽ do trình duyệt tạo. Trình duyệt sẽ giữ bí mật khoá riêng tư và sử dụng khoá đó để giải mã tải trọng.

Cần có ba giá trị này, auth, p256dhpayload làm dữ liệu đầu vào và kết quả của quá trình mã hoá sẽ là tải trọng đã mã hoá, một giá trị dữ liệu ngẫu nhiên và một khoá công khai chỉ dùng để mã hoá dữ liệu.

Muối

Dữ liệu ngẫu nhiên cần phải là 16 byte dữ liệu ngẫu nhiên. Trong NodeJS, chúng ta sẽ làm như sau để tạo dữ liệu ngẫu nhiên:

const salt = crypto.randomBytes(16);

Khoá công khai / Khoá riêng tư

Khoá công khai và riêng tư nên được tạo bằng đường cong elip P-256, cách thực hiện trong Node như sau:

const localKeysCurve = crypto.createECDH('prime256v1');
localKeysCurve.generateKeys();

const localPublicKey = localKeysCurve.getPublicKey();
const localPrivateKey = localKeysCurve.getPrivateKey();

Chúng tôi gọi các khoá này là "khoá cục bộ". Các khoá này chỉ được dùng để mã hoá và không liên quan gì đến khoá máy chủ ứng dụng.

Với tải trọng, khoá bí mật xác thực và khoá công khai của gói thuê bao làm dữ liệu đầu vào, đồng thời với một muối và tập hợp khoá cục bộ mới tạo, chúng ta đã sẵn sàng tiến hành một số quy trình mã hoá.

Khoá bí mật dùng chung

Bước đầu tiên là tạo khoá bí mật dùng chung bằng cách sử dụng khoá công khai của gói thuê bao và khoá riêng tư mới của chúng ta (có nhớ phần giải thích của ECDH cho Alice và Bob không? cứ thế).

const sharedSecret = localKeysCurve.computeSecret(
  subscription.keys.p256dh,
  'base64',
);

Khoá này được dùng trong bước tiếp theo để tính Khoá ngẫu nhiên được gán biệt danh (PRK).

Khoá ngẫu nhiên giả

Khoá ngẫu nhiên giả (PRK) là sự kết hợp giữa bí mật xác thực của gói thuê bao đẩy và khoá bí mật dùng chung mà chúng ta vừa tạo.

const authEncBuff = new Buffer('Content-Encoding: auth\0', 'utf8');
const prk = hkdf(subscription.keys.auth, sharedSecret, authEncBuff, 32);

Có thể bạn đang thắc mắc chuỗi Content-Encoding: auth\0 dùng để làm gì. Tóm lại, mã này không có mục đích rõ ràng, mặc dù các trình duyệt có thể giải mã tin nhắn đến và tìm phương thức mã hoá nội dung dự kiến. \0 thêm một byte có giá trị 0 vào cuối Vùng đệm. Điều này được dự kiến bởi các trình duyệt sẽ giải mã thông báo, vốn sẽ mong đợi nhiều byte như vậy để mã hoá nội dung, theo sau là một byte có giá trị 0, tiếp theo là dữ liệu đã mã hoá.

Khoá ngẫu nhiên giả của chúng tôi chỉ đơn giản là chạy quy trình xác thực, khoá bí mật dùng chung và một phần thông tin mã hoá thông qua HKDF (tức là tăng cường được mã hoá theo cách mã hoá).

Bối cảnh

"Ngữ cảnh" là một tập hợp các byte dùng để tính toán hai giá trị sau này trong trình duyệt mã hoá. Về cơ bản, đó là một mảng byte chứa khoá công khai của gói thuê bao và khoá công khai cục bộ.

const keyLabel = new Buffer('P-256\0', 'utf8');

// Convert subscription public key into a buffer.
const subscriptionPubKey = new Buffer(subscription.keys.p256dh, 'base64');

const subscriptionPubKeyLength = new Uint8Array(2);
subscriptionPubKeyLength[0] = 0;
subscriptionPubKeyLength[1] = subscriptionPubKey.length;

const localPublicKeyLength = new Uint8Array(2);
subscriptionPubKeyLength[0] = 0;
subscriptionPubKeyLength[1] = localPublicKey.length;

const contextBuffer = Buffer.concat([
  keyLabel,
  subscriptionPubKeyLength.buffer,
  subscriptionPubKey,
  localPublicKeyLength.buffer,
  localPublicKey,
]);

Vùng đệm ngữ cảnh cuối cùng là một nhãn, số lượng byte trong khoá công khai của gói thuê bao, theo sau là chính khoá đó, tiếp đến là số byte khoá công khai cục bộ, tiếp theo là chính khoá đó.

Với giá trị ngữ cảnh này, chúng ta có thể sử dụng giá trị đó trong việc tạo số chỉ dùng một lần và khoá mã hoá nội dung (CEK).

Khoá mã hoá nội dung và số chỉ dùng một lần

Số chỉ dùng một lần là một giá trị ngăn chặn các cuộc tấn công phát lại vì chỉ nên sử dụng một lần.

Khoá mã hoá nội dung (CEK) là khoá cuối cùng được dùng để mã hoá tải trọng.

Trước tiên, chúng ta cần tạo các byte dữ liệu cho số chỉ dùng một lần và CEK, đơn giản là một chuỗi mã hoá nội dung, theo sau là vùng đệm ngữ cảnh mà chúng ta vừa tính toán:

const nonceEncBuffer = new Buffer('Content-Encoding: nonce\0', 'utf8');
const nonceInfo = Buffer.concat([nonceEncBuffer, contextBuffer]);

const cekEncBuffer = new Buffer('Content-Encoding: aesgcm\0');
const cekInfo = Buffer.concat([cekEncBuffer, contextBuffer]);

Thông tin này được chạy thông qua HKDF kết hợp dữ liệu ngẫu nhiên và PRK với số chỉ dùng một lần và cekInfo:

// The nonce should be 12 bytes long
const nonce = hkdf(salt, prk, nonceInfo, 12);

// The CEK should be 16 bytes long
const contentEncryptionKey = hkdf(salt, prk, cekInfo, 16);

Việc này cung cấp cho chúng tôi số chỉ dùng một lần và khoá mã hoá nội dung.

Thực hiện quá trình mã hoá

Giờ đây, khi đã có khoá mã hoá nội dung, chúng ta có thể mã hoá tải trọng.

Chúng tôi tạo một thuật toán mật mã AES128 bằng cách sử dụng khoá mã hoá nội dung làm khoá và số chỉ dùng một lần là vectơ khởi tạo.

Trong Nút, thao tác này được thực hiện như sau:

const cipher = crypto.createCipheriv(
  'id-aes128-GCM',
  contentEncryptionKey,
  nonce,
);

Trước khi mã hoá tải trọng, chúng ta cần xác định lượng khoảng đệm mà chúng ta muốn thêm vào phía trước tải trọng. Lý do chúng tôi muốn thêm khoảng đệm là vì tính năng này giúp ngăn chặn nguy cơ kẻ nghe lén có thể xác định "loại" thông báo dựa trên kích thước tải trọng.

Bạn phải thêm 2 byte khoảng đệm để cho biết độ dài của bất kỳ khoảng đệm bổ sung nào.

Ví dụ: nếu bạn không thêm khoảng đệm, bạn sẽ có 2 byte với giá trị 0, tức là không có khoảng đệm nào, sau 2 byte này bạn sẽ đọc được tải trọng. Nếu bạn thêm 5 byte đệm, hai byte đầu tiên sẽ có giá trị là 5, vì vậy người tiêu dùng sẽ đọc thêm 5 byte và sau đó bắt đầu đọc tải trọng.

const padding = new Buffer(2 + paddingLength);
// The buffer must be only zeros, except the length
padding.fill(0);
padding.writeUInt16BE(paddingLength, 0);

Sau đó, chúng ta chạy khoảng đệm và tải trọng của mình thông qua thuật toán mật mã này.

const result = cipher.update(Buffer.concat(padding, payload));
cipher.final();

// Append the auth tag to the result -
// https://nodejs.org/api/crypto.html#crypto_cipher_getauthtag
const encryptedPayload = Buffer.concat([result, cipher.getAuthTag()]);

Giờ đây, chúng ta có tải trọng được mã hoá. Thật tuyệt vời!

Tất cả những việc còn lại là xác định cách gửi tải trọng này đến dịch vụ đẩy.

Phần tiêu đề và nội dung tải trọng đã mã hoá

Để gửi tải trọng đã mã hoá này đến dịch vụ đẩy, chúng ta cần xác định một vài tiêu đề khác nhau trong yêu cầu POST.

Tiêu đề mã hoá

Tiêu đề 'Mã hóa' phải chứa muối dùng để mã hóa tải trọng.

Dữ liệu ngẫu nhiên 16 byte phải được mã hoá an toàn cho URL base64 và được thêm vào tiêu đề Mã hoá, như sau:

Encryption: salt=[URL Safe Base64 Encoded Salt]

Tiêu đề Khoá mã hoá

Chúng tôi nhận thấy tiêu đề Crypto-Key được dùng trong phần "Khoá máy chủ ứng dụng" để chứa khoá máy chủ ứng dụng công khai.

Tiêu đề này cũng dùng để chia sẻ khoá công khai cục bộ dùng để mã hoá tải trọng.

Tiêu đề kết quả sẽ có dạng như sau:

Crypto-Key: dh=[URL Safe Base64 Encoded Local Public Key String]; p256ecdsa=[URL Safe Base64 Encoded Public Application Server Key]

Loại nội dung, độ dài và tiêu đề mã hoá

Tiêu đề Content-Length là số lượng byte trong tải trọng đã mã hoá. Tiêu đề "Loại nội dung" và "Mã hoá nội dung" là các giá trị cố định. Nội dung này được minh hoạ bên dưới.

Content-Length: [Number of Bytes in Encrypted Payload]
Content-Type: 'application/octet-stream'
Content-Encoding: 'aesgcm'

Khi đã đặt các tiêu đề này, chúng ta cần gửi tải trọng đã mã hoá dưới dạng phần nội dung của yêu cầu. Lưu ý rằng Content-Type được thiết lập thành application/octet-stream. Điều này là do tải trọng đã mã hoá phải được gửi dưới dạng luồng byte.

Trong NodeJS, chúng ta sẽ thực hiện việc này như sau:

const pushRequest = https.request(httpsOptions, function(pushResponse) {
pushRequest.write(encryptedPayload);
pushRequest.end();

Thêm tiêu đề?

Chúng ta đã đề cập đến các tiêu đề dùng cho JWT / Khoá máy chủ ứng dụng (tức là cách xác định ứng dụng bằng dịch vụ đẩy) cũng như đã đề cập đến các tiêu đề dùng để gửi tải trọng được mã hoá.

Các tiêu đề bổ sung mà dịch vụ đẩy sẽ sử dụng để thay đổi hành vi của thông báo đã gửi. Một số tiêu đề trong số này là bắt buộc, trong khi một số tiêu đề khác là không bắt buộc.

Tiêu đề TTL

Bắt buộc

TTL (hay thời gian tồn tại) là một số nguyên xác định số giây bạn muốn thông báo đẩy xuất hiện trên dịch vụ đẩy trước khi gửi đi. Khi TTL hết hạn, tin nhắn sẽ bị xoá khỏi hàng đợi của dịch vụ đẩy và sẽ không được gửi đi.

TTL: [Time to live in seconds]

Nếu bạn đặt TTL bằng 0, dịch vụ đẩy sẽ cố gắng gửi thông báo ngay lập tức. nhưng nếu không thể kết nối với thiết bị thì thông báo của bạn sẽ bị loại bỏ ngay lập tức khỏi hàng đợi của dịch vụ đẩy.

Về mặt kỹ thuật, dịch vụ đẩy có thể giảm TTL của thông báo đẩy nếu muốn. Bạn có thể biết liệu điều này đã xảy ra hay chưa bằng cách kiểm tra tiêu đề TTL trong phản hồi của một dịch vụ đẩy.

Chủ đề

Không bắt buộc

Chủ đề là các chuỗi có thể dùng để thay thế một thông báo đang chờ xử lý bằng một thông báo mới nếu chúng có tên chủ đề trùng khớp.

Điều này hữu ích trong các trường hợp gửi nhiều thông báo trong khi thiết bị không có kết nối mạng và bạn thực sự chỉ muốn người dùng xem thông báo mới nhất khi thiết bị được bật.

Khẩn cấp

Không bắt buộc

Tính khẩn cấp cho dịch vụ đẩy biết mức độ quan trọng của thông báo đối với người dùng. Dịch vụ đẩy có thể sử dụng tính năng này để giúp tiết kiệm thời lượng pin cho thiết bị của người dùng bằng cách chỉ đánh thức các thông báo quan trọng khi pin yếu.

Giá trị tiêu đề được xác định như sau. Giá trị mặc định là normal.

Urgency: [very-low | low | normal | high]

Mọi thứ cùng nhau

Nếu có thêm câu hỏi về cách hoạt động của tất cả những điều này, bạn có thể xem cách các thư viện kích hoạt thông báo đẩy trên web-push-libs org.

Sau khi có tải trọng đã mã hoá và các tiêu đề ở trên, bạn chỉ cần thực hiện yêu cầu POST cho endpoint trong PushSubscription.

Vậy chúng tôi sẽ làm gì với phản hồi cho yêu cầu POST này?

Phản hồi từ dịch vụ đẩy

Sau khi gửi yêu cầu đến một dịch vụ đẩy, bạn cần kiểm tra mã trạng thái của phản hồi vì điều đó sẽ cho bạn biết liệu yêu cầu có thành công hay không.

Mã trạng thái Nội dung mô tả
201 Đã tạo. Đã nhận được và chấp nhận yêu cầu gửi thông báo đẩy.
429 Quá nhiều yêu cầu. Tức là máy chủ ứng dụng của bạn đã đạt đến giới hạn tốc độ với một dịch vụ đẩy. Dịch vụ đẩy phải bao gồm một tiêu đề "Try-After" (Thử lại-Sau) để cho biết thời gian trước khi có thể thực hiện một yêu cầu khác.
400 Yêu cầu không hợp lệ. Thường thì điều này có nghĩa là một trong các tiêu đề của bạn không hợp lệ hoặc có định dạng không chính xác.
404 Không tìm thấy. Đây là dấu hiệu cho biết gói thuê bao đã hết hạn và không thể sử dụng được. Trong trường hợp này, bạn nên xoá "PushSubscription" (Gói thuê bao) rồi đợi ứng dụng đăng ký lại.
410 Không tồn tại. Gói thuê bao không còn hợp lệ và cần bị xoá khỏi máy chủ ứng dụng. Bạn có thể tái tạo vấn đề này bằng cách gọi phương thức "unsubscribe()" trên một "PushSubscription".
413 Kích thước tải trọng quá lớn. Tải trọng kích thước tối thiểu mà một dịch vụ đẩy phải hỗ trợ là 4096 byte (hoặc 4 kb).

Điểm đến tiếp theo

Lớp học lập trình