وبلاگ رسانگار
با ما حرفه ای باشید

سرور مجازی NVMe

پایتون ناهمزمان برای توسعه وب

0 8
زمان لازم برای مطالعه: 13 دقیقه


برنامه نویسی ناهمزمان برای کارهایی که شامل خواندن و نوشتن مکرر فایل ها یا ارسال داده ها از سرور به عقب و جلو می باشد، مناسب است. برنامه‌های ناهمزمان عملیات ورودی/خروجی را به صورت غیرمسدود انجام می‌دهند، به این معنی که می‌توانند وظایف دیگری را در حالی که منتظر بازگشت داده‌ها از مشتری هستند، انجام دهند، نه اینکه منتظر بمانند و منابع و زمان را هدر دهند.

پایتون، مانند بسیاری از زبان های دیگر، به طور پیش فرض از ناهمزمان نبودن رنج می برد. خوشبختانه، تغییرات سریع در دنیای فناوری اطلاعات به ما این امکان را می دهد که کدهای ناهمزمان را حتی با استفاده از زبان هایی بنویسیم که در ابتدا قرار نبوده اند. در طول سال‌ها، تقاضا برای سرعت از توانایی‌های سخت‌افزاری فراتر رفته و شرکت‌ها در سراسر جهان با مانیفست واکنشی به منظور مقابله با این موضوع.

رفتار غیرمسدود کننده برنامه های ناهمزمان می تواند منجر به مزایای عملکرد قابل توجهی در زمینه یک برنامه وب شود و به حل مشکل توسعه برنامه های واکنشی کمک کند.

برخی از ابزارهای قدرتمند برای نوشتن برنامه های ناهمزمان که در پایتون 3 ساخته شده اند. در این مقاله، ما برخی از این ابزارها را پوشش خواهیم داد، به خصوص که مربوط به توسعه وب هستند.

ما یک واکنشگر ساده را توسعه خواهیم داد aiohttp برنامه مبتنی بر نمایش مختصات فعلی آسمان مربوط به سیارات از منظومه شمسی، با توجه به مختصات جغرافیایی کاربر. می توانید برنامه را پیدا کنید اینجاو کد منبع اینجا.

ما در پایان با بحث در مورد چگونگی آماده سازی برنامه برای استقرار به پایان خواهیم رسید هروکو.

مقدمه ای بر پایتون ناهمزمان

برای کسانی که با نوشتن کد سنتی پایتون آشنا هستند، پرش به کد ناهمزمان می‌تواند از نظر مفهومی کمی دشوار باشد. کد ناهمزمان در پایتون متکی است روی روتین ها، که در ارتباط با یک حلقه رویداد امکان نوشتن کدی را فراهم می کند که به نظر می رسد بیش از یک کار را همزمان انجام می دهد.

کوروتین ها را می توان به عنوان توابعی در نظر گرفت که دارای نقاطی در کد هستند که کنترل برنامه را به زمینه فراخوانی برمی گرداند. این نقاط “بازده” علاوه بر تبادل داده ها بین زمینه ها، امکان توقف و از سرگیری اجرای کوروتین را فراهم می کند.

حلقه رویداد تصمیم می‌گیرد که چه تکه‌ای از کد در هر لحظه اجرا شود – این حلقه مسئول مکث، از سرگیری و برقراری ارتباط بین کوروتین‌ها است. این بدان معناست که بخش‌هایی از برنامه‌های مختلف ممکن است به ترتیبی غیر از ترتیبی که در آن برنامه‌ریزی شده بودند اجرا شوند. این ایده اجرای قطعات مختلف کد بدون نظم نامیده می شود همزمانی.

اندیشیدن به همزمانی در زمینه ساختن HTTP درخواست ها می توانند روشن شوند. تصور کنید که می خواهید درخواست های مستقل زیادی به یک سرور بدهید. برای مثال، ممکن است بخواهیم از یک وب سایت پرس و جو کنیم تا آماری در مورد همه بازیکنان ورزشی در یک فصل مشخص به دست آوریم.

ما میتوانست هر درخواست را به ترتیب انجام دهید با این حال، با هر درخواست، می‌توانیم تصور کنیم که کد خروجی ممکن است مدتی را منتظر بمانند تا درخواستی به سرور تحویل داده شود و پاسخ ارسال شود.

