22. خدمات وب با استفاده از چارچوب Flask
منظور ما از «سرویس وب» هر برنامه وبی است که دادههای خام را در اختیار کلاینت قرار میدهد – که اغلب در مثالهای بعدی یک اسکریپت کنسول است. ما به هیچ فناوری خاصی - مانند REST (انتقال وضعیت REpresentational) یا SOAP (پروتکل دسترسی ساده به شیء) - که دادهها را به شکلی کمتر یا بیشتر خام در قالبی کاملاً مشخص ارائه میدهند، نمیپردازیم. REST مقدار jSON را برمیگرداند، در حالی که SOAP مقدار XML را برمیگرداند. هر یک از این فناوریها دقیقاً توصیف میکند که کلاینت چگونه باید از سرور پرسوجو کند و پاسخ سرور باید چه قالبی داشته باشد. در این دوره، ما در مورد ماهیت درخواست کلاینت و پاسخ سرور انعطافپذیرتر خواهیم بود. با این حال، اسکریپتهای نوشته شده و ابزارهای مورد استفاده مشابه فناوری REST هستند.
22.1. مقدمه
اسکریپتهای پایتون میتوانند توسط یک وبسرور اجرا شوند. چنین اسکریپتی به یک برنامه سرور تبدیل میشود که قادر به خدمترسانی به چندین کلاینت است. از دیدگاه کلاینت، فراخوانی یک سرویس وب معادل درخواست URL آن سرویس است. کلاینت میتواند به هر زبانی، از جمله پایتون، نوشته شود. در این حالت، ما از توابع اینترنتی که به تازگی بررسی کردیم استفاده میکنیم. همچنین باید بدانیم چگونه با یک سرویس وب «ارتباط» برقرار کنیم، یعنی پروتکل ارتباطی HTTP بین یک وبسرور و کلاینتهای آن را درک کنیم. این هدف بخش |پروتکل HTTP| بود. کلاینتهای وب توصیفشده در این بخش از دوره به ما امکان دادهاند تا بخشی از پروتکل HTTP را بررسی کنیم.

در سادهترین شکل، تبادلههای کلاینت/سرور به شرح زیر است:
- کلاینت یک اتصال با پورت ۸۰ روی وبسرور برقرار میکند؛
- درخواست یک سند را ارسال میکند؛
- سرور وب سند درخواستی را ارسال میکند و اتصال را قطع میکند؛
- سپس کلاینت اتصال را قطع میکند؛
این سند میتواند از انواع مختلف باشد: متن در قالب HTML، یک تصویر، یک ویدئو و غیره. این سند میتواند یک سند موجود (سند ایستا) یا سندی باشد که بهصورت پویا توسط یک اسکریپت تولید میشود (سند پویا). در مورد دوم، به این امر برنامهنویسی وب گفته میشود. اسکریپت برای تولید پویا اسناد میتواند به زبانهای مختلفی نوشته شود: PHP، پایتون، پرل، جاوا، روبی، سیشارپ، VB.net و غیره.
در ادامه، از اسکریپتهای پایتون برای تولید پویا اسناد متنی استفاده خواهیم کرد.

- در [1]، کلاینت با سرور ارتباط برقرار میکند، یک اسکریپت پایتون را درخواست میکند و ممکن است پارامترها را برای آن اسکریپت ارسال کند یا نکند؛
- در [3]، سرور وب اسکریپت پایتون را توسط مفسر پایتون اجرا میکند. این اسکریپت یک سند تولید میکند که به کلاینت [2] ارسال میشود؛
- سرور اتصال را قطع میکند. کلاینت نیز همین کار را انجام میدهد؛
سرور وب میتواند همزمان چندین مشتری را مدیریت کند.
در ادامه، ما از دو وبسرور استفاده خواهیم کرد:
- سرور سبکوزن Werkzeug [https://werkzeug.palletsprojects.com/en/1.0.x/]. این سرور توسط فریمورک وب Flask [https://flask.palletsprojects.com/en/1.1.x/] استفاده میشود. ما معمولاً به آن به عنوان سرور Flask اشاره خواهیم کرد؛
- سرور آپاچی ۲ [https://httpd.apache.org/];
سرور Flask در تمام مثالها استفاده خواهد شد. سرور Apache برای میزبانی وباپلیکیشنهایی که قصد توسعه آنها را داریم، به کار گرفته میشود.
چارچوب Flask به زبان پایتون نوشته شده است. این یک ماژول است که از طریق ترمینال نصب میشود PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\inet\utilitaires>pip install flask
Collecting flask
Downloading Flask-1.1.2-py2.py3-none-any.whl (94 kB)
|| 94 kB 1.1 MB/s
Collecting click>=5.1
Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)
|| 82 kB 5.8 MB/s
Collecting itsdangerous>=0.24
Downloading itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB)
Collecting Jinja2>=2.10.1
Downloading Jinja2-2.11.2-py2.py3-none-any.whl (125 kB)
|| 125 kB 6.4 MB/s
Collecting Werkzeug>=0.15
Downloading Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB)
|| 298 kB 6.4 MB/s
Collecting MarkupSafe>=0.23
Downloading MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl (16 kB)
Installing collected packages: click, itsdangerous, MarkupSafe, Jinja2, Werkzeug, flask
Successfully installed Jinja2-2.11.2 MarkupSafe-1.1.1 Werkzeug-1.0.1 click-7.1.2 flask-1.1.2 itsdangerous-1.1.0
- خط ۱: فرمان اجرا شده؛
- خط ۱۹: کامپوننتهایی که نصب شدهاند:
- [flask-1.1.2]: یک چارچوب توسعه وب پایتون است؛
- [Werkzeug-1.0.1]: سرور وبی است که به درخواستهای کلاینت پاسخ میدهد؛
- [Jinja2-2.11.2]: ابزاری برای درج عناصر پویا در صفحاتی است که در غیر این صورت ایستا خواهند بود؛
22.2. اسکریپتهای [flask/01]: مقدمهای بر برنامهنویسی وب

نمونههای ما در معماری زیر اجرا خواهند شد:

- در [1]، یک اسکریپت پایتون به همان شیوهای که یک اسکریپت کنسول استاندارد اجرا میشود، اجرا خواهد شد؛
- در [2]، یک سرور وب بهصورت شفاف ایجاد میشود و منتظر درخواستها میماند. در واقع، این سرور تنها یک URL را میپذیرد؛
- در [3]، مرورگر تنها URL خود را از سرور درخواست میکند؛
- در [4]، سرور اسکریپت پایتون مشخصشده توسط کنسول [1] را اجرا خواهد کرد؛
- در [5]، اسکریپت نتایج خود را به شکل یک سند متنی به وبسرور بازمیگرداند؛
- در [6]، سرور وب این سند متنی را به مرورگر ارسال خواهد کرد؛
22.2.1. اسکریپت [exemple_01]: مبانی زبان HTML
یک مرورگر وب میتواند اسناد مختلفی را نمایش دهد که رایجترین آنها سند HTML (زبان نشانهگذاری HyperText) است. این متن با برچسبهایی به شکل <balise>texte</balise> قالببندی شده است. برای مثال، تگ <b>important</b> متن «important» را به صورت پررنگ نمایش میدهد. تگهای مستقل، مانند تگ <hr/> که یک خط افقی را نمایش میدهد، نیز وجود دارند. ما تمام تگهایی را که میتوان در یک متن HTML یافت، بررسی نمیکنیم. برنامههای نرمافزاری متعددی وجود دارند که به شما امکان میدهند یک صفحه WEB را بدون نوشتن حتی یک خط کد HTML بسازید. این ابزارها بهطور خودکار کد HTML را برای طرحبندی ایجادشده با استفاده از ماوس و کنترلهای از پیش تعریفشده تولید میکنند. بنابراین شما میتوانید (با استفاده از ماوس) یک جدول را در صفحه درج کرده و سپس کد HTML تولید شده توسط نرمافزار را مشاهده کنید تا بفهمید برای تعریف یک جدول در یک صفحه WEB از کدام تگها باید استفاده کنید. به همین سادگی. علاوه بر این، دانش زبان HTML ضروری است، زیرا برنامههای وب پویا باید کد HTML را خودشان تولید کنند تا به کلاینتهای وب ارسال کنند. این کد توسط یک برنامه تولید میشود و البته شما باید بدانید چه چیزی را تولید کنید تا کلاینت صفحه وب مورد نظر خود را دریافت کند.
خلاصه اینکه، برای شروع برنامهنویسی وب نیازی به دانستن کل زبان HTML نیست. با این حال، این دانش ضروری است و میتوان آن را از طریق استفاده از سازندگان صفحه وب WYSIWYG مانند WEB، DreamWeaver و دهها مورد دیگر به دست آورد. راه دیگری برای کشف پیچیدگیهای زبان HTML، گشتوگذار در وب و مشاهده کد منبع صفحاتی است که ویژگیهای جالبی دارند که شما هنوز با آنها آشنا نیستید.
بیایید مثال زیر را در نظر بگیریم که چند عنصر را که میتوان در یک سند وب یافت، برجسته میکند، مانند:
- یک جدول؛
- یک تصویر؛
- یک لینک؛

