Гузариш ба мундариҷа
Мавзӯи 6 аз 6

Сессияҳо, воридшавӣ ва саҳифаҳои хато

Чаро сайт меҳмонро дар хотир нигоҳ медорад

HTTP худ аз худ ду дархостро ба як инсон намепайвандад. Вале сайт бояд намуди интихобшуда, сабади харид ё ҳолати воридшавиро дар хотир нигоҳ дорад. Сессия додаҳоест, ки байни дархостҳо ба браузери муайян вобаста аст. Flask ба он тавассути объекти ба луғат монанди session дастрасӣ медиҳад.

Рӯйхати глобалии лоиҳаҳо метавонад барои тамоми сайт умумӣ бошад. Интихоби намуд ё нишонаи воридшавӣ бояд шахсӣ бошад, вагарна амали як меҳмон саҳифаи дигаронро тағйир медиҳад. Тағйирёбандаҳои глобалӣ танҳо дар раванди ҷорӣ зиндагӣ мекунанд: азнавоғозкунӣ онҳоро пок мекунад ва чанд раванди сервер нусхаҳои ҷудо мегиранд.

1. Cookie ва secret_key

Ба таври пешфарз Flask муҳтавои сессияро дар cookie нигоҳ медорад: қимати хурде, ки браузер нигоҳ дошта, бо дархостҳои минбаъда ба сайт мефиристад. Cookie бо secret_key имзо мешавад. Имзо тағйирро ошкор мекунад, вале муҳтаворо рамзгузорӣ намекунад: меҳмон метавонад онро хонад.

Калид бояд дароз, тасодуфӣ, махфӣ ва байни азнавоғозкуниҳо доимӣ бошад. Дар лоиҳа онро аз тағйирёбандаи муҳит, масалан os.environ["FLASK_SECRET_KEY"], мегиранд ва дар репозиторий нашр намекунанд. Дар мисолҳо калиди ошкори таълимӣ аст. Ҳамаи мисолҳоро алоҳида иҷро кунед.

from flask import Flask, request, session

app = Flask(__name__)
app.secret_key = "lesson-only-key"

@app.route("/theme", methods=["GET", "POST"])
def theme():
    if request.method == "POST":
        session["theme"] = request.form.get("theme", "light")
    return session.get("theme", "light")

a = app.test_client()
b = app.test_client()
r = a.post("/theme", data={"theme": "dark"})
print("session=" in r.headers["Set-Cookie"])
print(a.get("/theme").get_data(as_text=True))
print(b.get("/theme").get_data(as_text=True))

Натиҷа:

True
dark
light

Дар ин ҷо light ва dark шиносаҳои барномавии интихобанд. Мизоҷи a cookie-и худро нигоҳ дошт ва b бо ҳолати тоза оғоз кард. Санҷишҳои масъалаҳо бо чанд мизоҷ браузерҳои мустақилро тақлид мекунанд. Барои ҳар дархости як меҳмон мизоҷи нав насозед: ҳар бор cookie-и ӯ гум мешавад.

2. Хондан, ҳазф ва санҷиши ҳолат

session.get(key, default) қиматро бо варианти эҳтиётӣ мехонад. session[key] = value онро сабт мекунад; pop(key, None) як калидро бехатар мебардорад ва clear() ҳамаро пок мекунад. Рамзҳо, маълумоти махфӣ ва додаҳои калонро дар чунин сессия нагузоред: cookie хондашаванда ва аз рӯйи андоза маҳдуд аст, одатан чанд килобайт.

Барои омода кардани ҳолати санҷиш client.session_transaction() ҳаст: он сессияи тағйирёбандаи мизоҷро кушода, ҳангоми баромадан аз блок cookie-ро сабт мекунад. Ин воситаи санҷиш аст, на роҳи давр задани қоидаҳои воридшавӣ барои корбар.

from flask import Flask, session

app = Flask(__name__)
app.secret_key = "lesson-only-key"

@app.route("/preference")
def preference():
    return session.get("theme", "light")

client = app.test_client()
with client.session_transaction() as saved:
    saved["theme"] = "dark"
print(client.get("/preference").get_data(as_text=True))
with client.session_transaction() as saved:
    saved.pop("theme", None)
print(client.get("/preference").get_data(as_text=True))

Натиҷа:

dark
light

3. Воридшавӣ ва иҷозати дастрасӣ санҷишҳои гуногунанд

Ҳангоми воридшавӣ сервер аввал маълумоти ҳисобро месанҷад, баъд муваффақ будани воридшавиро сабт мекунад. Саҳифаи муҳофизатшуда иҷозатро алоҳида месанҷад. Мавҷудияти калид иҷозат нест: қимат метавонад False бошад. Ин ба рухсатномаи «бекоршуда» монанд аст: корт ҳаст, вале ҳаққи даромадан намедиҳад.

Дар мисолҳои содаи таълимӣ hmac.compare_digest истифода мешавад: он сатр ё байтҳоро тавре муқоиса мекунад, ки ихроҷи маълумот аз вақти худи муқоиса кам шавад. Он вақти тамоми коркардро яксон намекунад. Барои сатрҳо ASCII дастгирӣ мешавад; матни Unicode-ро ба байт рамзгузорӣ кардан мумкин аст. Дар лоиҳаи воқеӣ рамзро ошкор нигоҳ намедоранд: ҳешро сабт карда, бо функсияи махсус месанҷанд.

import hmac