گاهی اوقات، این عملیات ممکن است حتی چندین ثانیه طول بکشد. برنامه ممکن است به دلیل تعداد زیاد کاربران یا صرفاً به دلیل محدودیت سرعت سرور داده شده، تاخیر شبکه را تجربه کند.

چه می‌شود اگر کد ما بتواند کارهای دیگری را در حین انتظار برای پاسخ از سرور انجام دهد؟ علاوه بر این، اگر تنها پس از رسیدن اطلاعات پاسخ به پردازش یک درخواست معین بازگردد، چه؟ اگر مجبور نباشیم منتظر بمانیم تا هر یک از درخواست‌ها قبل از اینکه به درخواست بعدی در لیست برویم، می‌توانیم درخواست‌های زیادی را پشت سر هم انجام دهیم.

کوروتین‌ها با یک حلقه رویداد به ما امکان می‌دهند کدی بنویسیم که دقیقاً به این شکل عمل می‌کند.

asyncio

asyncio، بخشی از کتابخانه استاندارد پایتون، یک حلقه رویداد و مجموعه ای از ابزارها را برای کنترل آن فراهم می کند. با asyncio ما می توانیم کوروتین ها را برای اجرا برنامه ریزی کنیم، و کوروتین های جدیدی ایجاد کنیم (واقعا asyncio.Task اشیاء، با استفاده از اصطلاح asyncio) که تنها زمانی اجرا را به پایان می‌رساند که اجرای کوروتین‌های سازنده به پایان برسد.

برخلاف سایر زبان‌های برنامه‌نویسی ناهمزمان، پایتون ما را مجبور نمی‌کند از حلقه رویدادی که با زبان ارسال می‌شود استفاده کنیم. مانند برت کانن اشاره می کند، کوروتین های پایتون یک API ناهمزمان را تشکیل می دهند که با آن می توانیم از هر حلقه رویداد استفاده کنیم. پروژه هایی وجود دارند که یک حلقه رویداد کاملاً متفاوت را پیاده سازی می کنند کنجکاوی، یا اجازه حذف در یک سیاست حلقه رویداد دیگر را برای asyncio (سیاست حلقه رویداد چیزی است که حلقه رویداد را “پشت صحنه” مدیریت می کند)، مانند uvloop.

بیایید به یک قطعه کد نگاهی بیندازیم که دو برنامه را به طور همزمان اجرا می کند و هر کدام یک پیام را پس از یک ثانیه چاپ می کند:


import asyncio

async def wait_around(n, name):
    for i in range(n):
        print(f"{name}: iteration {i}")
        await asyncio.sleep(1.0)

async def main():
    await asyncio.gather(*(
        wait_around(2, "coroutine 0"), wait_around(5, "coroutine 1")
    ))

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
me@local:~$ time python example1.py
coroutine 1: iteration 0
coroutine 0: iteration 0
coroutine 1: iteration 1
coroutine 0: iteration 1
coroutine 1: iteration 2
coroutine 1: iteration 3
coroutine 1: iteration 4

real    0m5.138s
user    0m0.111s
sys     0m0.019s

این کد تقریباً در 5 ثانیه اجرا می شود asyncio.sleep coroutine نقاطی را ایجاد می کند که در آن حلقه رویداد می تواند به اجرای کدهای دیگر بپرد. علاوه بر این، به حلقه رویداد گفته ایم که هر دو را برنامه ریزی کند wait_around نمونه هایی برای اجرای همزمان با asyncio.gather تابع.

asyncio.gather فهرستی از “موارد قابل انتظار” (به عنوان مثال، روال‌ها، یا asyncio.Task اشیاء) و یک واحد را برمی گرداند asyncio.Task شی ای که فقط زمانی تمام می شود که تمام وظایف/کوروتین های تشکیل دهنده آن به پایان برسد. دو خط آخر هستند asyncio دیگ بخار برای اجرای یک کوروتین معین تا پایان اجرای آن.

کوروتین ها، بر خلاف توابع، بلافاصله پس از فراخوانی شروع به اجرا نمی کنند. را await کلمه کلیدی چیزی است که به حلقه رویداد می‌گوید تا یک کوروتین را برای اجرا برنامه‌ریزی کند.

اگر ما را بیرون بیاوریم await در مقابل asyncio.sleep، برنامه بلافاصله (تقریبا) به پایان می رسد، زیرا ما به حلقه رویداد نگفته ایم که در واقع کوروتین را اجرا کند، که در این مورد به کوروتین می گوید که برای مدت زمان مشخصی مکث کند.

