Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

Latest commit

 

History

History
458 lines (328 loc) · 23 KB

lesson34.md

File metadata and controls

458 lines (328 loc) · 23 KB

Урок 34. Куки, сессии, кеш

Куки и сессии

Что такое Cookie (печеньки), и причём тут сессия?

Понятие "сессий" основано на том, что состояние пользователя каким-то образом сохраняется, когда он переходит с одной страницы на другую. Вспомните, что HTTP не сохраняет состояний, поэтому только браузер или ваше приложение может "запомнить" то, что нужно запомнить.

Куки

Куки — это пары данных по типу "ключ-значение", которые сохраняются в браузере пользователя до истечения какого-то определенного срока. Они применимы практически для любой задачи, но чаще всего их используют, чтобы сохранить пользователя в том же месте веб-страницы, если он потеряет интернет-соединение, или, чтобы хранить простые настройки отображения сайта. Например, корзины интернет магазинов чаще всего делают именно через куки, ведь вы не теряете данные, при переходе со страницы на страницу, а хранить данные о том, что вы набираете в корзину, в базе данных, слишком избыточно. Вы можете также хранить в них данные пользователя или даже пароли, но это не очень хорошая идея, не стоит хранить в обычных куках браузера информацию, которая должна быть защищенной или сохраняться между сессиями браузера. Пользователь может легко потерять данные, очистив кэш, или украсть/использовать незащищенные данные из куков.

Куки добавляются в request/response для хранения совершенно разных данных. Например, стандартная Django авторизация добавляет куку с данными о пользователя, чтобы можно было определить, кто именно делает запрос. Поэтому там и нужны CSRF токены в формах или просто токены в REST запросах. Так как перехватить значение куки при запросе очень просто, а мы должны быть уверены, что запрос пришел именно от авторизированного пользователя. Куки хранит информацию, кто это, а токены позволяют проверить, что это был именно этот пользователь.

Сессия

Задумайтесь о том, каким образом браузеры следят, что пользователь залогинен, когда страница перезагружается. HTTP запросы не имеют состояний, так как же вы определите, что запрос пришел именно от залогиненного пользователя? Вот почему важны куки — они позволяют вам отслеживать пользователя от запроса к запросу, пока не истечет их срок действия.

Особый случай — это когда нужно отслеживать данные пользовательской "сессии", которая включает все, что пользователь делает, пока вы хотите "запоминать" это, обычно до тех пор, пока пользователь не закроет окно браузера. В этом случае каждая страница, которую пользователь посетил до закрытия браузера, будет частью одной сессии.

Если упростить, сессия - это набор запросов от одного и того же пользователя (или от разных в рамках одного процесса).

В случае с Django, при стандартных настройках сессия хранит набор куки, которые хранятся в формате JSON. (А значит, что данные можно сериализовать)

Сессии и HTTP

С точки зрения протокола HTTP, сессия является абстракцией. То есть физически её не существует. Если разобрать любой запрос, там вы сможете увидеть только куки, схему использования можно изучить на картинке.

Сессиями можно пользоваться только для залогиненных пользователей, иначе это не имело бы смысла.

При отправке успешного запроса на логин, на уровне бекенда, в нашем случае Django создаёт в базе данных объект сессии, для которого генерируется уникальный идентификатор (id), и уже он добавляется к каждому последующему реквесту и респонсу в качестве куки.

Как именно это значение попадает в каждый реквест, мы рассмотрим на следующем занятии, а пока давайте разберёмся, как мы можем это использовать?

Как этим пользоваться?

В Django сессия всегда хранится в реквесте, а значит, вы можете использовать сессию как временное хранилище в любом месте, где у вас есть доступ к реквесту. request.session в виде словаря.

Рассмотрим несколько примеров.

>> > request.session[0] = 'bar'
>> >  # subsequent requests following serialization & deserialization
>> >  # of session data
>> > request.session[0]  # KeyError
>> > request.session['0']
'bar'

Данные хранятся в формате JSON, а значит ключи будут преобразованы в строки.

Допустим, вам нужно "запомнить", комментировал ли этот пользователь только что статью, чтобы не позволить написать большое количество комментариев подряд. Конечно, можно сохранить эти данные в базе, но зачем? Проще воспользоваться сессией:

