Skip to content

Commit

Permalink
Merge pull request #1 from timofurrer/master
Browse files Browse the repository at this point in the history
Various improvements / cleanups
  • Loading branch information
neolynx committed Oct 17, 2016
2 parents 06ade7a + 9391059 commit 8181bfb
Show file tree
Hide file tree
Showing 11 changed files with 410 additions and 211 deletions.
13 changes: 12 additions & 1 deletion .gitignore
@@ -1,2 +1,13 @@
__pycache__
# vim swap files
*.swp

# python bytecode files
__pycache__/

# python virtualenv
env/

# python setuptools
*.egg-info/
build/
dist/
19 changes: 19 additions & 0 deletions .travis.yml
@@ -0,0 +1,19 @@
language: python
python:
- '3.5'

install:
- python setup.py build sdist
- pip install -r requirements.txt

script: nosetests --verbosity 2

deploy:
provider: pypi
user: tuxtimo
password:
secure: eNrDuSAQuR0xVFb+ZfHZ93mZBEKGWNnidSHTM+8DkuA3OKGTD/Gf3TGAVLaqqURtChukopOANvoBqTWSHMXe6mDSiTn6cPpqxa7WBcOkF4yzzSVQ+ch/wjgpabzgi+XfT6tTo7KI8JYwgxg4NivN/Iv6uxzVqmc7hxwrSJbwruav1ddvonGb+I4M1LYcvUWTFEwlCvBxr+a9CdQ0+JHTtgdCF0T/71NwQJKmA7NtDaS6hex7kJY/CADBsowECURraB4jsbbqnw8sKUDsi6M5/aHSrC0KlpPrSxoESy4EFB7x77WVQxLTcTOqnQQ1ZazNc0g9YfZ7TqcH6cXjcAxqmx/rsmE9EDpauml4IySJWYvEyCMkS2LsqWZI4Em8t2MRO96Zoou3FPgabuUmwEpLlBIfLxTW+XDt2M3fa7wtHP4rIA3e6ruSmVitdC6gFG0nMdGIC/o6s/99Qqx/05eNm1BQ5PdFCzKyVg82mZsqyoSV/37qaVic3pDYIYmGpsm1G/pS4UdH9qGcjP8P0n/986EBAenOeExg9a53XoDms6C/mkeMqJHkJ6WrEKDmaFiUHWz2D3mFTdpL+xWnpOP/b/4/2XSQVh86z+5wW0iXb+wxMdmMSv9T8VBb5kDWj6/6D/y0V90k1i8d2pkmJ3sJAqWNBfw2pC5D03J7qH8zPdU=
on:
tags: true
distributions: sdist bdist_wheel
repo: neolynx/cirrina
1 change: 1 addition & 0 deletions MANIFEST.in
@@ -0,0 +1 @@
include cirrina/static/*
205 changes: 10 additions & 195 deletions cirrina/__init__.py
@@ -1,200 +1,15 @@
import asyncio
from aiohttp_jrpc import Service, JError, JResponse
from aiohttp import web, WSMsgType
from cryptography import fernet
from aiohttp_session import setup, get_session, session_middleware
from aiohttp_session.cookie_storage import EncryptedCookieStorage
import base64
import json
"""
`cirrina` - Opinionated web framework
from aiohttp_jrpc import decode
from functools import wraps
from validictory import validate, ValidationError, SchemaError
Package file
def rpc_valid(schema=None):
""" Validation data by specific validictory configuration """
def dec(fun):
@wraps(fun)
def d_func(self, ctx, session, data, *a, **kw):
try:
validate(data['params'], schema)
except ValidationError as err:
raise InvalidParams(err)
except SchemaError as err:
raise InternalError(err)
return fun(self, ctx, session, data['params'], *a, **kw)
return d_func
return dec
:license: LGPL, see LICENSE for details
"""

class Server():

login_html = '''<!DOCTYPE HTML>
<html>
<body>
<form method="post" action="/login">
User name:<br/>
<input type="text" name="username"><br/>
User password:<br/>
<input type="password" name="password"><br/>
<input type="hidden" name="path" value="%s">
<input type="submit" value="Login"><br/>
</form>
</body>
</html>
'''

def __init__(self, bind, port):
self.bind = bind
self.port = port
self.loop = asyncio.get_event_loop()
self.app = web.Application(loop=self.loop) #, middlewares=[session_middleware])

fernet_key = fernet.Fernet.generate_key()
secret_key = base64.urlsafe_b64decode(fernet_key)
setup(self.app, EncryptedCookieStorage(secret_key))
self.GET ("/login", self._login)
self.POST("/login", self._auth)
self.login_html = Server.login_html
self.authenticate = self.dummy_auth
self.websockets = []

# decorator
def authenticated(func):
async def func_wrapper(self, request):
session = await get_session(request)
if session.new:
response = web.Response(status=302)
response.headers['Location'] = '/login?path='+request.path_qs
return response
return await func(self, request, session)
return func_wrapper

async def _start(self):
self.srv = await self.loop.create_server(self.app.make_handler(), self.bind, self.port)

async def _login(self, request):
resp = web.Response(text=(self.login_html%(request.GET.get('path', "/"))), content_type="text/html")
return resp

async def dummy_auth(self, username, password):
if username == 'test' and password == 'test':
return True
return False

async def _auth(self, request):
session = await get_session(request)
await request.post()
username = request.POST.get('username')
password = request.POST.get('password')
if username and password:
if await self.authenticate(username, password):
print("User authenticated:", username)
session['username'] = username
response = web.Response(status=302)
response.headers['Location'] = request.POST.get('path', "/")
return response

print("User authentication failed:", 'username')
response = web.Response(status=302)
response.headers['Location'] = '/login'
session.invalidate()
return response

async def _ws_handler(self, request):
ws = web.WebSocketResponse()
await ws.prepare(request)

session = await get_session(request)
if session.new:
print("websocket: not logged in")
ws.send_str(json.dumps({'status': 401, 'text': "Unauthorized"}))
ws.close()
return ws

self.websockets.append(ws)

self.websocket_connected(ws, session)

async for msg in ws:
print("websocket got:", msg)
if msg.type == WSMsgType.TEXT:
self.websocket_message(ws, session, msg.data)
elif msg.type == WSMsgType.ERROR:
print('websocket closed with exception %s' %
ws.exception())

self.websockets.remove(ws)
self.websocket_closed(session)

return ws

def websocket_broadcast(self, msg):
for ws in self.websockets:
ws.send_str(msg)

def _rpc_handler(self):
class MyRPC(object):
cirrina = self

def __new__(cls, ctx):
""" Return on call class """
return cls.__run(cls, ctx)

@asyncio.coroutine
def __run(self, ctx):
""" Run service """
try:
data = yield from decode(ctx)
except ParseError:
return JError().parse()
except InvalidRequest:
return JError().request()
except InternalError:
return JError().internal()

try:
i_app = getattr(MyRPC.cirrina, data['method'])
i_app = asyncio.coroutine(i_app)
except Exception:
return JError(data).method()

session = yield from get_session(ctx)
try:
resp = yield from i_app(ctx, session, data)
except InvalidParams:
return JError(data).params()
except InternalError:
return JError(data).internal()

return JResponse(jsonrpc={
"id": data['id'], "result": resp
})

return MyRPC

def GET(self, location, handler):
self.app.router.add_route('GET', location, handler)

def POST(self, location, handler):
self.app.router.add_route('POST', location, handler)

def WS(self):
self.app.router.add_route('GET', "/ws", self._ws_handler)

def RPC(self, location):
self.app.router.add_route('POST', location, self._rpc_handler())

def STATIC(self, location, path):
self.app.router.add_static(location, path)

def run(self):
self.loop.run_until_complete(self._start())
print("Server started at http://%s:%d"%(self.bind, self.port))
try:
self.loop.run_forever()
except KeyboardInterrupt:
pass
self.loop.close()
print("done")
from .server import Server, rpc_valid

# expose server class
__all__ = ['Server', 'rpc_valid']

# define package metadata
__VERSION__ = '0.1.0'

0 comments on commit 8181bfb

Please sign in to comment.