با درک اینکه کدهای ناهمزمان پایتون چگونه به نظر می رسد، بیایید حرکت کنیم روی به توسعه وب ناهمزمان

نصب aiohttp

aiohttp یک کتابخانه پایتون برای ایجاد ناهمزمان است HTTP درخواست ها. علاوه بر این، چارچوبی برای کنار هم قرار دادن بخش سرور یک برنامه وب فراهم می کند. استفاده از پایتون 3.5+ و pip، می توانیم نصب کنیم aiohttp:

pip install --user aiohttp

سمت مشتری: ایجاد درخواست

مثال‌های زیر نشان می‌دهند که چگونه می‌توانیم محتوای HTML وبسایت “example.com” را با استفاده از آن دانلود کنیم aiohttp:


import asyncio
import aiohttp

async def make_request():
    url = "https://example.com"
    print(f"making request to {url}")
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            if resp.status == 200:
                print(await resp.text())

loop = asyncio.get_event_loop()
loop.run_until_complete(make_request())

چند نکته قابل تاکید:

  • خیلی شبیه با await asyncio.sleep ما باید استفاده کنید await با resp.text() به منظور دریافت محتوای HTML از page. اگر آن را کنار بگذاریم، خروجی برنامه ما چیزی شبیه به زیر خواهد بود:
me@local:~$ python example2_basic_aiohttp_request.py
<coroutine object ClientResponse.text at 0x7fe64e574ba0>
  • async with یک مدیر زمینه است که به جای توابع با کوروتین ها کار می کند. در هر دو موردی که از آن استفاده می شود، می توانیم تصور کنیم که در داخل، aiohttp بستن اتصالات به سرورها یا آزاد کردن منابع است.

  • aiohttp.ClientSession روش هایی دارد که مطابقت دارند HTTP افعال در همان
    راه آن session.get در حال ساختن یک گرفتن درخواست، session.post یک را می سازد پست درخواست.

پیشنهاد می‌کنیم بخوانید:  صفحه بندی در جاوا اسکریپت Vanilla هنگام ایجاد یک وب سایت یا برنامه وب، به خصوص اگر دارای محتوای قالب زیادی باشد (مانند شبکه یا لیستی از موارد متعلق به یک دسته) - به طور کلی ایده خوبی است که آن را به صفحات تقسیم کنید تا تعداد آنها کاهش یابد. مواردی که ظاهر می شوند روی صفحه نمایش...

این مثال به خودی خود هیچ مزیت عملکردی نسبت به درخواست HTTP همزمان ندارد. زیبایی واقعی سمت مشتری aiohttp در ایجاد چندین درخواست همزمان نهفته است:


import asyncio
import aiohttp

async def make_request(session, req_n):
    url = "https://example.com"
    print(f"making request {req_n} to {url}")
    async with session.get(url) as resp:
        if resp.status == 200:
            await resp.text()

async def main():
    n_requests = 100
    async with aiohttp.ClientSession() as session:
        await asyncio.gather(
            *(make_request(session, i) for i in range(n_requests))
        )

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

به جای اینکه هر درخواست را به صورت متوالی انجام دهیم، درخواست می کنیم asyncio برای انجام آنها به طور همزمان، با asycio.gather.

برنامه وب PlanetTracker

در طول این بخش، من قصد دارم نشان دهم که چگونه می توان یک برنامه را جمع آوری کرد که مختصات فعلی سیارات در آسمان را در مکان کاربر (ephemerides) گزارش می کند.

کاربر موقعیت مکانی خود را با وب ارائه می کند API مکان جغرافیایی، که کار را برای ما انجام می دهد.

من در پایان با نشان دادن روش تنظیم a نمایه به منظور استقرار برنامه روی هروکو. اگر برنامه ریزی می کنید روی همانطور که من در کنار هم قرار دادن برنامه کار می کنم، باید موارد زیر را انجام دهید، با فرض اینکه پایتون 3.6 و pip نصب شده است:

me@local:~$ mkdir planettracker && cd planettracker
me@local:~/planettracker$ pip install --user pipenv
me@local:~/planettracker$ pipenv --python=3

Planet Ephemerides با PyEphem

زودگذر یک شی نجومی موقعیت فعلی آن در آسمان در یک مکان و زمان معین است روی زمین. PyEphem یک کتابخانه پایتون است که امکان محاسبه دقیق ephemerides را فراهم می کند.

