Сессия Flask не сохраняется (Postman работает, Javascript нет)

avatar
sam
7 апреля 2018 в 22:02
4855
2
6

Я разрабатываю сервер Flask для связи между некоторыми внутренними функциями Python и клиентами Javascript через Интернет. Я пытаюсь использовать переменную Flask session для хранения пользовательских данных в течение времени их взаимодействия с приложением. Я удалил большую часть приведенного ниже кода приложения, но основная проблема, с которой я столкнулся, осталась.

Вот мой код для моего (упрощенного) приложения Flask:

import json
import os
from flask import Flask, jsonify, request, session

app = Flask(__name__)
app.secret_key = 'my_secret_key'

@app.route('/', methods=['GET'])
def run():
  session['hello'] = 'world'
  return jsonify(session['hello'])

@app.route('/update', methods=['POST'])
def update():
  return jsonify(session['hello'])

if __name__ == '__main__':
  app.run(host='0.0.0.0')

Используя Postman, я могу сделать запрос GET на свой сервер и получить ожидаемый результат "world". Затем я могу сделать запрос POST с произвольным телом и получить тот же ожидаемый результат "world" (снова используя Postman).

При использовании Chrome я могу посетить IP-адрес своего сервера и увидеть ожидаемый результат "world" на странице. Я также могу вручную сделать запрос GET с помощью Javascript (в консоли Chrome) и получить тот же ответ, что и ожидалось. Однако моя проблема возникает при попытке отправить запрос POST на сервер с помощью Javascript; сервер показывает KeyError: 'hello' при попытке сделать этот запрос.

Вот код Javascript, который я использую для выполнения POST-запроса:

var url = 'http://my_server_ip/update';
fetch(url, {
  method: 'POST',
  body: JSON.stringify('arbitrary_string'),
  headers: new Headers({
    'Content-Type': 'application/json'
  })
})
.then(response => response.json())
.then((data) => {
  console.log(data);
})

Что здесь происходит? Почему я могу нормально выполнять запросы GET/POST с помощью Postman, но сталкиваюсь с ошибками при выполнении тех же запросов с помощью Javascript?

Источник
stamaimer
8 апреля 2018 в 06:09
0

Вы должны опубликовать свой запрос с файлом cookie, который вы получаете после запроса '/'.

amanb
8 апреля 2018 в 07:17
0

Удивительно, но GET работает в запросе JavaScript, возвращающем world. Я пытался с заголовком cookie сеанса, но запрос POST все еще терпит неудачу.

Ответы (2)

avatar
sam
8 апреля 2018 в 23:17
6

После нескольких часов тестирования мне удалось выяснить проблему. Хотя я думаю, что ответ @amanb подчеркивает проблему, я собираюсь ответить на свой вопрос, потому что то, что я нашел, в конечном итоге является более простым решением.

Чтобы запрос POST возвращал ожидаемое значение, мне просто нужно было добавить строку credentials: 'same-origin' в тело fetch. Это выглядит следующим образом:

var url = 'http://my_server_ip/update';
fetch(url, {
  method: 'POST',
  body: JSON.stringify('arbitrary_string'),
  credentials: 'same-origin',   // this line has been added
  headers: {
    'Content-Type': 'application/json'
  }
})
.then(response => response.json())
.then((data) => {
  console.log(data);
})

Согласно руководству по использованию Mozilla's Fetch,

По умолчанию fetch не отправляет и не получает файлы cookie с сервера. что приводит к неаутентифицированным запросам, если сайт полагается на поддержание сеанса пользователя.

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

amanb
9 апреля 2018 в 04:51
0

Ах, хорошо, мне было интересно, почему это не работает для меня? Похоже, я добавлял параметр учетных данных в качестве заголовка. Я удалил это из своего ответа сейчас, чтобы кому-то стало легче понять различия в обоих случаях.

avatar
amanb
8 апреля 2018 в 14:57
6

В разделе предостережения документации fetch говорится:

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

Рекомендуется использовать AJAX для обмена информацией с представлениями Flask.

Между тем, в вашем коде для приложения Flask объект session является словарем. Теперь, если вы обращаетесь к словарю с его ключом session['hello'] и если этот ключ не существует, возникает Keyerror. Чтобы обойти эту ошибку, вы можете использовать метод get() для словарей.

Происходит следующее: запрос fetch не находит ключ hello (или ПОЛУЧАЕТ значение сеанса из представления Flask) в сеансе Flask.

user = session.get('hello')
return jsonify(session_info=user)

Но это все равно даст вам значение null для сеанса { session_info: null }. Почему это так?

Когда вы отправляете запросы GET/POST на сервер Flask, сеанс инициализируется и запрашивается изнутри Flask. Однако, когда вы отправляете запрос POST fetch Javascript, вы должны сначала ПОЛУЧИТЬ значение сеанса из Flask, а затем отправить его как запрос POST в представление Flask, которое return содержит информацию о сеансе.

В вашем коде, когда запрос POST запускается из fetch, когда я отправляю данные полезной нагрузки в Flask, они принимаются правильно, и вы проверяете это, используя request.get_json() в представлении Flask:

@app.route('/update', methods=['POST'])
def update():
  user = session.get('hello')
  payload = request.get_json()
  return jsonify(session_info=user, payload=payload)

Это вернет { payload: 'arbitrary_string', session_info: null }. Это также показывает, что fetch не получает информацию о сеансе, потому что мы не вызвали GET сначала, чтобы получить информацию о сеансе из Flask.

Помните: сеанс Flask находится на сервере Flask. Чтобы отправлять/получать информацию через Javascript, вы должны делать отдельные вызовы, если не предусмотрено сохранение файлов cookie сеанса.

const fetch = require('node-fetch');

var url_get = 'http://my_server_ip';
var url_post = 'http://my_server_ip/update';
fetch(url_get, {
  method:'GET'
}).then((response)=>response.json()).then((data) =>fetch(url_post, {
  method: 'POST',
  body: JSON.stringify(data),
  dataType:'json',
  headers: {
    'Content-Type': 'application/json'
  }
})
.then(response => response.json())
.then((postdata) => {
  console.log(postdata);
}));

Представления Flask немного изменятся:

@app.route('/', methods=['GET'])
def set_session():
    session['hello'] = 'world'
    return jsonify(session['hello'])

@app.route('/update', methods=['POST'])
def update():
    payload = request.get_json()
    return jsonify(session_info=payload)

Когда вы инициируете запрос Javacript сейчас, вывод будет следующим: { session_info: 'world' }