ããããŒã°
ããã«ã¡ã¯ãHabrïŒãã®èšäºã§ã¯ãçŽ1é±éåã«ãªãªãŒã¹ãããæ¬¡ã®Pythonãã¬ãŒã ã¯ãŒã¯ã®é·æãšçæã®åæã«ã€ããŠèª¬æããŸãã
ã ãããå°ããªåæ çãªéžè±ãããç¥ãããŠããã€ãã³ãã®éãç§ãã¡ãå°ãèªç«ããŠãããšããç§ãã¡ã¯ããå°ãèªç±ãªæéãéãããŸããã誰ããèªæžã®ããã«åã£ãŠãããæç®ã®ãªã¹ãã«ãã©ãçãã誰ããå¥ã®å€åœèªãå匷ãå§ãã誰ããããŒã¿ã³ãæŒãç¶ããå€åã«æ³šæãæããªãã£ããããããç§ïŒç³ãèš³ãããŸãããããã®èšäºã«ã¯å€ãã®ãç§ããå«ãŸããŠããã®ã§ãå°ãæ¥ããããã§ãïŒã¯ãäœã圹ã«ç«ã€ããšãããããšæ±ºå¿ããŸããããã ãããã®æçšæ§ã«ã€ããŠã¯è°è«ã®äœå°ããããŸããèªè ãæåã«æã£ãŠããå¯èœæ§ãæãé«ãæãããªè³ªåïŒããããšãPythonãã¬ãŒã ã¯ãŒã¯ïŒå¥ã®ïŒãã¿ãŸãããããªãã§ããïŒçµå±ã®ãšãããç§ãã¡ã¯JavaScriptã§ã¯ãããŸããïŒã
å®éãããã¯ãŸãã«ãã®èšäºã§èª¬æããå 容ã§ããå¿ èŠã§ããïŒå¿ èŠã«å¿ããŠã誰ã«ïŒãã§ã«ãããã®ãšã®éãã¯äœã§ããïŒãããã©ã®ããã«é åçã§ãããããããŠãªããäŸãã°ããããæåã®èªçæ¥ãåŸ ããã«åããããããšãã§ãããããã®èšäºã¯å€ãã®ã³ãŒããèšç»ããŠããŸãã-ã¢ããªã±ãŒã·ã§ã³ãäœæããåã ã®ããŒãã䜿çšããäŸã¯ããã¥ã¡ã³ãã«ãããŸãïŒããã«ã¯ãã£ãšå€ãã®ã³ãŒãããããŸã;ïŒïŒããã®èšäºã¯æŠèŠã§ãã
誰ããããå¿ èŠãšããŸããïŒ
ãã®è³ªåã«å¯Ÿããããå©å·±çãªçã-ãŸã第äžã«ããã¡ãããç§èªèº«ãç§ã¯æ¢åã®ãã¬ãŒã ã¯ãŒã¯ã䜿çšããŠWebã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããçµéšãããã宿çã«æ¬¡ã®ããã«èããŠããŸãããã¯ãããã¹ãŠãã¯ãŒã«ã§ãããããããã®ãããªãã®ã§ããã°âŠããããŠããããã³ããŒã·ã£ã«ã§ã...ã..ãç§ãã¡ã®ã»ãšãã©ã¯ãé ããæ©ãããããã€ãã®ãã®ãæ°ã«å ¥ããã倿ŽãããïŒãŸãã¯å€æŽããªããã°ãªããªãïŒãšããäºå®ã«åºããããŸãã䜿ã£ãããŒã«ãã奜ããªãã®ããŸãšããŠã¿ãŸãããç§ã®å¥œã¿ã¯ç§ã ãã§ã¯ãªãããããã®ã¢ã€ãã¢ã身è¿ã«æããŠããã人ãããããšãé¡ã£ãŠããŸãã Craxã®èåŸã«ããäž»ãªèãæ¹ã¯ãç¹å®ã®éçºã¹ã¿ã€ã«ãå¯èœãªéã課ããªããšããããšã§ããããšãã°ãåååã¯å¿ èŠãããŸãããããžãã¯ãã¢ããªã±ãŒã·ã§ã³ã«åå²ããããããŸããã2ã€ã®ã«ãŒãããã°ããå±éããèŠæ±ãšå¿çãé§åããããšèããŠããŸããããããŸããããã®å Žåãåäžã®ãã¡ã€ã«ã¢ããªã±ãŒã·ã§ã³ãäœæããŠãå¿ èŠãªãã®ãååŸã§ããŸããããããéã®ç¶æ³ãå¯èœã§ããããããåé¡ã«ã¯ãªããŸããã Craxãæå±ãã2çªç®ã®ããšã¯ã·ã³ãã«ãã§ããéå§ããããã®æå°éã®ã³ãŒããšæå°éã®ããã¥ã¡ã³ãã®èªã¿åããPythonãåŠã³å§ããã°ããã®äººããã¬ãŒã ã¯ãŒã¯ã䜿çšããããšãèšç»ããŠããå Žåã圌ã¯èŠçã䌎ããã«ãšã³ããªã®ãããå€ãå æã§ããã¯ãã§ãã
ãã¹ãŠã®
TechEmpowerãã¹ãã«åæ Œããããã«å¿ èŠãªã³ãŒãã®è¡æ°ãèŠããšïŒè©³çްã¯ä»¥äžãåç §ïŒã1ã€ã®ãã¡ã€ã«ã§æ§æãããã¢ããªã±ãŒã·ã§ã³ã®Craxã¯ãä»ã®ãã¹ãŠã®åå è ãããã³ã³ãã¯ãã§ããããã®ãã¡ã€ã«ããçž®å°ãããç®çã¯ãããŸããã§ãããæžãããšã¯æ¬åœã«äœããããŸãããäžèšãèŠçŽãããšãCraxã¯ãéåžžã«ããŸããŸãªã¿ã¹ã¯ãšãããŸããŸãªçšåºŠã®ãã¬ãŒãã³ã°ãè¡ãéåžžã«å¹ åºãããã°ã©ããŒã«é©ããŠãããšèšããŸãã
æ¢åã®ããŒã«ã䜿çšããŠã¿ãŸãããïŒ
äœæ ãªã®ïŒäœ¿çšããããŒã«ãçŸåšã®ã¿ã¹ã¯ã«æãé©ããããŒã«ãæ£ç¢ºã«ç¥ã£ãŠããå Žåã¯ãããã«ããã®ããŒã«ã䜿çšããŠããã¹ãŠã®ãã¥ã¢ã³ã¹ãç¥ã£ãŠããŸãããã¡ãããããªãã¯ããªããç¥ã£ãŠããŠãåããã®ãéžã¶ã§ãããã CraxããïŒ framework_nameïŒ KillerããšããŠäœçœ®ä»ãããšããç®æšã¯ãããŸããïŒãããŠãããªãããšã¯ãããŸããïŒãæªæã¿ã€ãã¯ãããŸããïŒãç·æ¥ã«ïŒ framework_nameïŒ ãã¹ããŒããCraxã§ãã¹ãŠãæžãçŽããããã«ç®ç«ã€ããã«å£²äžé«ã®
ãŸããããã¯ååã«éãã§ãã ASGIã€ã³ã¿ãŒãã§ãŒã¹ã䜿çšããŠèšè¿°ãããŠããŸãïŒä»æ§ã¯ãã¡ããã芧ãã ããïŒïŒãããŠFlaskãDjango 1ãããã¯ããã«é«éã§ãã*ã2ã*ããããããã¡ããCraxã¯ASGIã䜿çšããå¯äžã®Pythonãã¬ãŒã ã¯ãŒã¯ã§ã¯ãªããäºåãã¹ãã§ã¯ããã®ãã¯ãããžãŒã䜿çšããä»ã®ãã¬ãŒã ã¯ãŒã¯ãšååã«ç«¶åããããšã瀺ãããŠããŸããæ¯èŒã®ããã«ãTechEmpower Performance Ratingãã¹ãã䜿çšããŸãããæ®å¿µãªãããCraxã¯ãçŸåšã®ã©ãŠã³ãã®éäžã§è¿œå ãããä»ã®ãã¬ãŒã ã¯ãŒã¯ãšåæ§ã«ã次ã®ãã¬ãŒã ã¯ãŒã¯ã«ã®ã¿å ¥ããçµæã¯ã°ã©ãã£ã«ã«ãªåé¡ã§ç¢ºèªã§ããŸãããã ããåãã«ãªã¯ãšã¹ãã®åŸã«ãTravisã¯ãã¹ããå®è¡ããTravisãã°ã§ãã¬ãŒã ã¯ãŒã¯ã®æ¯èŒç¹æ§ã確èªã§ããŸãããªã³ã¯ã®äžã«ã¯AããFãŸã§ã®ã¢ã«ãã¡ãããé ã«ååã®Pythonã®ãã¬ãŒã ã¯ãŒã¯ã®ãã©ãŽã£ã¹ãã°ã®é·ãfootclothã§ãããã..ããã°ãèªãã§Craxãæ¯èŒããŠã¿ãŠãã ãããããšãã°ãapidaoraãšæ¯èŒãããšãããªãè¯ãçµæãåŸãããŸããäžã®ã°ã©ãã¯ãã©ãŠã³ããªã19ãã¹ãã®çŸç¶ã§ãã
ãã¡ãããå®éã®çµæãšå®éã®çµæã¯æ¬¡ã®ã©ãŠã³ãã§ã®ã¿èŠãããšãã§ããŸãããããã§ããªãã§ãã
ãã ããåè¿°ã®ããã«ãé«éã§å®çžŸã®ããããŒã«ã¯ãããŸãã
åãéåæã§ãWebãœã±ããããã®ä»ã®åã³ããã€ãã£ãã§ãµããŒãããŸãã
StarletteãŸãã¯FastApiãšããŸãããããããã¯ããããã®è£œåã®éçºã«é¢å¿ã®ããå€§èŠæš¡ãªã³ãã¥ããã£ãæã€ã絶察ã«çŽ æŽããããã¬ãŒã ã¯ãŒã¯ã§ãã Craxã¯ãã®ã€ããªãã®ãŒã«ãããŠStarletteãŸãã¯FastAPIã«æãé¡äŒŒããŠãããããã€ãã®ã¢ã€ãã¢ã
from crax.utils import get_settings_variable
base_url = get_settings_variable('BASE_URL')
çãããå©ç¹ã®ããã«æãããŸãããæ§æãã¡ã€ã«ã倿°ãèšå®ã§å€§ãããªãå§ãããããã«ã¢ã¯ã»ã¹ãããå Žåã¯ããããéèŠã«ãªããŸãã
次ã«ã話ããããéèŠãªè©³çްã¯ãã¢ããªã±ãŒã·ã§ã³æ§é ã®æ§æã§ãããã¹ãŠã®ããžãã¯ã1ã€ã®ãã¡ã€ã«ã«é 眮ã§ããå°ããªãããžã§ã¯ããããå Žåãããã¯1ã€ã®ããšã§ãããã ããããã°ããŒãã«ãªãã®ãäœæããå Žåã¯ãããžãã¯ã«åŸã£ãŠããã¥ãŒãã¢ãã«ãã«ãŒãã®èª¬æãªã©ãåé¢ããããšããå§ãããŸãããã®ã³ã³ããã¹ãã§ã¯ãåªãããã©ã¹ã³ã®éåçãŸãã¯Djangoã¢ããªã±ãŒã·ã§ã³ãæãæµ®ãã³ãŸãã Craxã¯ããã®æå³ã§ååä»ãã«ã€ããŠè©±ããŸããåœåãã¢ããªã±ãŒã·ã§ã³ã¯
ã¡ã€ã³ãããžã§ã¯ããã¡ã€ã«ã«å«ãŸããŠããPythonããã±ãŒãžã®ã»ãããã¡ãªã¿ã«ãåååïŒã¢ããªã±ãŒã·ã§ã³ã®äžéšïŒã¯ååž°çã«ãã¹ãã§ãïŒhello FlaskïŒããã®äžã®ãã¡ã€ã«ã®ååã¯éèŠã§ã¯ãããŸããããªãããããã®ã§ããïŒãããŠããã¯ç§ãã¡ã«äœãäžããŸããïŒ
ãŸããã«ãŒãã£ã³ã°ãåå空éã¯ãååä»ãã®å Žæã«åºã¥ããŠuriãèªåçã«äœæããŸãïŒãã ããããã¯ãã¡ããå¶åŸ¡ã§ããŸãïŒãäŸãã°ïŒ
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')
]
ããããã¹ã©ãã·ã¥ã«çœ®ãæãããšãåååã«uriã远å ãããŸãïŒãã¡ãããæåŸã®ãã³ãã©ãŒã远å ããŸãïŒãã«ãŒãã£ã³ã°ã«ã€ããŠã¯ãã§ã«èª¬æããã®ã§ãããã«è©³ãã説æããŸãã
Craxã¯ãéåžžã®è¡šçŸã䜿çšããéåžžã®äœæ¥ã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>/")
ãã ããè€æ°ã®Urlã1ã€ã®ãã³ãã©ãŒã«ãã€ã³ãããããšã¯å¯èœã§ãã
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)
]
ããªãèªèº«ããããããªãã«ãšã£ãŠã©ãã«åœ¹ç«ã€ããèããããšãã§ããŸãããŸããããã¹ã«ã¬ãŒããã¢ãŒãã§ã®ãªãŸã«ããŒã®åäœã¢ãŒãããããŸããããšãã°ãããçš®ã®ãã£ã¬ã¯ããªããã³ãã¬ãŒããšãšãã«é åžãããã ãã§ãä»ã«ã¯äœãå¿ èŠãããŸããããããããããã¯Sphinxã®ããã¥ã¡ã³ãããŸãã¯åæ§ã®ãã®ã§ããããªãã¯ãã€ã§ããããè¡ãããšãã§ããŸãïŒ
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),
]
ããã§ãdocs / templatesãã£ã¬ã¯ããªã«ãããã¹ãŠã®ãã³ãã¬ãŒãã1ã€ã®ãã³ãã©ãŒã䜿çšããŠæ£åžžã«ã¬ã³ããªã³ã°ãããããã«ãªããŸããã奜å¥å¿æºçãªèªè ã¯ãããã§ã¯pythonã¯ãŸã£ããå¿ èŠãªããšèšãã§ããããããã¯ãã¹ãŠãæ¡ä»¶ä»ãNginxã®å©ããåããŠã®ã¿è¡ãããšãã§ããŸããããšãã°ããããã®ãã³ãã¬ãŒãã圹å²ããšã«é åžãããããµã€ãã®ã©ããã«é åžãããããå¿ èŠããããŸã§ã¯ã远å ã®ããžãã¯ã¯å¿ èŠãããŸããã
ãã ãã
Craxã«ã¯ORMã¯ãããŸããããããŠãããã¯æ³å®ãããŠããŸããããšã«ãããSQLAlchemyãéåæãœãªã¥ãŒã·ã§ã³ãæäŸãããŸã§ããã ããããŒã¿ããŒã¹ïŒPostgresãMySQLãããã³SQLiteïŒã§ã®äœæ¥ã¯å®£èšãããŠããŸããããã¯ãCraxBaseTableã«åºã¥ããŠç¬èªã®ã¢ãã«ãäœæã§ããããšãæå³ããŸãããã³ãããã®äžã§ã¯ãããã¯ã«ããã£ãŠéåžžã«èãã©ãããŒã§ãSQLAlchemyã®ã³ã¢è¡šãããã³ãããããããšããã¹ãŠè¡ãããšãã§ããã³ã¢è¡šãè¡ãããšãã§ããŸããããããå¿ èŠã«ãªããããããªããã®ã®ããã«ããããã䌌ããããªããšãããã§ãããã
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']
ãããŠãç§»è¡ãåŠçã§ããããã«ããããã§ãã Craxã®ç§»è¡ã¯ãSQLAlchemyAlembicã®äžã«ããã¡ãã£ãšããã³ãŒãã§ããååä»ããšããžãã¯ã®åé¢ã«ã€ããŠè©±ããŠããã®ã§ã
æããã«ããã®ååä»ãã®ä»ã®ããžãã¯ãšåãããã±ãŒãžã«ç§»è¡ãä¿åããããšæããŸãããããCraxç§»è¡ã®ä»çµã¿ã§ãããã¹ãŠã®ç§»è¡ã¯åååã«åŸã£ãŠé åžããããã®ååä»ããç°ãªãããŒã¿ããŒã¹ã§ã®äœæ¥ãæå³ããå Žåãç§»è¡ãã£ã¬ã¯ããªå ã§å¯Ÿå¿ããããŒã¿ããŒã¹ã®ãã£ã¬ã¯ããªã«åå²ãããŸããåãããšããªãã©ã€ã³ç§»è¡ã«ãåœãŠã¯ãŸããŸãããã¹ãŠã®* .sqlãã¡ã€ã«ã¯ãåååãšã¢ãã«ããŒã¿ããŒã¹ã«åŸã£ãŠåå²ãããŸããããã§ã¯ã¯ãšãªã®èšè¿°ã«ã€ããŠã¯èª¬æããŸãããããã¥ã¡ã³ãã«èšèŒãããŠããŸãããSQLAlchemyCoreãåŒãç¶ã䜿çšããŠãããšã ãèšã£ãŠãããŸãã
ç¹°ãè¿ãã«ãªããŸãããååä»ãã¯ãã³ãã¬ãŒãã®äŸ¿å©ãªã¹ãã¬ãŒãžãæå³ããŸãïŒç¶æ¿ããã³ãã®ä»ã®Jinja2æ©èœããµããŒããããŠããŸã+æ¢è£œã®CSRFããŒã¯ã³ãŸãã¯URLçæã®åœ¢ã§ããã€ãã®æ©èœããããŸãïŒãã€ãŸãããã¹ãŠã®ãã³ãã¬ãŒããæ§é åãããŠããŸãããã¡ãããç§ã¯èŒããã2007幎ã«ãšããããŠããŸããããã³ãã¬ãŒãã¯ïŒéåæã§ã¬ã³ããªã³ã°ããããšããŠãïŒ2020幎ã«ã¯ã»ãšãã©éèŠããªãããšãçè§£ããŠããŸãããããŠããããããããã³ããšã³ããšããã¯ãšã³ãã®ããžãã¯ãåé¢ããããšãåãã§ããŸãã Craxã¯ããã«ã€ããŠåªããä»äºãããŠãããçµæã¯Githubã§èŠãããšãã§ããŸãã
ããã«VueJsã¯ããã³ããšã³ããšããŠäœ¿çšãããŸãããŸããããçš®ã®APIãããã®ã§ãã€ã³ã¿ã©ã¯ãã£ããªããã¥ã¡ã³ããäœæããããšæãã§ãããã Craxã¯ãã«ãŒããªã¹ããšãã³ãã©ãŒã®docstringã«åºã¥ããŠãOpenAPIïŒSwaggerïŒããã¥ã¡ã³ããããã«äœæã§ããŸãããã¡ããããã¹ãŠã®äŸã¯ããã¥ã¡ã³ãã«ãããŸãã
ç°¡åãªæŠèŠã®æãè峿·±ãéšåã«ç§»ãåã«ãã©ã®æçšãªããããªãŒããã§ã«Craxã«ä»å±ããŠãããã«ã€ããŠå°ã話ã䟡å€ããããŸãã
åœç¶ããããã°ã¢ãŒãã¯ãäžå¹žãçºçããããŒãžã§ããšã©ãŒãšå®å šãªãã¬ãŒã¹ããã©ãŠã¶ã§çŽæ¥èªã¿åãããšãã§ããã¢ãŒãã§ãããããã°ã¢ãŒããç¡å¹ã«ããŠã
æå®ããããã¡ã€ã«ãžã®æžã蟌ã¿ãšã³ã³ãœãŒã«ãžã®ãã°ã®éä¿¡ãåæã«è¡ãïŒãŸãã¯1ã€ã®ããšãè¡ãïŒæ©èœãåããçµã¿èŸŒã¿ã®ãã¬ãŒãããã©ã«ãã®ãã¬ãŒã®ä»£ããã«ç¬èªã®ãã¬ãŒãå²ãåœãŠãæ©èœãæ§æã«2è¡ã远å ããããšã«ããSentryã®ãµããŒãïŒããã³å¿ èŠã«å¿ããŠã«ã¹ã¿ãã€ãºïŒã
2çš®é¡ã®ããªã€ã³ã¹ããŒã«ãããããã«ãŠã§ã¢ã 1ã€ç®ã¯ããªã¯ãšã¹ããã¢ããªã±ãŒã·ã§ã³ã«ãã£ãŠåŠçãããåã«åŠçããã2ã€ç®ã¯ã¢ããªã±ãŒã·ã§ã³ã«ãã£ãŠåŠçãããåã«åŠçãããŸãã
CORSããããŒã®çµã¿èŸŒã¿ãµããŒããæ§æã§CORSã«ãŒã«ã宣èšããã ãã§æžã¿ãŸãã
åãã³ãã©ãŒã§äœ¿çšå¯èœãªã¡ãœããããµã€ãã§çŽæ¥å®çŸ©ããæ©èœãåãã³ãã©ãŒã¯ãæå®ãããHTTPã¡ãœããã®ãªã¹ãïŒ+ HEADããã³OPTIONSïŒã§æ©èœããããGETãHEADããã³OPTIONSã§ã®ã¿æ©èœããŸãã
ãã®ãã³ãã©ãŒãèš±å¯ããããŠãŒã¶ãŒã®ã¿ãAdministratorsã°ã«ãŒãã®ãŠãŒã¶ãŒããŸãã¯ã¹ãŒããŒãŠãŒã¶ãŒããŒã«ã®ã¡ã³ããŒã®ã¿ã䜿çšã§ããããã«æå®ããæ©èœã
HMACã§çœ²åãããã»ãã·ã§ã³ã«ã¯æ¿èªããããããŒã¿ããŒã¹ã«ã¢ã¯ã»ã¹ããå¿ èŠã¯ãªãããŠãŒã¶ãŒãäœæããã³ç®¡çããããã®å€æ°ã®ããŒã«ããããŸããæ¿èªããã¯ãšã³ãã®ãµããŒããæå¹ã«ããŠãããªã»ãããŠãŒã¶ãŒãšå€æ°ã®ããŒã«ã䜿çšã§ããŸãããã ããã»ãšãã©ã®CraxããŒã«ãšåæ§ã«ããã®ãŸãŸã«ããŠã䜿çšããŠãç¬èªã«äœæããããšãã§ããŸããæ¿èªãããŒã¿ããŒã¹ãã¢ãã«ãç§»è¡ããã¥ãŒã䜿çšããŠãç¬èªã®ã«ã¹ã¿ã ãœãªã¥ãŒã·ã§ã³ãå®å šã«äœæããããšã¯ã§ããŸããããããè¡ãããã«äœã®åªåãããå¿ èŠã¯ãããŸãããããªãã¯ããããªã³ã«ããŠããŸãã-ããã§ã¯ãããŸããã
ã¢ããªã±ãŒã·ã§ã³ãããéããããç°¡æœã«äœæããã®ã«åœ¹ç«ã€ãããã€ãã®ã¿ã€ãã®å¿çãšããã€ãã®ã¿ã€ãã®ã¯ã©ã¹ããŒã¹ã®ãã³ãã©ãŒããããŸãããã®å Žåãçµã¿èŸŒã¿ã®ãã®ããç¶æ¿ããªãç¬èªã®ãã®ãæ©èœããŸãã
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
CSRFä¿è·ãµããŒããããŒã¯ã³ã®çæããªã¯ãšã¹ãæ¬æå ã®ããŒã¯ã³ã®ååšã®
確èªãç¹å®ã®ãã³ãã©ãŒã®æ€èšŒã®ç¡å¹åã
ClickJackingä¿è·ã®ãµããŒãïŒãã¬ãŒã ãiframeãåã蟌ã¿...ã¬ã³ããªã³ã°ããªã·ãŒïŒ
ã¢ããªã±ãŒã·ã§ã³ãåŠçãéå§ããåã«ããªã¯ãšã¹ãæ¬æã®æå€§èš±å®¹ãµã€ãºããã§ãã¯ããããã®ãµããŒãã
ãã€ãã£ãWebãœã±ããã®ãµããŒããããã¥ã¡ã³ãããäŸãåãäžããŠããããŒããã£ã¹ãããŠãŒã¶ãŒã°ã«ãŒãããšããŸãã¯ç¹å®ã®ãŠãŒã¶ãŒãžã®ã¡ãã»ãŒãžã«ãã£ãŠWebãœã±ããã¡ãã»ãŒãžãéä¿¡ã§ããç°¡åãªã¢ããªã±ãŒã·ã§ã³ãäœæããŸããããã°ã«ãŒããboysããšãgirlsãããããšããŸãïŒã°ã«ãŒããparentsãã远å ããããšã¯å¯èœã§ãïŒãäŸãšããŠäŒŒããããªãã®ãæžãããšãã§ããŸãïŒãã¡ãããããã¯è£œåã³ãŒãã§ã¯ãããŸããïŒã
#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>
å®å šãªã³ãŒãã¯ãCraxã®ããã¥ã¡ã³ãã§ç¢ºèªã§ããŸãã
ããŠããã®èšäºã§æãè峿·±ãæãæ¥ãŸããã
ãªãäžèŠãªã®ã§ããïŒ
ãŸããåè¿°ã®ããã«ãåãããšãè¡ãããã€ãã®ãã¬ãŒã ã¯ãŒã¯ãããããã§ã«ã³ãã¥ããã£ã圢æãããŠããŸãã Craxã¯1é±éœ¢ã®èµ€ã¡ããã§ããç¬èº«è»ã¯ãé ããæ©ãããããžã§ã¯ããæŸæ£ãããããšãã»ãŒä¿èšŒããŠããŸããæ²ããããšã§ãããSyktyvkarããèªåèªèº«ãšVasilyã®ããã ãã«ãªãªãŒã¹ãšã¢ããããŒãããªãªãŒã¹ãããšããäºå®ã¯ãã³ãã¥ããã£ããããžã§ã¯ãã«åãçµãã§ãããšããããã¯ããã«é·ããªããŸãããã®éããããžã§ã¯ãã«ã¯2020幎ã«å¿ é ã®æ©èœãããã€ããããŸãããäŸïŒJWTãµããŒããªãïŒJOSEïŒã OAuth2ããŒã«ã®ããã«äœ¿çšã§ãããµããŒãã¯ãããŸããã GraphQLã¯ãµããŒããããŠããŸããããããžã§ã¯ãçšã«ãããèªåã§äœæã§ããããšã¯æããã§ãããStarletteãŸãã¯FastAPIã«ã¯ãã§ã«å«ãŸããŠããŸããç§ã¯ãããæžããªããã°ãªããŸããïŒã¯ããããã¯èšç»ã«ãããŸãïŒãçµè«ãšããŠãèšç»ã«ã€ããŠå°ã説æããŸãã
NetflixãšMicrosoftã®éçºè ã¯ãFastAPIã«ã€ããŠæžããŠããŸãã Craxã«ã€ããŠã¯nonameãšæžããŠããŸããããããã©ãã«çŸããã®ãããããŠææ¥ã®æ·±æ·µã®ç¿æ¥ã«ã©ãã§äœãã§ããã®ãã¯èª°ã«ãããããŸããã 圌ã
ã¯ç§ã®ã°ãããååã§æ±œè¹ãåŒã¶ããšã¯ãããŸããã
ç§ã®æ¯ã¯ããªãŒã¯ãåºç£ããã®ã§å€ã«æ³£ããŸã...
ïŒcïŒ
ããã¯éèŠã§ããããã¯è©å€ãšãšã³ã·ã¹ãã ãšåŒã°ããŠããŸãã Craxã«ã¯ã©ã¡ãããããŸããããããã®éèŠãªããšããªããã°ããããžã§ã¯ãã¯çãŸããããšãªãåãç«ãŠã«è¡ãããšãä¿èšŒãããŠããŸãã
çè§£ãã䟡å€ããããŸããäžã«æžãããŠããã®ã¯ãã¯ã©ã¹ãã¿ã€ãããããšãã詊ã¿ã§ã¯ãªããé»è»ã®äžã§ããŒã ã¬ã¹ã®äººã®ããã¹ãã§ããããŸãããããã¯å·éãªè©äŸ¡ã§ãããããããã¯ã·ã§ã³ã¬ãã£ãœãªã¥ãŒã·ã§ã³ãã¯ãã¹ãã«ãããœãŒã¹ã³ãŒãã®ã«ãã¬ããžã®çµæã§ããã ãã§ãªãããããžã§ã¯ãã§äœ¿çšããããã¯ãããžãŒãã¢ãããŒããããã³ãœãªã¥ãŒã·ã§ã³ã®æç床ã®äžè¬çãªè©äŸ¡ã§ããããšããèŠåã§ãã
Pythonã䜿ãå§ããŠãã¬ãŒã ã¯ãŒã¯ã詊ããŠããã ãã®å Žåã¯ãå±éºã«ãããããŠããŸãããããããSOã«é¢ãã質åã«å¯ŸããåçãèŠã€ãããªãå¯èœæ§ããããŸãããããããçµéšè±å¯ãªä»²éãããªããå©ããŠãããã§ãããã
ç®æš
ãã¡ãããç§ãæåã«è¡ãããšãèšç»ããŠããã®ã¯ãJWTïŒJOSEïŒãOAuth2ãGraphQLãµããŒããªã©ã®å¿ é ã®ãã®ã远å ããããšã§ããããã¯ç§ãšèå³ã®ãã人ã ãåãããããããã®ã§ãããããŠãããã¯å®éãCraxã®äž»ãªç®æšã§ã-誰ãã®ä»äºãå°ãç°¡åã«ããããšã§ãããããããã®æãŸã§ã«ãTechEmpowerã§ã®æ°ããã©ãŠã³ããå§ãŸãããã³ãããŒã¯ãããæç¢ºã«ãªãã§ãããããã®åŸãã³ãã¥ããã£ã«ããçšåºŠã®é¢å¿ãå¯ããããå¯èœæ§ããããŸãã
Craxã«åºã¥ããŠCMSãäœæãããšããã¢ã€ãã¢ããããŸãã
ç§ãééã£ãŠããªãå ŽåïŒç§ãééã£ãŠããå Žå-ä¿®æ£ããŠãã ããïŒãããŒã«ãããã«ã¯ãŸã Pythonã®éåæCMSããããŸãããæ°ãå€ãã£ãŠãããçš®ã®eã³ããŒã¹ãœãªã¥ãŒã·ã§ã³ãæžãããšã«ãããããããŸãããããããæããã«ããã€ã«å°éããåã«Craxãæººããã®ãé²ãããã«ããã®ããŒã¹ã§äœãé¢çœãããšãããå¿ èŠããããŸããããããæå¥œå®¶ã¯ããã«èå³ãããã§ããããæå¥œå®¶ã¯ãããç¡æã®ãšãã§ããããã«ã¯ãéããªãã®ã§ããããããããŸãããCraxã¯èª°ã«ãšã£ãŠãå®å šã«ç¡æã§ãããç§ã¯ãã®ä»äºã®ããã«ãã€ã ãååŸããŸããã§ããããã®ããã«ãéçºã¯ãé·ãå¬ã®å€ãã«èšç»ãããŠãããããããæ¥å¹Žã«ã¯äœãé¢çœããã®ãçãŸããã§ãããã
çµè«
ç§ã¯ãã®èšäºãã©ã®ã°ã«ãŒãã«å«ããããèããŠããŸããïŒã¡ãªã¿ã«ãããã¯ãªãœãŒã¹ã«é¢ããç§ã®æåã®åºçç©ã§ãïŒã ãç§ã¯PRã§ãããšããã¿ã°ã®äžã«çœ®ã䟡å€ãããã£ãã®ãããããŸãããæ°ãå€ãã£ãã®ã¯ããŸã第äžã«ãåºåã®ãã£ã©ã¯ã¿ãŒããŸã£ãããªããšããäºå®ã§ãããç·ã®åãç·æ¥ã«ãã«ãªã¯ãšã¹ãã«ãµã€ã³ã¢ããããŠãã ããã
ãšããé»è©±ã¯ãããŸãããããã§ã¹ãã³ãµãŒãèŠã€ããããšã¯èããããŸãããç§ãããªãã«ä»ãŸã§èŠãããšã®ãªããã®ïŒãã¡ããèŠãããšã®ãªããã®ïŒãæã£ãŠãããšããèããããããŸãããç§ãèšäºãšãã®ããŒã«ã®äž¡æ¹ã®äœæè ã§ãããšããèãããæœè±¡åããã¬ãã¥ãŒãšããŠæžããããã®ãèªèããããšãã§ããŸãããããŠãã¯ãããããæåã®æ¹æ³ã§ãããããå¿ã«çããŠããã°ãç§ã«ãšã£ãŠçŽ æŽãããçµæã«ãªãã§ãããã ãããããããããã¹ãŠã§ãããããã§âŠããã¯é£ãæ£ãåãæéã§ãã - ãªãïŒ
-ããªã¹ã®èµ€ãåžœåã¯ãã¹ãŠã®éãæããããã
ïŒcïŒ
GitHubããã¥ã¡ã³ãã®ã³ãŒã