Giao thức đẩy web

Chúng ta đã xem cách dùng thư viện để kích hoạt thông báo đẩy, nhưng điều gì chính xác thì những thư viện này đang hoạt động như thế nào?

Ồ, họ tạo yêu cầu mạng trong khi vẫn đảm bảo rằng những yêu cầu như vậy theo đúng định dạng. Thông số xác định yêu cầu mạng này là Web Push Protocol (Giao thức đẩy web).

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

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 vấn đề quan trọng trong lĩnh vực web và tôi không có chuyên môn về mã hoá, nhưng hãy cùng xem qua từng phần vì việc biết các thư viện này đang làm gì 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. Chìa khoá này là được chuyển đến dịch vụ đẩy và dùng để kiểm tra xem ứng dụng đã đăng ký người dùng cũng là ứng dụng 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 cho phép dịch vụ đẩy xác thực ứng dụng. (Điều này được xác định bằng thông số kỹ thuật VAPID.)

Tất cả những điều này có ý nghĩa gì và chính xác thì xảy ra điều gì? 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ư của máy chủ đó.
  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 được lưu trữ nhận được từ pushManager.subscribe() để kiểm tra để kiểm tra xem thông tin đã nhận có được ký bằng khoá riêng tư liên quan đến khoá công khai. Lưu ý: Khoá công khai là applicationServerKey được chuyển vào cuộc gọi đăng ký.
  4. Nếu thông tin đã ký là hợp lệ, 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. (Chú thích ở 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 một
tin nhắn

"Thông tin đã ký" được thêm vào tiêu đề trong yêu cầu là 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 đã gửi tệp.

Khi nhận được thư, bên thứ ba cần phải liên hệ với người gửi và sử dụng khoá này để 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ó một loạt 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 đó khi 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 web và JWT đã ký

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

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

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. Phần này cung cấp thông tin về người gửi JWT, người nhận và thời hạn 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 web, hãy đẩy đối tượng 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 thông báo đẩy .

Giá trị exp là thời hạn của JWT, điều này ngăn những kẻ rình rập sử dụng lại JWT nếu họ chặn nó. 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;

Cần tránh 12 giờ thay vì 24 giờ bất kỳ vấn đề nào 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 địa chỉ email mailto. Nghĩa là nếu một dịch vụ đẩy cần liên hệ với người gửi, dịch vụ này có thể tìm thấy thông tin liên hệ của 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), kết hợp chúng bằng ký tự dấu chấm, chúng ta sẽ gọi "mã thông báo chưa ký" rồi ký tên đó.

Quy 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 khoá máy chủ ứng dụng công khai để giải mã chữ ký và đảm bảo rằng chuỗi được giải mã giống nhau dưới dạng "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õ 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, trong đó có thêm p256ecdsa= vào đầu.

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 phổ biến được đặt ra từ bất kỳ ai đã sử dụng các dịch vụ đẩy khác là tại sao web đẩy tải trọng dữ liệu có cần được mã hoá không? 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ý.

Điểm hay của hoạt động đẩ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), nhà phát triển không cần quan tâm đến ai đó là dịch vụ đẩy. Chúng tôi có thể đưa ra yêu cầu ở định dạng phù hợp và kỳ vọng thông báo đẩy được gửi đi. Nhược điểm của phương pháp 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. Theo khi mã hoá tải trọng, thì 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ệ .

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 chúng ta xem 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 sử dụng trong quá trình mã hoá của chúng tôi. (Xin cảm ơn Mat Scales rất nhiều vì bài viết tuyệt vời của anh về việ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 các lợi ích cho cho mục đích mã hoá thông tin.

ECDH: Trao đổi khóa Diffie-Hellman đườ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à riêng tư của riêng mình. Alice và Bob đều 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ũng có thể làm tương tự, lấy khoá riêng tư của mình và khoá công khai của Alice để tạo giá trị "X" giống nhau một cách độc lập. Thao tác này tạo thành "X" một bí mật dùng chung còn Alice và Bob chỉ phải chia sẻ khoá công khai. Bob và Alice có thể sử dụng "X" để mã hoá và giải mã thông báo giữa các dịch vụ đó.

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" này tạo một bí mật được chia sẻ "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ã; hầu hết các ngôn ngữ/nền tảng đều có thư việ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 có chức năng biến đổi bất kỳ khoá yếu nào thành vật liệu khoá mạnh được mã hoá. Có thể sử dụng, ví dụ: để chuyển đổi Diffie Hellman đã trao đổi các bí mật được chia sẻ thành tài liệu chính phù hợp để sử dụng trong 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 phương thứ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á thu được cho HKDF trong tính năng đẩ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 mũ cho bài viết của Mat Scale cho mã ví dụ này.

Phần này đề cập một cách lỏng lẻo đến ECDHHKDF.

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 đó.

Thông tin này sẽ được sử dụng trong quá trình mã hoá tải trọng của chúng tôi. Tiếp theo, hãy xem xét những gì chúng tôi coi là và cách mã hoá thông tin đó.

Thông tin đầu vào

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

  1. Chính tải trọng.
  2. Bí mật auth từ PushSubscription.
  3. Khoá p256dh trong PushSubscription.

Chúng ta thấy các giá trị authp256dh được truy xuất qua PushSubscription nhưng Xin lưu ý rằng với một gói thuê bao, chúng tôi cần những 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 gọi là khoá công khai của ứng dụng. Ở đây, chúng ta 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 đã được tạo theo trình duyệt. 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 cần thiết làm dữ liệu đầu vào và là kết quả của quá trình mã hoá sẽ là tải trọng được mã hoá, giá trị dữ liệu ngẫu nhiên và 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 có 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ư

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 gọi những khoá này là "khoá cục bộ". Chúng chỉ được dùng để mã hoá và có không liên quan đế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 và với một khoá mới được tạo ngẫu nhiên và tập hợp khoá cục bộ, chúng ta đã sẵn sàng thực hiện một số 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',
);

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