این به ویژه برای کار در دست مناسب است، زیرا دارای اشیاء نجومی معمولی است که در کتابخانه پخته می شوند. ابتدا بیایید نصب کنیم PyEphem:

me@local:~/planettracker$ pipenv install ephem

بدست آوردن مختصات فعلی مریخ به سادگی استفاده از یک نمونه از مریخ است Observer کلاس به compute مختصات آن:

import ephem
import math
convert = math.pi / 180.
mars = ephem.Mars()
greenwich = ephem.Observer()
greenwich.lat = "51.4769"
greenwich.lon = "-0.0005"
mars.compute(observer)
az_deg, alt_deg = mars.az*convert, mars.alt*convert
print(f"Mars' current azimuth and elevation: {az_deg:.2f} {alt_deg:.2f}")

به منظور آسان‌تر کردن اپیمریدهای سیاره‌ای، بیایید یک کلاس راه‌اندازی کنیم PlanetTracker با روشی که ارتفاع و ارتفاع فعلی یک سیاره را بر حسب درجه برمی گرداند (PyEphem به طور پیش‌فرض از رادیان استفاده می‌کند، نه درجه، برای نشان دادن زوایای داخلی):


import math
import ephem

class PlanetTracker(ephem.Observer):

    def __init__(self):
        super(PlanetTracker, self).__init__()
        self.planets = {
            "mercury": ephem.Mercury(),
            "venus": ephem.Venus(),
            "mars": ephem.Mars(),
            "jupiter": ephem.Jupiter(),
            "saturn": ephem.Saturn(),
            "uranus": ephem.Uranus(),
            "neptune": ephem.Neptune()
        }

    def calc_planet(self, planet_name, when=None):
        convert = 180./math.pi
        if when is None:
            when = ephem.now()

        self.date = when
        if planet_name in self.planets:
            planet = self.planets(planet_name)
            planet.compute(self)
            return {
                "az": float(planet.az)*convert,
                "alt": float(planet.alt)*convert,
                "name": planet_name
            }
        else:
            raise KeyError(f"Couldn't find {planet_name} in planets dict")

اکنون می توانیم هر یک از هفت سیاره دیگر در منظومه شمسی را به راحتی بدست آوریم:

from planet_tracker import PlanetTracker
tracker = PlanetTracker()
tracker.lat = "51.4769"
tracker.lon = "-0.0005"
tracker.calc_planet("mars")

اجرای این قطعه کد به دست می آید:

{'az': 92.90019644871396, 'alt': -23.146670983905302, 'name': 'mars'}

سمت سرور aiohttp: مسیرهای HTTP

با توجه به طول و عرض جغرافیایی، ما به راحتی می توانیم زودگذر فعلی یک سیاره را بر حسب درجه بدست آوریم. حالا بیایید یک را راه اندازی کنیم aiohttp مسیری که به مشتری اجازه می دهد تا با توجه به موقعیت جغرافیایی کاربر، گذر زمان سیاره را بدست آورد.

قبل از اینکه بتوانیم کدنویسی را شروع کنیم، باید در مورد چه چیزی فکر کنیم HTTP افعالی که می خواهیم با هر یک از این وظایف مرتبط کنیم. استفاده منطقی است پست برای اولین کار، همانطور که ما هستیم تنظیمات مختصات جغرافیایی ناظر با توجه به اینکه ما هستیم گرفتن ephemerides، استفاده از آن منطقی است گرفتن برای کار دوم:


from aiohttp import web

from planet_tracker import PlanetTracker


@routes.get("/planets/{name}")
async def get_planet_ephmeris(request):
    planet_name = request.match_info('name')
    data = request.query
    try:
        geo_location_data = {
            "lon": str(data("lon")),
            "lat": str(data("lat")),
            "elevation": float(data("elevation"))
        }
    except KeyError as err:
        
        geo_location_data = {
            "lon": "-0.0005",
            "lat": "51.4769",
            "elevation": 0.0,
        }
    print(f"get_planet_ephmeris: {planet_name}, {geo_location_data}")
    tracker = PlanetTracker()
    tracker.lon = geo_location_data("lon")
    tracker.lat = geo_location_data("lat")
    tracker.elevation = geo_location_data("elevation")
    planet_data = tracker.calc_planet(planet_name)
    return web.json_response(planet_data)


app = web.Application()
app.add_routes(routes)

web.run_app(app, host="localhost", port=8000)

