Клиент-серверное взаимодействие
Что такое клиент-серверная архитектура?
Клиент-серверная архитектура - это модель взаимодействия, где клиент запрашивает услуги или ресурсы, а сервер предоставляет их. Это основа работы веб-приложений, мобильных приложений и большинства современных IT-систем.
Зачем тестировщику понимать это:
- Определять, где возникают проблемы (клиент или сервер)
- Планировать тестирование API и интеграций
- Понимать архитектуру приложения для лучшего покрытия тестами
- Анализировать производительность и безопасность
- Эффективно взаимодействовать с командой разработки
Основные компоненты
Клиент (Client)
Что это: Программа или устройство, которое отправляет запросы к серверу
Типы клиентов:
Веб-браузеры:
- Chrome, Firefox, Safari, Edge
- Отправляют HTTP запросы
- Отображают HTML, выполняют JavaScript
Мобильные приложения:
- iOS приложения (Swift/Objective-C)
- Android приложения (Java/Kotlin)
- React Native, Flutter приложения
Desktop приложения:
- Electron приложения (VS Code, Discord)
- Нативные приложения (Windows, macOS, Linux)
API клиенты:
- Postman, Insomnia
- curl, wget
- Другие сервисы и микросервисы
Сервер (Server)
Что это: Программа, которая принимает запросы от клиентов и отправляет ответы
Типы серверов:
Веб-серверы:
- Apache HTTP Server
- Nginx
- IIS (Internet Information Services)
Серверы приложений:
- Node.js + Express
- Java + Spring Boot
- Python + Django/Flask
- PHP + Laravel
- .NET + ASP.NET Core
Серверы баз данных:
- MySQL, PostgreSQL
- MongoDB, Redis
- Oracle, SQL Server
Специализированные серверы:
- Email серверы (SMTP, IMAP)
- DNS серверы
- File серверы (FTP, SSH)
Модели взаимодействия
Синхронное взаимодействие
Как работает:
- Клиент отправляет запрос
- Клиент ждет ответа
- Сервер обрабатывает запрос
- Сервер отправляет ответ
- Клиент получает ответ и продолжает работу
Пример HTTP запроса:
Клиент → Сервер: GET /api/users/123
Клиент ожидает...
Сервер → Клиент: {"id": 123, "name": "John", "email": "john@example.com"}
Характеристики:
- Простота реализации
- Предсказуемый порядок операций
- Блокирование клиента на время запроса
- Подходит для критических операций
Асинхронное взаимодействие
Как работает:
- Клиент отправляет запрос
- Клиент продолжает работу, не ожидая ответа
- Сервер обрабатывает запрос в фоне
- Сервер отправляет ответ когда готов
- Клиент получает ответ через callback/promise/event
Пример JavaScript:
// Асинхронный запрос
fetch('/api/users/123')
.then(response => response.json())
.then(user => {
console.log('Получен пользователь:', user);
updateUI(user);
});
// Клиент продолжает выполнение других задач
console.log('Этот код выполнится сразу');
Характеристики:
- Не блокирует интерфейс пользователя
- Лучшая производительность
- Сложность в обработке ошибок
- Подходит для операций, не требующих немедленного результата
Протоколы взаимодействия
HTTP/HTTPS
HTTP (HyperText Transfer Protocol):
Характеристики:
✓ Простота использования
✓ Stateless (без состояния)
✓ Поддержка различных методов (GET, POST, PUT, DELETE)
✗ Передача данных в открытом виде
✗ Нет встроенной аутентификации
HTTPS (HTTP Secure):
HTTP + SSL/TLS шифрование
✓ Безопасная передача данных
✓ Аутентификация сервера
✓ Защита от прослушивания
✗ Дополнительная нагрузка на CPU
✗ Необходимость в SSL сертификатах
WebSocket
Характеристики:
- Двунаправленная связь в реальном времени
- Постоянное соединение
- Низкая задержка
Применение:
Чаты и мессенджеры:
- Мгновенная доставка сообщений
- Уведомления о статусе "онлайн"
Онлайн игры:
- Синхронизация действий игроков
- Real-time мультиплеер
Финансовые приложения:
- Live котировки акций
- Уведомления о транзакциях
Collaborative editing:
- Google Docs, Notion
- Одновременное редактирование
gRPC
Характеристики:
- Высокая производительность
- Строгое типизирование (Protocol Buffers)
- Поддержка streaming
- Кроссплатформенность
Пример использования:
Микросервисная архитектура:
UserService ←→ OrderService ←→ PaymentService
API между внутренними сервисами:
- Быстрая передача данных
- Автогенерация клиентов
- Валидация типов на уровне протокола
Архитектурные паттерны
Monolith (Монолит)
Структура:
┌─────────────────────┐
│ Единое приложение │
│ ┌─────────────────┐│
│ │ Frontend ││
│ ├─────────────────┤│
│ │ Backend ││
│ ├─────────────────┤│
│ │ Database ││
│ └─────────────────┘│
└─────────────────────┘
Преимущества:
- Простота разработки и развертывания
- Легкость тестирования
- Отсутствие сетевых задержек между компонентами
- Простая отладка
Недостатки:
- Сложность масштабирования
- Единая точка отказа
- Технологическая привязка
- Сложность изменений в большой кодовой базе
Microservices (Микросервисы)
Структура:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │ │ Order │ │ Payment │
│ Service │ │ Service │ │ Service │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└───────────────┼───────────────┘
│
┌───────┴──────┐
│ API Gateway │
└───────┬──────┘
│
┌─────┴─────┐
│ Frontend │
└───────────┘
Преимущества:
- Независимое развертывание сервисов
- Масштабирование отдельных компонентов
- Технологическое разнообразие
- Устойчивость к отказам
Недостатки:
- Сложность в тестировании интеграций
- Сетевые задержки между сервисами
- Сложность мониторинга и отладки
- Необходимость в DevOps практиках
Serverless (Бессерверная архитектура)
Принцип: Код выполняется в облачных функциях по требованию
Примеры платформ:
AWS Lambda - Amazon Web Services
Azure Functions - Microsoft Azure
Google Cloud Functions - Google Cloud Platform
Vercel Functions - Frontend платформа
Применение в тестировании:
Автоматические тесты:
- Запуск тестов по триггерам
- Обработка результатов тестирования
- Уведомления о результатах
Тестовые данные:
- Генерация тестовых данных
- Очистка после тестов
- Mock сервисы
Обмен данными
Форматы данных
JSON (JavaScript Object Notation):
{
"user": {
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"roles": ["user", "admin"],
"active": true,
"createdAt": "2024-01-15T10:30:00Z"
}
}
XML (eXtensible Markup Language):
<?xml version="1.0" encoding="UTF-8"?>
<user>
<id>123</id>
<name>John Doe</name>
<email>john@example.com</email>
<roles>
<role>user</role>
<role>admin</role>
</roles>
<active>true</active>
<createdAt>2024-01-15T10:30:00Z</createdAt>
</user>
Protocol Buffers:
syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
string email = 3;
repeated string roles = 4;
bool active = 5;
string created_at = 6;
}
Методы HTTP запросов
GET - получение данных:
GET /api/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
Ответ:
HTTP/1.1 200 OK
Content-Type: application/json
{"id": 123, "name": "John Doe"}
POST - создание данных:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
{
"name": "Jane Smith",
"email": "jane@example.com"
}
Ответ:
HTTP/1.1 201 Created
Location: /api/users/124
Content-Type: application/json
{"id": 124, "name": "Jane Smith", "email": "jane@example.com"}
PUT - полное обновление:
PUT /api/users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"id": 123,
"name": "John Updated",
"email": "john.updated@example.com"
}
PATCH - частичное обновление:
PATCH /api/users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "John Partially Updated"
}
DELETE - удаление:
DELETE /api/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
Ответ:
HTTP/1.1 204 No Content
Состояние и сессии
Stateless vs Stateful
Stateless (без состояния):
Характеристики:
- Сервер не сохраняет информацию о клиенте между запросами
- Каждый запрос содержит всю необходимую информацию
- Легко масштабировать
- Простота в обслуживании
Пример:
GET /api/users/123
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI...
Каждый запрос содержит токен авторизации
Stateful (с состоянием):
Характеристики:
- Сервер сохраняет информацию о сессии клиента
- Клиент идентифицируется по session ID
- Сложнее масштабировать
- Требует управления состоянием
Пример:
1. POST /login → Set-Cookie: sessionId=abc123
2. GET /profile → Cookie: sessionId=abc123
3. Сервер знает, что abc123 принадлежит пользователю John
Управление сессиями
Cookies:
# Сервер устанавливает cookie
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict
# Клиент отправляет cookie в последующих запросах
Cookie: sessionId=abc123
JWT Tokens:
// Токен содержит данные пользователя
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
// Декодированные данные
{
"userId": 123,
"email": "john@example.com",
"roles": ["user"],
"exp": 1643723400
}
Session Storage:
Серверная сессия:
SessionID: abc123
UserID: 123
LoginTime: 2024-01-15 10:30:00
LastActivity: 2024-01-15 11:45:00
Балансировка нагрузки
Load Balancer
Назначение: Распределение входящих запросов между несколькими серверами
Типы балансировки:
Round Robin:
Request 1 → Server A
Request 2 → Server B
Request 3 → Server C
Request 4 → Server A (цикл повторяется)
Least Connections:
Request → Сервер с наименьшим количеством активных соединений
Weighted:
Server A (вес 3) получает 3 запроса
Server B (вес 1) получает 1 запрос
IP Hash:
Клиент с IP 192.168.1.100 всегда попадает на Server A
Практические аспекты для тестирования
Проблемы при балансировке:
Sticky Sessions:
- Пользователь должен попадать на тот же сервер
- Проблемы при падении сервера
- Сложность в тестировании failover
Session Replication:
- Данные сессии копируются между серверами
- Дополнительная нагрузка на сеть
- Возможны задержки синхронизации
Stateless подход:
- Лучше для масштабирования
- Не зависит от конкретного сервера
- Проще тестировать
Кэширование
Уровни кэширования
Browser Cache:
# Сервер указывает время кэширования
Cache-Control: max-age=3600
ETag: "abc123"
# Браузер сохраняет ресурс на 1 час
# При повторном запросе проверяет ETag
CDN (Content Delivery Network):
Пользователь в Москве → CDN сервер в Москве → Origin сервер в США
Преимущества:
- Быстрая загрузка статических ресурсов
- Снижение нагрузки на основной сервер
- Географическое распределение
Application Cache:
Redis/Memcached:
- Кэширование результатов запросов к БД
- Сессии пользователей
- Временные данные
Database Query Cache:
- Кэширование результатов SQL запросов
- Автоматическая инвалидация при изменении данных
Безопасность
Аутентификация и авторизация
Authentication (кто вы):
Basic Auth:
Authorization: Basic dXNlcjpwYXNzd29yZA==
Bearer Token:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
API Key:
X-API-Key: your-secret-api-key
Authorization (что можете делать):
Role-based:
User role: "admin" → может удалять пользователей
User role: "user" → может только просматривать профиль
Permission-based:
User permissions: ["read:users", "write:posts"]
Endpoint требует: "write:posts"
CORS (Cross-Origin Resource Sharing)
Проблема:
Frontend: https://myapp.com
API: https://api.myapp.com
Браузер блокирует запросы между разными доменами
Решение:
# Сервер API отправляет заголовки CORS
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Практические сценарии тестирования
1. Тестирование производительности
Метрики для измерения:
Latency (задержка):
- Time to First Byte (TTFB)
- Response time
- Round-trip time
Throughput (пропускная способность):
- Requests per second (RPS)
- Bytes per second
- Concurrent users
Availability (доступность):
- Uptime percentage
- Error rate
- Recovery time
Инструменты:
# Apache Bench
ab -n 1000 -c 10 http://example.com/api/users
# curl с измерением времени
curl -w "@curl-format.txt" -o /dev/null -s http://example.com/
# wrk (современная альтернатива ab)
wrk -t12 -c400 -d30s http://example.com/
2. Тестирование отказоустойчивости
Сценарии тестирования:
Network failures:
- Потеря пакетов
- Высокая задержка (latency)
- Отключение сети
Server failures:
- Падение одного из серверов
- Перегрузка сервера
- Исчерпание ресурсов (CPU, память)
Database failures:
- Недоступность БД
- Медленные запросы
- Deadlock'и
3. Тестирование безопасности
Типы атак для тестирования:
SQL Injection:
POST /api/login
{"username": "admin'; DROP TABLE users; --", "password": "pass"}
XSS (Cross-Site Scripting):
POST /api/comments
{"text": "<script>alert('XSS')</script>"}
CSRF (Cross-Site Request Forgery):
<img src="http://api.example.com/transfer?amount=1000&to=attacker">
Rate Limiting:
Множественные запросы для проверки ограничений
4. Тестирование API интеграций
Чек-лист для API тестирования:
□ Все HTTP методы работают корректно
□ Статус-коды соответствуют ожиданиям
□ Валидация входных данных
□ Обработка ошибок
□ Форматы ответов (JSON schema validation)
□ Аутентификация и авторизация
□ Rate limiting
□ CORS настройки
□ Версионирование API
□ Документация актуальна
Мониторинг и наблюдаемость
Ключевые метрики
Application Performance Monitoring (APM):
Response time:
- Среднее время ответа
- 95th/99th percentile
- Тренды по времени
Error rate:
- Количество ошибок 4xx/5xx
- Типы ошибок
- Affected users
Throughput:
- Requests per minute
- Peak traffic handling
- Capacity planning
Логирование
Structured logging:
{
"timestamp": "2024-01-15T10:30:00Z",
"level": "INFO",
"service": "user-service",
"traceId": "abc123",
"userId": 123,
"action": "login",
"duration": 250,
"status": "success"
}
Distributed tracing:
Request ID: abc123
Frontend → API Gateway → User Service → Database
1ms → 50ms → 200ms → 100ms
Общее время: 351ms
Bottleneck: User Service (200ms)
Заключение
Понимание клиент-серверного взаимодействия критически важно для тестировщика:
- Помогает определить scope тестирования - что тестировать на клиенте, что на сервере
- Улучшает диагностику проблем - понимание где искать root cause
- Позволяет планировать интеграционное тестирование - какие компоненты как взаимодействуют
- Помогает в тестировании производительности - понимание bottlenecks
- Улучшает коммуникацию с командой - общий язык с разработчиками
Современные приложения используют сложные архитектуры, и понимание основ клиент-серверного взаимодействия - это фундамент для эффективного тестирования.