Как сеанс передается шаблонам и между функциями приложения, во Flask? - PullRequest
1 голос
/ 24 апреля 2019

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


Согласно документации Flask о сеансах session сам объект является прокси .

Мое понимание (это, по всей вероятности, неправильно в некотором смысле, и вот о чем этот вопрос =) что это означает:

  1. прокси session объект доступен из приложения и используется для изменения прочитанных данных по мере необходимости

  2. обычно, прокси-сервер session передает свои изменения в прокси-сервер session сразу (за исключением изменения изменяемых файлов в прокси-сервер session)

  3. в случае, если прокси-сервер session занят (в случае многопоточного приложения), прокси-сервер session будет ждать, пока прокси-сервер не станет доступным session, а затем перенесет изменения в прокси-сервер. session

  4. шаблоны получают «исходный» сеанс (т. Е. Proxied- session), поэтому отсутствует возможность / необходимость доступа к session._get_current_object() из шаблонов

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

  6. фактический session (объект прокси, доступный через session._get_current_object()) никогда не должен менять ID

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

Рассмотрим следующий код:

my_app.py

from flask import (
Flask,
render_template,
session,
)

app = Flask(__name__)
app.secret_key = 'some random secret key'

@app.route('/create/')
def create():
    session['example'] = ['one', 'two']
    print_ids()
    return str(session['example'])

@app.route('/modify/')
def modify():
    session['example'].append('three')
    print_ids()
    return render_template('my_template.html', id=id)

@app.route('/display/')
def display():
    print_ids()
    return str(session['example'])

def print_ids():
    import inspect
    calling_function = inspect.stack()[1][3]
    print('')
    print(calling_function + ": session ID is: {}".format(id(session)))
    print(calling_function + ": session['example'] ID is {}".format(id(session['example'])))
    print('________________________________')
    print(calling_function + ": session._get_current_object() ID is: {}".format(id(session._get_current_object())))
    print(calling_function + ": session._get_current_object()['example'] ID is: {}".format(id(session._get_current_object()['example'])))

my_template.html

