Tornado — Python veb-freymvorki va asinxron tarmoq kutubxonasini chuqur o‘rganing. Kengaytiriladigan, yuqori unumli ilovalar yaratishni o‘rganing.
Tornado Hujjatlari: Dunyo bo'ylab dasturchilar uchun keng qamrovli qo'llanma
Tornado — bu dastlab FriendFeed'da ishlab chiqilgan Python veb-freymvorki va asinxron tarmoq kutubxonasi. U ayniqsa uzoq so'rovlar (long-polling), WebSockets va har bir foydalanuvchi bilan uzoq muddatli aloqani talab qiladigan boshqa ilovalar uchun juda mos keladi. Uning bloklanmaydigan tarmoq I/O (kiritish/chiqarish) xususiyati uni juda kengaytiriluvchan va yuqori unumdorlikdagi veb-ilovalarni yaratish uchun kuchli tanlovga aylantiradi. Ushbu keng qamrovli qo'llanma sizni Tornado'ning asosiy tushunchalari bilan tanishtiradi va boshlashingiz uchun amaliy misollar taqdim etadi.
Tornado nima?
Asosan, Tornado — bu veb-freymvork va asinxron tarmoq kutubxonasi. An'anaviy sinxron veb-freymvorklardan farqli o'laroq, Tornado yagona oqimli, hodisalar tsikliga asoslangan arxitekturadan foydalanadi. Bu shuni anglatadiki, u har bir ulanish uchun alohida oqim talab qilmasdan ko'plab bir vaqtning o'zida ulanishlarni boshqara oladi, bu esa uni yanada samarali va kengaytiriluvchan qiladi.
Tornado'ning asosiy xususiyatlari:
- Asinxron Tarmoq: Tornado'ning yadrosi asinxron I/O atrofida qurilgan bo'lib, bu unga minglab bir vaqtning o'zida ulanishlarni samarali boshqarish imkonini beradi.
- Veb-freymvork: U so'rovlarni qayta ishlovchilar, marshrutlash, andozalash va autentifikatsiya kabi xususiyatlarni o'z ichiga oladi, bu esa uni to'liq veb-freymvorkka aylantiradi.
- WebSocket qo'llab-quvvatlashi: Tornado WebSockets uchun ajoyib yordam taqdim etadi, bu esa server va mijozlar o'rtasida real vaqtda aloqa o'rnatish imkonini beradi.
- Yengil va Tez: Unumdorlik uchun mo'ljallangan Tornado yengil va samarali bo'lib, qo'shimcha yuklamalarni minimallashtiradi va o'tkazuvchanlikni maksimal darajada oshiradi.
- Foydalanish oson: Murakkab xususiyatlariga qaramay, Tornado aniq va yaxshi hujjatlashtirilgan API bilan o'rganish va foydalanish uchun nisbatan oson.
Tornado muhitini sozlash
Tornado dasturlashiga sho'ng'ishdan oldin, siz o'z muhitingizni sozlashingiz kerak bo'ladi. Mana bosqichma-bosqich qo'llanma:
- Python o'rnatish: Sizda Python 3.6 yoki undan yuqori versiyasi o'rnatilganligiga ishonch hosil qiling. Uni rasmiy Python veb-saytidan (python.org) yuklab olishingiz mumkin.
- Virtual muhit yaratish (Tavsiya etiladi): Loyihangiz uchun izolyatsiya qilingan muhit yaratish uchun
venv
yokivirtualenv
dan foydalaning:python3 -m venv myenv source myenv/bin/activate # Linux/macOS uchun myenv\Scripts\activate # Windows uchun
- Tornado o'rnatish: pip yordamida Tornado'ni o'rnating:
pip install tornado
Sizning birinchi Tornado ilovangiz
Keling, Tornado yordamida oddiy "Hello, World!" ilovasini yaratamiz. app.py
nomli fayl yarating va quyidagi kodni qo'shing:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, World!")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Endi, ilovani terminalingizdan ishga tushiring:
python app.py
Veb-brauzeringizni oching va http://localhost:8888
manziliga o'ting. Siz "Hello, World!" xabarini ko'rishingiz kerak.
Tushuntirish:
tornado.ioloop
: Asinxron operatsiyalarni boshqaradigan asosiy hodisalar tsikli.tornado.web
: Veb-freymvork komponentlarini, masalan, so'rovlarni qayta ishlovchilar va marshrutlashni ta'minlaydi.MainHandler
: Kiruvchi HTTP so'rovlarini qanday boshqarishni belgilaydigan so'rovni qayta ishlovchi.get()
usuli GET so'rovlari uchun chaqiriladi.tornado.web.Application
: URL naqshlarini so'rovlarni qayta ishlovchilarga bog'lab, Tornado ilovasini yaratadi.app.listen(8888)
: Serverni ishga tushirib, 8888 portida kiruvchi ulanishlarni tinglaydi.tornado.ioloop.IOLoop.current().start()
: Kiruvchi so'rovlarni qayta ishlaydigan va asinxron operatsiyalarni boshqaradigan hodisalar tsiklini ishga tushiradi.
So'rovlarni qayta ishlovchilar va marshrutlash
So'rovlarni qayta ishlovchilar Tornado veb-ilovalarining asosidir. Ular URL manziliga qarab kiruvchi HTTP so'rovlarini qanday boshqarishni belgilaydi. Marshrutlash URL manzillarini ma'lum so'rovlarni qayta ishlovchilarga bog'laydi.
So'rovlarni qayta ishlovchilarni aniqlash:
So'rovni qayta ishlovchi yaratish uchun, tornado.web.RequestHandler
dan voris oling va tegishli HTTP usullarini (get
, post
, put
, delete
va hokazo) amalga oshiring.
class MyHandler(tornado.web.RequestHandler):
def get(self):
self.write("Bu GET so'rovi.")
def post(self):
data = self.request.body.decode('utf-8')
self.write(f"POST ma'lumotlari qabul qilindi: {data}")
Marshrutlash:
Marshrutlash tornado.web.Application
yaratilayotganda sozlanadi. Siz kortejlar ro'yxatini taqdim etasiz, bu erda har bir kortej URL naqshini va unga mos keladigan so'rovni qayta ishlovchini o'z ichiga oladi.
app = tornado.web.Application([
(r"/", MainHandler),
(r"/myhandler", MyHandler),
])
URL naqshlari:
URL naqshlari muntazam ifodalardir. Siz URL ning qismlarini ushlash va ularni so'rovni qayta ishlovchi usullariga argument sifatida uzatish uchun muntazam ifoda guruhlaridan foydalanishingiz mumkin.
class UserHandler(tornado.web.RequestHandler):
def get(self, user_id):
self.write(f"Foydalanuvchi IDsi: {user_id}")
app = tornado.web.Application([
(r"/user/([0-9]+)", UserHandler),
])
Ushbu misolda, /user/([0-9]+)
/user/123
kabi URL'larga mos keladi. ([0-9]+)
qismi bir yoki bir nechta raqamni ushlaydi va ularni UserHandler
ning get
usuliga user_id
argumenti sifatida uzatadi.
Andozalash (Templating)
Tornado oddiy va samarali andozalash mexanizmini o'z ichiga oladi. Andozalar taqdimot mantig'ini dastur mantig'idan ajratib, HTMLni dinamik ravishda yaratish uchun ishlatiladi.
Andozalarni yaratish:
Andozalar odatda alohida fayllarda (masalan, index.html
) saqlanadi. Mana oddiy misol:
<!DOCTYPE html>
<html>
<head>
<title>Mening veb-saytim</title>
</head>
<body>
<h1>Xush kelibsiz, {{ name }}!</h1>
<p>Bugun {{ today }}.</p>
</body>
</html>
{{ name }}
va {{ today }}
andoza render qilinganda haqiqiy qiymatlar bilan almashtiriladigan joy egalaridir.
Andozalarni render qilish:
Andozani render qilish uchun so'rovni qayta ishlovchingizda render()
usulidan foydalaning:
class TemplateHandler(tornado.web.RequestHandler):
def get(self):
name = "John Doe"
today = "2023-10-27"
self.render("index.html", name=name, today=today)
Ilova sozlamalarida template_path
sozlamasi to'g'ri sozlanganligiga ishonch hosil qiling. Odatiy bo'lib, Tornado andozalarni dastur faylingiz bilan bir xil katalogdagi templates
nomli papkadan qidiradi.
app = tornado.web.Application([
(r"/template", TemplateHandler),
], template_path="templates")
Andoza sintaksisi:
Tornado andozalari turli xususiyatlarni qo'llab-quvvatlaydi, jumladan:
- O'zgaruvchilar:
{{ variable }}
- Boshqaruv oqimi:
{% if condition %} ... {% else %} ... {% end %}
,{% for item in items %} ... {% end %}
- Funksiyalar:
{{ function(argument) }}
- Kiritmalar (Includes):
{% include "another_template.html" %}
- Ekranlash: Tornado saytlararo skripting (XSS) hujumlarining oldini olish uchun HTML obyektlarini avtomatik ravishda ekranlaydi. Siz
{% raw variable %}
yordamida ekranlashni o'chirib qo'yishingiz mumkin.
Asinxron operatsiyalar
Tornado'ning kuchi uning asinxron imkoniyatlarida yotadi. Asinxron operatsiyalar ilovangizga bloklanmaydigan I/O ni amalga oshirishga imkon beradi, bu esa unumdorlik va kengaytiriluvchanlikni oshiradi. Bu, ayniqsa, ma'lumotlar bazasi so'rovlari yoki tarmoq so'rovlari kabi tashqi resurslarni kutishni o'z ichiga olgan vazifalar uchun foydalidir.
@tornado.gen.coroutine
:
@tornado.gen.coroutine
dekoratori sizga yield
kalit so'zi yordamida asinxron kod yozishga imkon beradi. Bu asinxron kodni sinxron kod kabi ko'rinishiga va ishlashiga olib keladi, bu esa o'qilishi va saqlanishini yaxshilaydi.
import tornado.gen
import tornado.httpclient
class AsyncHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
http_client = tornado.httpclient.AsyncHTTPClient()
response = yield http_client.fetch("http://example.com")
self.write(response.body.decode('utf-8'))
Ushbu misolda http_client.fetch()
Future
obyektini qaytaradigan asinxron operatsiyadir. yield
kalit so'zi Future
hal etilguncha korutin (coroutine) bajarilishini to'xtatib turadi. Future
hal etilgandan so'ng, korutin davom etadi va javob tanasi mijozga yoziladi.
tornado.concurrent.Future
:
Future
hali mavjud bo'lmasligi mumkin bo'lgan asinxron operatsiya natijasini ifodalaydi. Siz Future
obyektlaridan asinxron operatsiyalarni bir-biriga bog'lash va xatoliklarni qayta ishlash uchun foydalanishingiz mumkin.
tornado.ioloop.IOLoop
:
IOLoop
— Tornado'ning asinxron mexanizmining yuragi. U fayl deskriptorlari va soketlarni hodisalar uchun kuzatib boradi va ularni tegishli qayta ishlovchilarga yuboradi. Odatda siz IOLoop
bilan to'g'ridan-to'g'ri ishlashingiz shart emas, lekin uning asinxron operatsiyalarni boshqarishdagi rolini tushunish muhimdir.
WebSockets
Tornado WebSockets uchun ajoyib yordam taqdim etadi, bu esa server va mijozlar o'rtasida real vaqtda aloqa o'rnatish imkonini beradi. WebSockets chat ilovalari, onlayn o'yinlar va real vaqtdagi boshqaruv panellari kabi ikki tomonlama, past kechikishli aloqani talab qiladigan ilovalar uchun idealdir.
WebSocket qayta ishlovchisini yaratish:
WebSocket qayta ishlovchisini yaratish uchun tornado.websocket.WebSocketHandler
dan voris oling va quyidagi usullarni amalga oshiring:
open()
: Yangi WebSocket ulanishi o'rnatilganda chaqiriladi.on_message(message)
: Mijozdan xabar qabul qilinganda chaqiriladi.on_close()
: WebSocket ulanishi yopilganda chaqiriladi.
import tornado.websocket
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
print("WebSocket ochildi")
def on_message(self, message):
self.write_message(f"Siz yubordingiz: {message}")
def on_close(self):
print("WebSocket yopildi")
def check_origin(self, origin):
return True # Turli manbalardan WebSocket ulanishlarini yoqish
WebSockets'ni ilovangizga integratsiya qilish:
WebSocket qayta ishlovchisini ilovangizning marshrutlash konfiguratsiyasiga qo'shing:
app = tornado.web.Application([
(r"/ws", WebSocketHandler),
])
Mijoz tomonidagi amalga oshirish:
Mijoz tomonida siz JavaScript yordamida WebSocket ulanishini o'rnatishingiz va xabarlarni yuborishingiz/qabul qilishingiz mumkin:
const websocket = new WebSocket("ws://localhost:8888/ws");
websocket.onopen = () => {
console.log("WebSocket ulanishi o'rnatildi");
websocket.send("Mijozdan salom!");
};
websocket.onmessage = (event) => {
console.log("Xabar qabul qilindi:", event.data);
};
websocket.onclose = () => {
console.log("WebSocket ulanishi yopildi");
};
Autentifikatsiya va Xavfsizlik
Xavfsizlik veb-ilovalarni ishlab chiqishning muhim jihatidir. Tornado ilovalaringizni himoya qilishga yordam beradigan bir nechta xususiyatlarni taqdim etadi, jumladan autentifikatsiya, avtorizatsiya va umumiy veb zaifliklaridan himoya.
Autentifikatsiya:
Autentifikatsiya — bu foydalanuvchining shaxsini tekshirish jarayoni. Tornado turli xil autentifikatsiya sxemalarini, jumladan, quyidagilarni o'rnatilgan holda qo'llab-quvvatlaydi:
- Cookie-ga asoslangan autentifikatsiya: Foydalanuvchi ma'lumotlarini cookie-fayllarda saqlash.
- Uchinchi tomon autentifikatsiyasi (OAuth): Google, Facebook va Twitter kabi mashhur ijtimoiy media platformalari bilan integratsiya qilish.
- API kalitlari: API so'rovlarini autentifikatsiya qilish uchun API kalitlaridan foydalanish.
Avtorizatsiya:
Avtorizatsiya — bu foydalanuvchining ma'lum bir resursga kirish huquqiga ega yoki ega emasligini aniqlash jarayoni. Siz foydalanuvchi rollari yoki ruxsatlariga asoslanib kirishni cheklash uchun so'rovlarni qayta ishlovchilaringizda avtorizatsiya mantig'ini amalga oshirishingiz mumkin.
Xavfsizlik bo'yicha ilg'or tajribalar:
- Saytlararo skripting (XSS) himoyasi: Tornado XSS hujumlarining oldini olish uchun HTML obyektlarini avtomatik ravishda ekranlaydi. Har doim andozalarni render qilish uchun
render()
usulidan foydalaning va so'rovlarni qayta ishlovchilaringizda to'g'ridan-to'g'ri HTML yaratishdan saqlaning. - Saytlararo so'rovlarni qalbakilashtirish (CSRF) himoyasi: CSRF hujumlarining oldini olish uchun ilova sozlamalarida CSRF himoyasini yoqing.
- HTTPS: Server va mijozlar o'rtasidagi aloqani shifrlash uchun har doim HTTPS dan foydalaning.
- Kiritilgan ma'lumotlarni tekshirish: Inyeksiya hujumlari va boshqa zaifliklarning oldini olish uchun barcha foydalanuvchi kiritgan ma'lumotlarni tekshiring.
- Muntazam xavfsizlik auditlari: Potensial zaifliklarni aniqlash va bartaraf etish uchun muntazam xavfsizlik auditlarini o'tkazing.
Joylashtirish (Deployment)
Tornado ilovasini joylashtirish bir necha bosqichlarni o'z ichiga oladi, jumladan veb-serverni sozlash, jarayonlar menejerini o'rnatish va unumdorlikni optimallashtirish.
Veb-server:
Siz Tornado'ni Nginx yoki Apache kabi veb-server orqasida joylashtirishingiz mumkin. Veb-server teskari proksi sifatida ishlaydi va kiruvchi so'rovlarni Tornado ilovasiga yo'naltiradi.
Jarayonlar menejeri:
Supervisor yoki systemd kabi jarayonlar menejeri Tornado jarayonini boshqarish uchun ishlatilishi mumkin, bu esa uning ishdan chiqqan taqdirda avtomatik ravishda qayta ishga tushirilishini ta'minlaydi.
Unumdorlikni optimallashtirish:
- Ishlab chiqarishga tayyor hodisalar tsiklidan foydalaning: Yaxshilangan unumdorlik uchun
uvloop
kabi ishlab chiqarishga tayyor hodisalar tsiklidan foydalaning. - gzip siqishni yoqing: HTTP javoblari hajmini kamaytirish uchun gzip siqishni yoqing.
- Statik fayllarni keshlash: Serverdagi yuklamani kamaytirish uchun statik fayllarni keshlash.
- Unumdorlikni kuzatish: New Relic yoki Prometheus kabi vositalar yordamida ilovangizning unumdorligini kuzatib boring.
Internatsionalizatsiya (i18n) va Lokalizatsiya (l10n)
Global auditoriya uchun ilovalar yaratishda internatsionalizatsiya (i18n) va lokalizatsiyani (l10n) hisobga olish muhim. i18n — bu ilovani muhandislik o'zgarishlarisiz turli tillar va mintaqalarga moslashtirish mumkin bo'lgan tarzda loyihalash jarayoni. l10n — bu mahalliy komponentlarni qo'shish va matnni tarjima qilish orqali internatsionallashtirilgan ilovani ma'lum bir til yoki mintaqa uchun moslashtirish jarayoni.
Tornado va i18n/l10n
Tornado o'zida o'rnatilgan i18n/l10n kutubxonalariga ega emas. Biroq, siz Tornado ilovangizda i18n/l10n ni boshqarish uchun `gettext` kabi standart Python kutubxonalarini yoki Babel kabi murakkabroq freymvorklarni osongina integratsiya qilishingiz mumkin.
`gettext` yordamida misol:
1. **Lokal sozlamalaringizni o'rnating:** Qo'llab-quvvatlamoqchi bo'lgan har bir til uchun papkalar yarating, ular xabarlar kataloglarini (odatda `.mo` fayllari) o'z ichiga oladi.
locales/
en/LC_MESSAGES/messages.mo
fr/LC_MESSAGES/messages.mo
de/LC_MESSAGES/messages.mo
2. **Tarjima qilinadigan satrlarni ajratib oling:** Python kodingizdan tarjima qilinadigan satrlarni `.po` fayliga (Portable Object) ajratib olish uchun `xgettext` kabi vositadan foydalaning. Bu fayl asl satrlarni va tarjimalar uchun joy egalarini o'z ichiga oladi.
xgettext -d messages -o locales/messages.po your_tornado_app.py
3. **Satrlarni tarjima qiling:** Har bir til uchun `.po` fayllaridagi satrlarni tarjima qiling.
4. **Tarjimalarni kompilyatsiya qiling:** `.po` fayllarini `gettext` tomonidan ish vaqtida ishlatiladigan `.mo` fayllariga (Machine Object) kompilyatsiya qiling.
msgfmt locales/fr/LC_MESSAGES/messages.po -o locales/fr/LC_MESSAGES/messages.mo
5. **Tornado ilovangizga integratsiya qiling:**
import gettext
import locale
import os
import tornado.web
class BaseHandler(tornado.web.RequestHandler):
def initialize(self):
try:
locale.setlocale(locale.LC_ALL, self.get_user_locale().code)
except locale.Error:
# Lokal tizim tomonidan qo'llab-quvvatlanmagan holatlarni qayta ishlash
print(f"Lokal {self.get_user_locale().code} qo'llab-quvvatlanmadi")
translation = gettext.translation('messages', 'locales', languages=[self.get_user_locale().code])
translation.install()
self._ = translation.gettext
def get_current_user_locale(self):
# Foydalanuvchi lokalini aniqlash mantig'i (masalan, Accept-Language sarlavhasidan, foydalanuvchi sozlamalaridan va hokazo)
# Bu soddalashtirilgan misol - sizga yanada mustahkamroq yechim kerak bo'ladi
accept_language = self.request.headers.get('Accept-Language', 'en')
return tornado.locale.get(accept_language.split(',')[0].split(';')[0])
class MainHandler(BaseHandler):
def get(self):
self.render("index.html", _=self._)
settings = {
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
}
app = tornado.web.Application([
(r"/", MainHandler),
], **settings)
6. **Andozalaringizni o'zgartiring:** Andozalaringizda tarjima uchun satrlarni belgilash uchun `_()` funksiyasidan (`gettext.gettext` ga bog'langan) foydalaning.
<h1>{{ _("Veb-saytimizga xush kelibsiz!") }}</h1>
<p>{{ _("Bu tarjima qilingan paragraf.") }}</p>
Global auditoriya uchun muhim mulohazalar:
- **Belgilar kodirovkasi:** Har doim keng doiradagi belgilarni qo'llab-quvvatlash uchun UTF-8 kodirovkasidan foydalaning.
- **Sana va vaqtni formatlash:** Lokalga xos sana va vaqt formatlashdan foydalaning. Python'ning `strftime` va `strptime` funksiyalarini lokal sozlamalari bilan ishlatish mumkin.
- **Raqamlarni formatlash:** Lokalga xos raqam formatlashdan foydalaning (masalan, o'nlik ajratgichlar, minglik ajratgichlar). `locale` moduli buning uchun funksiyalarni taqdim etadi.
- **Valyutani formatlash:** Lokalga xos valyuta formatlashdan foydalaning. Murakkabroq valyuta bilan ishlash uchun `Babel` kabi kutubxonadan foydalanishni o'ylab ko'ring.
- **O'ngdan chapga (RTL) yoziladigan tillar:** Arab va ibroniy kabi RTL tillarni qo'llab-quvvatlang. Bu veb-saytingizning tartibini aks ettirishni o'z ichiga olishi mumkin.
- **Tarjima sifati:** Aniq va madaniy jihatdan mos tarjimalarni ta'minlash uchun professional tarjimonlardan foydalaning. Mashina tarjimasi yaxshi boshlanish nuqtasi bo'lishi mumkin, lekin u ko'pincha inson tomonidan ko'rib chiqilishini talab qiladi.
- **Foydalanuvchi lokalini aniqlash:** Foydalanuvchi afzalliklari, brauzer sozlamalari yoki IP manziliga asoslangan mustahkam lokalni aniqlashni amalga oshiring. Foydalanuvchilarga o'zlari afzal ko'rgan tilni qo'lda tanlash imkoniyatini bering.
- **Sinovdan o'tkazish:** Hamma narsa to'g'ri ko'rsatilishini ta'minlash uchun ilovangizni turli lokallar bilan sinchkovlik bilan sinab ko'ring.
Murakkab mavzular
Maxsus xatolik sahifalari:
Xatolik yuz berganda Tornado ko'rsatadigan xatolik sahifalarini sozlashingiz mumkin. Bu sizga yanada qulay foydalanuvchi tajribasini taqdim etish va nosozliklarni tuzatish ma'lumotlarini kiritish imkonini beradi.
Maxsus sozlamalar:
Siz ilova konfiguratsiyasida maxsus sozlamalarni aniqlashingiz va ularga so'rovlarni qayta ishlovchilaringizda kirishingiz mumkin. Bu ma'lumotlar bazasiga ulanish satrlari yoki API kalitlari kabi dasturga xos parametrlarni saqlash uchun foydalidir.
Sinovdan o'tkazish:
Tornado ilovalaringizning to'g'ri va xavfsiz ishlashini ta'minlash uchun ularni sinchkovlik bilan sinab ko'ring. Ilovangizning barcha jihatlarini qamrab olish uchun birlik testlari, integratsiya testlari va uchdan-uchga testlardan foydalaning.
Xulosa
Tornado — bu kengaytiriladigan, yuqori unumdorlikdagi veb-ilovalarni yaratish uchun juda mos keladigan kuchli va ko'p qirrali veb-freymvork. Uning asinxron arxitekturasi, WebSocket'ni qo'llab-quvvatlashi va foydalanish oson bo'lgan API uni dunyo bo'ylab dasturchilar orasida mashhur tanlovga aylantiradi. Ushbu keng qamrovli qo'llanmadagi ko'rsatmalar va misollarga amal qilib, siz o'zingizning Tornado ilovalaringizni yaratishni boshlashingiz va uning ko'plab xususiyatlaridan foydalanishingiz mumkin.
Eng so'nggi ma'lumotlar va ilg'or tajribalar uchun rasmiy Tornado hujjatlariga murojaat qilishni unutmang. Dasturlashda omad!