Krax! Milenial menemukan kerangka Python

Prolog



Halo, Habr! Artikel ini dikhususkan untuk analisis pro dan kontra dari framework Python berikutnya, yang dirilis sekitar seminggu yang lalu.



Jadi, sedikit penyimpangan lirik. Selama peristiwa-peristiwa terkenal, ketika kami sedikit mengisolasi diri, kami memiliki lebih banyak waktu luang. Ada yang masuk ke daftar lektur yang disisihkan untuk dibaca, ada yang mulai belajar bahasa asing lain, ada yang terus menekan di Dotan dan tidak memperhatikan perubahannya. Tapi saya (maaf, artikel ini akan banyak mengandung "saya", dan saya sedikit malu) memutuskan dan mencoba melakukan sesuatu yang bermanfaat. Namun, kegunaannya masih bisa diperdebatkan. Pertanyaan-pertanyaan jelas yang kemungkinan besar dimiliki pembaca di tempat pertama:โ€œUm, kerangka Python? Lain? Permisi, tapi kenapa? Kami bukan JavaScript! "



Sebenarnya inilah yang akan dibahas di artikel ini: Apakah perlu? Jika perlu, kepada siapa? Apa bedanya dengan yang sudah ada? Bagaimana bisa menarik dan kenapa, misalnya bisa dikuburkan tanpa menunggu ulang tahun pertama. Artikel tersebut tidak merencanakan banyak kode - contoh penulisan aplikasi dan menggunakan bagian individu dapat ditemukan di dokumentasi (ada lebih banyak kode di sana;)). Artikel ini lebih merupakan ikhtisar.



Siapa yang membutuhkannya?



Jawaban yang agak egois untuk pertanyaan ini - pertama-tama, tentu saja, saya sendiri. Saya memiliki pengalaman dalam membangun aplikasi web menggunakan kerangka kerja yang ada dan saya sering berpikir: โ€œYa, semuanya keren, tapi kalau saja seperti iniโ€ฆ. Dan ini iklannya ... "... Kebanyakan dari kita, dengan satu atau lain cara, cepat atau lambat akan menemukan fakta bahwa beberapa hal tidak disukai dan ingin (atau bahkan harus) mengubahnya. Saya mencoba mengumpulkan apa yang saya suka dari alat yang telah saya gunakan. Saya berharap bahwa saya tidak sendirian dalam preferensi saya, dan bahwa ada orang yang akan menemukan ide ini dekat. Ide utama di balik Crax adalah bahwa Crax tidak memaksakan gaya pengembangan tertentu sebanyak mungkin. Misalnya, kami tidak memerlukan ruang nama, kami tidak ingin membagi logika ke dalam aplikasi, kami ingin dengan cepat menerapkan dua rute dan mendorong permintaan dan tanggapan. Ok, dalam hal ini kita bisa membuat aplikasi file tunggal dan mendapatkan apa yang kita inginkan. Tetapi situasi sebaliknya juga mungkin terjadi, dan ini juga tidak akan menjadi masalah. Hal kedua yang didukung Crax adalah kesederhanaan. Minimal kode dan minimal membaca dokumentasi untuk memulai.Jika seseorang yang baru mulai belajar Python berencana untuk bekerja dengan kerangka kerja, dia harus dapat mengatasi ambang pintu masuk tanpa rasa sakit.



Jika Anda melihat jumlah baris kode yang diperlukan untuk lulus semua tes

TechEmpower (lebih lanjut di bawah), maka Crax dalam aplikasi yang terdiri dari satu file lebih ringkas daripada semua peserta lainnya, dan tidak ada tujuan untuk "mengecilkan" file ini. Tidak ada lagi yang bisa ditulis. Meringkas hal di atas, kita dapat mengatakan bahwa Crax cocok untuk berbagai tugas yang sangat berbeda dan sangat banyak pemrogram dengan berbagai tingkat pelatihan.



Mengapa tidak menggunakan alat yang sudah ada?