اینجا route.get دکوراتور نشان می دهد که ما می خواهیم get_planet_ephmeris coroutine به عنوان کنترل کننده یک متغیر GET مسیر

قبل از اجرای این، بیایید نصب کنیم aiohttp با pipenv:

me@local:~/planettracker$ pipenv install aiohttp

اکنون می توانیم برنامه خود را اجرا کنیم:

me@local:~/planettracker$ pipenv run python aiohttp_app.py

وقتی این را اجرا می کنیم، می توانیم مرورگر خود را به مسیرهای مختلف خود هدایت کنیم تا داده هایی را که سرور ما برمی گرداند ببینیم. اگر بگذارم localhost:8000/planets/mars در نوار آدرس مرورگرم، باید پاسخی مانند زیر ببینم:

{"az": 98.72414165963292, "alt": -18.720718647020792, "name": "mars"}

این همان صدور زیر است حلقه دستور:

me@local:~$ curl localhost:8000/planets/mars
{"az": 98.72414165963292, "alt": -18.720718647020792, "name": "mars"}

اگر آشنایی ندارید حلقه، راحت است command-line ابزاری برای آزمایش مسیرهای HTTP شما از جمله موارد دیگر.

ما می توانیم عرضه کنیم گرفتن URL به حلقه:

me@local:~$ curl localhost:8000/planets/mars
{"az": 98.72414165963292, "alt": -18.720718647020792, "name": "mars"}

این به ما گذر زمان مریخ را در رصدخانه گرینویچ در بریتانیا می دهد.

ما می توانیم مختصات را در آدرس URL کدگذاری کنیم GET درخواست کنید تا بتوانیم گذر زمان مریخ را در مکان های دیگر دریافت کنیم (به نقل قول های اطراف URL توجه کنید):

