ç®æ¬¡- ã¯ããã«
- æ§é
- ã«ãŒã
- ãã³ãã©ãŒããªã¯ãšã¹ãããã³ã¬ã¹ãã³ã¹
- æ§æèšå®
- ããã«ãŠã§ã¢
- ããŒã¿ããŒã¹
- ãã¿ãŒã³
- ã»ãã·ã§ã³ãèªå¯
- éç
- Webãœã±ãã
- Herokuã«ã¢ããããŒããã
ã¯ããã«
æšå¹Žã®ç§ãç§ã¯ããšãã§ããã€ãã®pythonããŒãã¢ããã蚪åããŸããã
Nikolai Novikã¯ãã®ãã¡ã®1ã€ã§è©±ãããPythonã€ã³ã¿ãŒããªã¿ãŒã®ããŒãžã§ã³3ã§éåæåŒã³åºãã®ããã«asyncioã©ã€ãã©ãªã§å®è¡ãããæ°ããaiohttpéåæãã¬ãŒã ã¯ãŒã¯ã«ã€ããŠè©±ããŸããã ãã®ãã¬ãŒã ã¯ãŒã¯ã¯ãã³ã¢pythonéçºè
ã«ãã£ãŠäœæãããWebçšã®pythonãã¬ãŒã ã¯ãŒã¯ã®æŠå¿µãšããŠäœçœ®ä»ããããŠãããšããäºå®ã«èå³ãæã¡ãŸããã
çŸåšãèšå€§ãªæ°ã®ç°ãªããã¬ãŒã ã¯ãŒã¯ããããããããã«ç¬èªã®å²åŠããããŸãã
äžè¬çãªWebãã³ãã¬ãŒãã®æ§æãšå®è£
ã æéãçµã€ã«ã€ããŠããã®ãã¹ãŠã®å€æ§æ§ã
aiohttp-1ã€ã®åºæºã«ãªããŸãã
æ§é
aiohttpã®ãã¹ãŠã®æ©èœãæå€§éã«ãã¹ãããããã«ãWebãœã±ããã§ç°¡åãªãã£ãããéçºããããšããŸããã aiohttpã®äžæ žã¯ããã³ãã©ãŒãã¹ãã³ããç¡éã«ãŒãã§ãã ãã³ãã©ãŒ-ããããã³ã«ãŒãã³ãå
¥å/åºåïŒI / OïŒããããã¯ããªããªããžã§ã¯ãã ãã®ã¿ã€ãã®ãªããžã§ã¯ãã¯ãPython 3.4ã®asyncioã©ã€ãã©ãªã«ç»å ŽããŸããã äžãããããªããžã§ã¯ãã®ãã¹ãŠã®èšç®ãè¡ããããŸã§ãç ãã«èœã¡ãããã«èŠãããã®æç¹ã§ã€ã³ã¿ãŒããªã¿ãŒã¯ä»ã®ãªããžã§ã¯ããåŠçã§ããŸãã æç¢ºã«ããããã«ãäŸãæããŸãã å€ãã®å ŽåããµãŒããŒããã®å¿çã¯ããŒã¿ããŒã¹ããã®å¿çãåŸ
æ©ãããã®å¿çãå°çããŠåŠçããããŸã§ãä»ã®ãªããžã§ã¯ããé çªã«åŸ
æ©ãããšãã«ãã¹ãŠã®ãµãŒããŒé
å»¶ãçºçããŸãã ãã®å Žåãä»ã®ãªããžã§ã¯ãã¯ãããŒã¿ããŒã¹ããã®å¿çãå°çãããŸã§åŠçãããŸãã ãã ãããããå®è£
ããã«ã¯ãéåæãã©ã€ããŒãå¿
èŠã§ãã
çŸåšãaiohttpã«ã¯ãã»ãšãã©ã®äžè¬çãªããŒã¿ããŒã¹ïŒ postgresql ã mysql ã redis ïŒçšã®éåæãã©ã€ããŒãšã©ãããŒããããŸã
mongodbã«ã¯ããã£ããã§äœ¿çšãããã¢ãŒã¿ãŒããããŸãã
ãã£ããã®ãšã³ããªãã€ã³ãã¯app.pyãã¡ã€ã«ã§ãã ã¢ããªãªããžã§ã¯ããäœæãããŸãã
import asyncio from aiohttp import web loop = asyncio.get_event_loop() app = web.Application(loop=loop, middlewares=[ session_middleware(EncryptedCookieStorage(SECRET_KEY)), authorize, db_handler, ])
ã芧ã®ãšãããåæåäžã«ãã«ãŒãã¯ã¢ããªãšããã«ãŠã§ã¢ãªã¹ãã«æž¡ãããŸããããã«ãŠã§ã¢ãªã¹ãã«ã€ããŠã¯åŸè¿°ããŸãã
ã«ãŒã
aiohttpãéåžžã«ãã䌌ãŠãããã©ã¹ã³ãšã¯ç°ãªããã«ãŒãã¯æ¢ã«åæåãããã¢ããªã«è¿œå ãããŸãã
app.router.add_route('GET', '/{name}', handler)
ãšããã§ã Andrei Svetlovã®èª¬æããããŸãã
ã«ãŒãã¯å¥ã®routes.pyãã¡ã€ã«ã«èšå
¥ãããŸãã
from chat.views import ChatList, WebSocket from auth.views import Login, SignIn, SignOut routes = [ ('GET', '/', ChatList, 'main'), ('GET', '/ws', WebSocket, 'chat'), ('*', '/login', Login, 'login'), ('*', '/signin', SignIn, 'signin'), ('*', '/signout', SignOut, 'signout'), ]
æåã®èŠçŽ ã¯httpã¡ãœãããæ¬¡ã«urlãé
眮ããããã³ãã©ãŒãªããžã§ã¯ãã¯ã¿ãã«ã®3çªç®ãæåŸã«ã«ãŒãåãªã®ã§ãã³ãŒãã§åŒã³åºãã®ã䟿å©ã§ãã
次ã«ãã«ãŒããªã¹ããapp.pyã«ã€ã³ããŒããããã¢ããªã±ãŒã·ã§ã³ãžã®åçŽãªã«ãŒãã§åããããŸãã
from routes import routes for route in routes: app.router.add_route(route[0], route[1], route[2], name=route[3])
ãã¹ãŠãã·ã³ãã«ã§è«ççã§ãã
ãã³ãã©ãŒããªã¯ãšã¹ãããã³ã¬ã¹ãã³ã¹
Djangoãã¬ãŒã ã¯ãŒã¯ã®äŸã«åŸã£ãŠããªã¯ãšã¹ãåŠçãè¡ãããšã«ããŸããã authãã©ã«ããŒã«ã¯ããŠãŒã¶ãŒãæ¿èªããŠãŒã¶ãŒäœæãšãã°ã€ã³ã®åŠçã«é¢é£ãããã¹ãŠãå«ãŸããŠããŸãã ãŸãã ãã£ãããã©ã«ããŒã«ã¯ããããããã£ããã®ããžãã¯ããããŸãã aiohttpã§ã¯ã ãã³ãã©ãŒã颿°ãŸãã¯ã¯ã©ã¹ãšããŠå®è£
ã§ããŸãã
ã¯ã©ã¹ãéããŠå®è£
ãéžæããŸãã
class Login(web.View): async def get(self): session = await get_session(self.request) if session.get('user'): url = request.app.router['main'].url() raise web.HTTPFound(url) return b'Please enter login or email'
ã»ãã·ã§ã³ã«ã€ããŠã¯ä»¥äžã«æžãããŸãããããŠç§ãèããä»ã®ãã¹ãŠã¯æç¢ºã§ãã ãªãã€ã¬ã¯ãã¯ãæ»ãïŒæ»ãïŒãããã©ã¡ãŒã¿ãŒããã¹ã«æž¡ãããweb.HTTPFoundïŒïŒãªããžã§ã¯ãã®åœ¢åŒã§äŸå€ãã¹ããŒããããšã§çºçããããšã«æ³šæããŠãã ããã ã¯ã©ã¹ã®Httpã¡ãœããã¯ãéåæé¢æ°getãpostãªã©ãéããŠå®è£
ãããŸãã ã¯ãšãªãã©ã¡ãŒã¿ã䜿çšããå¿
èŠãããå Žåãããã€ãã®æ©èœããããŸãã
data = await self.request.post()
æ§æèšå®
ãã¹ãŠã®èšå®ã¯settings.pyãã¡ã€ã«ã«ä¿åãããŸãã æ©å¯ããŒã¿ã®ä¿åã«ã¯ã envparseã䜿çšããŸã ã ãã®ãŠãŒãã£ãªãã£ã䜿çšãããšãç°å¢å€æ°ããããŒã¿ãèªã¿åãããããã®å€æ°ãä¿åãããŠããç¹å¥ãªãã¡ã€ã«ãè§£æã§ããŸãã
if isfile('.env'): env.read_envfile('.env')
第äžã«ãHerokuã§ãããžã§ã¯ããäžããå¿
èŠãããã第äºã«ãéåžžã«äŸ¿å©ã§ããã æåã¯ããŒã«ã«ããŒã¿ããŒã¹ã䜿çšããŠããããªã¢ãŒãããŒã¿ããŒã¹ã§ãã¹ãããåãæ¿ãã¯.envãã¡ã€ã«ã®1è¡ã®ã¿ã倿Žããããšã§æ§æãããŸããã
ããã«ãŠã§ã¢
ã¢ããªã±ãŒã·ã§ã³ãåæåãããšãã«ãããã«ãŠã§ã¢ãæå®ã§ããŸãã ããã§ã¯ããããã¯å¥ã®ãã¡ã€ã«ã«é
眮ãããŸã ã æšæºå®è£
ã¯ãã³ã¬ãŒã¿é¢æ°ã§ãããã®é¢æ°ã§ã¯ããªã¯ãšã¹ãã䜿çšããŠãã§ãã¯ãŸãã¯ãã®ä»ã®ã¢ã¯ã·ã§ã³ãå®è¡ã§ããŸãã
èš±å¯ãã§ãã¯ã®äŸ
async def authorize(app, handler): async def middleware(request): def check_path(path): result = True for r in ['/login', '/static/', '/signin', '/signout', '/_debugtoolbar/']: if path.startswith(r): result = False return result session = await get_session(request) if session.get("user"): return await handler(request) elif check_path(request.path): url = request.app.router['login'].url() raise web.HTTPFound(url) return handler(request) else: return await handler(request) return middleware
ããŒã¿ããŒã¹ãæ¥ç¶ããããã®ããã«ãŠã§ã¢ããããŸãã
async def db_handler(app, handler): async def middleware(request): if request.path.startswith('/static/') or request.path.startswith('/_debugtoolbar'): response = await handler(request) return response request.db = app.db response = await handler(request) return response return middleware
æ¥ç¶ã®è©³çŽ°ã¯æ¬¡ã®ãšããã§ãã
ããŒã¿ããŒã¹
ãã£ããã¯ãMongodbãšMotorã®éåæãã©ã€ããŒã䜿çšããŸãã ããŒã¿ããŒã¹ãžã®æ¥ç¶ã¯ãã¢ããªã±ãŒã·ã§ã³ã®åæåäžã«çºçããŸãã
app.client = ma.AsyncIOMotorClient(MONGO_HOST) app.db = app.client[MONGO_DB_NAME]
ãããŠãç¹å¥ãªã·ã£ããããŠã³æ©èœã§æ¥ç¶ãéããããŸãã
async def shutdown(server, app, handler): server.close() await server.wait_closed() app.client.close()
éåæãµãŒããŒã®å Žåããã¹ãŠã®äžŠåã¿ã¹ã¯ãæ£ããå®äºããå¿
èŠãããããšã«æ³šæããŠãã ããã
ã€ãã³ãã«ãŒãã®äœæã«ã€ããŠããå°ãã
loop = asyncio.get_event_loop() serv_generator, handler, app = loop.run_until_complete(init(loop)) serv = loop.run_until_complete(serv_generator) log.debug('start server', serv.sockets[0].getsockname()) try: loop.run_forever() except KeyboardInterrupt: log.debug(' Stop server begin') finally: loop.run_until_complete(shutdown(serv, app, handler)) loop.close() log.debug('Stop server end')
ã«ãŒãèªäœã¯asyncioããäœæãããŸãã
serv_generator, handler, app = loop.run_until_complete(init(loop))
run_until_completeã¡ãœããã¯ãã«ãŒãã«corutinesã远å ããŸãã ãã®å Žåãã¢ããªã±ãŒã·ã§ã³åæå颿°ã远å ãããŸãã
try: loop.run_forever() except KeyboardInterrupt: log.debug(' Stop server begin') finally: loop.run_until_complete(shutdown(serv, app, handler)) loop.close()
å®éã«ã¯ãäŸå€ã®å Žåã«äžæãããç¡éã«ãŒãèªäœã®å®è£
ã çµäºããåã«ããã¹ãŠã®æ¥ç¶ãçµäºãããµãŒããŒãæ£ãã忢ããã·ã£ããããŠã³é¢æ°ãåŒã³åºãããŸãã
次ã«ãã¯ãšãªã®äœææ¹æ³ãããŒã¿ã®ååŸããã³å€æŽæ¹æ³ãçè§£ããå¿
èŠããããŸã
class Message(): def __init__(self, db, **kwargs): self.collection = db[MESSAGE_COLLECTION] async def save(self, user, msg, **kw): result = await self.collection.insert({'user': user, 'msg': msg, 'time': datetime.now()}) return result async def get_messages(self): messages = self.collection.find().sort([('time', 1)]) return await messages.to_list(length=None)
ORMã¯äœ¿çšããŠããŸããããããŒã¿ããŒã¹ã¯ãšãªã¯å¥ã®ã¯ã©ã¹ã§è¡ãæ¹ã䟿å©ã§ãã ãã£ãããã©ã«ããŒã«ãMessageã¯ã©ã¹ã眮ãããŠããmodels.pyãã¡ã€ã«ãäœæãããŸããã get_messagesã¡ãœããã§ã¯ãä¿åããããã¹ãŠã®ã¡ãã»ãŒãžãæéã§ãœãŒãããŠååŸãããªã¯ãšã¹ããäœæãããŸãã saveã¡ãœããã§ã¯ãã¡ãã»ãŒãžãããŒã¿ããŒã¹ã«ä¿åãããªã¯ãšã¹ããäœæãããŸãã
ãã¿ãŒã³
人æ°ã®ãããã³ãã¬ãŒããšã³ãžã³ãç¹ã«aiohttp_jinja2ãšaiohttp_makoã® aiohttpçšã«ãããã€ãã®éåæã©ãããŒãèšè¿°ãããŠããŸãã ç§ã¯jinja2ããã£ããã«äœ¿çšããŠããŸãã
aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader('templates'))
ããã¯ããã³ãã¬ãŒããµããŒããã¢ããªã±ãŒã·ã§ã³ã§åæåãããæ¹æ³ã§ãã
FileSystemLoaderïŒ 'templates'ïŒã¯ããã³ãã¬ãŒãããã³ãã¬ãŒããã©ã«ããŒã«ããããšãjinja2ã«äŒããŸãã
class ChatList(web.View): @aiohttp_jinja2.template('chat/index.html') async def get(self): message = Message(self.request.db) messages = await message.get_messages() return {'messages': messages}
ãã³ã¬ãŒã¿ã䜿çšããŠããã¥ãŒã§äœ¿çšãããã³ãã¬ãŒããæå®ããã³ã³ããã¹ããåããããã«ã倿°ã§èŸæžãè¿ãããã³ãã¬ãŒãã§äœ¿çšããŸãã
ã»ãã·ã§ã³ãèªå¯
ã»ãã·ã§ã³ã®æäœã«ã¯ã aiohttp_sessionã©ã€ãã©ãªããããŸãã æå·åã䜿çšããŠãã»ãã·ã§ã³ãRedisãŸãã¯Cookieã«æå·åããã圢åŒã§ä¿åã§ããŸãã ã©ã€ãã©ãªãã€ã³ã¹ããŒã«ããå Žåã§ããä¿åæ¹æ³ã瀺ãããŸãã
aiohttp_session[secure]
ã»ãã·ã§ã³ãåæåããã«ã¯ãããã«ãŠã§ã¢ã«è¿œå ããŸãã
session_middleware(EncryptedCookieStorage(SECRET_KEY)),
ã»ãã·ã§ã³ã§å€ãååŸãŸãã¯é
眮ããã«ã¯ãæåã«ãªã¯ãšã¹ãããå€ãæœåºããå¿
èŠããããŸãã
session = await get_session(request)
ãŠãŒã¶ãŒãèªèšŒããã«ã¯ããŠãŒã¶ãŒã®IDãã»ãã·ã§ã³ã«è¿œå ããããã«ãŠã§ã¢ã§ãã®å¯çšæ§ã確èªããŸãã ãã¡ãããã»ãã¥ãªãã£ã«ã¯ããã«ãã§ãã¯ãå¿
èŠã§ãããã³ã³ã»ããããã¹ãããã«ã¯ããã§ååã§ãã
éç
éçã³ã³ãã³ããå«ããã©ã«ããŒã¯ãã¢ããªã±ãŒã·ã§ã³ã®åæåäžã«å¥ã®ã«ãŒãã§æ¥ç¶ãããŸãã
app.router.add_static('/static', 'static', name='static')
ãã³ãã¬ãŒãã§äœ¿çšããã«ã¯ãã¢ããªããååŸããå¿
èŠããããŸãã
<script src="{{ app.router.static.url(filename='js/main.js') }}"></script>
ãã¹ãŠãåçŽã§ãè€éãªãã®ã¯äœããããŸããã
Webãœã±ãã
æåŸã«ãaiohttpã®æãããããéšåã«å°éããŸããïŒã ãœã±ããã®å®è£
ã¯éåžžã«ç°¡åã§ãã javascriptã§ã¯ãåäœããããã«æäœéå¿
èŠãªæ©èœã远å ããŸããã
try{ var sock = new WebSocket('ws://' + window.location.host + '/ws'); } catch(err){ var sock = new WebSocket('wss://' + window.location.host + '/ws'); }
ãµãŒããŒåŽãå®è£
ããã«ã¯ãWebSocketã¯ã©ã¹ã䜿çšããŸã
class WebSocket(web.View): async def get(self): ws = web.WebSocketResponse() await ws.prepare(self.request) session = await get_session(self.request) user = User(self.request.db, {'id': session.get('user')}) login = await user.get_login() for _ws in self.request.app['websockets']: _ws.send_str('%s joined' % login) self.request.app['websockets'].append(ws) async for msg in ws: if msg.tp == MsgType.text: if msg.data == 'close': await ws.close() else: message = Message(self.request.db) result = await message.save(user=login, msg=msg.data) log.debug(result) for _ws in self.request.app['websockets']: _ws.send_str('(%s) %s' % (login, msg.data)) elif msg.tp == MsgType.error: log.debug('ws connection closed with exception %s' % ws.exception()) self.request.app['websockets'].remove(ws) for _ws in self.request.app['websockets']: _ws.send_str('%s disconected' % login) log.debug('websocket connection closed') return ws
ãœã±ããèªäœã¯ãWebSocketResponseïŒïŒé¢æ°ã䜿çšããŠäœæãããŸãã 調çããåã«å¿
ã䜿çšããŠãã ããã éããŠãããœã±ããã®ãªã¹ãã¯ãã¢ããªã±ãŒã·ã§ã³ã«ä¿åãããŸãïŒãµãŒããŒãéãããããšãã«æ£ããéããããšãã§ããããã«ïŒã æ°ãããŠãŒã¶ãŒãæ¥ç¶ãããšããã¹ãŠã®åå è
ã¯ãæ°ããåå è
ããã£ããã«åå ãããšããéç¥ãåãåããŸãã æ¬¡ã«ããŠãŒã¶ãŒããã®ã¡ãã»ãŒãžãæåŸ
ããŸãã æå¹ãªå ŽåãããŒã¿ããŒã¹ã«ä¿åããä»ã®ãã£ããåå è
ã«éä¿¡ããŸãã
ãœã±ãããéãããšããªã¹ããããœã±ãããåé€ããåå è
ã®1人ããœã±ãããé¢ããããšããã£ããã«éç¥ããŸãã éåžžã«åçŽãªå®è£
ã§ãèŠèŠçã«åæã¹ã¿ã€ã«ã§ãããšãã°Tornadoã®ããã«å€ãã®ã³ãŒã«ããã¯ããããŸããã 䜿çšããŠãã ããã
Herokuã«ã¢ããããŒããã
èŠèŠçãªãã¢ã³ã¹ãã¬ãŒã·ã§ã³ã®ããã«ãHerokuã«ãã¹ããã£ãããæçš¿ããŸããã ã€ã³ã¹ããŒã«äžã«ããã€ãã®åé¡ããããŸãããç¹ã«ãå
éšmongodbããŒã¿ããŒã¹ã䜿çšããããã«ãã¯ã¬ãžããã«ãŒãæ
å ±ãå
¥åããå¿
èŠããããŸããããããã¯ããããªãã®ã§ã MongoLabã®ãµãŒãã¹ã䜿çšããŠããŒã¿ããŒã¹ãäœæããŸããã ããã«ãã¢ããªã±ãŒã·ã§ã³èªäœã®ã€ã³ã¹ããŒã«ã«åé¡ããããŸããã æå·åãã€ã³ã¹ããŒã«ããã«ã¯ãrequirements.txtã§æç€ºçã«æå®ããå¿
èŠããããŸããã ãŸããpythonã®ããŒãžã§ã³ã瀺ãã«ã¯ããããžã§ã¯ãã«ãŒãã«runtime.txtãã¡ã€ã«ãäœæããå¿
èŠããããŸãã
çµè«
äžè¬ã«ããã£ããã®äœæãaiohttpã®åŠç¿ããœã±ããããã®ä»ã®ãã¯ãããžãŒã®åæã¯ããããŸã§äžç·ã«è¡ã£ãããšããªãã£ãããã倿¹ã鱿«ã«ã¯ãã£ãã«3é±éããããŸããã
aiohttpã®ããã¥ã¡ã³ãã¯éåžžã«åªããŠãããå€ãã®éåæãã©ã€ããŒãšã©ãããŒããã¹ãã®æºåãã§ããŠããŸãã
ããããããã¹ãŠã®è£œåã®æºåãæŽã£ãŠããããã§ã¯ãããŸããããéçºã¯éåžžã«æŽ»çºã§ãïŒaiohttpã¯3é±éã§ããŒãžã§ã³0.19ãã0.21ã«æŽæ°ãããŸããïŒã
ãããžã§ã¯ãã«ãœã±ããã远å ããå¿
èŠãããå Žåããã®ãªãã·ã§ã³ã¯ãéãç«å·»ã远å ããªãããã«æé©ã§ãã
åç
§è³æ
ãã¹ãŠã®ãšã©ãŒã𿬠ç¹ãPMã«éä¿¡ããŠãã ãã:)
UPD
èšäºãå
¬éãããŠããå°ãæéãçµã¡ãŸãããæ®å¿µãªãããHerokuã®ã¢ããªã±ãŒã·ã§ã³ã¯é·ãéå©çšã§ããªãã£ãããããã¢ã®ãã¹ãã¯ããæ©èœããŸããã ã³ãŒããšäŸåé¢ä¿ãæŽæ°ããæéã¯ã»ãšãã©ãããŸããã§ããããèšäºã«ã¯ãŸã å€ãã³ãŒãããããŸãã