Kenapa tidak? Jika Anda tahu persis alat mana yang akan digunakan, apa yang paling cocok untuk tugas Anda saat ini, terlebih lagi, Anda telah bekerja dengan alat ini dan mengetahui semua nuansanya. Tentu saja, Anda akan memilih apa yang Anda ketahui dan sesuai. Tidak ada (dan tidak akan) tujuan untuk memposisikan Crax sebagai "% framework_name% Killer". Tidak akan ada jenis agitasi: "Lemparkan segera% framework_name%, tulis ulang semua yang ada di Crax dan segera tingkatkan anggota yang terlihat dari jumlah penjualan" . Tidak ada yang seperti ini. Anda dapat mencatat sendiri bahwa Anda memiliki yang lain di kotak peralatan Anda seminggu yang lalu. Untuk menggunakannya atau tidak, itu terserah Anda. Namun, mengapa patut dicoba.



Pertama, cukup cepat. Itu ditulis menggunakan antarmuka ASGI (baca spesifikasinya di sini) dan jauh lebih cepat dari Flask atau Django 1. *, 2. *. Tetapi Crax tentu saja bukan satu-satunya framework Python yang menggunakan ASGI, dan tes pendahuluan menunjukkan bahwa Crax bersaing dengan baik dengan framework lain yang menggunakan teknologi ini. Sebagai perbandingan, kami menggunakan tes Peringkat Kinerja TechEmpower . Sayangnya, Crax, seperti kerangka lain yang ditambahkan di tengah putaran saat ini, hanya akan masuk ke putaran berikutnya, dan kemudian Anda dapat melihat hasilnya dalam masalah grafis. Namun, setelah setiap permintaan tarik, Travis menjalankan pengujian dan Anda dapat melihat karakteristik komparatif kerangka kerja di log Travis. Di bawah tautan adalah footcloth panjang dari log Travis untuk kerangka kerja Python dengan nama dalam urutan abjad dari A hingga F Di sini... Anda dapat mencoba membaca log dan membandingkan Crax, misalnya, dengan apidaora, hasilnya akan cukup bagus. Di bawah ini pada grafik adalah keadaan saat ini dalam tes Putaran ke 19.







Tentu saja, kami akan dapat melihat hasil nyata dan hasil nyata hanya di babak berikutnya, namun demikian.



Namun, kami, seperti yang disebutkan di atas, memiliki alat yang tidak kalah cepat dan sudah terbukti.

Asynchronous yang sama, dengan dukungan asli untuk websockets dan kesenangan lainnya.



Katakanlah Starlette atau FastApi. Mereka benar-benar kerangka kerja yang luar biasa dengan komunitas besar yang tertarik untuk mengembangkan produk ini. Perlu dicatat bahwa Crax paling mirip dengan Starlette atau FastAPI dalam ideologinya, dan beberapa ide telah dicuri.memata-matai Starlette (misalnya, Respon Middleware). Namun, ada beberapa hal yang mungkin Anda sukai dari Crax dan membuat Anda berpikir: "Mungkin mencobanya untuk proyek berikutnya."... Misalnya file konfigurasi. Tentu saja, Starlette juga memiliki kemampuan untuk membuat file konfigurasi, tetapi ini agak rumit untuk pemula, dan pada akhirnya intinya bermuara pada fakta bahwa semua variabel konfigurasi pada akhirnya diteruskan ke penginisialisasi kelas aplikasi. Jika Anda mengumpulkan SEMUA variabel yang memungkinkan, misalnya, mengkonfigurasi logger, middleware, CORS, dll., Itu akan menjadi terlalu banyak. Di Crax, semua variabel dideklarasikan dalam file utama (konfigurasi) (seperti Django), dan Anda tidak perlu meneruskannya ke mana pun. Selain itu, semua variabel yang dideklarasikan dalam file konfigurasi selalu dapat diakses saat runtime (baik dari aplikasi yang sedang berjalan maupun dari luar, halo Django ).



from crax.utils import get_settings_variable
base_url = get_settings_variable('BASE_URL')


Akan tampak keuntungan yang meragukan, namun, ketika file konfigurasi mulai bertambah banyak dengan variabel dan pengaturan, dan kita ingin memiliki akses ke sana, ini menjadi penting.