Khoá ngẫu nhiên

Khoá ngẫu nhiên (PRK) là tổ hợp tính xác thực của gói thuê bao đẩy bí mật và bí mật chung mà chúng ta vừa tạo ra.

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ù trình duyệt có thể giải mã một thông báo đến và tìm kiếm 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. Đây là điều mà trình duyệt dự kiến khi giải mã thông báo, người dùng sẽ mong đợi nhiều byte cho quá trình mã hoá nội dung, theo sau là một byte có giá trị 0, theo sau là dữ liệu đã mã hoá.

Khoá giả mạo của chúng tôi chỉ đơn giản là chạy tính năng xác thực, bí mật được chia sẻ và một phần thông tin mã hoá thông qua HKDF (tức là làm cho mã hoá mạnh hơn).

Ngữ cảnh

"Ngữ cảnh" là một tập hợp byte dùng để tính hai giá trị sau này trong quá trình mã hoá trình duyệt. 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, tiếp đến là khoá, rồi đến số byte khoá công khai cục bộ, theo sau là 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 hoạt độ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á sẽ được dùng để mã hoá tải trọng của chúng tôi.

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 nội dung chuỗi mã hoá 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 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);

Mã 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 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 thuật toán mật mã AES128 bằng khoá mã hoá nội dung vì khoá và số chỉ dùng một lần là vectơ khởi tạo.

Trong Node, bạn có thể 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 khoảng đệm bổ sung.

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, sau 2 byte này, bạn sẽ đọ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 được 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à phần tải trọng đã mã hoá cơ thể

Để 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 của chúng tôi.

Tiêu đề mã hoá

Chế độ 'Mã hoá' tiêu đề phải chứa muối được dùng để mã hoá tải trọng.

Giá trị số chỉ dùng một lần 16 byte phải được mã hoá an toàn theo URL base64 và thêm vào tiêu đề Mã hoá, như sau:

Encryption: salt=[URL Safe Base64 Encoded Salt]

Tiêu đề khoá mã hoá

Chúng ta thấy tiêu đề Crypto-Key được dùng trong "Application Server Key" (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 trên máy 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, thời lượng và tiêu đề mã hoá

Tiêu đề Content-Length là số byte trong mã hoá tải trọng. Tiêu đề "Content-Type" và "Content-Encoding" là các giá trị cố định. Thông tin này được hiển thị bên dưới.

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 về yêu cầu của chúng tôi. Lưu ý rằng Content-Type được đặt 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ẽ làm như sau:

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

Tiêu đề khác?

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

Có các tiêu đề bổ sung mà các dịch vụ đẩy sử dụng để thay đổi hành vi của tin nhắn đã 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 (hay thời gian tồn tại) là một số nguyên chỉ định số giây bạn muốn thông báo đẩy của mình hoạt động trên dịch vụ đẩy trước khi nó được đã gử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, dịch vụ đẩy sẽ cố gắng phân phố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ị xoá 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ừ 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 tin nhắn đang chờ xử lý bằng một tin nhắn mới nếu các tin nhắn đó có tên chủ đề trùng khớp.

Điều này rất hữu ích trong trường hợp nhiều thư được gửi trong khi thiết bị của bạn đang không kết nối mạng và bạn thực sự chỉ muốn người dùng thấy dữ liệu mới nhất khi thiết bị được 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 tầm 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 thông tin này để giúp tiết kiệm thời lượng pin của thiết bị người dùng bằng cách chỉ đánh thức thiết bị khi pin yếu để nhận thông báo quan trọng.

Giá trị tiêu đề được xác định như sau. Mặc định có giá trị 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 hoạt động của tính năng này, bạn luôn có thể xem cách thư viện kích hoạt thông báo đẩy trên web-push-libs org.

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

Vậy chúng ta 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 dịch vụ đẩy, bạn cần kiểm tra mã trạng thái của phản hồi để 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 tỷ lệ bằng dịch vụ đẩy. Dịch vụ đẩy phải có chế độ "Thử lại sau" để cho biết khoảng 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ệ. Điều 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. Đây là dấu hiệu cho biết gói thuê bao đã hết hạn và không dùng được. Trong trường hợp này, bạn nên xoá "PushSubscription" và đợi máy khách đăng ký lại người dùng.
410 Không tồn tại. Gói thuê bao không còn hiệu lực nữa và cần phải xoá từ máy chủ ứng dụng. Có thể tái tạo thao tác 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. Tải trọng kích thước tối thiểu mà một dịch vụ đẩy phải kích thước hỗ trợ là 4096 byte (hoặc 4kb).

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

Bước tiếp theo

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