def post_comment(request, new_comment):
    if request.session.get('has_commented', False):
        return HttpResponse("You've already commented.")
    c = comments.Comment(comment=new_comment)
    c.save()
    request.session['has_commented'] = True
    return HttpResponse('Thanks for your comment!')

Сохраним это состояние в сессии и будем перепроверять именно его.

Допустим, вам нужно хранить, сколько времени назад пользователь последний раз совершал действие после логина.

request.session['last_action'] = timezone.now()

Теперь мы можем проверить, когда было выполнено последнее действие, и добавить любую нужную нам логику.

Если нам нужно воспользоваться сессией вне мест, где есть доступ к реквесту (никогда не пользовался, но мало ли):

from django.contrib.sessions.backends.db import SessionStore

s = SessionStore()
# stored as seconds since epoch since datetimes are not serializable in JSON.
s['last_login'] = 1376587691
s.create()
s.session_key
'2b1189a188b44ad18c35e113ac6ceead'
SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
s['last_login']
1376587691

Мы можем получить сессию по ключу (любая созданная Django сессия автоматически хранит переменную session_key), по которой можно получить нужные нам данные.

Данные в любой сессии хранятся в кодированном виде, чтобы получить все данные сессии, не зная конкретного ключа, их можно получить через метод .get_decoded()

s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
s.get_decoded()
{'user_id': 42}

Сохранение данных в сессии происходит только тогда, когда меняется значение request.session:

# Session is modified.
request.session['foo'] = 'bar'

# Session is modified.
del request.session['foo']

# Session is modified.
request.session['foo'] = {}

# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session['foo']['bar'] = 'baz'

В последнем случае данные не будут сохранены, т. к. модифицируется не request.session, а request.session['foo']

Это поведение можно изменить, если добавить настройку в settings.py SESSION_SAVE_EVERY_REQUEST = True, тогда запись в сессию будет происходить при каждом запросе, а не только в момент изменения. . Так как сессии хранятся в базе данных, то теоретически может произойти так, что через какое-то длительное время сессии будут занимать большой объем в базе. Если нам они не нужны, то необходимо их периодически очищать. Если мы хотим удалить все данные, то мы можем воспользоваться manage-командой python manage.py clearsessions.

Если же нужно удалить только часть сессии, то можно воспользоваться тем, что сессия - это такая же модель, как и все остальные. Значит, мы можем импортировать её, отфильтровать и удалить необходимые нам данные.

Некоторые настройки можно поменять и перенастроить, как и полностью кастомизировать любые действия с сессиями. Подробнее об этом Тут

Кеш

Официальная документация Тут

Что такое кеш?

Кеш — промежуточный буфер с быстрым доступом к нему, содержащий информацию, которая может быть запрошена с наибольшей вероятностью. Доступ к данным в кэше осуществляется быстрее, чем выборка исходных данных из более медленной памяти или удалённого источника, однако её объём существенно ограничен по сравнению с хранилищем исходных данных.

Если упростить, то это хранилище для часто запрашиваемых данных.

Пример

Предположим, мы разрабатываем новостной сайт, и знаем, что новости у нас обновляются раз в час.

В течение часа, пока новости не обновятся, абсолютно каждый заходящий на сайт пользователь будет видеть один и тот же набор статей, а значит, нам необязательно каждый раз доставать этот набор из базы данных, мы можем закешировать его!

Для использования кеша мы можем воспользоваться огромным количеством заранее заготовленных решений для кеша.

Виды кеша

Два основных используемых в реальности вида кеша - это Memcached и redis.

Как можно догадаться, всё настраивается через settings.py.

Memcached (memory cached - кэшированная память)

Способ хранения данных в виде хеш-таблицы (словаря) в оперативной памяти.

Требует предварительной установки!

Как и с большинством необходимых сторонних приложений очень легко ставится на Linux и с определёнными сложностями на Windows, изучите это самостоятельно.

Memcached запускается как отдельный сервис или служба, и стандартным портом для доступа к нему является 11211.

Хранение данных с использованием Memcached на практике:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

Хранение в файле сокета (временный файл хранилища в UNIX системах):

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': 'unix:/tmp/memcached.sock',
    }
}

Хранение на нескольких серверах для уменьшения нагрузки:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}

Redis