me@local:~$ curl "localhost:8000/planets/mars؟lon=145.051&lat=-39.754&elevation=0"
{"az": 102.30273048280189, "alt": 11.690380174890928, "name": "mars"

curl همچنین می تواند برای ایجاد درخواست های POST نیز استفاده شود:

me@local:~$ curl --header "Content-Type: application/x-www-form-urlencoded" --data "lat=48.93&lon=2.45&elevation=0" localhost:8000/geo_location
{"lon": "2.45", "lat": "48.93", "elevation": 0.0}

توجه داشته باشید که با ارائه --data رشته، curl به طور خودکار فرض می کند که ما در حال انجام یک درخواست POST هستیم.

قبل از اینکه حرکت کنیم روی، باید توجه داشته باشم که web.run_app تابع برنامه ما را به صورت مسدود کننده اجرا می کند. قطعاً این چیزی نیست که ما به دنبال آن هستیم!

برای اجرای همزمان، باید کمی کد اضافه کنیم:


import asyncio
...



async def start_app():
    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(
        runner, parsed.host, parsed.port)
    await site.start()
    print(f"Serving up app روی {parsed.host}:{parsed.port}")
    return runner, site

loop = asyncio.get_event_loop()
runner, site = loop.run_until_complete(start_async_app())
try:
    loop.run_forever()
except KeyboardInterrupt as err:
    loop.run_until_complete(runner.cleanup())

به حضور توجه کنید loop.run_forever به جای تماس به loop.run_until_complete که قبلا دیدیم به‌جای اجرای مجموعه‌ای از کوروتین‌ها، می‌خواهیم برنامه ما سروری راه‌اندازی کند که درخواست‌ها را تا زمانی که با ctrl+c، در این مرحله سرور را به آرامی خاموش می کند.

کلاینت HTML/JavaScript

aiohttp به ما اجازه می دهد تا فایل های HTML و جاوا اسکریپت را ارائه دهیم. استفاده کردن aiohttp برای ارائه دارایی های “ایستا” مانند CSS و جاوا اسکریپت توصیه نمی شود، اما برای اهداف این برنامه، نباید مشکلی ایجاد کند.

بیایید چند خط به خط خود اضافه کنیم aiohttp_app.py فایل برای ارائه یک فایل HTML که به یک فایل جاوا اسکریپت ارجاع می دهد:


...
@routes.get('/')
async def hello(request):
    return web.FileResponse("./index.html")


app = web.Application()
app.add_routes(routes)
app.router.add_static("/", "./")
...

را hello coroutine راه اندازی یک مسیر GET در localhost:8000/ که در خدمت مطالب index.html، در همان دایرکتوری قرار دارد که سرور خود را از آن اجرا می کنیم.

را app.router.add_static خط در حال راه اندازی یک مسیر در localhost:8000/ برای ارائه فایل‌ها در همان دایرکتوری که سرور خود را از آن اجرا می‌کنیم. این بدان معنی است که مرورگر ما قادر خواهد بود فایل جاوا اسکریپت مورد نظر ما را پیدا کند index.html.

توجه داشته باشید: در تولید، انتقال فایل‌های HTML، CSS و JS به یک دایرکتوری جداگانه که سرویس می‌شود منطقی است. روی خودش این باعث می شود که کاربر کنجکاو نتواند به کد سرور ما دسترسی پیدا کند.

پیشنهاد می‌کنیم بخوانید:  زمان استفاده از Shebangs در اسکریپت پایتون

فایل HTML بسیار ساده است:

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Planet Tracker</title>
</head>
<body>
    <div id="app">
        <label id="lon">Longitude: <input type="text"/></label><br/>
        <label id="lat">Latitude: <input type="text"/></label><br/>
        <label id="elevation">Elevation: <input type="text"/></label><br/>
    </div>
    <script src="/app.js"></script>
</body>

اگرچه، فایل جاوا اسکریپت کمی بیشتر درگیر است:

var App = function() {

    this.planetNames = (
        "mercury",
        "venus",
        "mars",
        "jupiter",
        "saturn",
        "uranus",
        "neptune"
    )

    this.geoLocationIds = (
        "lon",
        "lat",
        "elevation"
    )

    this.keyUpInterval = 500
    this.keyUpTimer = null
    this.planetDisplayCreated = false
    this.updateInterval = 2000 
    this.updateTimer = null
    this.geoLocation = null

    this.init = function() {
        this.getGeoLocation().then((position) => {
            var coords = this.processCoordinates(position)
            this.geoLocation = coords
            this.initGeoLocationDisplay()
            this.updateGeoLocationDisplay()
            return this.getPlanetEphemerides()
        }).then((planetData) => {
            this.createPlanetDisplay()
            this.updatePlanetDisplay(planetData)
        }).then(() => {
            return this.initUpdateTimer()
        })
    }

    this.update = function() {
        if (this.planetDisplayCreated) {
            this.getPlanetEphemerides().then((planetData) => {
                this.updatePlanetDisplay(planetData)
            })
        }
    }

    this.get = function(url, data) {
        var request = new XMLHttpRequest()
        if (data !== undefined) {
            url += `?${data}`
        }
        
        request.open("GET", url, true)
        return new Promise((resolve, reject) => {
            request.send()
            request.onreadystatechange = function(){
                if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
                    resolve(this)
                }
            }
            request.onerror = reject
        })
    }

    this.processCoordinates = function(position) {
        var coordMap = {
            'longitude': 'lon',
            'latitude': 'lat',
            'altitude': 'elevation'
        }
        var coords = Object.keys(coordMap).reduce((obj, name) => {
            var coord = position.coords(name)
            if (coord === null || isNaN(coord)) {
                coord = 0.0
            }
            obj(coordMap(name)) = coord
            return obj
        }, {})
        return coords
    }

    this.coordDataUrl = function (coords) {
        postUrl = Object.keys(coords).map((c) => {
            return `${c}=${coords(c)}`
        })
        return postUrl
    }

    this.getGeoLocation = function() {
        return new Promise((resolve, reject) => {
            navigator.geolocation.getCurrentPosition(resolve)
        })
    }

    this.getPlanetEphemeris = function(planetName) {
        var postUrlArr = this.coordDataUrl(this.geoLocation)
        return this.get(`/planets/${planetName}`, postUrlArr.join("&")).then((req) => {
            return JSON.parse(req.response)
        })
    }

    this.getPlanetEphemerides = function() {
        return Promise.all(
            this.planetNames.map((name) => {
                return this.getPlanetEphemeris(name)
            })
        )
    }

    this.createPlanetDisplay = function() {
        var div = document.getElementById("app")
        var table = document.createElement("table")
        var header = document.createElement("tr")
        var headerNames = ("Name", "Azimuth", "Altitude")
        headerNames.forEach((headerName) => {
            var headerElement = document.createElement("th")
            headerElement.textContent = headerName
            header.appendChild(headerElement)
        })
        table.appendChild(header)
        this.planetNames.forEach((name) => {
            var planetRow = document.createElement("tr")
            headerNames.forEach((headerName) => {
                planetRow.appendChild(
                    document.createElement("td")
                )
            })
            planetRow.setAttribute("id", name)
            table.appendChild(planetRow)
        })
        div.appendChild(table)
        this.planetDisplayCreated = true
    }

    this.updatePlanetDisplay = function(planetData) {
        planetData.forEach((d) => {
            var content = (d.name, d.az, d.alt)
            var planetRow = document.getElementById(d.name)
            planetRow.childNodes.forEach((node, idx) => {
                var contentFloat = parseFloat(content(idx))
                if (isNaN(contentFloat)) {
                    node.textContent = content(idx)
                } else {
                    node.textContent = contentFloat.toFixed(2)
                }
            })
        })
    }

    this.initGeoLocationDisplay = function() {
        this.geoLocationIds.forEach((id) => {
            var node = document.getElementById(id)
            node.childNodes(1).onkeyup = this.onGeoLocationKeyUp()
        })
        var appNode = document.getElementById("app")
        var resetLocationButton = document.createElement("button")
        resetLocationButton.setAttribute("id", "reset-location")
        resetLocationButton.onclick = this.onResetLocationClick()
        resetLocationButton.textContent = "Reset Geo Location"
        appNode.appendChild(resetLocationButton)
    }

    this.updateGeoLocationDisplay = function() {
        Object.keys(this.geoLocation).forEach((id) => {
            var node = document.getElementById(id)
            node.childNodes(1).value = parseFloat(
                this.geoLocation(id)
            ).toFixed(2)
        })
    }

    this.getDisplayedGeoLocation = function() {
        var displayedGeoLocation = this.geoLocationIds.reduce((val, id) => {
            var node = document.getElementById(id)
            var nodeVal = parseFloat(node.childNodes(1).value)
            val(id) = nodeVal
            if (isNaN(nodeVal)) {
                val.valid = false
            }
            return val
        }, {valid: true})
        return displayedGeoLocation
    }

    this.onGeoLocationKeyUp = function() {
        return (evt) => {
            
            var currentTime = new Date()
            if (this.keyUpTimer !== null){
                clearTimeout(this.keyUpTimer)
            }
            this.keyUpTimer = setTimeout(() => {
                var displayedGeoLocation = this.getDisplayedGeoLocation()
                if (displayedGeoLocation.valid) {
                    delete displayedGeoLocation.valid
                    this.geoLocation = displayedGeoLocation
                    console.log("Using user supplied geo location")
                }
            }, this.keyUpInterval)
        }
    }

    this.onResetLocationClick = function() {
        return (evt) => {
            console.log("Geo location reset clicked")
            this.getGeoLocation().then((coords) => {
                this.geoLocation = this.processCoordinates(coords)
                this.updateGeoLocationDisplay()
            })
        }
    }

    this.initUpdateTimer = function () {
        if (this.updateTimer !== null) {
            clearInterval(this.updateTimer)
        }
        this.updateTimer = setInterval(
            this.update.bind(this),
            this.updateInterval
        )
        return this.updateTimer
    }

    this.testPerformance = function(n) {
        var t0 = performance.now()
        var promises = ()
        for (var i=0; i<n; i++) {
            promises.push(this.getPlanetEphemeris("mars"))
        }
        Promise.all(promises).then(() => {
            var delta = (performance.now() - t0)/1000
            console.log(`Took ${delta.toFixed(4)} seconds to do ${n} requests`)
        })
    }
}