Detail penting berikutnya yang ingin saya bicarakan adalah organisasi struktur aplikasi. Ketika Anda memiliki proyek kecil, semua logikanya dapat ditempatkan dalam satu file, ini adalah satu hal. Tetapi ketika Anda menulis sesuatu yang lebih global, Anda mungkin ingin memisahkan tampilan, model, deskripsi rute, dll., Sesuai dengan logikanya. Dalam konteks ini, cetak biru Flask yang bagus atau aplikasi Django muncul di benak. Crax berbicara tentang namespace dalam pengertian ini. Awalnya, aplikasi Anda dimaksudkan untuk menjadi



satu set paket python yang termasuk dalam file proyek utama. Ngomong-ngomong, namespace (bagian aplikasi Anda) dapat disarangkan secara rekursif (hello Flask), dan nama file di dalamnya tidak menjadi masalah. Mengapa demikian? Dan apa yang diberikannya kepada kita?



Pertama, perutean. Namespaces akan membuat uri berdasarkan lokasi namespace secara otomatis (tapi ini, tentu saja, dapat dikontrol). Misalnya:



from crax.urls import Route, Url, include

url_list = [
    Route(Url('/'), Home),
    Route(Url('/guest_book'), guest_view_coroutine),
    include('second_app.urls'),
    include('second_app.nested.urls'),
    include('third_app.urls')
]


Ganti titik dengan garis miring dan Anda akan mendapatkan uri ke namespace Anda (tentu saja, dengan menambahkan penangan terakhir). Karena kami telah menyebutkan perutean, kami akan membahasnya lebih detail.

Crax menawarkan beberapa kemungkinan menarik, selain pekerjaan biasa dengan ekspresi reguler atau pekerjaan melalui jalur Django.



# URL defined as regex with one floating (optional) parameter
Url(r"/cabinet/(?P<username>\w{0,30})/(?:(?P<optional>\w+))?", type="re_path")
# General way to define URL
Url("/v1/customer/<customer_id>/<discount_name>/")


Namun, dimungkinkan untuk mengikat beberapa Url ke satu penangan.



from crax.urls import Route, Url

class APIView(TemplateView):
    template = "index.html"

urls = [
    Route(
        urls=(
            Url("/"),
            Url("/v1/customers"),
            Url("/v1/discounts"),
            Url("/v1/cart"),
            Url("/v1/customer/<customer_id:int>"),
            Url("/v1/discount/<discount_id:int>/<optional:str>/"),
        ),
        handler=APIView)
    ]


Anda sendiri dapat memikirkan di mana hal itu dapat berguna bagi Anda. Dan juga, ada mode operasi resolver dalam mode "masquerading". Misalnya, Anda hanya ingin mendistribusikan beberapa jenis direktori dengan templat, dan tidak menginginkan yang lain. Mungkin ini dokumentasi Sphinx, atau yang serupa. Anda selalu dapat melakukan ini:



import os
from crax.urls import Url, Route

class Docs(TemplateView):
    template = 'index.html'
    scope = os.listdir('docs/templates')