<!doctype html>
<html>
    <head><title>Display session['example']</title></head>
    <body>
        <div>
            {% if session['example'] %}
                {{ session['example'] }}
                <br />
                session ID is: {{ id(session) }}
                <br />
                session['example'] ID is: {{ id(session['example']) }}
                <br />
            {% else %}
                session['example'] is not set =(
            {% endif %}
        </div>
    </body>
</html>

Идея состоит в том, чтобы напечатать идентификаторы прокси- session, session['example'] (это список), прокси-session (т.е. session._get_current_object()) и прокси-session['example'] (т.е. session._get_current_object()['example']) из каждая функция, а также id(session) и id(session['example']) в отображаемом шаблоне, чтобы отследить, что и где используется.

Вот результаты:

.../create/
    # id(session)                                 4338311808 
    # id(session._get_current_object())           4343709776
    # id(session['example'])                                 4343654376
    # id(session._get_current_object()['example'])           4343654376

.../modify/
    # id(session)                                  4338311808
    # id(session._get_current_object())            4344315984
    # id(session['example'])                                  4343652720      
    # id(session._get_current_object()['example'])            4343652720
rendered my_template.html
    # id(session)                                  4344315984
    # id(session['example'])                                  4343652720

.../display/
    # id(session)                                  4338311808         
    # id(session._get_current_object())            4344471632
    # id(session['example'])                                  4341829576
    # id(session._get_current_object()['example'])            4341829576

# one more time
.../display/
    # id(session)                                  4338311808         
    # id(session._get_current_object())            4344471632
    # id(session['example'])                                  4344378072
    # id(session._get_current_object()['example'])            4344378072

Я стараюсь понять следующее:

  1. Каковы мои недопонимания / неправильные предположения относительно концепции сеансов Flask в целом?
  2. Почему идентификаторы session['example'] и session._get_current_object()['example'] меняются при каждом попадании на дисплей (и любой другой метод, кроме отображения, в частности, поскольку он ничего не меняет, я ожидаю, что все идентификаторы останутся неизменными)
  3. Почему идентификатор session._get_current_object() меняется, а идентификатор session нет?
  4. Поскольку идентификаторы session['example'] и session._get_current_object()['example'] идентичны в контексте любой функции, я бы предположил, что если один объект изменяется, то оба изменяются, поскольку они являются одним и тем же объектом.

    С учетом сказанного и с учетом того, что session._get_current_object()['example'] находится внутри прокси (т. Е. 'Реально') session Я ожидал бы следующее:

    .../create/ # return ['one', 'two']
    .../modify/ # will render page containing ['one', 'two', 'three']
    .../display/ # return ['one', 'two', 'three'] as proxy and proxied sessions should have been modified

Но как я ранее обнаружил - этого не происходит. Так почему же идентификаторы?

Ответы [ 2 ]

2 голосов
/ 24 апреля 2019

Большая часть вашего замешательства связана с недопониманием прокси-объектов Flask, таких как session, g и request.

Все, что делают эти объекты, - это убедитесь, что вы получаете правильные данные для текущего потока; они прокси между глобальным объектом (доступным для всех потоков, легко импортируемым и используемым в вашем коде Flask), объектом, хранящимся в локальном хранилище потоков , которое представляет собой объект, который прозрачно дифференцирует доступ к атрибутам по потокам Я бы. В этом нет необходимости блокировать или «ждать», прокси-объекты никогда не используются более чем одним потоком. session.foo косвенно обращается и возвращает тот же объект, что и session._get_current_object().foo (именно поэтому их идентификаторы всегда совпадают).

Таким образом, при доступе к объекту session прокси становится прозрачным . Это не то, о чем вам когда-либо нужно беспокоиться, если вы не хотите поделиться прокси-объектом с другим потоком.

Прокси-объект, к которому вы обращаетесь, создается новым для каждого запроса . Это потому, что содержание сеанса зависит от данных в каждом запросе. Механизм сеансов Flask является подключаемым, но реализация по умолчанию хранит все данные в криптографически подписанном файле cookie, который необходимо декодировать в данные Python, если вы хотите иметь возможность взаимодействовать с ним. Каждый из ваших URL /create/, /modify/ и /display/ обрабатывается как отдельные запросы, поэтому все они загружают данные сеанса из вашего запроса в новые объекты Python; их идентификаторы обычно отличаются.

После выполнения запроса объект сеанса снова исчезает. Вы не можете использовать это по-другому, потому что новый запрос, поступающий в тот же поток, должен представить данные сеанса из этого нового запроса в код Flask, а не данные из старого запроса.

Все это означает, что вывод id() здесь не имеет смысла здесь. id() - это число, уникальное для всех текущих активных объектов в текущем процессе Python. Это означает, что идентификаторы объектов, которые удалены из памяти, могут быть использованы повторно, просто потому, что вы видели одно и то же значение id() в два момента времени, не означает, что у вас один и тот же объект. И то, что у вас одни и те же данные (равенство значений), не означает, что у вас в памяти один и тот же объект, даже если их идентификатор одинаков. Старый объект мог быть удален, а новый объект мог быть просто воссоздан с тем же значением.

Под капотом Flask вызывает метод open_session() для объекта, назначенного Flask().session_interface в начале каждого запроса. В конце вызывается метод save_session() для повторного сохранения сеанса, и объект сеанса отбрасывается. Реализация по умолчанию - это SecureSessionInterface объект , который ищет определенный cookie-файл в запросе и, если он присутствует и имеет действительную подпись, декодирует данные как тегированный JSON (компактная сериализация JSON) и возвращает SecureCookieSession экземпляр с этими данными. Это объект, к которому session относится прокси, и возвращается session._get_current_object(). При сохранении данные снова сериализуются в тегированный JSON, подписываются и добавляются в ответ как исходящий заголовок Set-Cookie.

Сохранение происходит только тогда, когда объект сеанса был «изменен» (session.modified установлен на True). Обратите внимание, что реализация по умолчанию устанавливает modified в True только при непосредственном манипулировании отображением сеанса (установка, обновление или удаление ключей в самом отображении), а не при изменении изменяемых объектов, хранящихся в сеансе; session['foo'] = 'bar' обнаруживается, но если вы сохранили список или словарь в сеансе, то мутирование с такими выражениями, как session['spam'][0] = 'ham' не будет обнаружено Либо заново установите изменяемый объект (session[key] = session[key]), либо установите флаг modified на True вручную.

0 голосов
/ 24 апреля 2019

Посвящение =) : этот ответ появился только благодаря пользователям: brunns и shmee и их ответам: 1 (brunns) , 2 (шми) на другие мои вопросы.


Вот ответы на список ( моих собственных ) вопросов:

  1. Основная ошибка в том, что: да - session - прокси, да - объект, проксируемый session, возвращается session._get_current_object(), НО: объект, проксируемый session отличается для каждого запроса .

  2. Дело в том, что объект, проксированный session (и, следовательно, всем, что в нем содержится), отличается для каждого запроса. Подробнее: см. Ответ на пункт 3 ниже.

  3. Скважина:

    • этот ответ указал мне, что, поскольку session является объектом, импортированным из модуля flask, и поскольку он импортируется только один раз - его id() никогда не изменяется

    • есть один базовый объект (который возвращается session._get_current_object()) для каждого запроса, и как предложено ответом на другой вопрос, а также в документации Flask - объект, проксированный session, принадлежит RequestContext и, таким образом, отличается для каждого нового запроса. Следовательно, разные значения для разных запросов (единственная неясность здесь заключается в том, что иногда session._get_current_object() остается неизменным между последовательными запросами и, как указано в одном и том же ответе ( жирным шрифтом) это мое ), это:

      вероятно из-за того, что новый объект сеанса создается по тому же адресу памяти, который занимал старый из предыдущего запроса.

  4. Здесь ожидания неверны, а не результаты. Причина, по которой session['example'] не изменяется из одного запроса в другой, четко указана в документации для атрибута modified session:

    Имейте в виду, что модификации изменчивых структур не собраны автоматически, в этой ситуации вы должны явно установить приписать True себя.

    Поскольку session['example'] представляет собой список , а списки изменяемые - для того, чтобы изменения могли быть получены, нам нужно изменить код для функции modify следующим образом:

    @app.route('/modify/')
    def modify():
        session['example'].append('three')
        session.modified = True
        print_ids()
        return render_template('my_template.html', id=id)
    

    После этого изменения:

    .../create/ # returns ['one', 'two']
    .../modify/ # renders page containing ['one', 'two', 'three']
    .../display/ # returns ['one', 'two', 'three']
    
...