var app
document.addEventListener("DOMContentLoaded", (evt) => {
    app = new App()
    app.init()
})

این برنامه به طور دوره ای (هر 2 ثانیه) به روز رسانی و نمایش سیاره های گذرا. ما می‌توانیم مختصات جغرافیایی خود را ارائه دهیم یا به Web Geolocation API اجازه دهیم موقعیت فعلی ما را تعیین کند. اگر کاربر تایپ کردن را برای نیم ثانیه یا بیشتر متوقف کند، برنامه موقعیت جغرافیایی را به روز می کند.

در حالی که این یک آموزش جاوا اسکریپت نیست، فکر می‌کنم برای درک کارهایی که بخش‌های مختلف اسکریپت انجام می‌دهند مفید باشد:

  • createPlanetDisplay به صورت پویا عناصر HTML را ایجاد می کند و آنها را به Document Object Model (DOM) متصل می کند.
  • updatePlanetDisplay داده های دریافتی از سرور را می گیرد و عناصر ایجاد شده توسط را پر می کند createPlanetDisplay
  • get یک درخواست GET به سرور می دهد. را XMLHttpRequest شی اجازه می دهد تا این کار بدون بارگیری مجدد انجام شود page.
  • post یک درخواست POST به سرور می دهد. مانند با get این کار بدون بارگیری مجدد انجام می شود page.
  • getGeoLocation استفاده می کند Web Geolocation API برای دریافت مختصات جغرافیایی فعلی کاربر. این باید “در یک زمینه امن” انجام شود (یعنی باید از آن استفاده کنیم HTTPS نه HTTP).
  • getPlanetEphemeris و getPlanetEphemerides برای دریافت ephemeris برای یک سیاره خاص و برای دریافت ephemerides برای همه سیارات به ترتیب درخواست های GET را به سرور ارائه دهید.
  • testPerformance باعث می شود n به سرور درخواست می کند و تعیین می کند که چقدر طول می کشد.