Для использования кеша через Redis необходимо знать, что такое Redis.

Мы будем изучать целый вид баз данных среди, которых будет и Redis. На данном этапе нам нужно знать, что Redis - это специальная база данных, которая может хранить данные в виде хеш-таблицы (ключ-значение).

Для использования такого кеша нам необходимо, установить Redis, стандартный порт 6379.

И установить сторонние библиотеки для работы с ним:

pip install django-redis

После чего прописать настройки в settings.py и держать сервис Redis запущенным:

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient"
        },
        "KEY_PREFIX": "example"
    }
}

Другие способы хранения кеша

Можно хранить кеш прям в базе данных, для этого нужно указать таблицу, в которую складывать кеш:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
    }
}

Для использования кеша через базу таблицу нужно предварительно создать, сделать это можно при помощи manage-команды:

python manage.py createcachetable

Можно хранить кеш в обычном файле:

Linux\MacOS:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
    }
}

Windows:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': 'c:/foo/bar',
    }
}

Есть упрощенная схема кеширования для разработки:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

Как и всё остальное, кеш можно кастомизировать, написав собственные классы для управления кешем:

CACHES = {
    'default': {
        'BACKEND': 'path.to.backend',
    }
}

Любой тип кеширования поддерживает большое количество дополнительных настроек, подробно о которых в документации.

Как же этим пользоваться?

Существует два основных способа использовать кеш.

Кешировать весь сайт или кешировать конкретную вью.

Чтобы кешировать весь сайт, нужно добавить две middleware (как это работает, на следующем занятии) до и после CommonMiddleware (это важно, иначе работать не будет):

В settings.py

MIDDLEWARE = [
    ...
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
    ...
]

Время кеширования или ограничения на кеш выставляются через переменные settings.py, подробно в документации.

Для того чтобы кешировать отдельный метод или класс, используется декоратор cache_page

from django.views.decorators.cache import cache_page


@cache_page(60 * 15)
def my_view(request):
    ...

В скобках указывается время, которое кеш должен храниться, обычно записывается в виде умножения на секунды\минуты для простоты чтения (15*60 - это 15 минут, никакой разницы от того, чтобы записать 900, но так проще воспринимать на вид).

Чаще всего декоратор используется в URL:

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
]

Для кеширования class-based view кешируется весь класс:

from django.views.decorators.cache import cache_page

url(r'^my_url/?$', cache_page(60 * 60)(MyView.as_view())),

Также можно закешировать часть темплейта при помощи темплейт тега cache:

{ % load cache %}
{ % cache 500 sidebar %}
..sidebar..
{ % endcache %}

В кеш можно записать любые кастомные данные, если это необходимо

from django.core.cache import cache

cache.set('my_key', 'hello, world!', 30)
cache.get('my_key')
'hello, world!'
# Wait 30 seconds for 'my_key' to expire...
cache.get('my_key')
None

cache.set('add_key', 'Initial value')
cache.add('add_key', 'New value')
# .add() сработает, только если в указанном ключе ничего не было
cache.get('add_key')
'Initial value'

cache.get_or_set('my_new_key', 'my new value', 100)
'my new value'

import datetime

cache.get_or_set('some-timestamp-key', datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)

cache.set('a', 1)
cache.set('b', 2)
cache.set('c', 3)
cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

cache.set_many({'a': 1, 'b': 2, 'c': 3})
cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

cache.delete('a')
cache.delete_many(['a', 'b', 'c'])

cache.clear()

cache.touch('a', 10)  # обновить время хранения

И многие другие тонкости и особенности, например, декоратор from django.views.decorators.cache import never_cache, который можно использовать, чтобы не кешировать данные, если вы уже кешируете весь сайт. И многое другое, подробности в документации.

Практика:

  1. Пользователь открывает одну и ту же страницу. Каждый четвертый раз, когда он открывает страницу, добавьте вверху надпись "Это был 4-ый раз". Если обновить страницу еще раз, то счёт 4-х открытий начинаем с начала.

  2. Множество пользователей открывает одну и ту же страницу, каждый 10-ый, кто открывает страницу, должен видеть надпись "Вы наш 10-ый покупатель" (Если один пользователь открыл 10 раз, это тоже подходит, один пользователь 6 раз и еще один 4 раза, тоже ок).