211 lines
7.9 KiB
Markdown
211 lines
7.9 KiB
Markdown
# Исправление проблемы кэширования сообщений
|
||
|
||
## Проблема
|
||
|
||
При отправке сообщения в чате, затем возврате к списку чатов (кнопка "К чатам") и повторном открытии того же чата - **отправленное сообщение исчезает**.
|
||
|
||
### Причина
|
||
|
||
Браузер агрессивно кэширует HTML страницы чатов. Когда вы:
|
||
1. Открываете чат → браузер получает HTML с N сообщениями
|
||
2. Отправляете сообщение через WebSocket → сообщение добавляется в DOM И сохраняется в БД
|
||
3. Возвращаетесь к списку чатов
|
||
4. Снова открываете тот же чат → **браузер возвращает закэшированный HTML** (без нового сообщения)
|
||
|
||
### Диагностика
|
||
|
||
Сообщение **правильно сохраняется в БД**:
|
||
```python
|
||
# communications/consumers.py
|
||
@database_sync_to_async
|
||
def _create_message(self, chat: Chat, user, text: str, reply_to_id=None) -> Message:
|
||
msg = Message.objects.create( # ✅ Создается в БД
|
||
chat=chat,
|
||
author=user,
|
||
content=text[:2000],
|
||
reply_to_id=reply_to_id
|
||
)
|
||
return msg
|
||
```
|
||
|
||
Но Django view возвращает полный HTML со всеми сообщениями из БД, а браузер его кэширует.
|
||
|
||
## Решение
|
||
|
||
Добавлены HTTP заголовки для **запрета кэширования** в двух view:
|
||
|
||
### 1. ChatDetailView (страница чата)
|
||
|
||
```python
|
||
class ChatDetailView(LoginRequiredMixin, DetailView, FormView):
|
||
# ...
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
"""Добавляем заголовки для отключения кэширования"""
|
||
response = super().dispatch(request, *args, **kwargs)
|
||
# Запрещаем кэширование страницы чата в браузере
|
||
response['Cache-Control'] = (
|
||
'no-cache, no-store, must-revalidate, max-age=0'
|
||
)
|
||
response['Pragma'] = 'no-cache'
|
||
response['Expires'] = '0'
|
||
return response
|
||
```
|
||
|
||
### 2. ChatListView (список чатов)
|
||
|
||
```python
|
||
class ChatListView(LoginRequiredMixin, ListView):
|
||
# ...
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
"""Добавляем заголовки для отключения кэширования"""
|
||
response = super().dispatch(request, *args, **kwargs)
|
||
# Запрещаем кэширование списка чатов
|
||
response['Cache-Control'] = (
|
||
'no-cache, no-store, must-revalidate, max-age=0'
|
||
)
|
||
response['Pragma'] = 'no-cache'
|
||
response['Expires'] = '0'
|
||
return response
|
||
```
|
||
|
||
## HTTP заголовки
|
||
|
||
### Cache-Control: no-cache, no-store, must-revalidate, max-age=0
|
||
|
||
- **no-cache** - браузер должен проверить с сервером перед использованием кэша
|
||
- **no-store** - полностью запрещает сохранение в кэш
|
||
- **must-revalidate** - кэш должен быть проверен после истечения
|
||
- **max-age=0** - кэш истекает немедленно
|
||
|
||
### Pragma: no-cache
|
||
|
||
- Обратная совместимость с HTTP/1.0
|
||
|
||
### Expires: 0
|
||
|
||
- Дата истечения кэша (в прошлом)
|
||
|
||
## Результат
|
||
|
||
✅ **До**: Браузер кэшировал HTML чата → при повторном открытии показывал старую версию
|
||
✅ **После**: Браузер запрашивает свежий HTML каждый раз → всегда показывает актуальные сообщения из БД
|
||
|
||
## Тестирование
|
||
|
||
### Сценарий 1: Отправка и проверка сообщения
|
||
|
||
1. Откройте любой чат
|
||
2. Отправьте сообщение "Тест 123"
|
||
3. Нажмите "К чатам"
|
||
4. Снова откройте тот же чат
|
||
5. ✅ Сообщение "Тест 123" должно быть видно
|
||
|
||
### Сценарий 2: Множественная отправка
|
||
|
||
1. Откройте чат
|
||
2. Отправьте 3 сообщения подряд
|
||
3. Закройте и откройте чат несколько раз
|
||
4. ✅ Все 3 сообщения должны оставаться видимыми
|
||
|
||
### Сценарий 3: Проверка в разных браузерах
|
||
|
||
- ✅ Chrome
|
||
- ✅ Firefox
|
||
- ✅ Edge
|
||
- ✅ Safari
|
||
|
||
### Проверка HTTP заголовков (DevTools)
|
||
|
||
Откройте DevTools → Network → выберите запрос к `/communications/chats/{id}/`
|
||
|
||
**Response Headers должны содержать:**
|
||
```
|
||
Cache-Control: no-cache, no-store, must-revalidate, max-age=0
|
||
Pragma: no-cache
|
||
Expires: 0
|
||
```
|
||
|
||
## Альтернативные решения (не использованы)
|
||
|
||
### 1. ❌ Инвалидация Django кэша при создании сообщения
|
||
|
||
```python
|
||
# В consumers.py после создания сообщения
|
||
from django.core.cache import cache
|
||
cache.delete(f'chat_detail_{chat.id}')
|
||
```
|
||
|
||
**Минус**: Нужно кэшировать view с `@cache_page`, что усложняет код
|
||
|
||
### 2. ❌ Версионирование URL с timestamp
|
||
|
||
```javascript
|
||
// При возврате к чату добавлять ?v=timestamp
|
||
location.href = `/communications/chats/${chatId}/?v=${Date.now()}`;
|
||
```
|
||
|
||
**Минус**: Загрязняет history браузера, не работает с back button
|
||
|
||
### 3. ❌ Service Worker для управления кэшем
|
||
|
||
```javascript
|
||
// Отлавливать запросы к /communications/chats/ и всегда делать network-first
|
||
self.addEventListener('fetch', event => {
|
||
if (event.request.url.includes('/communications/chats/')) {
|
||
event.respondWith(fetch(event.request));
|
||
}
|
||
});
|
||
```
|
||
|
||
**Минус**: Требует регистрации Service Worker, сложнее в поддержке
|
||
|
||
### 4. ✅ HTTP заголовки Cache-Control (выбрано)
|
||
|
||
**Плюсы**:
|
||
- Простое решение
|
||
- Работает во всех браузерах
|
||
- Не требует изменений в JS
|
||
- Соответствует HTTP стандарту
|
||
|
||
## Влияние на производительность
|
||
|
||
### Потенциальные проблемы
|
||
|
||
❌ **Больше запросов к серверу** - каждое открытие чата = новый HTTP запрос
|
||
❌ **Нет оффлайн режима** - без кэша страница не загрузится без интернета
|
||
|
||
### Оптимизация (если потребуется)
|
||
|
||
Можно использовать **частичное кэширование**:
|
||
|
||
```python
|
||
response['Cache-Control'] = 'max-age=10, must-revalidate' # Кэш на 10 сек
|
||
```
|
||
|
||
Или **ETag** для условных запросов:
|
||
|
||
```python
|
||
from django.views.decorators.http import condition
|
||
|
||
@condition(etag_func=lambda r, *a, **k: f"chat-{kwargs['pk']}-{Chat.objects.get(pk=kwargs['pk']).updated_at.timestamp()}")
|
||
class ChatDetailView(...):
|
||
...
|
||
```
|
||
|
||
Но для текущей задачи полное отключение кэша - оптимальное решение.
|
||
|
||
## Связанные файлы
|
||
|
||
- `backend/communications/views.py` - добавлены методы `dispatch()` в ChatDetailView и ChatListView
|
||
- `backend/communications/consumers.py` - логика создания сообщений (не изменялась)
|
||
|
||
## Дата исправления
|
||
|
||
30 ноября 2025 г.
|
||
|
||
## Статус
|
||
|
||
✅ **Исправлено и протестировано**
|