Додаткове заняття:
1. Websocket
2. gRCP
це комунікаційний протокол, який забезпечує двосторонній обмін даними через одне, тривале TCP-з'єднання*. На відміну від HTTP, який працює за моделлю "запит-відповідь", WebSocket дозволяє постійний двосторонній обмін повідомленнями між клієнтом (наприклад, веб-браузером) та сервером.
Основні характеристики WebSocket:
🤖 Двостороннє спілкування: клієнт і сервер можуть незалежно спілкуватися, без необхідності постійно встановлювати нові з'єднання.
🤖 Менше навантаження: менші витрати порівняно з традиційними HTTP-запитами.
🤖 Використання для реального часу: ідеально підходить для додатків, які потребують швидкого та постійного обміну даними.
* TCP-з'єднання — це комунікаційний механізм в мережах, що базується на протоколі передачі даних TCP (Transmission Control Protocol).
HTTP базується на TCP. Коли ви відкриваєте вебсайт, HTTP використовує TCP для передачі даних між вашим комп'ютером (клієнтом) і сервером.
Основні моменти:
Встановлення з'єднання: HTTP використовує TCP для надійного встановлення з'єднання. Клієнт і сервер створюють TCP-з'єднання перед тим, як почати обмін даними.
Передача даних: HTTP передає веб-сторінки, зображення, відео та інші ресурси через TCP. TCP гарантує, що всі ці дані прийдуть без помилок і в правильному порядку.
Закриття з'єднання: після того, як всі дані передані, TCP закриває з'єднання.
Отже, TCP забезпечує стабільність і надійність, тоді як HTTP визначає правила для обміну інформацією, наприклад, що саме передається та як це виглядає.
image source
Перший крок — це HTTP-запит від клієнта до сервера для встановлення WebSocket-з'єднання. Це виглядає як звичайний HTTP-запит з додатковими заголовками.
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
GET /chat HTTP/1.1: Клієнт намагається підключитися до ресурсу /chat.
Upgrade: websocket: Цей заголовок специфічний для запитів WebSocket. Він повідомляє серверу, що клієнт хоче перейти з протоколу HTTP на протокол WebSocket.
Connection: Upgrade: Вказує, що з'єднання повинно бути оновлене.
Sec-WebSocket-Key: Унікальний ключ, який клієнт відправляє для аутентифікації з'єднання. Сервер використовує цей ключ для створення відповідного ключа для відповіді.
Sec-WebSocket-Version: Вказує версію WebSocket-протоколу (в більшості випадків це версія 13).
Перший крок — це HTTP-запит від клієнта до сервера для встановлення WebSocket-з'єднання. Це виглядає як звичайний HTTP-запит з додатковими заголовками.
image source
Якщо сервер підтримує WebSocket і погоджується на з'єднання, він відповідає зі спеціальними заголовками, підтверджуючи перехід на WebSocket-протокол.
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
HTTP/1.1 101 Switching Protocols: Це код відповіді, який вказує, що сервер погоджується на зміну протоколу.
Upgrade: websocket: Сервер підтверджує, що з'єднання буде переведено на WebSocket.
Connection: Upgrade: Вказує, що з'єднання оновлене.
Sec-WebSocket-Accept: Сервер генерує відповідний ключ на основі ключа клієнта (Sec-WebSocket-Key) для підтвердження безпеки.
image source
Після успішного встановлення з'єднання, починається двостороння передача даних у вигляді фреймів (frames). Дані кодуються у форматі WebSocket frame, який може бути текстовим або бінарним.
Client: Hello, server!
Дані передаються у форматі фреймів WebSocket, що забезпечує мінімальні накладні витрати, оскільки не потрібно відкривати нові запити для кожного обміну даними, як у HTTP.
WebSocket підтримує два основні типи даних для передачі повідомлень:
Текстові повідомлення (Text Frames) — це повідомлення у вигляді рядків тексту, зазвичай у форматі UTF-8. Цей тип підходить для передачі текстових даних, таких як JSON або інші текстові формати.
Бінарні повідомлення (Binary Frames) — це повідомлення, які можуть містити будь-які бінарні дані, такі як зображення, аудіо, відео або серіалізовані дані.
Server: Hello, client!
Серіалізовані дані — це спосіб перетворення складних об'єктів або структур даних у формат, придатний для передачі через мережу або збереження у файл. Серіалізація дозволяє представити ці дані у вигляді послідовності байтів (бінарний формат) або тексту (наприклад, JSON, XML), яку потім можна відправити або зберегти.
Серіалізація — це процес перетворення об'єкта або структури даних (наприклад, масиву, об'єкта класу, словника) в послідовність байтів або символів, щоб її можна було:
- Передати через мережу.
- Зберегти у файлі або базі даних.
- Відправити в повідомленні (наприклад, через WebSocket або gRPC).
Фрейм складається з кількох частин:
FIN (1 біт): Це прапорець, який вказує, чи є цей фрейм останнім у повідомленні. Якщо встановлений у 1, це означає, що фрейм є останнім, і повідомлення завершено. Якщо 0 — будуть ще фрейми для цього повідомлення.
Opcode (4 біти): Вказує тип даних, які передаються:
0x0 — продовження попереднього фрейму.
0x1 — текстовий фрейм (корисне навантаження в форматі UTF-8).
0x2 — бінарний фрейм (дані в бінарному форматі).
0x8 — фрейм для закриття з'єднання.
0x9 — ping (для перевірки зв'язку).
0xA — pong (відповідь на ping).
Mask (1 біт): Цей біт вказує, чи масковане корисне навантаження (payload). У запитах від клієнта завжди встановлений у 1 (дані мають бути масковані). Для відповідей сервера він зазвичай 0.
Payload length: Це довжина корисного навантаження. Якщо довжина даних менша за 126 байтів, то ця частина займатиме 7 біт. Якщо довжина більше, то використовуються додаткові біти для представлення повної довжини.
Masking key (32 біти): Якщо Mask біт встановлений в 1, цей ключ використовується для маскування (шифрування) корисних даних. Маскування є обов'язковим для даних, які надсилає клієнт, щоб запобігти певним атакам на мережевому рівні.
Payload (змінна довжина): Це основні дані, які передаються (текст або бінарні дані).
image source
Будь-яка зі сторін (клієнт або сервер) може закрити WebSocket-з'єднання. Це робиться шляхом відправлення спеціального фрейму закриття.
Frame Type: Close
Code: 1000 (Normal closure)
Код 1000
означає "нормальне закриття", що вказує, що з'єднання завершилося без помилок. Після відправлення цього фрейму обидві сторони більше не можуть передавати дані.
Інші коди закриття: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
1. Тестування встановлення з'єднання
2. Тестування обміну повідомленнями
Що перевірити:
👉 Коректність відправлення та отримання повідомлень.
👉 Чи відповідає сервер на повідомлення клієнта.
👉 Чи підтримується правильний формат даних (наприклад, текст або бінарні дані).
3. Тестування помилок протоколу: Переконатися, що сервер коректно обробляє неправильні або непідтримувані повідомлення.
Що перевірити:
👉 Відправка некоректних фреймів (наприклад, неправильний формат).
👉 Як сервер реагує на некоректні або надто великі повідомлення.
4. Тестування закриття з'єднання - чекнути, що WebSocket-з'єднання закривається коректно.
Що перевірити:
👉 Закриття з'єднання з ініціативи клієнта та сервера.
👉 Чи надсилаються правильні коди закриття.
👉 Чи не залишається з'єднання відкритим після закриття.
5. Тестування навантаження (Load Testing) - k6, Gatling
6. Тестування безпеки
Що перевірити:
👉 чи з'єднання працює через wss:// (WebSocket Secure).
👉 Чи правильно працює автентифікація та авторизація при WebSocket-з'єднаннях.
gRPC (Google Remote Procedure Call*) — це сучасний високопродуктивний фреймворк віддалених викликів процедур. Він дозволяє додаткам працювати на різних машинах або в різних процесах і спілкуватися один з одним так, ніби вони є частинами одного додатку.
Основні особливості gRPC:
🤖 Використання HTTP/2 — gRPC працює на базі протоколу HTTP/2, який забезпечує двонаправлену потокову передачу, мультиплексування та ефективне управління з'єднаннями.
🤖 Протоколи на основі Protobuf — дані в gRPC передаються в форматі Protocol Buffers (Protobuf), що дозволяє ефективно серіалізувати й десеріалізувати повідомлення.
🤖 Підтримка різних мов програмування — gRPC підтримує багато мов програмування, таких як C++, Java, Python, Go, та інші.
🤖 Підтримка двонаправленого стримінгу — gRPC дозволяє здійснювати одночасну передачу даних у дві сторони.
Remote Procedure Call (RPC) — це метод, який дозволяє комп'ютерним програмам викликати функції або процедури, що виконуються на іншому комп'ютері, як ніби ці функції знаходяться локально. Простіше кажучи, з RPC програми можуть викликати функції на віддаленому сервері так само, як локальні функції, без необхідності турбуватися про деталі комунікації через мережу.
Як це працює:
Клієнт викликає функцію: клієнт викликає функцію на віддаленому сервері так само, як і локальну. Наприклад, клієнт може викликати метод addNumbers(3, 5
), щоб додати два числа на сервері.
Запит передається через мережу: цей виклик перетворюється на повідомлення, яке містить ім'я функції, параметри і, можливо, іншу інформацію, необхідну для її виконання. Це повідомлення відправляється через мережу до сервера.
Сервер виконує функцію: сервер отримує це повідомлення, викликає відповідну функцію з переданими параметрами (у цьому випадку — addNumbers(3, 5)), виконує її і отримує результат.
Результат повертається клієнту: сервер формує відповідь з результатом і відправляє її назад клієнту.
Клієнт отримує результат: клієнт отримує відповідь і продовжує працювати з отриманим результатом (наприклад, сума чисел 3 і 5 — це 8).
👻 Клієнт (Client) - Клієнт формує запит і передає його через мережу. Клієнт використовує автоматично згенерований код на основі .proto файлу для формування запиту.
👹 Сервер (Server) - отримує RPC запит від клієнта і виконує відповідну функцію. Після виконання функції сервер повертає результат клієнту. У gRPC сервер також використовує код, згенерований на основі .proto файлу, для обробки запитів.
👾 Протокол передачі (Transport Protocol) - TCP/IP HTTP/2
👻 Формат повідомлень (Message Format)
👻 Файл визначення інтерфейсу (.proto файл)
Файл .proto у gRPC описує сервіси (функції) та структури даних (повідомлення), які можуть використовуватися для взаємодії між клієнтом і сервером. Це свого роду контракт, який визначає, які методи можуть викликати клієнти і які параметри необхідно передати.
syntax = "proto3"; // Використовуємо синтаксис Protocol Buffers версії 3.
package greeter; // Пакет для організації коду.
// Визначаємо сервіс Greeter з одним методом SayHello.
service Greeter {
// Метод SayHello приймає HelloRequest і повертає HelloReply або помилку.
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/greeter/sayhello"
body: "*"
};
}
}
// Повідомлення, яке клієнт відправляє на сервер.
message HelloRequest {
string name = 1; // Ім'я користувача, якому ми будемо казати "Привіт".
string surname = 2;
}
// Повідомлення, яке сервер повертає клієнту.
message HelloReply {
string message = 1; // Привітальне повідомлення, яке сервер відправить клієнту.
}
// Визначаємо можливі помилки.
enum ErrorCode {
UNKNOWN = 0; // Невідома помилка.
INVALID_NAME = 1; // Некоректне ім'я (наприклад, порожнє).
INTERNAL_ERROR = 2; // Внутрішня помилка на сервері.
}
// Повідомлення для помилки.
message ErrorResponse {
ErrorCode code = 1; // Код помилки.
string message = 2; // Опис помилки.
}
Stub у gRPC — це проміжний код, який перетворює виклик функції на стороні клієнта на повідомлення, яке можна відправити на сервер. Stub на сервері, своєю чергою, отримує це повідомлення і викликає відповідну функцію. У gRPC stub автоматично генерується з файлу .proto. Це дозволяє клієнту і серверу взаємодіяти без ручного написання складного мережевого коду.
Protocol Buffers (Protobuf) — це формат серіалізації даних, розроблений Google. Він дозволяє ефективно кодувати та декодувати структури даних у бінарному форматі, що робить його швидким і компактним у порівнянні з текстовими форматами, такими як JSON або XML.
Основні характеристики Protobuf:
Бінарний формат: протоколи на основі Protobuf перетворюють дані у бінарний формат, що дозволяє значно зменшити розмір даних при передачі.
Ефективність: рrotobuf є дуже компактним, що робить його придатним для високошвидкісної передачі даних у мережах з низькими затримками.
Мовна незалежність: Protobuf підтримує багато мов програмування, таких як Java, C++, Python, Go, JavaScript, і дозволяє генерувати код для цих мов на основі одного .proto файлу.
Статична типізація: дані в Protobuf типізовані на рівні файлу .proto, що забезпечує жорстку структуру і виявлення помилок на ранніх етапах.
Як працює Protobuf?
1. Файл визначення .proto: файл з розширенням .proto, в якому описуються структури даних (повідомлення) і сервіси (функції, які можна викликати). Це схоже на те, як JSON чи XML описують структури, але з чіткішими типами даних.
2. Компіляція файлу .proto: Спеціальний компілятор protoc перетворює .proto файл у відповідні класи для різних мов програмування. Ці класи включають методи для серіалізації (перетворення об'єктів у бінарний формат) і десеріалізації (відновлення об'єктів із бінарного формату).
3. Серіалізація та десеріалізація:
Серіалізація: Коли ви передаєте дані, наприклад, через мережу, об'єкти кодуються в бінарний формат Protobuf. Цей процес називається серіалізацією.
Десеріалізація: Коли дані повертаються або отримуються від сервера, Protobuf перетворює ці бінарні дані назад у об'єкти для зручного використання програмою.
Стрімінг і додаткові функції gRPC
gRPC підтримує різні типи взаємодії між клієнтом і сервером, включаючи не тільки класичний підхід "запит-відповідь", але й стрімінг, де дані передаються постійним потоком. Ось роз'яснення цих функцій:
1. Unary RPC (Одноразовий виклик)
Це найпростіший тип виклику, коли клієнт відправляє один запит і отримує одну відповідь.
Приклад: клієнт викликає метод SayHello і отримує відповідь "Привіт!".
Підходить для: стандартних викликів, де потрібно лише одне повідомлення для виконання операції.
2. Server Streaming RPC (Стрімінг з сервера)
Клієнт відправляє один запит, а сервер відповідає потоком даних. Клієнт отримує кілька відповідей протягом певного часу.
Приклад: клієнт запитує історію замовлень, і сервер надсилає потоком всі замовлення по черзі.
Підходить для: великих обсягів даних або випадків, коли сервер поступово отримує результати.
3. Client Streaming RPC (Стрімінг з клієнта)
клієнт передає дані потоком (кілька запитів), а сервер надсилає одну відповідь після отримання всіх запитів.
Приклад: клієнт надсилає великий файл, розбитий на частини, і сервер повертає результат, коли всі частини файлу отримані.
Як це працює?
Клієнт відправляє потік запитів.
Сервер чекає, поки всі запити будуть отримані.
Після отримання всіх даних сервер формує відповідь і повертає її клієнту.
Підходить для: коли клієнт поступово надсилає багато даних.
4. Bidirectional Streaming RPC (Двосторонній стрімінг)
І клієнт, і сервер можуть надсилати дані потоком одночасно. Це двостороння комунікація, де обидві сторони можуть відправляти й отримувати повідомлення у будь-який час.
Приклад: Клієнт і сервер ведуть постійну сесію обміну даними в режимі реального часу, наприклад, для голосового або відеочату.
Клієнт і сервер одночасно надсилають та отримують дані.
Вони можуть передавати повідомлення незалежно один від одного, без необхідності чекати, коли інша сторона завершить відправку.
Підходить для: сценаріїв, де потрібно постійний обмін даними в режимі реального часу
1. Тестування встановлення з'єднання
2. Тестування обміну повідомленнями
Що перевірити:
👉 Коректність відправлення та отримання повідомлень.
👉 Чи відповідає сервер на повідомлення клієнта.
👉 Чи підтримується правильний формат даних (наприклад, текст або бінарні дані).
3. Тестування помилок протоколу: Переконатися, що сервер коректно обробляє неправильні або непідтримувані повідомлення.
Що перевірити:
👉 Відправка некоректних фреймів (наприклад, неправильний формат).
👉 Як сервер реагує на некоректні або надто великі повідомлення.
4. Тестування закриття з'єднання - чекнути, що WebSocket-з'єднання закривається коректно.
Що перевірити:
👉 Закриття з'єднання з ініціативи клієнта та сервера.
👉 Чи надсилаються правильні коди закриття.
👉 Чи не залишається з'єднання відкритим після закриття.
5. Тестування навантаження (Load Testing) - k6, Gatling
6. Тестування безпеки
Що перевірити:
👉 чи з'єднання працює через wss:// (WebSocket Secure).
👉 Чи правильно працює автентифікація та авторизація при WebSocket-з'єднаннях.
gRPC status codes: https://grpc.io/docs/guides/status-codes/