API testing: lesson +

Додаткове заняття:

 

1. Websocket

2. gRCP

 

це комунікаційний протокол, який забезпечує двосторонній обмін даними через одне, тривале TCP-з'єднання*. На відміну від HTTP, який працює за моделлю "запит-відповідь", WebSocket дозволяє постійний двосторонній обмін повідомленнями між клієнтом (наприклад, веб-браузером) та сервером. 

 

Основні характеристики WebSocket:

🤖  Двостороннє спілкування: клієнт і сервер можуть незалежно спілкуватися, без необхідності постійно встановлювати нові з'єднання.
🤖  Менше навантаження: менші витрати порівняно з традиційними HTTP-запитами.
🤖  Використання для реального часу: ідеально підходить для додатків, які потребують швидкого та постійного обміну даними.

* TCP-з'єднання — це комунікаційний механізм в мережах, що базується на протоколі передачі даних TCP (Transmission Control Protocol).

HTTP  базується на TCP. Коли ви відкриваєте вебсайт, HTTP використовує TCP для передачі даних між вашим комп'ютером (клієнтом) і сервером.

Основні моменти:

  1. Встановлення з'єднання: HTTP використовує TCP для надійного встановлення з'єднання. Клієнт і сервер створюють TCP-з'єднання перед тим, як почати обмін даними.

  2. Передача даних: HTTP передає веб-сторінки, зображення, відео та інші ресурси через TCP. TCP гарантує, що всі ці дані прийдуть без помилок і в правильному порядку.

  3. Закриття з'єднання: після того, як всі дані передані, TCP закриває з'єднання.

Отже, TCP забезпечує стабільність і надійність, тоді як HTTP визначає правила для обміну інформацією, наприклад, що саме передається та як це виглядає.

image source

1. Запит на встановлення з'єднання

(Handshake Request)

Перший крок — це 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).

1. Запит на встановлення з'єднання

(Handshake Request)

Перший крок — це HTTP-запит від клієнта до сервера для встановлення WebSocket-з'єднання. Це виглядає як звичайний HTTP-запит з додатковими заголовками.

image source

2. Відповідь сервера на запит (Handshake Response)

Якщо сервер підтримує 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

3. Передача даних (Data Frames)

Після успішного встановлення з'єднання, починається двостороння передача даних у вигляді фреймів (frames). Дані кодуються у форматі WebSocket frame, який може бути текстовим або бінарним.

Client: Hello, server!

 

Дані передаються у форматі фреймів WebSocket, що забезпечує мінімальні накладні витрати, оскільки не потрібно відкривати нові запити для кожного обміну даними, як у HTTP.

 

WebSocket підтримує два основні типи даних для передачі повідомлень:

  1. Текстові повідомлення (Text Frames) — це повідомлення у вигляді рядків тексту, зазвичай у форматі UTF-8. Цей тип підходить для передачі текстових даних, таких як JSON або інші текстові формати.

  2. Бінарні повідомлення (Binary Frames) — це повідомлення, які можуть містити будь-які бінарні дані, такі як зображення, аудіо, відео або серіалізовані дані.

 

Server: Hello, client!

Серіалізовані дані — це спосіб перетворення складних об'єктів або структур даних у формат, придатний для передачі через мережу або збереження у файл. Серіалізація дозволяє представити ці дані у вигляді послідовності байтів (бінарний формат) або тексту (наприклад, JSON, XML), яку потім можна відправити або зберегти.

Серіалізація — це процес перетворення об'єкта або структури даних (наприклад, масиву, об'єкта класу, словника) в послідовність байтів або символів, щоб її можна було:

- Передати через мережу.
- Зберегти у файлі або базі даних.
- Відправити в повідомленні (наприклад, через WebSocket або gRPC).

Data Frames

Фрейм складається з кількох частин:

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 (змінна довжина): Це основні дані, які передаються (текст або бінарні дані).

metadata

payload

image source

4. Закриття з'єднання (Closing Frame)

Будь-яка зі сторін (клієнт або сервер) може закрити 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)

 

  • RPC передає дані у вигляді повідомлень. Це можуть бути параметри, які клієнт відправляє на сервер, і результат, який сервер повертає.
  • У gRPC для передачі даних використовується формат Protocol Buffers (Protobuf). Це бінарний формат, який компактний і ефективний для серіалізації та десеріалізації даних.

👻 Файл визначення інтерфейсу (.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 підтримує різні типи взаємодії між клієнтом і сервером, включаючи не тільки класичний підхід "запит-відповідь", але й стрімінг, де дані передаються постійним потоком. Ось роз'яснення цих функцій:

 

4. 

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/