URL_PATTERNS = [
    Route(urls=(
        Url('/documentation', masquerade=True),
        handler=Docs),
]


Bagus, sekarang semua templat yang ada di direktori docs / templates akan berhasil dirender menggunakan satu penangan. Seorang pembaca yang ingin tahu akan mengatakan bahwa python tidak diperlukan sama sekali di sini, dan semua ini dapat dilakukan hanya dengan bantuan Nginx bersyarat. Saya sangat setuju, persis sampai perlu, misalnya, untuk mendistribusikan templat ini berdasarkan peran atau di suatu tempat di samping, logika tambahan tidak diperlukan.



Namun, kembali ke ruang nama domba jantan kami . Akan sangat menyedihkan jika namespace (meskipun bersarang) diperlukan hanya untuk mengatur penyelesaian url. Tentu saja, tujuan namespace sedikit lebih luas. Misalnya, bekerja dengan model database dan migrasi.



Tidak ada ORM di Crax. Dan itu tidak seharusnya. Bagaimanapun, sampai SQLAlchemy menawarkan solusi asynchronous. Namun, bekerja dengan database (Postgres, MySQL dan SQLite) dideklarasikan. Ini berarti dimungkinkan untuk menulis model Anda sendiri berdasarkan Crax BaseTable . Di balik terpal, ini adalah pembungkus yang sangat tipis di atas Tabel Inti SQLAlchemy , dan dapat melakukan semua yang dapat dilakukan Tabel Inti . Untuk apa itu mungkin dibutuhkan. Mungkin untuk melakukan sesuatu yang serupa.



from crax.database.model import BaseTable
import sqlalchemy as sa

class BaseModelOne(BaseTable):
    # This model just passes it's fields to the child
    # Will not be created in database because the abstract is defined
    parent_one = sa.Column(sa.String(length=50), nullable=False)

    class Meta:
        abstract = True

class BaseModelTwo(BaseTable):
    # Also passes it's fields to the child
    # Will be created in database
    parent_two = sa.Column(sa.String(length=50), nullable=False)

class MyModel(BaseModelOne, BaseModelTwo):
    name = sa.Column(sa.String(length=50), nullable=False)

print([y.name for x in MyModel.metadata.sorted_tables for y in x._columns])
# Let's check our fields ['name', 'id', 'parent_one', 'parent_two']


Dan agar dapat bekerja dengan migrasi. Migrasi Crax adalah sedikit kode di atas SQLAlchemy Alembic. Karena kita berbicara tentang ruang nama dan pemisahan logika, maka,

jelas, kita ingin menyimpan migrasi dalam paket yang sama dengan logika lain dari ruang nama ini. Beginilah cara kerja migrasi Crax. Semua migrasi akan didistribusikan sesuai dengan namespace mereka, dan jika namespace ini menyiratkan bekerja dengan database yang berbeda, maka di dalam direktori migrasi akan ada pembagian ke direktori dari database yang sesuai. Hal yang sama berlaku untuk migrasi offline - semua file * .sql akan dibagi menurut namespace dan database model. Saya tidak akan melukis di sini tentang menulis kueri - ini ada dalam dokumentasi, saya hanya akan mengatakan bahwa Anda masih bekerja dengan SQLAlchemy Core.



Sekali lagi, namespace menyiratkan penyimpanan templat yang nyaman (warisan dan fitur Jinja2 lainnya didukung + beberapa fasilitas dalam bentuk token CSRF atau pembuatan url yang sudah jadi). Artinya, semua template Anda terstruktur. Yah, tentu saja, saya tidak terjebak di tahun 2007 yang gemilang, saya memahami bahwa templat (meskipun dibuat secara asinkron) akan sedikit diminati pada tahun 2020. Dan itu, kemungkinan besar, Anda senang memisahkan logika frontend dan backend. Crax melakukan pekerjaan ini dengan sangat baik, hasilnya dapat dilihat di Github.

SiniVueJs digunakan sebagai frontend. Dan karena kami memiliki sejenis API, kami mungkin ingin membuat dokumentasi interaktif. Crax dapat membuat dokumentasi OpenAPI (Swagger) di luar kotak berdasarkan daftar rute dan dokumen penangan Anda. Semua contoh, tentu saja, ada di dokumentasi.



Sebelum kita beralih ke bagian paling menarik dari ikhtisar singkat kami, ada baiknya berbicara sedikit tentang baterai berguna mana yang sudah disediakan dengan Crax.



Secara alami, mode debug adalah ketika kesalahan dan jejak lengkap dapat dibaca langsung di browser, di halaman tempat kemalangan terjadi. Mode debug dapat dinonaktifkan dan disesuaikan dengan wallpaper yang membosankandengan penangan mereka. Cetak tampilan unik untuk setiap kode status http. Ini dilakukan dengan sangat sederhana, seperti semua yang ada di Crax.



Logger internal dengan kemampuan untuk secara bersamaan menulis ke file yang ditentukan dan mengirim log ke konsol (atau melakukan satu hal). Kemampuan untuk menetapkan logger Anda sendiri, bukan yang default. Sentry support dengan menambahkan dua baris ke konfigurasi (dan, jika perlu, kustomisasi).



Dua jenis middleware prainstal. Yang pertama diproses SEBELUM permintaan diproses oleh aplikasi, dan yang kedua SETELAH.



Dukungan bawaan untuk header CORS. Anda hanya perlu mendeklarasikan aturan CORS di konfigurasi.

Kemampuan untuk menentukan metode yang tersedia untuk setiap penangan langsung di situs. Setiap penangan akan bekerja dengan daftar metode HTTP yang ditentukan (+ KEPALA dan OPSI), atau hanya dengan GET, KEPALA, dan OPSI.



Kemampuan untuk menentukan bahwa penangan ini hanya tersedia untuk pengguna yang berwenang, atau hanya untuk pengguna dari grup Administrator, atau hanya untuk anggota peran superuser.

Ada otorisasi untuk sesi bertanda HMAC, yang tidak perlu masuk ke database dan sejumlah alat untuk membuat dan mengelola pengguna. Anda dapat mengaktifkan dukungan backend otorisasi dan mendapatkan pengguna prasetel dan sejumlah alat untuk digunakan. Namun, seperti kebanyakan alat Crax, Anda dapat membiarkannya, menggunakannya, dan menulisnya sendiri. Anda tidak dapat menggunakan otorisasi, database, model, migrasi, tampilan, dan sepenuhnya menulis solusi kustom Anda sendiri. Anda tidak perlu melakukan apa pun untuk melakukan ini, Anda belum menyalakannya - belum.



Ada beberapa jenis Respon dan beberapa jenis penangan Berbasis Kelas yang akan membantu Anda menulis aplikasi lebih cepat dan lebih ringkas. Dalam hal ini, keinginan Anda juga akan berfungsi, yang tidak mewarisi dari yang sudah ada.



from crax.views import BaseView

# Written your own stuff
class CustomView:
    methods = ['GET', 'POST']
    def __init__(self, request):
        self.request = request
    async def __call__(self, scope, receive, send):
        if self.request.method == 'GET':
            response = TextResponse(self.request, "Hello world")
            await response(scope, receive, send)
        elif self.request.method == 'POST':
            response = JSONResponse(self.request, {"Hello": "world"})
            await response(scope, receive, send)

# Crax based stuff
class CustomView(BaseView):
    methods = ['GET', 'POST']
    async def get(self):
        response = TextResponse(self.request, "Hello world")
        return response

    async def post(self):
        response = JSONResponse(self.request, {"Hello": "world"})
        return response

class CustomersList(TemplateView):
    template = 'second.html'

    # No need return anything in case if it is TemplateView.
    # Template will be rendered with params
    async def get(self):
        self.context['params'] = self.request.params


Dukungan perlindungan CSRF. Menghasilkan token, memeriksa keberadaan token di badan permintaan,

menonaktifkan verifikasi untuk penangan tertentu.



Dukungan untuk perlindungan ClickJacking (Bingkai, iframe, embed ... kebijakan rendering)



Dukungan untuk memeriksa ukuran tubuh maksimum yang diizinkan dari permintaan SEBELUM aplikasi mulai memprosesnya.



Dukungan websocket asli. Mari kita ambil contoh dari dokumentasi dan tulis aplikasi sederhana yang dapat mengirim pesan websocket melalui siaran, per grup pengguna, atau pesan ke pengguna tertentu. Misalkan kita memiliki kelompok "laki-laki" dan "perempuan" (dimungkinkan untuk menambahkan kelompok "orang tua"). Kita dapat menulis sesuatu yang serupa sebagai contoh (tentu saja, ini bukan kode produk).



#app.py

import asyncio
import json
import os
from base64 import b64decode
from functools import reduce

from crax.auth import login
from crax.auth.authentication import create_session_signer
from crax.auth.models import Group, UserGroup
from crax.response_types import JSONResponse
from crax.urls import Route, Url
from crax.views import TemplateView, WsView
from sqlalchemy import and_, select
from websockets import ConnectionClosedOK

BASE_URL = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = "SuperSecret"
MIDDLEWARE = [
    "crax.auth.middleware.AuthMiddleware",
    "crax.auth.middleware.SessionMiddleware",
]

APPLICATIONS = ["ws_app"]
CLIENTS = {'boys': [], 'girls': []}


class Home(TemplateView):
    template = "index.html"
    login_required = True


class Login(TemplateView):
    template = "login.html"
    methods = ["GET", "POST"]

    async def post(self):
        credentials = json.loads(self.request.post)
        try:
            await login(self.request, **credentials)
            if hasattr(self.request.user, "first_name"):
                context = {'success': f"Welcome back, {self.request.user.username}"}
                status_code = 200
            else:
                context = {'error': f"User or password wrong"}
                status_code = 401
        except Exception as e:
            context = {'error': str(e)}
            status_code = 500
        response = JSONResponse(self.request, context)
        response.status_code = status_code
        return response


class WebSocketsHome(WsView):

    def __init__(self, request):
        super(WebSocketsHome, self).__init__(request)
        self.group_name = None

    async def on_connect(self, scope, receive, send):
        # This coroutine will be called every time a client connects.
        # So at this point we can do some useful things when we find a new connection.

        await super(WebSocketsHome, self).on_connect(scope, receive, send)
        if self.request.user.username:
            cookies = self.request.cookies
            # In our example, we want to check a group and store the user in the desired location.

            query = select([Group.c.name]).where(
                and_(UserGroup.c.user_id == self.request.user.pk, Group.c.id == UserGroup.c.group_id)
            )
            group = await Group.query.fetch_one(query=query)
            self.group_name = group['name']

            # We also want to get the username from the user's session key for future access via direct messaging

            exists = any(x for x in CLIENTS[self.group_name] if cookies['session_id'] in list(x)[0])
            signer, max_age, _, _ = create_session_signer()
            session_cookie = b64decode(cookies['session_id'])
            user = signer.unsign(session_cookie, max_age=max_age)
            user = user.decode("utf-8")
            username = user.split(":")[0]
            val = {f"{cookies['session_id']}:{cookies['ws_secret']}:{username}": receive.__self__}

            # Since we have all the information we need, we can save the user
            # The key will be session: ws_cookie: username and the value will be an instance of uvicorn.WebSocketProtocol

            if not exists:
                CLIENTS[self.group_name].append(val)
            else:
                # We should clean up our storage to prevent existence of the same clients.
                # For example due to page reloading
                [
                    CLIENTS[self.group_name].remove(x) for x in
                    CLIENTS[self.group_name] if cookies['session_id'] in list(x)[0]
                ]
                CLIENTS[self.group_name].append(val)

    async def on_disconnect(self, scope, receive, send):
        # This coroutine will be called every time a client disconnects.
        # So at this point we can do some useful things when we find a client disconnects.
        # We remove the client from the storage

        cookies = self.request.cookies
        if self.group_name:
            try:
                [
                    CLIENTS[self.group_name].remove(x) for x in
                    CLIENTS[self.group_name] if cookies['session_id'] in list(x)[0]
                ]
            except ValueError:
                pass

    async def on_receive(self, scope, receive, send):
        # This coroutine will be called every time we receive a new incoming websocket message.
        # Check the type of message received and send a response according to the message type.

        if "text" in self.kwargs:
            message = json.loads(self.kwargs["text"])
            message_text = message["text"]
            clients = []
            if message["type"] == 'BroadCast':
                clients = reduce(lambda x, y: x + y, CLIENTS.values())

            elif message["type"] == 'Group':
                clients = CLIENTS[message['group']]

            elif message["type"] == 'Direct':
                username = message["user_name"]
                client_list = reduce(lambda x, y: x + y, CLIENTS.values())
                clients = [client for client in client_list if username.lower() in list(client)[0]]
            for client in clients:
                if isinstance(client, dict):
                    client = list(client.values())[0]
                    try:
                        await client.send(message_text)
                    except (ConnectionClosedOK, asyncio.streams.IncompleteReadError):
                        await client.close()
                        clients.remove(client)


URL_PATTERNS = [Route(Url("/"), Home), Route(Url("/", scheme="websocket"), WebSocketsHome), Route(Url("/login"), Login)]
DATABASES = {
        "default": {
            "driver": "sqlite",
            "name": f"/{BASE_URL}/ws_crax.sqlite",
        },
    }
app = Crax('ws_app.app')

if __name__ == "__main__":
    if sys.argv:
        from_shell(sys.argv, app.settings)




<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Crax Websockets</title>
    </head>
    <body>
        <div id="wsText"></div>
        <form>
            <input id="messageText"><br>
            <select id="targetGroup">
                <option>boys</option>
                <option>girls</option>
            </select>
            <select id="messageType">
                <option>BroadCast</option>
                <option>Group</option>
                <option>Direct</option>
            </select>
            <select id="userNames">
                <option>Greg</option>
                <option>Chuck</option>
                <option>Mike</option>
                <option>Amanda</option>
                <option>Lisa</option>
                <option>Anny</option>
            </select>
        </form>
        <a href="#" id="sendWs">Send Message</a>
        <script>
            var wsText = document.getElementById("wsText")
            var messageType = document.getElementById("messageType")
            var messageText = document.getElementById("messageText")
            var targetGroup = document.getElementById("targetGroup")
            var userName = document.getElementById("userNames")
            var sendButton = document.getElementById("sendWs")
            ws = new WebSocket("ws://127.0.0.1:8000")
            ws.onmessage = function(e){
                wsText.innerHTML+=e.data
            }

            sendButton.addEventListener("click", function (e) {
                e.preventDefault()
                var message = {type: messageType.value, text: messageText.value}
                var data
                if (messageText.value !== "") {
                    if (messageType.value === "BroadCast"){
                        // send broadcast message
                        data = message
                    }
                    else if (messageType.value === "Group"){
                        // send message to group
                        data = Object.assign(message, {group: targetGroup.value})
                    }
                    else if (messageType.value === "Direct"){
                        // send message to certain user
                        data = Object.assign(message, {user_name: userName.value})
                    }
                    ws.send(JSON.stringify(data))
                }
            })
        </script>
    </body>
    </html>


<!-- login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Crax Websockets</title>
</head>
<body>
    <form>
        <input id="username">
        <input id="password" type="password">
    </form>
    <div id="loginResults"></div>
    <a href="#" id="sendLogin">Login</a>

    <script>
        var loginButton = document.getElementById("sendLogin")
        var loginResults = document.getElementById("loginResults")
        var username = document.getElementById("username")
        var password = document.getElementById("password")
        loginButton.addEventListener("click", function (e) {
            e.preventDefault()
            if (username.value !== "" && password.value !== "") {
                var xhr = new XMLHttpRequest()
                xhr.overrideMimeType("application/json")
                xhr.open("POST", "/login")
                xhr.send(JSON.stringify({username: username.value, password: password.value}))
                xhr.onload = function () {
                    var result = JSON.parse(xhr.responseText)
                    if ("success" in result){
                        loginResults.innerHTML+="<h5 style='color: green'>"+result.success+ "</h5>"
                    }
                    else if ("error" in result) {
                        loginResults.innerHTML+="<h5 style='color: red'>"+result.error+ "</h5>"
                    }
                }
            }
        })
    </script>
</body>
</html>


Kode lengkapnya bisa dilihat di dokumentasi Crax.



Nah, waktunya telah tiba untuk yang paling menarik dalam artikel ini.



Mengapa tidak perlu?



Pertama, seperti disebutkan di atas, ada beberapa kerangka kerja yang melakukan hal yang sama, dan komunitas sudah terbentuk. Sedangkan Crax adalah seorang bayi yang baru berusia satu minggu. Tentara lajang hampir menjadi jaminan bahwa cepat atau lambat proyek itu akan ditinggalkan. Ini menyedihkan, tetapi fakta bahwa bekerja di atas meja, merilis rilis dan pembaruan hanya untuk Anda sendiri dan Vasily dari Syktyvkar, jauh lebih lama daripada ketika komunitas mengerjakan proyek. Sementara itu, proyek tersebut belum memiliki sejumlah fitur yang harus dimiliki di tahun 2020. Misalnya: tidak ada dukungan JWT (JOSE). Tidak ada dukungan out-of-the-box untuk OAuth2. Tidak ada dukungan GraphQL. Jelas bahwa Anda dapat menulis ini sendiri untuk proyek Anda, tetapi Starlette atau FastAPI sudah memilikinya. Saya hanya perlu menulis ini (ya, itu ada dalam rencana). Akan ada sedikit tentang rencana sebagai kesimpulan.



Pengembang Netflix dan Microsoft menulis tentang FastAPI. Tentang Crax menulis noname, tidak diketahui di mana ia muncul, dan siapa yang tahu di mana mampu tepat lusa jurang. Mereka



tidak akan memanggil kapal uap dengan nama idiot saya.

Ibuku menangis di malam hari, karena dia melahirkan anak aneh ...

(c)


Ini penting. Ini disebut reputasi dan ekosistem. Crax juga tidak memilikinya. Tanpa hal-hal penting tersebut, proyek dijamin masuk ke TPA tanpa pernah lahir.



Itu sangat berharga untuk dipahami. Apa yang tertulis di atas bukanlah upaya untuk mengetik kelas dan bukan teks dari seorang tunawisma di kereta. Ini adalah penilaian yang bijaksana dan peringatan bahwa "solusi siap produksi" tidak hanya merupakan hasil dari cakupan kode sumber melalui pengujian, tetapi juga penilaian umum tentang kematangan teknologi, pendekatan, dan solusi yang digunakan dalam proyek.



Jika Anda baru saja mulai mengenal Python dan mencoba kerangka kerja, Anda berada dalam bahaya: Kemungkinan besar, Anda tidak akan menemukan jawaban atas pertanyaan di SO, mungkin rekan yang lebih berpengalaman akan membantu Anda, yang sayangnya mungkin tidak ada di sana.



Tujuan



Hal pertama yang saya rencanakan adalah, tentu saja, menambahkan beberapa hal yang harus dimiliki seperti JWT (JOSE), OAuth2 dan dukungan GraphQL. Inilah yang akan memudahkan saya dan orang-orang yang tertarik untuk bekerja. Dan inilah, sebenarnya, tujuan utama Crax - membuat pekerjaan seseorang sedikit lebih mudah. Mungkin pada saat itu babak baru di TechEmpower akan dimulai dan tolok ukurnya akan menjadi lebih jelas. Bahkan tidak menutup kemungkinan setelah ini akan ada kepentingan tertentu di masyarakat.

Ada ide untuk menulis CMS berdasarkan Crax.

Jika saya tidak salah (jika saya salah - perbaiki), kami belum memiliki CMS asinkron dengan Python di toolkit kami. Saya mungkin berubah pikiran dan memutuskan untuk menulis semacam solusi e-commerce. Tapi, jelas, untuk mencegah Crax tenggelam sebelum mencapai pelampung, sesuatu yang menarik perlu dilakukan di dasarnya. Mungkin para peminat akan tertarik dengan ini. Penggemar saat itu gratis. Karena tidak ada uang di sini dan kemungkinan besar tidak akan ada. Crax benar-benar gratis untuk semua orang dan saya tidak mendapat sepeser pun untuk pekerjaan ini. Dengan demikian, pembangunan direncanakan untuk "malam musim dingin yang panjang" dan, mungkin, di tahun mendatang, sesuatu yang menarik akan lahir.



Kesimpulan



Saya sedang memikirkan tentang grup mana yang akan menyertakan artikel ini (omong-omong, ini adalah publikasi pertama saya tentang sumber daya). Mungkin layak untuk menempatkannya di bawah tag "Saya PR". Apa yang membuat saya berubah pikiran: pertama-tama, fakta bahwa iklan itu tidak memiliki karakter iklan apa pun.



Tidak ada panggilan "Anak laki-laki, segera daftar untuk permintaan tarik . " Tidak ada ide untuk mencari sponsor di sini. Bahkan tidak ada ide bahwa saya membawakan Anda sesuatu yang belum pernah Anda lihat (tentu saja, lihat). Anda dapat mengabstraksikan gagasan bahwa saya adalah penulis kedua artikel dan alat ini, dan memahami apa yang telah ditulis sebagai tinjauan. Dan, ya, itulah cara terbaik. Ini akan menjadi hasil yang sangat baik bagi saya jika Anda terus mengingatnya.

Dalam hal ini, mungkin, semuanya.



โ€œJadiโ€ฆ saatnya mengambil pancing.

- Kenapa?

- Topi merah Harris membuat takut semua ikan.

(c)


Kode pada Dokumentasi GitHub




All Articles