آغازگر روی در حال اعزام به هروکو

هروکو سرویسی برای استقرار آسان برنامه های وب است. Heroku از پیکربندی مولفه‌های وب یک برنامه مانند پیکربندی پراکسی‌های معکوس یا نگرانی در مورد تعادل بار مراقبت می‌کند. برای برنامه‌هایی که درخواست‌های کمی را مدیریت می‌کنند و تعداد کمی از کاربران، Heroku یک سرویس میزبانی رایگان عالی است.

استقرار برنامه های پایتون در Heroku در سال های اخیر بسیار آسان شده است. در هسته آن، ما باید دو فایل ایجاد کنیم که وابستگی های برنامه ما را فهرست کرده و به Heroku بگوییم که چگونه برنامه ما را اجرا کند.

آ پیپ فایل از اولی مراقبت می کند، در حالی که الف نمایه از دومی مراقبت می کند. یک Pipfile با استفاده از آن نگهداری می شود pipenv – هر بار که یک وابستگی نصب می کنیم به Pipfile خود (و Pipfile.lock) اضافه می کنیم.

به منظور اجرای برنامه ما روی هروکو، ما باید یک وابستگی دیگر اضافه کنیم:

me@local:~/planettracker$ pipenv install gunicorn

ما می توانیم پروفیل خود را ایجاد کنیم و خط زیر را به آن اضافه کنیم:

web: gunicorn aiohttp_app:app --worker-class aiohttp.GunicornWebWorker

اساساً این به هروکو می‌گوید که از آن استفاده کند گونیکورن برای اجرای برنامه ما، با استفاده از ویژه aiohttp کارگر وب

قبل از اینکه بتوانید در Heroku مستقر شوید، باید برنامه را با Git ردیابی کنید:

me@local:~/planettracker$ git init
me@local:~/planettracker$ git add .
me@local:~/planettracker$ git commit -m "first commit"

اکنون می توانید دستورالعمل ها را دنبال کنید روی مرکز توسعه هروکو اینجا برای استقرار برنامه شما توجه داشته باشید که می‌توانید مرحله «آماده کردن برنامه» این آموزش را نادیده بگیرید، زیرا از قبل یک برنامه git tracked دارید.

هنگامی که برنامه شما مستقر شد، می توانید به URL انتخابی Heroku در مرورگر خود بروید و برنامه را مشاهده کنید، که چیزی شبیه به این خواهد بود:

پایتون ناهمزمان برای توسعه وب

نتیجه

در این مقاله، به این موضوع پرداختیم که توسعه وب ناهمزمان در پایتون چگونه است – مزایا و کاربردهای آن. پس از آن، ما یک واکنشگر ساده ساختیم aiohttp برنامه مبتنی بر این است که به صورت پویا مختصات آسمان مربوط به سیارات منظومه شمسی را با توجه به مختصات جغرافیایی کاربر نمایش می دهد.

پس از ساخت برنامه، ما آن را برای استقرار آماده کرده ایم روی هیروکو.

همانطور که قبلا ذکر شد، شما می توانید هر دو را پیدا کنید کد منبع و نسخه ی نمایشی برنامه در صورت نیاز

(برچسب‌ها به ترجمه)# python



منتشر شده در 1403-01-26 10:56:03

امتیاز شما به این مطلب
دیدگاه شما در خصوص مطلب چیست ؟

آدرس ایمیل شما منتشر نخواهد شد.

لطفا دیدگاه خود را با احترام به دیدگاه های دیگران و با توجه به محتوای مطلب درج کنید