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ì?

Họ đang đưa ra yêu cầu mạng trong khi vẫn đảm bảo rằng những yêu cầu như vậy đều ở đị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ơ đồ về việc 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 và cách gửi tải trọng đã mã hoá cũng như dữ liệu liên kết.

Đây không phải là một mặt khoa học về việc đẩy dữ liệu web và tôi không có chuyên môn về mã hoá, nhưng chúng ta hãy xem qua từng phần vì việc biết được các thư viện này đang làm gì nâng cao sẽ rất hữu ích.

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

Khi đăng ký 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ý cho người dùng cũng là ứng dụng đang kích hoạt thông báo đẩy.

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

Tất cả những điều này có ý nghĩa gì và điều gì thực sự xảy ra? Dưới đây là các bướ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ư.
  2. Thông tin đã ký này được gửi đến 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 đã lưu trữ mà dịch vụ 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 cho 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 để chỉ định khoá công khai và khoá 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 (hoặc gọi tắt là JWT) là một cách để gửi thông báo cho bên thứ ba để người nhận có thể xác thực ai đã gửi thông báo đó.

Khi nhận được thông báo, bên thứ ba 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 khớp với người gửi dự kiến.

Có rất 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 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 đây là 3 chuỗi được nối 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 phần của JSON đã được mã hoá base64, nghĩa là chuỗi này có thể đọc công khai.

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

Thông tin JWT cho thông báo đẩy web phải chứa các thông tin sau:

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

Chuỗi thứ hai là Dữ liệu JWT. Trường này cung cấp thông tin về người gửi JWT, đối tượng mục tiêu và khoảng thời gian có hiệu lực của JWT đó.

Đối với thông báo đẩy 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à "audience", tức là đối tượng mà JWT nhắm đến. Đối với dịch vụ đẩy trên web, đối tượng là dịch vụ đẩy, vì vậy chúng tôi đặt đối tượng này thành gốc của dịch vụ đẩy.

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

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

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

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

Cuối cùng, giá trị sub cần phải là một URL hoặc một địa chỉ email mailto. Điều này là để nếu cần liên hệ với người gửi, dịch vụ đẩy có thể tìm thấy thông tin liên hệ từ JWT. (Đây là lý do thư viện web-push 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 cho 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), nối chúng với một ký tự dấu chấm, chúng ta sẽ gọi là "mã thông báo chưa ký" và ký mã thông báo đó.

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

// 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 ký" (tức là hai chuỗi đầu tiên trong JWT).

JWT đã ký (tức là cả ba chuỗi được nối với nhau bằng dấu chấm) được gửi đến dịch vụ đẩy web dưới dạng tiêu đề AuthorizationWebPush ở đầu, như sau:

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

Giao thức đẩy web cũng nêu rõ 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ó thêm p256ecdsa= ở phía 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 tải trọng 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 thường gặp của những người đã sử dụng các dịch vụ đẩy khác là tại sao tải trọng đẩy web cần đượ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ý.

Một điểm tuyệt vời của công nghệ đẩy trên web là vì tất cả các 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 cần quan tâm đến dịch vụ đẩy. Chúng tôi có thể đưa ra yêu cầu ở định dạng phù hợp và dự kiến sẽ gửi thông báo đẩy. Nhược điểm của việc này là nhà phát triển có thể gửi thông báo đến 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. Điều này giúp bảo vệ dữ liệu của người dùng.

Quá trình mã hoá tải trọng được xác định trong thông số kỹ thuật về Mã hoá thông báo.