یک سند HTML در میان تگهای <html>…</html> قرار دارد. این سند از دو بخش تشکیل شده است:
- <head>…</head>: این بخش غیرقابل نمایش سند است. این بخش اطلاعاتی را در اختیار مرورگری که سند را نمایش میدهد قرار میدهد. این بخش اغلب شامل تگ <title>…</title> است که متنی را برای نمایش در نوار عنوان مرورگر تعیین میکند. همچنین ممکن است شامل تگهای دیگری باشد، بهویژه آنهایی که کلمات کلیدی سند را تعریف میکنند و بعداً توسط موتورهای جستجو استفاده میشوند. این بخش همچنین ممکن است حاوی اسکریپتها باشد، که معمولاً به زبان جاوااسکریپت یا ویبیاسکریپت نوشته میشوند و توسط مرورگر اجرا خواهند شد؛
- <body attributes>…</body>: این بخشی است که توسط مرورگر نمایش داده میشود. تگهای موجود در این بخش، چیدمان بصری «دلخواه» سند را به مرورگر میگویند. هر مرورگر این تگها را به شیوه خود تفسیر خواهد کرد. بنابراین ممکن است دو مرورگر یک سند وب را به طور متفاوت نمایش دهند. این معمولاً یکی از سردردهای طراحان وب است؛
کد HTML برای سند مثال ما به شرح زیر است:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Quelques balises HTML</title>
</head>
<body style="background-image: url(/static/images/standard.jpg)">
<h1 style="text-align: left">Quelques balises HTML</h1>
<hr />
<table border="1">
<thead>
<tr>
<th>Colonne 1</th>
<th>Colonne 2</th>
<th>Colonne 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>cellule(1,1)</td>
<td style="text-align: center;">cellule(1,2)</td>
<td>cellule(1,3)</td>
</tr>
<tr>
<td>cellule(2,1)</td>
<td>cellule(2,2)</td>
<td>cellule(2,3</td>
</tr>
</tbody>
</table>
<br /><br />
<table border="0">
<tr>
<td>Une image</td>
<td>
<img border="0" src="/static/images/cerisier.jpg" />
</td>
</tr>
<tr>
<td>Le site de Polytech'Angers</td>
<td><a href="http://www.polytech-angers.fr/fr/index.html">ici</a></td>
</tr>
</table>
</body>
</html>
عنصر | برچسبها و مثالها HTML |
<title>برخی از برچسبهای HTML</title> (خط ۵) متن [Quelques balises HTML] هنگام نمایش سند در نوار عنوان مرورگر ظاهر خواهد شد | |
<hr />: یک خط افقی نمایش میدهد (خط ۱۰) | |
<table attributes>….</table>: برای تعریف جدول (خطوط ۱۲، ۳۲) <thead>…</thead>: برای تعریف سربرگهای ستون (خطوط ۱۳، ۱۹) <tbody>…</tbody>: برای تعریف محتوای جدول (خطوط ۲۰، ۳۱) <tr attributes>…</tr>: برای تعریف یک سطر (خطوط ۲۱، ۲۵) <td attributes>…</td>: برای تعریف یک سلول (خط ۲۲) مثالها: <table border="1">…</table>: ویژگی border ضخامت حاشیه جدول را تعریف میکند <td style="text-align: center;">cell(1,2)</td> (خط ۲۳): یک سلول را تعریف میکند که محتوای آن cell(1,2) خواهد بود. این محتوا به صورت افقی مرکزدهی میشود (text-align: center). | |
<img border="0" src="/static/images/cerisier.jpg"/> (خط ۳۸): تصویری بدون حاشیه (border="0") را تعریف میکند که فایل منبعی آن [/static/images/cerisier.jpg] در سرور وب (src="/static/images/cerisier.jpg") است. اگر این لینک در یک سند وب تولید شده با استفاده از URL [http://server/chemin/balises.html] ظاهر شود، مرورگر برای بازیابی تصویر مورد اشاره، URL [http://server/ static/images/cerisier.jpg] را درخواست خواهد کرد. | |
<a href="http://www.polytech-angers.fr/fr/index.html">اینجا (خط ۴۳): متن ici را بهعنوان پیوندی به URL http://www.polytech-angers.fr/fr/index.html درمیآورد. | |
<body style="background-image: url(/static/images/standard.jpg)"> (خط ۸): نشان میدهد که تصویری که باید بهعنوان پسزمینه صفحه استفاده شود، در URL [/static/images/standard.jpg] روی سرور وب قرار دارد. در چارچوب مثال ما، مرورگر برای بازیابی این تصویر پسزمینه، URL و [http://server/static/images/standard.jpg] را درخواست خواهد کرد. |
این مثال ساده نشان میدهد که برای ساخت کل سند، مرورگر باید سه درخواست به سرور ارسال کند:
- [http://server/chemin/balises.html] برای بازیابی منبع HTML سند؛
- [http://server/static/images/cerisier.jpg] برای بازیابی تصویر cerisier.jpg؛
- [http://server/static/images/standard.jpg] برای بازیابی تصویر پسزمینه standard.jpg؛
اسکریپت [exemple_01] به ما امکان میدهد صفحهٔ ثابت قبلی، [balises.html] را نمایش دهیم:

- در [1]، اسکریپت [exemple_01] که اجرا خواهد شد؛
- در [3]، سند HTML که توسط اسکریپت نمایش داده خواهد شد؛
- در [2]، تصاویر سند HTML؛
اسکریپت [exemple_01] به شرح زیر است:
import os
from flask import Flask, make_response, render_template
# برنامه فلسک
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")
# خانه URL
@app.route('/')
def index():
# صفحه نمایش داده شد
return make_response(render_template("balises.html"))
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- خط ۷: یک برنامه Flask ایجاد میشود. یک برنامه Flask یک برنامه وب است؛
- پارامتر اول، نامی است که به برنامه داده میشود. شما میتوانید هر نامی را که دوست دارید انتخاب کنید. در اینجا، ما از ویژگی از پیش تعریفشده [__name__] استفاده کردهایم که روی [__main__] (خط ۱۸) تنظیم شده است؛
- پارامتر دوم یک پارامتر نامگذاری شده است، یعنی موقعیت آن در لیست پارامترها اهمیتی ندارد. پارامتر نامگذاری شده [template_folder] پوشهای را که صفحات استاتیک وباپلیکیشن در آن قرار دارند مشخص میکند. صفحات استاتیک همانطور که هستند به مرورگر ارائه میشوند. در اینجا، صفحات ایستا در پوشه [templates] در ساختار دایرکتوری پروژه یافت میشوند. در خط ۷، ما یک مسیر نسبی به پوشه [script_dir] که حاوی اسکریپت [exemple_01] برای اجرا است، مشخص کردهایم؛
- پارامتر سوم نیز یک پارامتر نامگذاری شده است. [static_folder] به پوشهای اشاره دارد که منابع سند HTML (تصاویر، ویدیوها و غیره) در آن قرار دارند. در اینجا نیز، ما یک مسیر نسبی به پوشه [script_dir] که حاوی اسکریپت اجرا شده [exemple_01] است، مشخص کردهایم؛
- خطوط ۱۰–۱۴: ما URLهایی را که توسط برنامه وب پذیرفته میشوند، تعریف میکنیم. هر URL با تابعی مرتبط است که هنگام درخواست URL توسط یک مرورگر وب، اجرا میشود؛
- خط ۱۱: تنها URL برنامه، URL [/] است. توجه داشته باشید که در [@app.route('/')]، [app] متغیری است که در خط ۷ مقداردهی اولیه میشود. بنابراین، تعریف مسیرها (URLهای مختلفی که توسط برنامه مدیریت میشوند) باید پس از تعریف برنامه [app] بیاید. این نام اخیر دلخواه است؛
- خطوط ۱۲–۱۴: تابع زمانی اجرا میشود که URL از برنامه وب [exemple_01] درخواست شود؛
- خط ۱۲: تابع مرتبط با URL میتواند هر نامی داشته باشد. گاهی اوقات ممکن است پارامترهایی برای بازیابی عناصر از URL مرتبط داشته باشد. در اینجا، هیچیک ندارد؛
- خط 14:
- تابع [render_template] یک رشته را که نمایانگر سند متنی تولید شده توسط پارامتر آن است، بازمیگرداند. در اینجا، این پارامتر [balises.html] است. به دلیل وجود [template_folder] در خط ۷، این سند در پوشه [f"{script_dir}/../templates"] جستجو خواهد شد. در واقع، این همان جایی است که در آن قرار دارد؛
- تابع [make_response] یک پاسخ HTTP برای مرورگری که URL [/] را درخواست کرده است، تولید میکند. ما در بخش |پروتکل HTTP| دیدیم که یک پاسخ HTTP دارای دو عنصر است:
- سربرگهای HTTP؛
- سندی که توسط مرورگر درخواست شده است، در این مورد یک سند HTML؛
در خط ۱۴، هیچ پارامترهایی به تابع [make_response] برای تولید هدرهای HTTP ارسال نشده است. بنابراین، این هدرها بهصورت پیشفرض تولید خواهند شد. بعداً خواهیم دید که چگونه این هدرهای HTTP را تنظیم کنیم.
- در نهایت، هنگامی که مرورگر URL را از برنامه Flask درخواست میکند، صفحه [balises.html] را دریافت میکند؛
- خطوط ۱۷–۲۰: این خطوط برای راهاندازی سرور وبی استفاده میشوند که برنامه وب [exemple_01] را اجرا خواهد کرد؛
- خط ۱۸: این شرط تنها زمانی درست است که اسکریپت [exemple_01] در داخل یک کنسول اجرا شود؛
- خط ۱۹: برنامه [app] از خط ۷ پیکربندی میشود:
- پارامتر با نام [ENV="development"] وبسرور را روی حالت توسعه تنظیم میکند: به محض اینکه توسعهدهنده یکی از عناصر برنامه را تغییر دهد، آن عنصر مجدداً تولید شده و به وبسرور تحویل داده میشود. توسعهدهنده نیازی به درخواست اجرای مجدد ندارد؛
- پارامتر با نام [DEBUG=True] به توسعهدهنده اجازه میدهد تا در کد برنامه نقطههای توقف (breakpoint) تعیین کند؛
- خط ۲۰: اپلیکیشن وب راهاندازی میشود: یک سرور وب ایجاد شده و اپلیکیشن وب روی آن مستقر میشود تا به درخواستهای کلاینتهای وب پاسخ دهد؛
در اینجا مثالی از اجرای آن آمده است:

سپس لاگهای زیر در کنسول اجرا ظاهر میشوند:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/01/main/exemple_01.py
* Serving Flask app "exemple_01" (lazy loading)
* Environment: development
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (برای خروج CTRL+C را فشار دهید)
- خط ۲: سرور اسکریپت اجرا شده را نمایش میدهد؛
- خط ۳: سیستم در حالت توسعه قرار دارد؛
- خطوط ۴–۵: سرور تشخیص میدهد که در حالت [debug] راهاندازی شده است. سپس مجدداً راهاندازی میشود (خط ۵). بنابراین حالت [debug] فرآیند راهاندازی را کمی کند میکند؛
- خط ۸: URL جایی که برنامه وب مستقر شده [exemple_01] در دسترس است؛
با استفاده از یک مرورگر وب، بیایید URL [http://127.0.0.1:5000/] را درخواست کنیم:

ما در واقع سند مورد انتظار [balises.html] را دریافت میکنیم.
22.2.2. اسکریپت [exemple_02]: تولید پویا یک سند HTML

اسکریپت [exemple_02] [1] سند زیر [exemple_02.html] [2] را تولید خواهد کرد:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>{{page.title}}</title>
</head>
<body>
<b>{{page.contents}}</b>
</body>
</html>
این سند پویا است زیرا محتوای آن تنها در لحظه ارائه توسط وبسرور به طور کامل مشخص میشود. خطوط ۵ و ۸ حاوی دو عنصری هستند که در زمان نوشتن صفحه ناشناخته بودند. آنها تنها زمانی که صفحه به کلاینت ارسال میشود، مشخص میگردند. سپس با مقادیرشان که رشتههای کاراکتری هستند، جایگزین میشوند.
- خطوط ۵ و ۸: نحو {{expression}} بخشی از زبان قالب Jinja2 [https://jinja.palletsprojects.com/en/2.11.x/] است. قبل از ارسال صفحه به کلاینت، عناصر پویا صفحه (خطوط ۵ و ۸) ارزیابی شده و با مقادیرشان جایگزین میشوند؛
- خط ۵: از دستکاری [page.title] استفاده شده است. بنابراین ما فرض کردهایم که هنگام تولید صفحه قبل از ارسال آن، متغیری به نام [page] شناخته شده است؛ خواهیم دید چگونه. در دستکاری {{expression}}، میتوانیم از هر نام متغیری که بخواهیم استفاده کنیم. بنابراین در خطوط ۵ و ۸ میتوانستیم {{title}} و {{contents}} را داشته باشیم. سپس میتوانیم بگوییم که [title] و [contents] پارامترهای صفحه هستند. در ادامه، همیشه از همین تکنیک استفاده خواهیم کرد:
- تنها پارامتر صفحه یک دیکشنری [page] خواهد بود؛
- ویژگیهای این دیکشنری در داخل صفحه استفاده خواهند شد. در اینجا، [page.title] در خط ۵ و [page.contents] در خط ۸؛
برنامه وب [exemple_02.py] به شرح زیر است:
from flask import Flask, make_response, render_template
# برنامه Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")
# خانه URL
@app.route('/')
def index():
#محتوای صفحه به صورت یک فرهنگ لغت
page = {"title": "un titre", "contents": "un contenu"}
# نمایش صفحه
return make_response(render_template("exemple_02.html", page=page))
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- ما قبلاً این موضوع را در مثال قبلی، در خطوط ۴–۵ و ۱۸–۲۰ توضیح دادهایم. ما به استفاده از این ساختار در مثالهایمان ادامه خواهیم داد؛
- خط ۹: تنها URL که توسط برنامه وب ارائه میشود، URL است /;
- خط ۱۴: سند ارائهشده به URL / همان سند [exemple_02.html] است که همین حالا در مورد آن بحث کردیم. میدانیم که این سند یک پارامتر دارد، یک دیکشنری به نام [page]؛
- خط ۱۲: ما واژگان را تعریف میکنیم که بهعنوان پارامتر به صفحه [exemple_02.html] ارسال خواهد شد. این واژگان میتواند هر نامی داشته باشد. با این حال، باید دارای ویژگیهای [title, contents] باشد که در سند HTML استفاده شدهاند؛
- خط ۱۴: تابع [render_template] مسئول رندر کردن رشتهٔ کاراکتری از سند [exemple_02.html] است. از آنجا که این یک سند پارامتریک است، ما پارامتر(های) مورد انتظار را به تابع [render_template] پاس میکنیم. ما این کار را در اینجا با تخصیص یک مقدار به پارامتر با نام [page] انجام میدهیم. در عملیات [page=page]:
- در سمت چپ علامت =، پارامتر [page] که در سند [exemple_02.html] استفاده شده است، قرار دارد؛
- در سمت راست علامت =، مقدار [page] را داریم که در خط ۱۲ تعریف شده است؛
- بهطور کلی، اگر یک سند HTML دارای پارامترهای [param1, param2, …, paramn] باشد، مقادیر آنها در قالب [render_template(document, param1=valeur1, param2=valeur2, …] به تابع [render_template] ارسال خواهد شد؛
قبل از اجرای [exemple_02]، باید اجرای [exemple_01] را متوقف کنیم:

اگر در حین اجرای اسکریپت ۱، به نظر برسد که اسکریپت ۲ در حال اجراست، این احتمالاً به این دلیل است که اسکریپت ۲ هنوز در حال اجراست. برای بازگشت به وضعیت شناختهشده، میتوانید تمام فرآیندهای در حال اجرا در PyCharm (بالای سمت راست پنجره PyCharm) را متوقف کنید:

بیایید اسکریپت [exemple_02] را اجرا کنیم:

لاگهای کنسول به شرح زیر است:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/01/main/exemple_02.py
* Serving Flask app "exemple_02" (lazy loading)
* Environment: development
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (برای خروج CTRL+C را فشار دهید)
خط ۸ پورت استقرار (۵۰۰۰) را برای برنامه [exemple_02] (خط ۱) روی ماشین [localhost] نشان میدهد. از آنجایی که خطوط قبلی همیشه یکسان هستند، آنها را دوباره نمایش نمیدهیم.
با استفاده از یک مرورگر وب، ما URL [http://localhost:5000/] را درخواست میکنیم:

- بیان {{page.title}} مقدار [1] را تولید کرد؛
- بیان {{page.contents}} مقدار [2] را تولید کرد؛
22.2.3. اسکریپت [exemple_03]: استفاده از قطعات صفحه

- در [1]، اسکریپت [exemple_03.py] سند پویا [exemple_03.html] [2] را تولید خواهد کرد. این از قطعات صفحه [fragment_01.html, fragment_02.html] و [3] ساخته خواهد شد؛
سند [exemple_03.html] به شرح زیر خواهد بود:
<!DOCTYPE html>
<html lang="fr">
{% include "fragments/fragment_01.html" %}
<body>
{% include "fragments/fragment_02.html" %}
</body>
</html>
- در خطوط ۳ و ۵، دستور Jinja2 [include] برای درج عناصر خارجی از سند استفاده میشود؛
- سینتکس آن {% include … %} است. پارامتر دستور [include] مسیر سندی است که باید وارد شود. این مسیر نسبت به پارامتر [template_folder] برنامه Flask است:
app = Flask(__name__, template_folder="../templates", static_folder="../static")
پس در اینجا، مسیرهای اسناد نسبت به پوشه [templates] هستند.
قطعه [fragment_01.html] (البته نامها دلخواه هستند) به شرح زیر است:
<meta charset="UTF-8">
<title>{{page.title}}</title>
قطعه [fragment_02.html] به شرح زیر است:
<b>{{page.contents}}</b>
اگر سند [exemple_03.html] را با استفاده از این قطعات بازسازی کنیم، کد زیر را به دست میآوریم:
<!DOCTYPE html>
<html lang="fr">
<meta charset="UTF-8">
<title>{{page.title}}</title>
<body>
<b>{{page.contents}}</b>
</body>
</html>
بنابراین ما یک سند یکسان با [exemple_02.html] داریم اما ساختهشده از قطعات.
اسکریپت وب [exemple_03.py] به شرح زیر است:
import os
from flask import Flask, make_response, render_template
# برنامه Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")
# خانه URL
@app.route('/')
def index():
# محتوای صفحه
page = {"title": "un autre titre", "contents": "un autre contenu"}
# نمایش صفحه
return make_response(render_template("views/exemple_03.html", page=page))
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
این کد مشابه کد [exemple_02.py] است. خط ۱۶ نشان میدهد چگونه از خط ۷ به اسنادی که در پوشههای فرعی [template_folder] قرار دارند ارجاع دهیم.
اجرای اسکریپت [exemple_03.py] نتایج زیر را در مرورگر تولید میکند:

22.3. اسکریپتهای [flask/02]: سرویس وب تاریخ و زمان

سند [date_time_server.html] به شرح زیر است:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Date et heure du moment</title>
</head>
<body>
<b>Date et heure du moment : {{page.date_heure}}</b>
</body>
</html>
- خط ۸: صفحه پارامتر [page.date_heure] را میپذیرد؛
سرویس وب [date_time_server.py] به شرح زیر است:
# وارداتها
import os
import time
from flask import Flask, make_response, render_template
# برنامه Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}")
# خانه URL
@app.route('/')
def index():
# زمان ارسال به کلاینت
# time.localtime: تعداد میلیثانیهها از ۱ ژانویه ۱۹۷۰
# time.strftime امکان قالببندی زمان و تاریخ را فراهم میکند
# قالب نمایش تاریخ و زمان
# d: روز (۲ رقم)
# m: ماه دو رقمی
# y: سال (۲ رقم)
# H: ساعت 0.23
# M: دقیقه
# S: ثانیهها
# تاریخ و زمان فعلی
time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
#سند قابل ارسال به مشتری تولید میشود
page = {"date_heure": time_of_day}
document = render_template("date_time_server.html", page=page)
print("document", type(document), document)
# پاسخ HTTP به مشتری
response = make_response(document)
print("response", type(response), response)
return response
# فقط دستی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- خط ۱۳: برنامه وب فقط URL / را ارائه میدهد؛
- خطوط ۱۵–۲۴: توضیح میدهد که چگونه تاریخ و زمان را بازیابی کرده و چگونه آنها را نمایش دهیم؛
- خط ۲۷: یک رشته که نمایانگر تاریخ و زمان فعلی است؛
- خطوط ۲۸–۳۰: سند پویا [date_time_server.html] با ارسال فرهنگ لغت [page] از خط ۲۹ ایجاد میشود؛
- خط ۳۱: نوع [document] و خود سند نمایش داده میشوند. هدف این است که نشان داده شود این یک رشته است؛
- خط ۳۳: پاسخ HTTP تولید میشود که به کلاینت ارسال خواهد شد (هنوز ارسال نشده است)؛
- خط ۳۴: نوع و مقدار آن نمایش داده میشود؛
- خط ۳۵: پاسخ HTTP برای کلاینت ارسال میشود؛
اجرای اسکریپت نتیجه زیر را در مرورگر تولید میکند:

لاگهای کنسول به شرح زیر است:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\flask\02\date_time_server.py
* Serving Flask app "date_time_server" (lazy loading)
* Environment: development
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (برای خروج CTRL+C را فشار دهید)
127.0.0.1 - - [10/Jul/2020 09:32:09] "GET / HTTP/1.1" 200 -
document <class 'str'> <!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Date et heure du moment</title>
</head>
<body>
<b>Date et heure du moment : 10/07/20 09:42:33</b>
</body>
</html>
response <class 'flask.wrappers.Response'> <Response 195 bytes [200 OK]>
- خط ۱۰: میبینیم که نوع مقداری که توسط [render_template] بازگردانده میشود، از نوع [str] است. این رشته کاراکتری چیزی جز سند [date_time_server.html] پس از تفسیر (خطوط ۱۰–۱۹) نیست؛
- خط ۲۰: میبینیم که نوع مقداری که توسط [make_response] بازگردانده میشود، [flask.wrappers.Response] است. تابع [Response.__str__] بهطور ضمنی برای نمایش شیء [Response] فراخوانی شده است. رشته بازگشتی این تابع دو مورد اطلاعات درباره پاسخ HTTP که قرار است تولید شود، ارائه میدهد:
- مقدار سند ارسالشده ۱۹۵ بایت است؛
- وضعیت پاسخ HTTP برابر [200 OK] است. بعداً خواهیم دید که به این کد وضعیت دسترسی داریم؛
22.4. اسکریپتهای [flask/03]: سرویسهای وب تولیدکننده متن ساده
در مثال قبلی دیدیم که سرویس وب سند زیر را بازگردانده است:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Date et heure du moment</title>
</head>
<body>
<b>Date et heure du moment : {{page.date_heure}}</b>
</body>
</html>
یک کلاینت وب ممکن است تنها به اطلاعات [page.date_heure] در خط ۸ علاقهمند باشد و نه به قالببندی اطراف آن HTML. سرویس وب میتواند این اطلاعات را به صورت یک رشته ساده از کاراکترها بازگرداند. ما در اینجا مثالهایی از این نوع سرویس وب را ارائه خواهیم داد.
22.4.1. اسکریپت [main_01]

- [main_01] سرویس وب است؛
- [config] اسکریپت پیکربندی برنامه وب است؛
- سرویس وب از برخی از عناصر تعریفشده در [2] استفاده میکند؛
اسکریپت [config] به شرح زیر است:
def configure():
# مسیر مطلق که به مسیرهای نسبی در پیکربندی ارجاع میدهد
rootDir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
#وابستگیهای برنامه
absolute_dependencies = [
# افراد، ابزارها، MyException
f"{rootDir}/classes/02/entities",
]
# مسیر سیستم را تنظیم کنید
from myutils import set_syspath
set_syspath(absolute_dependencies)
# اعمال پیکربندی
return {}
هدف اصلی این پیکربندی تعریف مسیر پایتون برای سرویس وب است. باید امکان یافتن اجزا در [2] (خط ۸) وجود داشته باشد.
اسکریپت وب [main_01] به شرح زیر است:
# راهاندازی برنامه
import config
config=config.configure()
# وارداتها
from flask import Flask, make_response
from flask_api import status
# وابستگیها
from Personne import Personne
# برنامه Flask (در اینجا هیچ سند ایستا وجود ندارد)
app = Flask(__name__)
# خانه URL
@app.route('/')
def index():
# یک شخص
personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
# پاسخ HTTP
response = make_response(str(personne))
# سربرگها HTTP
response.headers.set("Content-type", "application/json; charser=utf8")
# پاسخ به صورت HTTP بازگردانده میشود
return response, status.HTTP_200_OK
#فقط اصلی
if __name__ == '__main__':
# سرور راهاندازی شده است
app.config.update(ENV="development", DEBUG=True)
app.run()
- خطوط ۱–۳: مسیر پایتون برنامه تنظیم میشود؛
- خطوط ۵–۱۰: عناصر مورد نیاز اسکریپت وارد میشوند؛
- خط 17: سرویس وب تنها URL / را ارائه میدهد؛
- خط ۲۰: یک شیء [Personne] ایجاد میشود؛
- خط ۲۲: یک پاسخ HTTP با رشتهای که نمایندهٔ شخص است ایجاد میشود. تابع [Personne.__str__] فراخوانی خواهد شد. این تابع رشته jSON را از فرهنگ لغت شخص [asdict] برمیگرداند (به |کلاس BaseEntity| مراجعه کنید). پارامتر تابع [make_response] سند متنی ارسالشده به کلاینت است، که در این مورد رشته jSON برای یک شخص است؛
- خط ۲۴: در هدرهای HTTP پاسخ، ما یک هدر [Content-type] را شامل میشویم که به کلاینت میگوید چه نوع سندی دریافت خواهد کرد، در این مورد یک سند jSON که در UTF-8 رمزگذاری شده است؛
- خط ۲۶: یک تپلی از دو عنصر بازگردانده میشود:
- پاسخ به کلاینت، شامل سربرگهای HTTP و سند؛
- کد وضعیت پاسخ. در اینجا میخواهیم کد وضعیت [200 OK] را بازگردانیم. کدهای وضعیت مختلف توسط ثابتها در ماژول [flask_api] تعریف شدهاند که در خط ۷ وارد شده است؛
ماژول [flask_api] بهصورت پیشفرض در دسترس نیست. باید نصب شود. این کار در یک ترمینال PyCharm انجام میشود:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\inet\utilitaires>pip install flask_api
Collecting flask_api
Downloading Flask_API-2.0-py3-none-any.whl (119 kB)
|| 119 kB 544 kB/s
Requirement already satisfied: Flask>=1.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from flask_api) (1.1.2)
Requirement already satisfied: Jinja2>=2.10.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (2.11.2)
Requirement already satisfied: Werkzeug>=0.15 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (1.0.1)
Requirement already satisfied: click>=5.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (7.1.2)
Requirement already satisfied: itsdangerous>=0.24 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (1.1.0)
Requirement already satisfied: MarkupSafe>=0.23 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Jinja2>=2.10.1->Flask>=1.1->flask_api) (1.1.1
)
Installing collected packages: flask-api
Successfully installed flask-api-2.0
هنگامی که اسکریپت وب [main_01] اجرا میشود، نتایج زیر در یک مرورگر نمایش داده میشوند:

- در [2]، رشته دریافتی jSON;
- در [3-4]، محتوای سند دریافتشده نمایش داده میشود. میبینیم که هیچ قالببندی HTML وجود ندارد، فقط رشته jSON;
حال بیایید به نقش هدر [Content-Type] که توسط سرویس وب برای کلاینت ارسال شده است، نگاهی بیندازیم. ما مرورگر را به حالت توسعهدهنده (معمولاً F12) تغییر میدهیم و دوباره همان URL را درخواست میکنیم. در زیر یک اسکرینشات از مرورگر کروم آمده است:

- در [1]، زبانه [Network] را انتخاب کنید؛
- در [2, 4]: URL که توسط مرورگر درخواست شده است؛
- در [3]، زبانه [Headers] را انتخاب کنید (سربرگهای HTTP);
- در [5]، کد وضعیت پاسخ HTTP دریافتی؛
- در [6]، هدر به کلاینت اطلاع میدهد که در آستانه دریافت یک متن jSON است. این امکان را به کلاینت میدهد تا خود را با پاسخ تطبیق دهد. در نتیجه، فونت مورد استفاده کروم برای نمایش یک پاسخ jSON با فونتی که برای یک پاسخ متن پایه استفاده میشود، یکسان نیست؛

- در [8]، زبانه [Response] را برای دسترسی به سند ارسالشده توسط سرویس وب انتخاب کنید – در این مورد، یک رشته ساده jSON؛
22.4.2. Postman
[Postman] ابزاری است که به ما امکان میدهد تا نقاط پایانی مختلف URL یک برنامه وب را استعلام کنیم. این ابزار به ما اجازه میدهد:
- استفاده از هر URL: اینها بهصورت دستی تولید میشوند؛
- ارسال درخواستها به سرور وب با استفاده از GET، POST، PUT، OPTIONS…؛
- برای مشخص کردن پارامترها برای GET یا POST;
- برای تنظیم هدرهای HTTP برای درخواست؛
- برای دریافت پاسخ در قالب jSON, XML, HTML,
- برای دسترسی به هدرهای HTTP پاسخ. این امر در نتیجه دسترسی به پاسخ کامل HTTP از سرور را فراهم میکند؛
[Postman] یک ابزار آموزشی عالی برای درک ارتباطات کلاینت/سرور با استفاده از پروتکل HTTP است.
[Postman] در URL [https://www.getpostman.com/downloads/] موجود است. با نصب نسخه خود از [Postman] ادامه دهید. در حین نصب، از شما خواسته میشود یک حساب کاربری ایجاد کنید: این کار در اینجا الزامی نیست. حساب [Postman] برای همگامسازی دستگاههای مختلف استفاده میشود تا تنظیمات یک دستگاه روی دستگاه دیگر کپی شود. هیچکدام از این موارد در اینجا الزامی نیست.
پس از نصب، [Postman] رابط کاربری زیر را نمایش میدهد:

- در [2-3] میتوانید به تنظیمات محصول دسترسی پیدا کنید؛

- در [6]، نسخهای که در این سند استفاده شده است؛
در اینجا، از [Postman] برای آزمایش سرویس وب قبلی، jSON، استفاده خواهیم کرد:
- ما اسکریپت [flask/03/main_01] را اجرا میکنیم؛
- سپس با استفاده از Postman درخواستی به URL [http://localhost:5000/] ارسال میکنیم؛
- در [1]، یک درخواست ایجاد میکنیم؛
- در [2]، این یک درخواست برای HTTP و GET خواهد بود؛
- در [3]، URL از سرویس وب مورد پرسوجو است؛
- در [4]، درخواست به سرویس وب ارسال میشود؛
- در [5]، زبانه [Body] را انتخاب کنید که سند دریافتی را نمایش میدهد؛
- در [6]، زبانه [Pretty] انتخاب میشود که سند دریافتی را با قالببندی مناسب نمایش میدهد، در این مورد قالبی مناسب برای یک رشته jSON؛
- در [7]، سند دریافتشده jSON؛
- در [8-9]، سند دریافتشده بدون قالببندی؛
- در [10]، سربرگهای HTTP دریافتشده از طریق Postman نمایش داده میشوند؛
- در [11]، وضعیت HTTP پاسخ دریافتی؛
- در [12]، سربرگهای HTTP دریافتشده؛
- در [13]، هدر [Content-type] که به Postman اطلاع میداد که قرار است یک رشته jSON دریافت کند. Postman از این اطلاعات برای قالببندی سند دریافتی به روشی خاص استفاده کرد؛
راه دیگری برای استفاده از Postman وجود دارد. این روش شامل استفاده از کنسول Postman (Ctrl-Alt-C) است. این امکان را به شما میدهد تا دیالوگ کلاینت/سرور را مشاهده کنید. علاوه بر میانبر Ctrl-Alt-C، کنسول Postman از طریق آیکونی در گوشه پایین سمت چپ پنجره اصلی Postman نیز قابل دسترسی است:

کنسول Postman مکالمه کلاینت/سرور را که هنگام اجرای یک درخواست Postman رخ میدهد، ثبت میکند:

- در [3]، فهرست درخواستهایی که از زمان راهاندازی Postman انجام شدهاند. جدیدترینها در پایین فهرست قرار دارند؛
- در [4]، درخواستی که توسط Postman ارسال شده است؛
- در [5-6]، پاسخی که توسط وب سرور بازگردانده شده است؛
- در [7]، میتوانید لاگها را در حالت [raw] مشاهده کنید، یعنی بدون هیچگونه قالببندی؛
در حالت [raw]، پنجرهٔ کنسول به شکل زیر است:

- در [8]، درخواستی که توسط Postman به سرور وب ارسال شده است HTTP؛
- در [9]، پاسخی که توسط سرور وب ارسال شده است؛
- در [10]، میتوانیم به حالت [pretty logs] بازگردیم؛
برای روشنتر شدن توضیحات، خطوط دریافتشده از کنسول Postman را شمارهگذاری خواهیم کرد.
برای کلاینت:
برای سرور:
از این پس، ما عمدتاً از موارد زیر استفاده خواهیم کرد:
- [Postman] بهعنوان کلاینت وب؛
- کنسول [Postman] و [raw mode] برای توضیح دیالوگ کلاینت/سرور؛
22.4.3. اسکریپت [main_02]

اسکریپت وب [main_02] به شرح زیر است:
# برنامه پیکربندی شده است
import config
config=config.configure()
# واردات
from flask import Flask, make_response
from flask_api import status
#وابستگیها
from Personne import Personne
# برنامه Flask
app = Flask(__name__)
# خانه URL
@app.route('/')
def index():
# یک شخص
personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
# محتوا
response = make_response(f"personne[{personne.prénom}, {personne.nom}, {personne.âge}]")
# سربرگها HTTP
response.headers.set("Content-Type", "text/plain; charset=utf8")
# پاسخ HTTP
return response, status.HTTP_200_OK
#فقط اصلی
if __name__ == '__main__':
# سرور راهاندازی میشود
app.config.update(ENV="development", DEBUG=True)
app.run()
- اسکریپت [main_02] مشابه اسکریپت [main_01] است. این اسکریپت از دو جهت با آن تفاوت دارد:
- خط ۲۲: سند ارسالشده به کلاینت یک رشته کاراکتری خام است، نه یک رشته jSON؛
- خط ۲۴: این موضوع در هدر HTTP [Content-Type] منعکس شده است، که نوع [text/plain] را برای سند مشخص میکند؛
ما اسکریپت وب [main_02] را اجرا میکنیم و سپس از [Postman] برای پرسوجوی آن استفاده میکنیم:

- در [1-3]، درخواست به سرویس وب ارسال میشود؛
- در [5]، وضعیت OK پاسخ؛
- در [4, 6]، سربرگهای پاسخ HTTP؛
- در [7]، هدر [Content-Type]؛
- به [8-10]، سند ارسالشده توسط سرویس وب، یک رشته از کاراکترها؛
کنسول Postman لاگهای زیر را نمایش میدهد:
درخواست کلاینت:
پاسخ سرور:
HTTP/1.0 200 OK
Content-Type: text/plain; charset=utf8
Content-Length: 34
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Mon, 13 Jul 2020 17:34:22 GMT
personne[Aglaë, de la Hûche, 87]
22.4.4. script [main_03]

اسکریپت وب [main_03] به شرح زیر است:
#در حال پیکربندی برنامه
import config
config = config.configure()
# وارداتها
from flask import Flask, make_response
from flask_api import status
# وابستگیها
from MyException import MyException
from Personne import Personne
# برنامه Flask
app = Flask(__name__)
# خانه URL
@app.route('/')
def index():
# شخص نادرست
msg_erreur = None
try:
personne = Personne().fromdict({"prénom": "", "nom": "", "âge": 87})
except MyException as erreur:
msg_erreur = f"{erreur}"
# خطا؟
if msg_erreur:
response = make_response(msg_erreur)
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
else:
response = make_response(f"personne[{personne.prénom}, {personne.nom}, {personne.âge}]")
status_code = status.HTTP_200_OK
# سربرگها HTTP
response.headers.set("Content-Type", "text/plain; charset=utf8")
# پاسخ HTTP
return response, status_code
# فقط دستی
if __name__ == '__main__':
# راهاندازی سرور
app.config.update(ENV="development", DEBUG=True)
app.run()
- خط ۲۳: خطایی به دلیل ایجاد یک شخص نادرست رخ میدهد؛
- خطوط ۲۷–۲۹: به دلیل خطا:
- خط ۲۸: یک پاسخ HTTP آماده میشود که حاوی پیام خطا است؛
- خط ۲۹: کد وضعیت HTTP مقدار خطای [500 Internal Server Error] را دریافت میکند؛
- خط ۳۴: به کلاینت اطلاع داده میشود که متن ساده در حال ارسال است؛
- خط ۳۶: پاسخ HTTP برای کلاینت ارسال میشود؛
ما سرویس وب [main_03] را راهاندازی میکنیم و با استفاده از Postman آن را فراخوانی میکنیم:

- در [1-3]، درخواست را ارسال میکنیم؛
- در [4]، ما پاسخی با کد وضعیت [500 INTERNAL SERVER ERROR] دریافت میکنیم؛
- در [5-7]: پاسخ متنی است که خطای رخداده را توصیف میکند؛

- برای [8-10]، سربرگهای HTTP از پاسخ سرویس وب؛
در کنسول Postman، نتایج در حالت [raw] به شرح زیر است:
درخواست کلاینت:
پاسخ سرور:
HTTP/1.0 500 INTERNAL SERVER ERROR
Content-Type: text/plain; charset=utf8
Content-Length: 74
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Mon, 13 Jul 2020 17:39:24 GMT
MyException[11, Le prénom doit être une chaîne de caractères non vide]
22.5. اسکریپتهای [flask/04]: اطلاعاتی که در درخواست گنجانده شده است

اسکریپت [request_parameters.py] با هدف نشان دادن اینکه سرویس وب به بخشهای مختلفی از اطلاعات جاسازیشده در درخواست یک کلاینت وب دسترسی دارد، طراحی شده است. کد به شرح زیر است:
# وارد کردن
from flask import Flask, make_response, request
from flask_api import status
# برنامه Flask
app = Flask(__name__)
# خانه URL
@app.route('/', methods=['GET', 'POST'])
def index():
# پارامترهای درخواست
request_data = {}
request_data["environ"] = f"{request.environ}"
request_data["path"] = request.path
request_data["full_path"] = request.full_path
request_data["script_root"] = request.script_root
request_data["url"] = request.url
request_data["base_url"] = request.base_url
request_data["url_root"] = request.url_root
request_data["accept_charsets"] = request.accept_charsets
request_data["accept_encodings"] = request.accept_encodings
request_data["accept_languages"] = request.accept_languages
request_data["accept_mimetypes"] = request.accept_mimetypes
request_data["args"] = request.args
request_data["content_encoding"] = request.content_encoding
request_data["content_length"] = request.content_length
request_data["content_type"] = request.content_type
request_data["endpoint"] = request.endpoint
request_data["files"] = request.files
request_data["form"] = request.form
request_data["host"] = request.host
request_data["method"] = request.method
request_data["query_string"] = request.query_string.decode()
request_data["referrer"] = request.referrer
request_data["remote_addr"] = request.remote_addr
request_data["remote_user"] = request.remote_user
request_data["scheme"] = request.scheme
request_data["script_root"] = request.script_root
request_data["user_agent"] = f"{request.user_agent}"
request_data["values"] = request.values
# پاسخ HTTP
response = make_response(request_data)
# سربرگها HTTP
response.headers["Content-Type"] = "application/json; charset=utf-8"
# پاسخ ارسال شده HTTP
return response, status.HTTP_200_OK
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- خط ۹: ما تغییری ایجاد میکنیم. ما مشخص میکنیم که کدام افعال در درخواست کلاینت مجاز هستند. Postman فهرست را ارائه میدهد:

دو مورد اول، [GET, POST]، رایجترین موارد هستند و همچنین تنها مواردی خواهند بود که در این سند استفاده میشوند. با بازگشت به خط ۹ کد، پارامتر [methods] حاوی لیست متدهای فوق است که توسط URL مجاز شدهاند. در صورت عدم وجود این پارامتر، تنها روش [GET] مجاز است. تا کنون نیز همین وضعیت برقرار بوده است؛
- خط ۱۲: ما در حال ایجاد دیکشنری [request_data] هستیم؛
- خط ۱۳: درخواست مشتری در یک شیء از پیش تعریفشده [request] که در خط ۲ وارد شده و از نوع [werkzeug.local.LocalProxy] است، در دسترس است. خطوط زیر ویژگیهای مختلفی را از این شیء بازیابی میکنند؛
- به جای پرداختن به جزئیات هر یک از ویژگیهای شیء [request]، این کد را اجرا کرده و نتایج را بررسی خواهیم کرد. در این صورت درک بهتری از معنای ویژگیهای مختلف نمایشدادهشده خواهیم داشت؛
- خط ۴۲: دیکشنری [request_data] محتوای پاسخ HTTP خواهد بود. به یاد داشته باشید که این باید متن باشد. Flask بهطور خودکار دیکشنریها را به رشتهها jSON تبدیل میکند؛
- خط ۴۴: به کلاینت اطلاع داده میشود که jSON را دریافت خواهد کرد؛
- خط ۴۶: پاسخ به کلاینت ارسال میشود؛
با استفاده از کلاینت Postman، درخواست زیر را به سرویس وب قبلی ارسال میکنیم:

- در [1-2]، درخواست ارسال میشود؛
- در [2]، درخواست پیکربندی میشود. پارامترها به شکل [ ?param1=valeur1¶m2=valeur2] به URL افزوده میشوند. دو روش برای وارد کردن این پارامترها در Postman وجود دارد:
- آنها را مستقیماً در URL وارد کنید؛
- آنها را در [3-4] وارد کنید؛
هر دو روش معادل هستند؛
ما پارامترهای بیشتری به درخواست اضافه میکنیم:

- در [5-7]، ما پارامترها را به بدنه درخواست اضافه میکنیم. در حالی که پارامترهای موجود در URL در مرورگر وب برای کاربر قابل مشاهده هستند، پارامترهای موجود در بدنه درخواست قابل مشاهده نیستند. مرورگر (یا در این مورد Postman) آنها را پس از هدرهای HTTP به سرور ارسال میکند. درخواست کلاینت وب سپس ساختار مشابهی با پاسخ سرور وب خواهد داشت: هدرهای HTTP که با یک سند دنبال میشوند. این امر منجر به ظاهر شدن دو هدر جدید HTTP در درخواست کلاینت میشود:
- [Content-Type]: کلاینت به سرور میگوید چه نوع مستندی را ارسال میکند؛
- [Content-Length]: اندازه سند به بایت؛
- در [6]، رمزگذاری مورد استفاده برای پارامترهای اعلام شده در [7]. اینها میتوانند به روشهای مختلفی رمزگذاری شوند. [x-www-form-urlencoded] روشی است که اغلب توسط مرورگرها استفاده میشود؛
درخواست تولیدشده به این صورت است:

پاسخ این درخواست به شرح زیر است:

- در [1-5]، ما یک رشته jSON [3] دریافت کردیم؛
- آنچه معمولاً سرویس وب به آن علاقهمند است، پارامترهای URL و [ ?param1=valeur1¶m2=valeur2] و همچنین پارامترهایی است که در بدنه درخواست (اسناد) ارسال شدهاند. این معمولاً روشی است که کلاینت اطلاعات را برای آن ارسال میکند. میتوانیم در [5] ببینیم که پارامترهای URL در [request.args] موجود هستند؛
بقیه پاسخ به شرح زیر است:

- در [9]، ویژگیهای پارامترهای گنجاندهشده در بدنه درخواست:
- [content_type] نوع سندی است که همراه درخواست ارسال میشود. ما دیدهایم که این سند حاوی اطلاعاتی از نوع [param=valeur] بود که به صورت [x-www-form-urlencoded] رمزگذاری شده بود. بنابراین Postman یک هدر HTTP [Content-Type] ایجاد کرد که ماهیت سند را نشان میدهد؛
- [content_length] اندازه این سند را به بایت نشان میدهد؛
- در [10]، ویژگی [request.environ] حاوی اطلاعات زیادی در مورد محیطی است که درخواست کلاینت در آن پردازش میشود. بیشتر این اطلاعات در ویژگیهای دیگر شیء [request] یافت میشود؛
- در [11]، پارامترهای موجود در بدنه درخواست در ویژگی [request.form] در دسترس هستند؛
- در [12]، متدی که برای ارسال درخواست استفاده شده است، در این مورد متد [GET]؛
- در [13]، ویژگی [request.values] فرهنگ لغت حاوی تمام پارامترها، از جمله پارامترهای URL و پارامترهای بدنهٔ سند است. برای بازیابی پارامترهای درخواست، از ویژگی زیر استفاده کنید:
- [request.args] برای بازیابی پارامترهای موجود در URL؛
- [request.form] برای بازیابی پارامترهای موجود در بدنه سند؛
در کنسول Postman، لاگها به شرح زیر است:
درخواست کلاینت:
- خط ۹: نوع سند ارسالشده به سرور در خط ۱۲؛
- خط ۱۱: سربرگهای درخواست HTTP با یک خط خالی از سند ارسالشده جدا شدهاند. اینگونه است که سرور پایان سربرگهای HTTP کلاینت را تشخیص میدهد؛
- خط ۱۲: سند «URL-encoded». تمام کاراکترهای دارای نشانهٔ نگارشی رمزگذاری شدهاند؛
پاسخ کلاینت به شرح زیر است:
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 2433
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:09:09 GMT
{
"accept_charsets": [],
"accept_encodings": [
[
"gzip",
1
],
[
"deflate",
1
],
[
"br",
1
]
],
"accept_languages": [],
"accept_mimetypes": [
[
"*/*",
1
]
],
"args": {
"param1": "valeur1",
"param2": "valeur2"
},
"base_url": "http://localhost:5000/",
"content_encoding": null,
"content_length": 60,
"content_type": "application/x-www-form-urlencoded",
"endpoint": "index",
"environ": "{'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': <_io.BufferedReader name=908>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'werkzeug.server.shutdown': <function WSGIRequestHandler.make_environ.<locals>.shutdown_server at 0x00000173CA6E5160>, 'SERVER_SOFTWARE': 'Werkzeug/1.0.1', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'PATH_INFO': '/', 'QUERY_STRING': 'param1=valeur1¶m2=valeur2', 'REQUEST_URI': '/?param1=valeur1¶m2=valeur2', 'RAW_URI': '/?param1=valeur1¶m2=valeur2', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': 50592, 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '5000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_USER_AGENT': 'PostmanRuntime/7.26.1', 'HTTP_ACCEPT': '*/*', 'HTTP_CACHE_CONTROL': 'no-cache', 'HTTP_POSTMAN_TOKEN': 'cbfac6aa-71a0-4076-a0c3-91d36d74a4c0', 'HTTP_HOST': 'localhost:5000', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_CONNECTION': 'keep-alive', 'CONTENT_TYPE': 'application/x-www-form-urlencoded', 'CONTENT_LENGTH': '60', 'werkzeug.request': <Request 'http://localhost:5000/?param1=valeur1¶m2=valeur2' [GET]>}",
"files": {},
"form": {
"nom": "s\u00e9l\u00e9n\u00e9",
"pr\u00e9nom": "agla\u00eb",
"\u00e2ge": "77"
},
"full_path": "/?param1=valeur1¶m2=valeur2",
"host": "localhost:5000",
"method": "GET",
"path": "/",
"query_string": "param1=valeur1¶m2=valeur2",
"referrer": null,
"remote_addr": "127.0.0.1",
"remote_user": null,
"scheme": "http",
"script_root": "",
"url": "http://localhost:5000/?param1=valeur1¶m2=valeur2",
"url_root": "http://localhost:5000/",
"user_agent": "PostmanRuntime/7.26.1",
"values": {
"nom": "s\u00e9l\u00e9n\u00e9",
"param1": "valeur1",
"param2": "valeur2",
"pr\u00e9nom": "agla\u00eb",
"\u00e2ge": "77"
}
}
- خطوط ۱–۵: سربرگهای HTTP پاسخ، که با یک خط خالی پایان مییابد؛
- خطوط ۴۱–۴۵: کاراکترهای دارای نشانه به صورت UTF-8 رمزگذاری شدهاند؛
اگر اکنون از روش [POST] برای ارسال همان درخواست با همان پارامترها استفاده کنیم، همان پاسخ را دریافت خواهیم کرد، با این تفاوت که با [12]، [‘method’ : ‘POST’] را دریافت خواهیم کرد.
پس تفاوت بین روشهای GET و POST چیست؟ این تفاوت ظریف است و ریشه در نحوه استفاده تاریخی مرورگرها از آنها دارد:
- پارامترهای موجود در URL مناسب هستند زیرا یک URL که به این صورت پیکربندی شده باشد، میتواند به عنوان یک لینک در داخل یک سند HTML استفاده شود. کاربر همچنین میتواند خود پارامترها را تغییر دهد تا پاسخهای متفاوتی از سرور دریافت کند. در این حالت، مرورگرها معمولاً از روش [GET] استفاده میکنند و در درخواست ارسالشده به سرور وب هیچ بدنه (content_length=0) وجود ندارد (بدون پارامترهای مخفی)؛
- گاهی اوقات ممکن است نخواهیم پارامترها در URL نمایش داده شوند. این مورد در مورد رمزهای عبور ارسال شده به سرور صادق است. علاوه بر این، اندازه اشغالشده توسط پارامترها در URL محدود است (یک URL نمیتواند از اندازه مشخصی فراتر رود). پارامترها در بدنه درخواست این محدودیت را ندارند. علاوه بر این، پارامترهای بیش از حد در URL آن را غیرقابل خواندن میکند. بیایید مثال رایج یک فرم ثبتنام در وبسایت را در نظر بگیریم. در گذشته، زمانی که صفحات HTML هنوز شامل جاوااسکریپت نبودند، مرورگرها اطلاعات وارد شده را از طریق یک POST ارسال میکردند. به این اطلاعات «مقادیر POST» گفته میشد؛
بنابراین، در روزهای اولیه برنامهنویسی وب:
- روشهای GET عموماً با درخواست اطلاعات از یک سرور وب مرتبط بودند؛
- روشهای POST عموماً با ارسال اطلاعات از مرورگر به سرور مرتبط بودند. سپس سرور با این اطلاعات «غنیسازی» میشد؛
از آن زمان، جاوااسکریپت وارد عمل شده است. در حالی که در مثالهای قبلی، توسعهدهنده هیچ کنترلی نداشت (کلیک روی یک لینک ناگزیر یک GET را فعال میکرد و ارسال یک فرم ناگزیر شامل یک POST میشد)، جاوااسکریپت کنترل را به دست آنها بازگردانده است. در این مدل، صفحه HTML با کُد جاوااسکریپت مرتبط است که میتواند مرورگر را دور بزند. بنابراین، کلیک روی یک لینک میتواند توسط کُد جاوااسکریپت رهگیری شود، که سپس میتواند کدی را اجرا کند که درخواستی را به سرور ارسال میکند. این درخواست برای کاربر شفاف خواهد بود. کاربر آن را نخواهد دید. این کد بهعنوان یک کلاینت وب عمل میکند و توسعهدهنده میتواند، همانطور که با Postman انجام دادیم، هر درخواستی را که بخواهد ایجاد کند. برای بازگشت به مثال کلیک روی یک لینک، آنها میتوانند یک POST تولید کنند، در حالی که بهطور پیشفرض مرورگر یک GET تولید میکرد. این تغییرات تفاوتهای بین GET و POST را کمتر مرتبط کردهاند.
با این حال، توسعهدهندگان اغلب قواعد زیر را رعایت میکنند:
- یک GET نباید وضعیت سرور را تغییر دهد. درخواستهای متوالی GET که با پارامترهای یکسان با URL ارسال میشوند، باید همان سند را بازگردانند. علاوه بر این، یک GET معمولاً بدنه ندارد (سند مرتبطی ندارد)، فقط پارامترها در URL ارسال میشوند؛
- درخواست POST ممکن است وضعیت سرور را تغییر دهد. پارامترها معمولاً در بدنه درخواست ارسال میشوند. به اینها مقادیر «ارسالشده» (posted) گفته میشود. مثال فرم این موضوع را به بهترین شکل نشان میدهد: مقادیری که کاربر وارد میکند در بدنه POST قرار میگیرند و سرور آنها را در جایی، اغلب در یک پایگاه داده، ذخیره میکند؛
در بقیه این سند، ما به هیچ قاعده خاصی پایبند نیستیم.
22.6. اسکریپتهای [flask-05]: مدیریت حافظه کاربر
22.6.1. مقدمه
در مثالهای قبلی مشتری/سرور، روند به این صورت بود:
- کلاینت یک اتصال به پورت ۸۰ روی ماشین وبسرور باز میکند؛
- آن دنبالهٔ متنی را ارسال میکند: سربرگهای [flask-05]، خط خالی، [document]؛
- در پاسخ، سرور توالیای از همان نوع را ارسال میکند؛
- سرور اتصال را با کلاینت قطع میکند؛
- کلاینت اتصال به سرور را قطع میکند؛
اگر همان کلاینت کمی بعد درخواست جدیدی به وب سرور ارسال کند، یک اتصال جدید بین کلاینت و سرور برقرار میشود. سرور نمیتواند تشخیص دهد که آیا کلاینت متصلشونده قبلاً بازدید کرده است یا اینکه این اولین درخواست اوست. بین اتصالات، سرور کلاینت خود را «فراموش میکند». به همین دلیل، پروتکل HTTP یک پروتکل بدون حالت (stateless) نامیده میشود. با این حال، به یاد داشتن مشتریان برای سرور مفید است. برای مثال، اگر یک برنامه امن باشد، کلاینت برای احراز هویت خود، نام کاربری و رمز عبور را برای سرور ارسال میکند. اگر سرور بین اتصالات، مشتری خود را «فراموش» کند، کلاینت مجبور است در هر اتصال جدید خود را احراز هویت کند، که این امر امکانپذیر نیست.
برای پیگیری یک مشتری، سرور میتواند به روشهای مختلفی عمل کند:
- وقتی یک مشتری اولین درخواست خود را ارسال میکند، سرور یک شناسه را در پاسخ خود قرار میدهد که مشتری باید آن را در هر درخواست بعدی بازگرداند. سرور با استفاده از این شناسه که برای هر کلاینت منحصربهفرد است، میتواند کلاینت را تشخیص دهد. سپس میتواند یک فضای حافظه برای آن کلاینت به شکل یک فضای حافظه که منحصراً با شناسه کلاینت مرتبط است، مدیریت کند. به عنوان مثال، خدمات PHP به این صورت کار میکنند؛
- زمانی که یک کلاینت اولین درخواست خود را ارسال میکند، سرور در پاسخ خود نه یک شناسگر، بلکه خودِ حافظهٔ کاربر را گنجانده است. هیچ چیزی در سمت سرور ذخیره نمیشود. کلاینت وب برای حفظ کش خود، باید این کش را با هر درخواست جدید مجدداً ارسال کند. کش با هر درخواست جدید اصلاح میشود (یا نمیشود) و به کلاینت بازگردانده میشود (یا نمیشود). این روشی است که توسط فریمورک Flask استفاده میشود؛
تفاوتهای بین این دو روش به شرح زیر است:
- روش اول از پهنای باند کمتری استفاده میکند. تنها یک شناسگر بین کلاینت و سرور مبادله میشود. هنگامی که حافظه کاربر افزایش مییابد، این امر هیچ تأثیری بر شناسگر ندارد و شناسگر ثابت میماند. این موضوع در مورد روش دوم صادق نیست، زیرا در این روش حافظه کاربر در هر درخواست مبادله میشود و میتواند در طول چندین درخواست افزایش یابد؛
- روش اول به فضای حافظه بیشتری نیاز دارد. این به این دلیل است که سرور حافظه کاربر را در سیستم فایل خود ذخیره میکند. اگر یک میلیون کاربر وجود داشته باشد، این موضوع میتواند به طور بالقوه مشکلساز باشد. روش دوم هیچ چیزی را روی سرور ذخیره نمیکند؛
از نظر فنی، این نحوه عملکرد در هر دو روش است:
- در پاسخ به یک کلاینت جدید، سرور هدر HTTP، [Set-Cookie : MotClé=Identifiant] یا [Set-Cookie : mémoire] را درج میکند. در روش اول، این کار فقط در اولین درخواست انجام میشود. در روش دوم، هر بار که حافظه کاربر تغییر میکند، این کار انجام میشود؛
- در درخواستهای خود، کلاینت بهطور سیستماتیک آنچه را دریافت کرده است، چه یک شناسگر و چه یک حافظه، بازمیگرداند. آنها این کار را از طریق هدر HTTP [Cookie : MotClé=Valeur] انجام میدهند؛
ممکن است این سؤال پیش بیاید که سرور چگونه میداند در حال تعامل با یک مشتری جدید است و نه یک مشتری بازگشتی. حضور هدر HTTP Cookie در میان هدرهای HTTP کلاینت است که این موضوع را به سرور نشان میدهد. برای یک کلاینت جدید، این هدر وجود ندارد.
تمام اتصالات انجامشده توسط یک مشتری مشخص، یک جلسه نامیده میشود.
سرور ممکن است انواع دیگری از حافظه را نیز نگهداری کند:

- در [1]، حافظه در سطح درخواست منحصر به فرد است. این حافظه زمانی استفاده میشود که درخواست یک کلاینت وب نه توسط یک سرویس (یا برنامه) واحد، بلکه توسط چندین سرویس پردازش شود. برای ارسال اطلاعات به سرویس i+1، سرویس i میتواند درخواست در حال پردازش را با این اطلاعات غنیسازی کند. این پدیده به عنوان حافظه در سطح درخواست شناخته میشود. ما در این سند از این نوع حافظه استفاده نخواهیم کرد؛
- در [2, 4]، حافظه کاربری را که به تازگی شرح دادیم. این حافظه میتواند به صورت محلی ([2]) پیادهسازی شود یا توسط کلاینت ([4]) نگهداری گردد؛
- در [3]، حافظه «درجه-برنامه» به طور کلی فقط-خواندنی است. این حافظه بین همه کاربران مشترک است. این حافظه اغلب حاوی عناصری از پیکربندی برنامه وب است که بین همه کاربران برنامه مشترک است. باید در مورد این نوع حافظه دقت کرد: نوشتن در آن باید در زمانی انجام شود که کاربران هنوز هیچ درخواستی ارسال نکردهاند، معمولاً هنگام راهاندازی برنامه. به محض اینکه درخواستها شروع به ارسال میشوند، نوشتن در این حافظه دشوار میشود. هنگامی که وبسرور به طور همزمان به چندین کاربر خدمات ارائه میدهد و دو نفر از آنها سعی در نوشتن در حافظه سطح «برنامه» دارند، این خطر وجود دارد که این حافظه خراب شود. این به آن دلیل است که در حالی که کاربر 1 شروع به نوشتن در حافظه سطح «برنامه» کرده است، ممکن است قبل از اینکه حتی کارش را تمام کند، متوقف شود. این امر منجر به حافظه ناقص برنامه میشود. از آنجایی که این حافظه مشترک است، کاربر ۲ ممکن است آن را بخواند و وضعیت نادرستی به دست آورد؛
22.6.2. اسکریپت [session_scope_01]

اسکریپتهای [session_scope_xx] مدیریت حافظههای کاربر را نشان میدهند.
اسکریپت [session_scope_01] به شرح زیر است:
#پیکربندی برنامه
import config
config = config.configure()
#وابستگیها
import json
from flask import Flask, make_response, session
from flask_api import status
# برنامه Flask
app = Flask(__name__)
# کلید مخفی جلسه
app.secret_key = config["SECRET_KEY"]
@app.route('/set-session', methods=['GET'])
def set_session():
# افزودن چیزی به جلسه
session['nom'] = 'séléné'
#ارسال یک پاسخ خالی
response = make_response()
response.headers['Content-Length'] = 0
return response, status.HTTP_200_OK
@app.route('/get-session', methods=['GET'])
def get_session():
# جلسه را بازیابی کرده و پاسخ را ارسال میکند
response = make_response(json.dumps({"nom": session['nom']}, ensure_ascii=False))
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
#فقط دستی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- خط ۱۱: یک برنامه Flask ایجاد میشود؛
- خط 14: مقدار ویژگی [secret_key] این برنامه از فایل پیکربندی مورد استفاده در خطوط 1–3 گرفته میشود. یک جلسه Flask تنها در صورتی امکانپذیر است که این ویژگی مقداردهی اولیه شود. شما میتوانید هر چیزی را در آن قرار دهید. از آن برای رمزگذاری بخشی از «حافظه کاربر» که به کلاینت ارسال میشود، استفاده میشود. معمولاً مقداری برای آن تعیین میشود که حدس زدن آن دشوار باشد. در فایل [config]، کلید مخفی به شرح زیر تعریف شده است:
# بازگرداندن پیکربندی
config = {
#پیکربندی Flask
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
- برای اولین بار، ما در حال تعریف یک برنامه وب هستیم که چیزی غیر از URL / را ارائه میدهد.
- خط ۱۷: URL [/set-session] برای راهاندازی جلسه کاربر استفاده میشود؛
- خط ۲۷: URL [/get-session] برای بازیابی حافظه کاربر (یا جلسه کاربر) استفاده میشود؛
- خط ۲۰: ما چیزی را در حافظه کاربر (= جلسه) ذخیره میکنیم، در این مورد یک نام. جلسه مانند یک دیکشنری مدیریت میشود. شما نمیتوانید هر چیزی را در جلسه ذخیره کنید. مقادیر ذخیرهشده در آنجا باید قابل تبدیل به jSON باشند. برای انواع از پیش تعریفشده پایتون، این کار به طور خودکار و بدون هیچ مداخلهای از سوی توسعهدهنده انجام میشود. برای اشیاء سفارشی که پایتون آنها را تشخیص نمیدهد، شما باید خودتان تبدیل به jSON را انجام دهید؛
- خط ۲۲: ما یک پاسخ HTTP بدون محتوا ایجاد میکنیم (هیچ پارامتر به make_response ارسال نشده است);
- خط ۲۳: به کلاینت اطلاع داده میشود که یک سند خالی (با اندازه ۰ بایت) دریافت خواهد کرد؛
- خط ۲۴: پاسخ HTTP به کلاینت ارسال میشود. بنابراین URL و [/set-session] کاری جز راهاندازی یک جلسه کاربری انجام نمیدهند؛
- خط ۲۷: پاسخهای URL و [/get-session] به کاربر اجازه میدهند تا ببیند در جلسه (session) خود چه چیزی وجود دارد؛
- خط ۳۰: ما یک پاسخ HTTP ایجاد میکنیم که حاوی رشته jSON از جلسه کاربر است. در اینجا، ما خودمان رشته jSON را ایجاد کردهایم به جای اینکه اجازه دهیم Flask آن را تولید کند. این به این دلیل است که ما نمیخواهیم کاراکترهای دارای علامت (accented characters) فرار داده شوند (ensure_ascii=False);
- خط ۳۱: به کلاینت میگوییم که در حال ارسال jSON برای او هستیم؛
- خط ۳۲: ما پاسخ HTTP را به کلاینت ارسال میکنیم؛
هدف این اسکریپت نشان دادن این است که جلسه کاربری امکان ایجاد پیوند بین درخواستهای متوالی کاربر را فراهم میکند:
- درخواست ۱ برای URL و [/set-session] درخواست خواهد کرد؛
- درخواست ۲، URL و [/get-session] را درخواست خواهد کرد و نامی را که توسط درخواست ۱ مقداردهی اولیه شده است، بازیابی خواهد نمود؛
اسکریپت [config]، که اسکریپتها را در پوشه [flask/05] پیکربندی میکند، به شرح زیر است:
def configure():
# مسیر مطلق بهعنوان مرجع برای مسیرهای نسبی در پیکربندی
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# وابستگیهای برنامه
absolute_dependencies = [
# افراد، ابزارها، MyException
f"{root_dir}/classes/02/entities",
]
# مسیر سیستم را تنظیم کنید
from myutils import set_syspath
set_syspath(absolute_dependencies)
# بارگذاری پیکربندی
config = {
#پیکربندی Flask
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
return config
ما اسکریپت [session_scope_01] را اجرا میکنیم، سپس با استفاده از Postman درخواستهایی به URL و [/set-session] ارسال میکنیم. پیش از انجام این کار، چند عنصر از درخواستی را که قرار است ارسال شود بررسی خواهیم کرد:
![]()
- در [1]، به کوکیهای Postman دسترسی پیدا میکنیم؛
- در [2-4]، کوکیهای شناختهشدهی Postman را بررسی کرده و همهی آنها را حذف میکنیم ([4-5]);
اکنون بیایید درخواست HTTP را که تولید خواهد شد بررسی کنیم:

- در [9]: برخی از سربرگها HTTP که Postman بر اساس پیکربندیای که برای آن تنظیم کردهایم، در درخواست قرار خواهد داد. این بررسی به شما امکان میدهد تا تأیید کنید که هیچ پارامتری را از قلم نیانداختهاید یا برعکس، هیچ پارامتر غیرضروری را باقی نگذاشتهاید؛
پس از انجام این کار، میتوانید پرسوجو را اجرا کنید:
![]()
روشهای مختلفی برای بررسی نتیجه وجود دارد. میتوانید با نگاه کردن به پنجرهٔ اصلی شروع کنید:

- در [1-2]، درخواست ارسالشده به سرویس وب؛
- در [3-6]، سربرگهای پاسخ؛
- در [4]، از آنجا که نوع پاسخ در کد مشخص نشده بود، Flask از نوع پیشفرض [text/html] استفاده کرد؛
- در [5]، کلاینت میداند که در پاسخ هیچ سندی وجود ندارد؛
- خط ۶: هدر [Set-Cookie] توسط سرور Flask ارسال شده است. مقدار آن به عنوان کوکی جلسه شناخته میشود. این کوکی از سه عنصر تشکیل شده است:
- [session=valeur]: این مقدار نمایانگر جلسه کاربر در قالبی رمزگذاریشده است. این جلسه را میتوان رمزگشایی کرد (به |https://blog.miguelgrinberg.com/post/how-secure-is-the-flask-user-session| مراجعه کنید). با این حال، به دلیل کلید مخفی مورد استفاده توسط سرور، کاربر نمیتواند دادههای دریافتی را تغییر دهد و سپس آن را به سرور بازگرداند. بنابراین، هنگامی که سرور یک جلسه را دریافت میکند، از دریافت یک جلسه سالم و دستنخورده اطمینان حاصل میکند؛
- [HttpOnly]: وجود این عنصر به مرورگری که آن را دریافت میکند میگوید که کوکی نباید برای هیچیک از جاوااسکریپتهای موجود در صفحهای که در حال نمایش آن است، قابل دسترسی باشد؛
- [Path=/] مسیری است که کوکی جلسه باید به آن بازگردانده شود؛ در این مورد، این به هر مسیری در داخل برنامه وب اشاره دارد. هر زمان که کاربر صراحتاً (با تایپ URL) یا تلویحاً (با کلیک روی یک لینک) یک URL را از این دامنه درخواست کند، مرورگر به طور خودکار کوکی جلسه را که دریافت کرده است، بازخواهد گرداند؛
نقطهضعف پنجرهٔ اصلی این است که ما به درخواست کامل منجر به این پاسخ دسترسی نداریم. آنچه در این پنجره نمایش داده میشود گیجکننده است:

- در هدرهای HTTP [3-4]، یک کوکی جلسه به صورت [5] نمایش داده میشود. بنابراین ممکن است فرض شود که Postman یک کوکی جلسه را در درخواست گنجانده است، در حالی که در واقع چنین نیست. سرورهای [3] در واقع نمایانگر سرورهای HTTP هستند که با درخواست بعدی ارسال خواهند شد، همانطور که در حال حاضر پیکربندی شدهاند. پستمن به تازگی یک کوکی جلسه دریافت کرده است که آن را در درخواست بعدی بازخواهد فرستاد. به همین دلیل است که [5] را مشاهده میکنیم؛
میتوانید با فشردن Ctrl-Alt-C به دیالوگ کلاینت/سرور در کنسول Postman دسترسی پیدا کنید:
GET /set-session HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 3673b73f-7600-4df4-8c4b-c37973e50df8
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Vary: Cookie
Set-Cookie: session=eyJub20iOiJzXHUwMGU5bFx1MDBlOW5cdTAwZTkifQ.Xw6jGQ.y5Icu70wTIN-B0o_hwx0xDH247I; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:32:57 GMT
- خط ۱۴: کوکی جلسه ارسالشده توسط سرور؛
اکنون بیایید URL [/get-session] را درخواست کنیم:
- خط ۹: کلاینت پستمن کوکی جلسه را که دریافت کرده بود، دوباره برای سرور ارسال کرد؛
- خط ۱۸: رشته jSON ارسالشده توسط سرور؛
این مثال چندین نکته را نشان میدهد:
- کلاینت پستمن کوکی جلسه را که از سرور فلاسک دریافت میکند، بازمیفرستد. مرورگرهای وب همیشه این کار را انجام میدهند؛
- میتوانیم ببینیم که درخواست ۲ ([/get-session]) اطلاعاتی را که در طول درخواست ۱ ([/set-session]) ایجاد شده بود، بازیابی کرد. بنابراین این یک نوع وضعیت کاربر محسوب میشود؛
- خطوط ۱۱–۱۶: سرور Flask کوکی جلسه را بازنمیگرداند. این همیشه صادق نیست. سرور Flask فقط در صورتی کوکی جلسه را بازمیگرداند که درخواست آخر وضعیت کاربر را تغییر داده باشد؛
22.6.3. اسکریپت [session_scope_02]

اسکریپت [session_02] به شرح زیر است:
#وابستگیها
import os
from flask import Flask, make_response, session
from flask_api import status
# برنامه Flask
app = Flask(__name__)
# کلید مخفی جلسه
app.secret_key = os.urandom(12).hex()
# خانه URL
@app.route('/', methods=['GET'])
def index():
# ما سه شمارنده را مدیریت میکنیم
if session.get('n1') is None:
session['n1'] = 0
else:
session['n1'] = session['n1'] + 1
if session.get('n2') is None:
session['n2'] = 10
else:
session['n2'] = session['n2'] + 1
if session.get('n3') is None:
session['n3'] = 100
else:
session['n3'] = session['n3'] + 1
# واژهنامه کنتور
compteurs = {"n1": session['n1'], "n2": session['n2'], "n3": session['n3']}
#ارسال پاسخ
response = make_response(compteurs)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- خط ۱۱: در اینجا، کلید مخفی با استفاده از یک تابع تولید میشود. مزیت این کار این است که یک رشته کاراکتری پیچیده و تصادفی تولید میکند. توجه داشته باشید که متغیر [app]، نمونه کلاس Flask است که در خط ۸ ایجاد شده است؛
- خط ۱۵: این بار، تنها یک مسیر، مسیر / وجود خواهد داشت؛
- خطوط 17–29: ما یک جلسه را مدیریت میکنیم که شامل سه شمارنده است: [n1, n2, n3]. در اولین درخواست کاربر، [n1, n2, n3] = [0, 10, 100]؛ سپس، در هر درخواست بعدی، این شمارندهها به اندازه 1 افزایش مییابند؛
- خط ۱۸: در اولین درخواست، جلسهٔ برنامه خالی است. عبارت [session.get(‘clé’)] مقدار [None] را برمیگرداند. برای درخواستهای بعدی، این عبارت مقدار مرتبط با کلید را برمیگرداند؛
- خط ۳۱: این شمارندهها در یک دیکشنری قرار داده میشوند؛
- خط ۳۳: این دیکشنری، بدنه پاسخ HTTP است. به یاد داشته باشید که Flask به طور خودکار دیکشنریها را به رشتههایی مانند jSON تبدیل میکند؛
- خط ۳۴: به کلاینت وب گفته میشود که jSON را دریافت خواهد کرد؛
- خط ۳۵: پاسخ HTTP به کلاینت ارسال میشود؛
بیایید این اسکریپت را اجرا کرده و با استفاده از Postman، پس از حذف تمام کوکیها از کلاینت Postman [1-3]، به وباپلیکیشن ایجادشده استعلام ارسال کنیم:

در کنسول Postman، تبادلهای کلاینت/سرور به شرح زیر است:
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: c7db536d-9352-4aa6-9877-04560e03d935
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 41
Vary: Cookie
Set-Cookie: session=eyJuMSI6MCwibjIiOjEwLCJuMyI6MTAwfQ.Xw6nLg.v49CeDWwqP-6Dp9Qt330GAe-dNA; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:50:22 GMT
{
"n1": 0,
"n2": 10,
"n3": 100
}
- در [14]، کوکی جلسه ارسالشده توسط سرور؛
- به [18-22]، پاسخ سرور به شکل یک رشته jSON؛
بیایید همان درخواست را بار دوم ارسال کنیم. لاگها به شرح زیر تغییر میکنند:
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 8205ad85-37b3-41f2-a171-70dd3b3a1679
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session=eyJuMSI6MCwibjIiOjEwLCJuMyI6MTAwfQ.Xw6nLg.v49CeDWwqP-6Dp9Qt330GAe-dNA
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 41
Vary: Cookie
Set-Cookie: session=eyJuMSI6MSwibjIiOjExLCJuMyI6MTAxfQ.Xw6nsw.OuxIQnGhmhSsan5Qu_FL3Iyu-9k; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:52:35 GMT
{
"n1": 1,
"n2": 11,
"n3": 101
}
- خط ۹: کلاینت Postman کوکی جلسه را که دریافت کرده است، بازمیفرستد؛
- خط ۱۵: در پاسخ خود، سرور یک کوکی جلسه جدید ارسال میکند، زیرا درخواست کلاینت وضعیت کاربر (= جلسه) را تغییر داده است؛
- خطوط ۱۹–۲۳: مقادیر جدید شمارنده؛
22.6.4. اسکریپت [session_scope_03]
این اسکریپت جدید با هدف نشان دادن این است که انواع مختلف پایتون را میتوان در یک جلسه قرار داد: لیستها، دیکشنریها و اشیاء. تنها محدودیت این است که اشیایی که در جلسه قرار میگیرند باید در jSON قابل سریالسازی باشند. اگر آنها به طور پیشفرض قابل سریالسازی نباشند (لیستها، دیکشنریها)، باید خودتان تبدیل را در jSON انجام دهید.
#برنامه پیکربندی شده است
import config
config = config.configure()
#وابستگیها
import json
import os
from flask import Flask, make_response, session
from flask_api import status
from Personne import Personne
# برنامه Flask
app = Flask(__name__)
#کلید مخفی جلسه
app.secret_key = os.urandom(12).hex()
# خانه URL
@app.route('/', methods=['GET'])
def index():
# مدیریت لیست
liste = session.get('liste')
if liste is None:
# اولین درخواست
liste = [0, 10, 100]
else:
# درخواستهای بعدی
for i in range(len(liste)):
liste[i] += 1
# فهرست به جلسه بازگردانده میشود
session['liste'] = liste
#مدیریت فرهنگ لغت
dico = session.get('dico')
if not dico:
# اولین پرسوجو
dico = {"un": 0, "deux": 10, "trois": 100}
else:
# پرسوجوهای بعدی
dico = session['dico']
for key in dico.keys():
dico[key] += 1
# واژهنامه به جلسه بازگردانده میشود
session['dico'] = dico
# مدیریت یک شخص
personne_json = session.get('personne')
if personne_json is None:
# اولین درخواست
personne = Personne().fromdict({"prénom": "aglaë", "nom": "séléné", "âge": 70})
else:
# پرسوجوهای بعدی
personne = Personne().fromjson(personne_json)
personne.âge += 1
# فرد به جلسه بازگردانده میشود
session['personne'] = personne.asjson()
# فرهنگ نتایج
résultats = {"liste": liste, "dict": dico, "personne": personne.asdict()}
#ارسال یک پاسخ jSON
response = make_response(json.dumps(résultats, ensure_ascii=False))
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- خطوط ۱–۳: برنامه وب پیکربندی میشود؛
- خطوط ۵–۱۱: وابستگیها وارد میشوند؛
- خط ۱۴: اپلیکیشن Flask نمونه برداری میشود؛
- خط ۱۷: ویژگی [secret_key] مقداردهی اولیه میشود. این کار استفاده از جلسات را امکانپذیر میسازد؛
- خط ۲۱: تنها مسیر برنامه؛
- خطوط ۲۳–۳۳: مدیریت یک لیست در جلسه. ما عناصری را به آن اضافه کردهایم که به طور پیشفرض در jSON قابل سریالسازی هستند؛
- خطوط ۳۵–۴۶: مدیریت یک فرهنگ لغت در جلسه. به طور پیشفرض، عناصر قابل سریالسازی به این فرهنگ لغت به صورت jSON قرار داده شدهاند؛
- خطوط ۴۸–۵۸: مدیریت یک شخص. یک شیء [Personne] بهطور پیشفرض قابل سریالسازی به jSON نیست. بنابراین باید احتیاطهای لازم بهعمل آید؛
- خط ۵۸: متد [BaseEntity.asjson] برای ذخیره کردن رشته jSON شخص در جلسه استفاده میشود. توجه داشته باشید که میتوانستیم از [personne.asdict] استفاده کنیم، زیرا [personne.asdict] یک دیکشنری حاوی مقادیری است که به طور پیشفرض به jSON سریالیزه میشوند؛
- خط ۵۵: از آنجا که ما یک رشته jSON را در جلسه ذخیره کردهایم، شخص را با استفاده از متد [BaseEntity.fromjson] از آن بازیابی میکنیم؛
- خط ۶۱: ما دیکشنری [résultats] را ایجاد میکنیم که بهعنوان پاسخ به کلاینت ارسال خواهد شد. میدانیم که در این مورد، Flask رشته jSON را از دیکشنری ارسال میکند. بنابراین دیکشنری باید فقط شامل مقادیری باشد که به طور پیشفرض در jSON قابل سریالسازی هستند؛
- خط ۶۴: ما به صراحت رشته jSON را از دیکشنری [résultats] در پاسخ HTTP قرار میدهیم. Flask به طور پیشفرض این کار را انجام میداد. با این حال، به طور پیشفرض، از پارامتر [ensure_ascii=True] استفاده میکند که با نیازهای ما سازگار نبود؛
- خط ۶۵: به کلاینت میگوییم که jSON را دریافت خواهد کرد؛
- خط ۶۶: پاسخ را برای آن ارسال میکنیم؛
ما برنامه وب را راهاندازی میکنیم. تمام کوکیها را از کلاینت Postman حذف میکنیم. سپس کلاینت URL و [http://localhost:5000] را درخواست میکند. مکالمه کلاینت/سرور در کنسول Postman به شرح زیر است:
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 5f8b7c63-aa8a-4429-a2fa-62141423d933
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 135
Vary: Cookie
Set-Cookie: session=.eJw9isEKwyAQRH-lzHkPm15K91dqD2mzBMFq0AgF8d-jsRQG9u3MK1jsO0AKFs1fyMSEPQabOjbOHsKV4GzaFfJgmnr4Sdg0puB9a1EMtmgys959-BjIxWBe3XxWLwNq_39IQ3Q_f5zhnHxdtYs3rqgH4gQvMg.Xw6yGw.Bwpt3q-sH03gFLmg2FIPXV_ZNt8; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 07:36:59 GMT
{"liste": [0, 10, 100], "dict": {"un": 0, "deux": 10, "trois": 100}, "personne": {"prénom": "aglaë", "nom": "séléné", "âge": 70}}
ما درخواست را برای بار دوم انجام میدهیم:
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 40fd00ea-d45c-46b7-a51e-d4d433a37b5c
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session=.eJw9isEKwyAQRH-lzHkPm15K91dqD2mzBMFq0AgF8d-jsRQG9u3MK1jsO0AKFs1fyMSEPQabOjbOHsKV4GzaFfJgmnr4Sdg0puB9a1EMtmgys959-BjIxWBe3XxWLwNq_39IQ3Q_f5zhnHxdtYs3rqgH4gQvMg.Xw6yGw.Bwpt3q-sH03gFLmg2FIPXV_ZNt8
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 135
Vary: Cookie
Set-Cookie: session=.eJw9isEKwyAQRH-lzHkP2kupv9LtIW2WIBgNGqEg_nu3seQ0b2Zew-zfCa5hlvqBs5aw5-SLolGuUaETgi-7wD0sqaHPk7BJLilGXdEYW-ZqjNxjWhnuwpiWMB3Ti0Haz6MMMfz9EcM5-LrIT7zZjv4F5NYvOQ.Xw6ydQ.PMWRCqKx9HNnb_DyK-ha-9pCF7M; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 07:38:29 GMT
{"liste": [1, 11, 101], "dict": {"deux": 11, "trois": 101, "un": 1}, "personne": {"prénom": "aglaë", "nom": "séléné", "âge": 71}}
- خط ۹: کلاینت کوکی سشن دریافتی را بازمیفرستد؛
- خط ۱۵: سرور یکی دیگر را بازمیگرداند زیرا محتوای جلسه تغییر کرده است (خط ۱۹). توجه داشته باشید که این محتوا به صورت رمزگذاریشده در کوکی جلسه ذخیره میشود؛
22.7. [flask/06] اسکریپتها: اطلاعاتی که توسط همه کاربران به اشتراک گذاشته میشود
22.7.1. مقدمه
این بخش با هدف نشان دادن نحوه مدیریت اطلاعات در سراسر برنامه، یعنی اطلاعاتی که توسط همه کاربران به اشتراک گذاشته میشود، ارائه شده است. این اطلاعات معمولاً شامل جزئیات پیکربندی برنامه است. ما دیدهایم که یک برنامه وب میتواند انواع مختلفی از حافظه را حفظ کند:

در اینجا، ما بر حافظهٔ برنامهٔ [3] تمرکز میکنیم.
22.7.2. اسکریپت [application_scope_01]

اسکریپت [application_scope_01] یکی از روشهای مدیریت دادههای در مقیاس «برنامه» را نشان میدهد:
# راهاندازی برنامه
import config
config = config.configure()
#وابستگیها
from flask import Flask, make_response
from flask_api import status
# برنامه Flask
app = Flask(__name__)
# خانه URL
@app.route('/', methods=['GET'])
def index():
#هدف نشان دادن این است که برنامه بین درخواستهای مشتریان مختلف در حافظه باقی میماند
# هر کلاینت با همان برنامه تعامل میکند
#app_infos نمایانگر اطلاعات در سطح برنامه است، نه اطلاعات در سطح جلسه
#یعنی این اطلاعات مربوط به همه کاربران است و نه صرفاً یک کاربر خاص
#این اطلاعات در [config] (اختیاری) ذخیره میشود
#فرهنگ نتایج
résultats = {"config": config}
# پاسخ ارسال میشود
response = make_response(résultats)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# اصلی
if __name__ == '__main__':
#بررسی میکند که آیا این کد چندین بار اجرا میشود
print("application app lancée")
# راهاندازی برنامه وب
app.config.update(ENV="development", DEBUG=True)
app.run()
- خطوط ۱–۳: دیکشنری پیکربندی بازیابی میشود. نشان خواهیم داد که کدی که خارج از توابع مسیریابی قرار دارد، تنها یک بار اجرا میشود. اپلیکیشن Flask در حافظه باقی میماند. تمام اطلاعاتی که خارج از مسیرها اولیه شدهاند، برای آنها جهانی است و بنابراین در دسترس آنها قرار دارد. بنابراین، دیکشنری [config] در خط ۳ توسط مسیر / (line 24) رندر خواهد شد. ما نشان خواهیم داد که همه کلاینتهای وب یک دیکشنری یکسان دریافت خواهند کرد و بنابراین این دیکشنری توسط همه کلاینتها به اشتراک گذاشته میشود. بنابراین این اطلاعات در مقیاس «اپلیکیشن» هستند؛
- خط ۳۵: یک لاگ اضافه میکنیم تا بررسی کنیم که آیا کد در خطوط خارج از تابع مسیریابی (خطوط ۱–۱۰، ۳۲–۳۸) چندین بار اجرا میشود؛
پیکربندی [config] به شرح زیر است:
def configure():
# بازگرداندن پیکربندی
config = {
#پیکربندی Flask
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
return config
ما این برنامه را راهاندازی میکنیم. لاگهای کنسول PyCharm به شرح زیر است:

- در [1]، راهاندازی اولیهٔ برنامه؛
- در [2]، از آنجا که حالت [Debug] را درخواست کردیم، برنامه در حالت [Debug] مجدداً راهاندازی میشود؛
اکنون، با استفاده از یک مرورگر (در اینجا کروم)، URL [http://127.0.0.1:5000/] را درخواست میکنیم:

اکنون، با استفاده از فایرفاکس:

اکنون با استفاده از کلاینت Postman:
اکنون، بیایید به کنسول پایتون چرم (Pycharm) بازگردیم: [Run]:

- دو لاگ [1, 2] هنوز وجود دارند، اما هیچ لاگ دیگری وجود ندارد، هرچند میتوانیم سه درخواست دریافتشده توسط وبسرور را ببینیم؛
برای اطمینان کامل از اینکه برنامه با هر درخواست جدید دوباره بارگذاری نمیشود، میتوانیم یک شمارنده به پیکربندی اضافه کرده و آن را با هر درخواست جدید افزایش دهیم. در این صورت خواهیم دید که هر کلاینت شمارنده را در وضعیتی که کلاینت قبلی به جا گذاشته است، مشاهده میکند. با این حال، باید توجه داشت که کلاینتها نباید دادههای در مقیاس برنامه را تغییر دهند، زیرا این دادهها بین همه کلاینتها مشترک است؛ و در سناریویی که سرور همزمان به چندین کلاینت خدمات ارائه میدهد و هیچ تضمینی وجود ندارد که درخواست یک کلاینت به طور کامل و بدون وقفه اجرا شود، کلاینت ۱ که درخواست ۱ را ارسال کرده و پیش از تکمیل متوقف شده است، ممکن است دادههای مشترک را برای کلاینتهای بعدی در وضعیتی خراب رها کند.
22.7.3. script [application_scope_02]

اسکریپت [application_scope_02] دقیقاً کاری را انجام میدهد که نباید انجام دهد: به کلاینتها اجازه میدهد اطلاعات مشترک با سایر کاربران را تغییر دهند. ما یک شمارنده را بین کاربران به اشتراک میگذاریم که هر یک آن را افزایش میدهند. خواهیم دید که هر کاربر میتواند تغییرات اعمالشده بر روی شمارنده توسط سایر کاربران را مشاهده کند.
اسکریپت به شرح زیر است:
#وابستگیها
from flask import Flask, make_response
from flask_api import status
# برنامه Flask
app = Flask(__name__)
# دادههای دامنهٔ برنامه
config = {
"counter": 0
}
# خانه URL
@app.route('/', methods=['GET'])
def index():
#هدف این است که نشان دهیم دیکشنری [config] در میان همه کلاینتها مشترک است
# دامنهٔ برنامهٔ وب
#شمارنده افزایش مییابد
config["counter"] += 1
#پاسخ ارسال میشود
response = make_response(config)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- خطوط ۱۰–۱۲: دیکشنری [config] که بین کاربران مشترک است. این شامل یک شمارنده است؛
- خط ۲۲: هر بار که یک کاربر URL / را درخواست میکند، شمارنده در پیکربندی افزایش مییابد؛
- خطوط 23–26: رشته jSON از دیکشنری به هر کلاینت ارسال میشود؛
ما این اسکریپت را اجرا میکنیم. سپس با استفاده از مرورگر اول، URL [http://127.0.0.1:5000/] را درخواست میکنیم:

سپس همین کار را با یک مرورگر دوم انجام میدهیم:

سپس برای بار سوم با استفاده از Postman:

میتوانیم ببینیم که هر کلاینت شمارنده را در وضعیتی که کلاینت قبلی رها کرده است، بازیابی میکند. بنابراین، آنها واقعاً به همان اطلاعات دسترسی دارند.
22.7.4. اسکریپت [application_scope_03]
اسکریپت [application_scope_03] نشان میدهد که چرا اطلاعاتی که بین کاربران به اشتراک گذاشته میشود باید فقط-خواندنی باشد.

اسکریپت به شرح زیر است:
#وابستگیها
import threading
from time import sleep
from flask import Flask, make_response
from flask_api import status
# برنامه Flask
app = Flask(__name__)
# دادههای دامنهٔ برنامه
config = {
"counter": 0
}
# خانه URL
@app.route('/', methods=['GET'])
def index():
#هدف نشان دادن این است که دیکشنری [config] در میان همه کلاینتها مشترک است
#مربوط به وباپلیکیشن است و باید فقط-خواندنی باشد
#نام نخ
thread_name = threading.current_thread().name
# شمارنده را میخوانیم
counter = config["counter"]
print(f"compteur lu : {counter}, par le thread {thread_name}")
# ما به مدت ۵ ثانیه مکث میکنیم – تا سایر کلاینتها نیز سرویسدهی شوند
sleep(5)
# افزایش شمارنده پیکربندی
config["counter"] = counter + 1
# لاگ
print(f"compteur écrit : {config['counter']}, par le thread {thread_name}")
#ارسال پاسخ
response = make_response(config)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run(threaded=True)
- خط ۴۳: ما حالت اجرای وباپلیکیشن را تغییر دادیم. ما [threaded=True] را نوشتیم تا نشان دهیم که اپلیکیشن باید به طور همزمان به کاربران خدمترسانی کند. این کار با استفاده از نخهای اجرایی (execution threads) انجام میشود:
- ممکن است چندین نخ اجرایی همزمان وجود داشته باشد که هر کدام به یک کاربر خدمت میکنند؛
- پردازندهٔ ماشین توسط این نخها مشترکاً استفاده میشود؛
- ممکن است یک نخ قبل از اتمام کارش متوقف شود. اجرای آن در زمان دیگری از سر گرفته خواهد شد؛
- خط ۱۹: تابع [index] میتواند بهطور همزمان توسط چندین نخ اجرا شود؛
- خط ۲۴: نام رشتهای که تابع [index] را اجرا میکند، بازیابی میشود؛
- خط ۲۶: مقدار شمارنده خوانده میشود. برای اهداف نمایش ما، افزایش شمارنده را به شرح زیر تجزیه میکنیم:
- مرحله ۱: نخ ۱ شمارنده را میخواند (برای مثال، ۱);
- مرحله ۲: نخ ۱ به مدت ۵ ثانیه مکث میکند (خط ۲۹). از آنجا که نخ ۱ درخواست مکث کرده است، پردازنده به نخ دیگری، یعنی نخ ۲، واگذار میشود. هدف این است که این نخ جدید نیز همان مقدار شمارنده (=۱) را بخواند. سپس آن نیز به مدت ۵ ثانیه مکث میکند و پردازنده را از دست میدهد؛
- مرحله ۳: شمارنده بر اساس مقدار خوانده شده در مرحله ۱ (=۱) افزایش مییابد (خط ۳۱). تِرد ۱ اولین کسی است که این کار را انجام میدهد: آن شمارنده را روی ۲ تنظیم میکند و سپس اجرای تابع [index] را به پایان میرساند. سپس نوبت رشته ۲ است که بیدار شود و نیز شمارنده را از مقداری که در مرحله ۱ خوانده شده است (=۱) شروع کرده و روی ۲ تنظیم کند. در نهایت، پس از اینکه هر دو رشته اجرا شدند، شمارنده روی ۲ قرار دارد در حالی که باید روی ۳ باشد؛
- خط ۳۳: مقدار شمارنده را برای تأیید نمایش میدهیم؛
اسکریپت را اجرا میکنیم و سپس با استفاده از دو مرورگر و سپس Postman، URL [http://loaclhost :5000/] را درخواست میکنیم. لاگهای کنسول PyCharm به شرح زیر است:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/06/application_scope_03.py
* Serving Flask app "application_scope_03" (lazy loading)
* Environment: development
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (برای خروج کلید CTRL+C را فشار دهید)
compteur lu : 0, par le thread Thread-2
compteur lu : 0, par le thread Thread-4
compteur écrit : 1, par le thread Thread-2
127.0.0.1 - - [16/Jul/2020 08:55:37] "GET / HTTP/1.1" 200 -
compteur écrit : 1, par le thread Thread-4
127.0.0.1 - - [16/Jul/2020 08:55:40] "GET / HTTP/1.1" 200 -
compteur lu : 1, par le thread Thread-5
compteur écrit : 2, par le thread Thread-5
127.0.0.1 - - [16/Jul/2020 08:55:46] "GET / HTTP/1.1" 200 -
- خطوط ۹–۱۰: دو نخ اول، ۲ و ۴، مقدار یکسان شمارنده را که ۰ است، میخوانند؛
- خط ۱۱: رشته ۲ شمارشگر را روی ۱ تنظیم میکند؛
- خط ۱۳: رشته ۴ شمارنده را به ۱ افزایش میدهد. از این نقطه به بعد، مقدار شمارنده نادرست است؛
- خطوط ۱۵–۱۶: رشته ۵ متوقف نمیشود و مقدار شمارنده را به درستی مدیریت میکند؛
نکته کلیدی این مثال این است که کد یک برنامه وب نباید مقدار اطلاعاتی را که بین کاربران به اشتراک گذاشته میشود، تغییر دهد.
22.8. اسکریپتهای [flask/07]: مدیریت مسیر

در اینجا ما در حال بررسی مدیریت مسیرهای یک برنامه، یعنی URL ارائهشده توسط برنامه وب هستیم.
22.8.1. اسکریپت [main_01]: مسیرهای پیکربندیشده
اسکریپت [main_01] قابلیت پیکربندی مسیرها را معرفی میکند:
from flask import Flask, make_response
from flask_api import status
# برنامه Flask
app = Flask(__name__)
# ارسال پاسخ
def send_plain_response(réponse: str):
#ارسال پاسخ
response = make_response(réponse)
response.headers['Content-Type'] = 'text/plain; charset=utf-8'
return response, status.HTTP_200_OK
# /نام خانوادگی/نام
@app.route('/<string:nom>/<string:prenom>', methods=['GET'])
def index(nom, prenom):
# پاسخ
return send_plain_response(f"{prenom} {nom}")
# init-session
@app.route('/init-session/<string:type>', methods=['GET'])
def init_session(type: str):
# پاسخ
return send_plain_response(f"/init-session/{type}")
# احراز-هویت-کاربر
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur():
# پاسخ
return send_plain_response("/authentifier-utilisateur")
# محاسبه-مالیات
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot():
# پاسخ
return send_plain_response("/calculer-impot")
# فهرست-شبیهسازیها
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations():
# پاسخ
return send_plain_response("/lister-simulations")
# حذف-شبیهسازی
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int):
# پاسخ
return send_plain_response(f"/supprimer-simulation/{numero}")
#پایان-جلسه
@app.route('/fin-session', methods=['GET'])
def fin_session():
# پاسخ
return send_plain_response(f"/fin-session")
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- خط ۱۷: ما نوع پارامترهای URL را مشخص میکنیم. این کار به Flask اجازه میدهد تا بررسیها را انجام دهد. اگر پارامتر از نوع مورد انتظار نباشد، درخواست کلاینت رد خواهد شد (خطای 400 Bad Request). بنابراین Flask بخشی از کاری را که در غیر این صورت مجبور به انجام آن بودیم، انجام میدهد؛
- خط ۱۸: برای پارامترها، باید از نامهای دقیق پارامترها از خط ۱۷ استفاده کنیم، اما لزوماً به همان ترتیب نه؛
- خط ۲۰: ما از تابع [send_plain_response] برای ارسال پاسخ به کلاینت وب استفاده میکنیم؛
- خط ۹: تابع [send_plain_response] رشتهٔ ارسالشده به کلاینت را دریافت میکند؛
- خط ۱۱: بدنه پاسخ HTTP ساخته میشود؛
- خط ۱۲: به کلاینت اطلاع داده میشود که متن ساده ارسال میشود؛
- خط ۱۳: پاسخ HTTP ارسال میشود؛
- خطوط ۲۳–۶۲: مسیرهای پیکربندیشده دیگر که بعداً در یک تمرین عملی استفاده خواهند شد؛
اسکریپت را اجرا کرده و با استفاده از کلاینت Postman آن را فراخوانی میکنیم:

22.8.2. اسکریپت [main_02]: خارجیسازی مسیرها
در اسکریپت قبلی [main_01]، اگر مسیرهای زیادی وجود داشته باشد، کد میتواند بسیار طولانی شود. اسکریپت [main_02] نشان میدهد چگونه مسیرها را به صورت خارجی درآوریم.

اسکریپت [routes_02] توابع مربوط به جادهها را از اسکریپت قبلی گردآوری میکند:
from flask import make_response
from flask_api import status
def send_response(réponse: str):
#ارسال پاسخ
response = make_response(réponse)
response.headers['Content-Type'] = 'text/plain; charset=utf-8'
return response, status.HTTP_200_OK
# خانه URL
def index(nom, prenom):
# پاسخ
return send_response(f"{prenom} {nom}")
# init-session
def init_session(type: str):
# پاسخ
return send_response(f"/init-session/{type}")
# احراز هویت کاربر
def authentifier_utilisateur():
# پاسخ
return send_response("/authentifier-utilisateur")
# محاسبه-مالیات
def calculer_impot():
#پاسخ
return send_response("/calculer-impot")
# فهرست شبیهسازیها
def lister_simulations():
# پاسخ
return send_response("/lister-simulations")
# حذف-شبیهسازی
def supprimer_simulation(numero: int):
# پاسخ
return send_response(f"/supprimer-simulation/{numero}")
#پایان-جلسه
def fin_session():
# پاسخ
return send_response(f"/fin-session")
توجه داشته باشید که اسکریپت [routes_02] یک اسکریپت مسیر نیست. این یک لیست از توابع است. این اسکریپت اصلی، [main_02]، است که مسیرها را به توابع پیوند میدهد:
from flask import Flask
# ما توابع مسیر را به اسکریپت اختصاصی خود منتقل میکنیم
import routes_02
# برنامه Flask
app = Flask(__name__)
#نگاشت مسیر/تابع
app.add_url_rule('/<string:nom>/<string:prenom>', methods=['GET'], view_func=routes_02.index)
app.add_url_rule('/init-session/<string:type>', methods=['GET'], view_func=routes_02.init_session)
app.add_url_rule('/authentifier-utilisateur', methods=['POST'], view_func=routes_02.authentifier_utilisateur)
app.add_url_rule('/calculer-impot', methods=['POST'], view_func=routes_02.calculer_impot)
app.add_url_rule('/lister-simulations', methods=['GET'], view_func=routes_02.lister_simulations)
app.add_url_rule('/supprimer-simulation/<int:numero>', methods=['GET'], view_func=routes_02.supprimer_simulation)
app.add_url_rule('/fin-session', methods=['GET'], view_func=routes_02.fin_session)
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- خط ۴: اسکریپت حاوی توابع مرتبط با مسیرها وارد میشود؛
- خطوط ۹–۱۶: نگاشت مسیر/تابع؛
با این روش، هر تابع مرتبط با یک مسیر در صورت لزوم میتواند موضوع یک اسکریپت جداگانه باشد.
نتایج با آنچه با اسکریپت قبلی، [main_01]، به دست آمده یکسان است.