expected = "demo-pass".encode("utf-8")
for candidate in ("wrong", "demo-pass"):
    print(hmac.compare_digest(candidate.encode("utf-8"), expected))
flags = {"allowed": False}
print("allowed" in flags, flags["allowed"] is True)

Натиҷа:

False
True
True False

Werkzeug, вобастагии Flask, generate_password_hash ва check_password_hash медиҳад. Ҳешро дар пойгоҳи додаҳо нигоҳ медоранд; рамзи ошкор танҳо ҳангоми санҷиш истифода мешавад. Намак (salt)-и тасодуфӣ ҳатто барои рамзҳои яксон сатрҳои гуногуни ҳеш месозад, бинобар ин ду ҳеши тавлидшударо мустақим муқоиса накунед. Функсияи санҷишро бе чопи ҳеши тасодуфӣ месанҷем.

from werkzeug.security import generate_password_hash, check_password_hash

stored = generate_password_hash("demo-pass")
print(check_password_hash(stored, "demo-pass"))
print(check_password_hash(stored, "wrong"))

Натиҷа:

True
False

4. Саҳифаҳои хатои худ

Коркарди хато бо ороишгари @app.errorhandler(code) сабт мешавад. Он ба ҷойи саҳифаи стандартӣ муҳтавои шуморо медиҳад. Вале рамзи ҳолатро бояд ошкоро нишон диҳед: танҳо сатри HTML тибқи қоидаҳои одӣ ҷавоби 200 мешуд. Инро бо 403 Forbidden месанҷем: дархост фаҳмо аст, вале дастрасӣ манъ аст.

abort(403) идоракуниро ба коркарди хато медиҳад. Параметри e объекти хаторо мегирад. Барои API ба ҷойи HTML луғат баргардондан мумкин аст, вале ҳар ду формат рамзи дуруст мехоҳанд. Рамзи 404 набудани захира аст, на манъи дастрасӣ.

from flask import Flask, abort, render_template_string

app = Flask(__name__)

@app.route("/private")
def private():
    abort(403)

@app.errorhandler(403)
def denied(e):
    return render_template_string("<h1>Дастрасӣ манъ аст</h1>"), 403

client = app.test_client()
r = client.get("/private")
print(r.status_code, r.get_data(as_text=True))
print(client.get("/unknown").status_code)

Натиҷа:

403 <h1>Дастрасӣ манъ аст</h1>
404

Сессия дар дохил чӣ тавр кор мекунад

Дар оғози дархост Flask cookie-ро мехонад, имзоро месанҷад ва луғатро барқарор мекунад. Ҳангоми тағйири сессия ба ҷавоб Set-Cookie илова мешавад. Браузер қимати навро нигоҳ дошта, дафъаи дигар мефиристад. Бе cookie меҳмон аз сессияи холӣ оғоз мекунад. Имзои нодуруст ба меҳмон имкон намедиҳад, ки ҳолати дилхоҳро ҳамчун боэътимод ворид кунад.

Тағйири худи луғат пайгирӣ мешавад, вале тағйири рӯйхати дохилӣ метавонад пай бурда нашавад. Пас аз session["cart"].append(item) бояд session.modified = True гузоред ё рӯйхати навро боз ба session["cart"] таъин кунед. session ва request-ро дар дохили дархост хонед; барои санҷиш контекст ё session_transaction истифода кунед.

Хатоҳои маъмул ва ислоҳи онҳо

  1. session["theme"] = "dark" бе secret_key: Flask сессияро сабт карда наметавонад ва хато медиҳад. Ислоҳ: пеш аз коркарди дархостҳо калиди доимӣ диҳед.
  2. "allowed" in flags ҳамчун санҷиши ҳуқуқ қимати False-ро ҳам мегузаронад. Ислоҳ: қимати иҷозатро аз рӯйи қоидаи муайяншуда санҷед, на мавҷудияти сабтро.
  3. Рамз дар session гузошта шудааст: он ба cookie-и хондашаванда меравад. Ислоҳ: танҳо шиноса ё нишонаи заруриро нигоҳ доред, маълумоти махфиро дар сервер гузоред.
  4. Коркарди хато танҳо HTML медиҳад: натиҷа 200 мешавад. Ислоҳ: кортеж бо рамзи хато баргардонед.
  5. Дар ҳар оғоз secret_key-и нав сохта мешавад: cookie-ҳои пешин дигар санҷишро намегузаранд. Ислоҳ: як калидро дар муҳит нигоҳ доред ва онро огоҳона иваз кунед.

Ёддошт

Вазифа Восита
Танзими имзо app.secret_key
Хондан / навиштан session.get(key, default) / session[key] = value
Ҳазфи як калид / ҳама session.pop(key, None) / session.clear()
Омода кардани сессияи санҷиш client.session_transaction()
Муқоисаи маълумоти махфӣ дар мисоли таълимӣ hmac.compare_digest
Санҷиши ҳеши рамз check_password_hash
Саҳифаи хатои худ @app.errorhandler(code) ва return body, code

Акнун қисмҳои сайтро пайвастан мумкин аст: view дархост мегирад, додаҳо ва ҳуқуқро месанҷад, анборро нав карда, HTML, JSON ё равонасозӣ медиҳад. Сайти ҷойгиршуда ба ҷойи app.run-и debug сервери WSGI ва барои додаҳои доимӣ пойгоҳи додаҳо мехоҳад. Ҳолати корбарро аз додаҳои умумии барнома ҷудо нигоҳ доред.