Trước khi xem các bước cụ thể để mã hoá tải trọng của thông báo đẩy, chúng ta nên đề cập đến một số kỹ thuật sẽ được sử dụng trong quá trình mã hoá. (Mời bạn ngả mũ rộng rãi để Mat Scales đọc bài viết xuất sắc về phương thức mã hoá đẩ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 lợi ích cho mục đích mã hoá thông tin.

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

Hãy tưởng tượng 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à khoá riêng tư riêng. Alice và Bob chia sẻ khoá công khai 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 tương tự, lấy khoá riêng tư của anh và khoá công khai của Alice để tạo cùng một giá trị "X" một cách độc lập. Điều này khiến "X" trở thành một bí mật dùng chung và Alice và Bob chỉ cần chia sẻ khoá công khai của họ. Giờ đây, Bob và Alice có thể sử dụng "X" để mã hoá và giải mã tin nhắn 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ạo ra "tính năng" bí mật dùng chung "X".

Đâ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ặt 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ó nội dung mô tả ngắn gọn về HKDF:

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

Về cơ bản, HKDF sẽ lấy dữ liệu đầu vào không an toàn cụ thể và tăng tính bảo mật.

Thông số kỹ thuật xác định lớp mã hoá này yêu cầu sử dụng SHA-256 làm thuật toán băm của chúng tôi và khoá kết quả cho HKDF trong quá trình đẩy web không được dài quá 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 cập nhật cho bài viết của Mat Scale cho mã ví dụ này.

Nội dung này bao gồm ECDHHKDF một cách lỏng lẻo.

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à bảo mật tài liệu đó.

Giá trị này sẽ được dùng trong quá trình mã hoá tải trọng. Tiếp theo, hãy xem xét những gì chúng ta coi là 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. Mã bí mật auth từ PushSubscription.
  3. Khoá p256dh từ PushSubscription.

Chúng ta đã thấy các giá trị authp256dh được truy xuất từ PushSubscription, nhưng để nhắc lại nhanh, 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');

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

Khoá p256dh là khoá công khai, đôi khi đượ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á này để giải mã tải trọng.

Ba giá trị này (auth, p256dhpayload) là cần thiết để 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á, giá trị muối và khoá công khai chỉ dùng để mã hoá dữ liệu.

Muối

Dữ liệu ngẫu nhiên này phải là 16 byte. Trong NodeJS, chúng ta sẽ làm như sau để tạo một giá trị salt:

const salt = crypto.randomBytes(16);

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

Bạn nên tạo khoá công khai và khoá riêng tư bằng cách sử dụng đường cong elip P-256. Chúng ta sẽ thực hiện việc này trong Node như sau:

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

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

Chúng tôi sẽ 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 đến khoá máy chủ ứng dụng.

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

Khoá bí mật dùng chung

Bước đầu tiên là tạo một khoá bí mật dùng chung bằng khoá công khai của gói thuê bao và khoá riêng mới của chúng ta (hãy nhớ nội dung giải thích về ECDH với Alice và Bob? Vậy là xong).

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

Giá trị này được dùng trong bước tiếp theo để tính Khoá ngẫu nhiên giả (PRK).

Khoá giả ngẫu nhiên

Khoá ngẫu nhiên giả lập (PRK) là tổ hợp của khoá 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);

Bạn có thể thắc mắc chuỗi Content-Encoding: auth\0 dùng để làm gì. Tóm lại, thuộc tính 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ã thông báo đế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ị là 0 vào cuối vùng đệm. Điều này xảy ra khi trình duyệt giải mã thông báo sẽ dự kiến có quá nhiều byte để mã hoá nội dung, theo sau là một byte có giá trị 0, theo sau là dữ liệu đã mã hoá.

Khoá ngẫu nhiên giả lập của chúng ta chỉ đơn giản là chạy thông tin xác thực, thông tin 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 khả năng mã hoá).

Ngữ cảnh

"Ngữ cảnh" là một tập hợp các byte được 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ố byte trong khoá công khai của gói thuê bao, theo sau là khoá chính rồi mới đến 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ị đó để tạo một 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ì giá trị này chỉ được dùng một lần.

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

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. Đây chỉ là một chuỗi mã hoá nội dung, theo sau là bộ đệ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 salt và PRK với nonceInfo 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);

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

Thực hiện 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 ta tạo một thuật toán mã hoá 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 đầu tải trọng. Lý do chúng ta muốn thêm khoảng đệm là để 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 không thêm khoảng đệm, bạn sẽ có hai byte có giá trị 0, tức là không có khoảng đệm nào, sau hai byte này, bạn sẽ đọc được tải trọng. Nếu bạn thêm 5 byte khoảng đệm, thì hai byte đầu tiên sẽ có giá trị là 5, do đó, trình dùng sẽ đọc thêm 5 byte rồi 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 sẽ 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()]);

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

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

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ã hoá" phải chứa muối dùng để mã hoá 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à thêm vào tiêu đề Mã hóa, như sau:

Encryption: salt=[URL Safe Base64 Encoded Salt]

Tiêu đề khoá mã hoá

Chúng ta đã thấy tiêu đề Crypto-Key được sử 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 được dùng để chia sẻ khoá công khai cục bộ dùng để mã hoá tải trọng.

Tiêu đề thu được 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ố byte trong tải trọng đã mã hoá. Tiêu đề "Content-Type" và "Content-Encoding" là các giá trị cố định. Điều này được thể hiện dưới đây.

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

Khi đã thiết lập 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. Xin lưu ý rằng Content-Type được đặt thành application/octet-stream. Lý do là 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 như sau:

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

Bạn muốn thêm tiêu đề khác?

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), đồng thời đã đề cập đến các tiêu đề dùng để gửi tải trọng được mã hoá.

Có các tiêu đề bổ sung mà các dịch vụ đẩy 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, còn lại là không bắt buộc.

Tiêu đề TTL

Bắt buộc

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

TTL: [Time to live in seconds]

Nếu bạn đặt TTL bằng 0, thì dịch vụ đẩy sẽ tìm cách 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ông báo của bạn sẽ ngay lập tức bị loại bỏ khỏi hàng đợi 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 có xảy ra hay không bằng cách kiểm tra tiêu đề TTL trong phản hồi từ 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 điệp đang chờ xử lý bằng một thông báo mới nếu có tên chủ đề trùng khớp.

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

Khẩn cấp

Không bắt buộc

Mức độ khẩn cấp cho dịch vụ đẩy biết mức độ quan trọng của một 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 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]

Tất cả cùng nhau

Nếu có thêm câu hỏi về cách thức hoạt động của tất cả những điều này, bạn luô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 tạo một yêu cầu POST đến endpoint trong PushSubscription.

Vậy chúng ta 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 tạo yêu cầu cho 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ì mã đó sẽ cho bạn biết yêu cầu có thành công hay không.

Mã trạng thái Mô tả
201 Đã tạo. Yêu cầu gửi thông báo đẩy đã được nhận và chấp nhận.
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 độ sử dụng dịch vụ đẩy. Dịch vụ đẩy phải có tiêu đề "Retry-After" (Thử lại sau) để cho biết thời gian chờ trước khi có thể thực hiện yêu cầu khác.
400 Yêu cầu không hợp lệ. Thông báo này thường 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. Điều này cho biết gói thuê bao đã hết hạn và không thể sử dụng. Trong trường hợp này, bạn nên xoá `PushSubscription` và chờ ứng dụng đăng ký lại người dùng.
410 Đã xoá. Gói thuê bao không còn hợp lệ và cần được xoá khỏi máy chủ ứng dụng. Bạn có thể tái hiện lỗi này bằng cách gọi `unsubscribe()` trên một `PushSubscription`.
413 Kích thước tải trọng quá lớn. Kích thước tải trọng tối thiểu mà dịch vụ đẩy phải hỗ trợ là 4096 byte (hoặc 4 kb).

Bạn cũng có thể đọc Tiêu chuẩn Web Push (RFC8030) để biết thêm thông tin về mã trạng thái HTTP.

Điểm đến tiếp theo

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