Commit 01bf4636 by Oleksandr Barabash

init

parents
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# dotenv
.env
# virtualenv
.venv
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# idea ide
.idea
.DS_Store
**/.DS_Store
This diff is collapsed. Click to expand it.
# OpenRedPhone
Dependencies:
1. Tornado.
2. Simplejson
3. Futures
4. Tornadis
5. Momoko
6. Django (models)
What has to be added:
1. Add Static socket connection
2. Add Static socket handler
3. Create models
4. Create redis, pushserver, session clients.
5. Remove Django from dependencies.
from __future__ import unicode_literals
from info.s1z.utils.json_serializable import JsonSerializable
from info.s1z.utils.json_serializable import json_property
from .account_device import AccountDevice
class AccountData(JsonSerializable):
@json_property(int, null=True)
def whitelisted(self): pass
@json_property(bytes)
def number(self): pass
@json_property([AccountDevice], AccountDevice)
def devices(self): pass
@json_property(bytes)
def identity_key(self): pass
def get_devices(self):
return self.devices
def get_device(self, _id):
# type: (int) -> AccountDevice
for device in self.devices:
if _id == device.id:
return device
from __future__ import unicode_literals
from info.s1z.utils.json_serializable import JsonSerializable
from info.s1z.utils.json_serializable import json_property
from .signed_pre_key import SignedPreKey
class AccountDevice(JsonSerializable):
@json_property(int)
def id(self): pass
@json_property(bytes, null=True)
def name(self): pass
@json_property(bytes)
def auth_token(self): pass
@json_property(bytes)
def salt(self): pass
@json_property(bytes)
def signaling_key(self): pass
@json_property(bytes, null=True)
def gcm_id(self): pass
@json_property(bytes, null=True)
def apn_id(self): pass
@json_property(bytes, null=True)
def voip_apn_id(self): pass
@json_property(int)
def push_timestamp(self): pass
@json_property(bool)
def fetches_messages(self): pass
@json_property(int)
def registration_id(self): pass
@json_property(SignedPreKey, SignedPreKey)
def signed_pre_key(self): pass
@json_property(int)
def last_seen(self): pass
@json_property(int)
def created(self): pass
@json_property(bool)
def voice(self): pass
@json_property(bytes)
def user_agent(self): pass
from __future__ import unicode_literals
from info.s1z.utils.json_serializable import JsonSerializable
from info.s1z.utils.json_serializable import json_property
class SignedPreKey(JsonSerializable):
@json_property(int)
def key_id(self): pass
@json_property(bytes)
def public_key(self): pass
@json_property(bytes)
def signature(self): pass
from __future__ import unicode_literals
import tornado.gen
from hashlib import sha1
from info.s1z.utils.log import Log
from info.s1z.utils.jsonutil import JsonUtil
from info.s1z import apsql as models
from entities.json.account_data import AccountData
from entities.json.account_device import AccountDevice
class Account(models.Model):
id = None # type: int
number = None # type: bytes
data = None # type: AccountData
devices = None # type: list
_all_fields = ["id", "number", "data", "devices"]
def __init__(self, _id, number, data):
# type: (int, bytes, dict) -> None
self.id = _id
self.number = number
self.data = JsonUtil.from_json(data, AccountData)
self.devices = self.data.get_devices()
def get_device(self, _id):
# type: (int) -> AccountDevice
return self.data.get_device(_id)
def auth(self, passw, count=None):
# TODO(s1z): Please IMPLEMENT ME :)
return True
#type = "otp" if count is not None else "basic"
#for device in self.devices:
# salt = device.salt
# if self.otp == sha1(device.salt+passw).hexdigest():
# return True
#return False
@classmethod
@tornado.gen.coroutine
def get(cls, **kwargs):
request, params = cls.get_select(*cls._all_fields, **kwargs)
result = yield cls.execute(request, params)
if result is None:
raise cls.DoesNotExist()
raise tornado.gen.Return(cls._object(result))
import tornado.gen
import tornado.web
from info.s1z.utils.log import Log
from .static_handler import StaticSocketHandler
TAG = "MainHandler"
class MainHandler(StaticSocketHandler):
_active_session = None # type: int # Do we need it here?
account = None # type: Account # TODO(s1z): Create a model
# Managers
relay = None
push = None
session = None
managers = ["relay", "push", "session"]
SUPPORTED_METHODS = (
'GET', 'DELETE', 'RING', 'BUSY'
)
def initialize(self, *args, **kwargs):
try:
for client_name in self.managers:
setattr(self, client_name, kwargs.pop(client_name))
except KeyError:
raise AttributeError(
"Clients must be set (%s)" % ','.join(self.managers)
)
@tornado.gen.coroutine
def handle_create(self, device_id, number):
Log.d(TAG, "handle_create: %r, %r" % (device_id, number))
self.set_status(200)
self.finish()
@tornado.gen.coroutine
def handle_ring(self, device_id, number):
self.set_status(200)
self.finish()
@tornado.gen.coroutine
def handle_busy(self, device_id, number):
self.set_status(200)
self.finish()
@tornado.gen.coroutine
def handle_delete(self, device_id, number):
self.set_status(200)
self.finish()
@StaticSocketHandler.static
def get(self, device_id, number):
Log.d(TAG, "get")
return self.handle_create(device_id, number)
@StaticSocketHandler.static
def ring(self, device_id, number):
Log.d(TAG, "ring")
return self.handle_ring(device_id, number)
@StaticSocketHandler.static
def busy(self, device_id, number):
Log.d(TAG, "busy")
return self.handle_busy(device_id, number)
@StaticSocketHandler.static
def delete(self, device_id, number):
Log.d(TAG, "delete")
return self.handle_delete(device_id, number)
def __del__(self):
# Will not be invoke if there is a memory leak somewhere
# TODO(s1z): Remove it when everything is done
Log.d(TAG, b"__del__ %r" % MainHandler.__name__)
# -*- coding: utf-8 -*-
import tornado.escape
import tornado.web
import tornado.gen
import tornado.httputil
from utils.static_socket_connection import StaticSocketConnection
from info.s1z.utils.log import Log
TAG = "StaticSocketHandler"
class StaticSocketHandler(tornado.web.RequestHandler):
_status_code = None # type: int
_static_connection = None # type: StaticSocketConnection
open_args = None # type: tuple
open_kwargs = None # type: dict
is_static = False # type: bool
is_closed = False # type: bool
@staticmethod
def static(real_func):
def basic_auth(wrap_func):
@tornado.web.asynchronous
def deco(self, *args, **kwargs):
def callback():
return wrap_func(self, *args, **kwargs)
self.auth(callback)
return deco
@basic_auth
def wrapper(self, *args, **kwargs):
if not self.is_static:
self.start_static()
real_func(self, *args, **kwargs)
return wrapper
@tornado.gen.coroutine
def auth(self, callback):
Log.d(TAG, "auth")
if "Authorization" in self.request.headers:
login, passw = get_loginpass(self.request.headers["Authorization"])
if login == self.login and passw == self.passw:
callback()
return
#try:
# raise NotImplementedError()
#except NotImplementedError as e:
# Log.e(TAG, "auth is not implemented", e)
#self.r500()
def start_static(self, *args, **kwargs):
self.open_args = args
self.open_kwargs = kwargs
self.is_static = True
self.is_closed = False
self._static_connection = StaticSocketConnection(self)
self._static_connection.add_on_open_callback(self.on_open)
self._static_connection.add_on_close_callback(self.on_close)
self._static_connection.accept_connection()
def on_open(self, *args, **kwargs):
Log.d(TAG, "on_open %r" % id(self))
def on_close(self, *args, **kwargs):
Log.d(TAG, "on_open %r" % id(self))
if not self.is_closed and self.is_static:
self.is_closed = True
if self._static_connection is not None:
self._static_connection.abort()
self._static_connection = None
super(StaticSocketHandler, self)._break_cycles()
def static_flush(self, include_footers=False, callback=None):
chunk = b"".join(self._write_buffer)
self._write_buffer = []
for transform in self._transforms:
self._status_code, self._headers, chunk = \
transform.transform_first_chunk(
self._status_code, self._headers,
chunk, include_footers)
if self.request.method == "HEAD":
chunk = None
if hasattr(self, "_new_cookie"):
for cookie in self._new_cookie.values():
self.add_header("Set-Cookie", cookie.OutputString(None))
start_line = tornado.httputil.ResponseStartLine(self.request.version,
self._status_code,
self._reason)
if self._static_connection:
self._static_connection.write_headers(
start_line, self._headers, chunk
)
return self._static_connection._stream.write(
chunk, callback=callback
)
def static_finish(self, chunk=None):
if self._status_code == 304:
assert not self._write_buffer, "Cannot send body with 304"
self._clear_headers_for_304()
elif "Content-Length" not in self._headers:
content_length = sum(len(part) for part in self._write_buffer)
self.set_header("Content-Length", content_length)
self.static_flush(include_footers=True)
def finish(self, *args, **kwargs):
if not self.is_static:
super(StaticSocketHandler, self).finish(*args, **kwargs)
else:
self.static_finish(*args, **kwargs)
def close(self, *args, **kwargs):
self.finish(*args, **kwargs)
self.on_close()
def r400(self):
self.set_status(400, "Bad Request")
self.finish()
def r401(self):
self.set_status(401, "Unauthorized")
self.add_header("WWW-Authenticate", 'Basic realm="Unauthorized"')
self.finish()
def r402(self, data=None):
self.set_status(402)
self.write(data or b"Unknown error")
self.finish()
def r404(self):
self.set_status(404)
self.finish()
def r500(self):
self.set_status(500)
self.finish()
def __del__(self):
# Will not be invoke if there is a memory leak somewhere
# TODO(s1z): Remove it when everything is done
Log.d(TAG, "__del__ %s" % StaticSocketHandler.__name__)
# -*- encoding: utf-8 -*-
import momoko
import tornado.gen
import tornado.ioloop
class BaseModel(object):
class DoesNotExist(Exception):
pass
class UpdateFailed(Exception):
pass
class InsertError(Exception):
pass
class Model(BaseModel):
_table = None # this should be overwritten
_pool = None
_pool_read = None
#
_min_size = 5 # Minimal numbers of connections
_max_size = 100 # Max numbers of connections
_raise_c_errors = False
_reconnect_interval = 50 # milliseconds
_auto_shrink = True
#
host = None
port = None
login = None
password = None
db = None
@classmethod
def _object(cls, args):
for obj in args:
return cls(*obj)
@classmethod
def _objects(cls, args):
return [cls._object([x]) for x in args]
@classmethod
def _strip_count(cls, args):
for val in args:
for _id in val:
return _id
return 0
@classmethod
def get_returning_insert(cls, *returning, **fields):
str_fields = ','.join(fields.keys())
str_values = ','.join(['%s' for i in range(len(fields.keys()))])
str_returning = "id, {fields}".format(
fields=str_fields
) if len(returning) > 0 else ','.join(returning)
return (
" INSERT INTO {table} ({fields}) "
" VALUES ({values}) "
" RETURNING {returning} "
).format(
table=cls._table,
fields=str_fields,
values=str_values,
returning=str_returning
), fields.values()
@classmethod
def get_select(cls, *fields, **where):
return (
" SELECT {fields}"
" FROM {table} "
" WHERE {where} "
).format(
table=cls._table,
fields=','.join(fields),
where=' AND '.join(['{} = %s'.format(f) for f in where.keys()])
), where.values()
@staticmethod
def set_parameters(host, port, login, password, db):
Model.host = host
Model.port = port
Model.login = login
Model.password = password
Model.db = db
Model._pool = Model.current()
Model.connect()
@staticmethod
def set_read_parameters(host, port, login, password, db):
# TODO(s1z): Add Read only DB instance
Model.host = host
Model.port = port
Model.login = login
Model.password = password
Model.db = db
Model._pool_read = Model.current()
Model.connect()
@staticmethod
def new_instance():
pool = momoko.Pool(
dsn='dbname=%s user=%s password=%s host=%s port=%s' % (
Model.db,
Model.login,
Model.password,
Model.host,
Model.port
),
size=Model._min_size,
max_size=Model._max_size,
ioloop=tornado.ioloop.IOLoop.current(),
setsession=["SET TIME ZONE UTC"],
raise_connect_errors=Model._raise_c_errors,
auto_shrink=Model._auto_shrink
)
return pool
@staticmethod
def current():
current = getattr(Model, "_pool", None)
if current is None:
return Model.new_instance()
return current
@staticmethod
def connect():
if Model._pool is not None:
Model._pool.connect()
@staticmethod
def disconnect():
if Model._pool is not None and not Model._pool.closed:
# Warning: blocking method !!!
Model._pool.close()
@classmethod
@tornado.gen.coroutine
def execute(cls, *args):
if cls._pool is None:
raise IOError("Momoko not connected to DB instance!")
cursor = yield cls._pool.execute(*args)
if cursor.description:
# if there is a description in the cursor then we can fetch it
raise tornado.gen.Return(cursor.fetchall())
from __future__ import unicode_literals
import sys
import base64
if sys.version_info[0] == 3:
unicode = str
class Base64(object):
@staticmethod
def encode_without_padding(data):
# type: ([str, unicode, bytes, bytearray]) -> bytes
if isinstance(data, (str, unicode)):
data = data.encode("utf-8")
return base64.b64encode(bytes(data)).rstrip(b'=')
@staticmethod
def decode_without_padding(data):
# type: ([str, unicode, bytes, bytearray]) -> bytes
if isinstance(data, (str, unicode)):
data = data.encode("utf-8")
return base64.b64decode(bytes(data) + b'===')
@staticmethod
def encode(data):
# type: ([str, unicode, bytes, bytearray]) -> bytes
if isinstance(data, (str, unicode)):
data = data.encode("utf-8")
return base64.b64encode(bytes(data))
@staticmethod
def decode(data):
# type: ([str, unicode, bytes, bytearray]) -> bytes
if isinstance(data, (str, unicode)):
data = data.encode("utf-8")
return base64.b64decode(bytes(data))
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# TODO(s1z): fallback to json
# TODO(s1z): This is madness! You have to clean this shit :)
import sys
import simplejson as json
from .log import Log
try:
range = xrange
except ImportError:
# python 3 has no unicode
unicode = str
TAG = "JsonSerializable"
class DefaultSerializer(object):
@staticmethod
def loads(data):
return data
@staticmethod
def dumps(data):
return data
class JProperty(property):
value_type = None
serializer = None
null = None
def __init__(self, fget, fset, fdel, value_type, serializer, null, jsonize):
super(JProperty, self).__init__(fget, fset, fdel)
self.value_type = value_type
self.serializer = serializer
self.null = null
self.jsonize = jsonize
def get_value_type(self):
return self.value_type
def get_serializer(self):
return self.serializer
def get_null(self):
return self.null
def get_jsonize(self):
# type: (self) -> bool
return self.jsonize is True
def json_property(value_type, serializer=DefaultSerializer, null=False, j=True):
def check_type(self, name, value):
setattr(self, "_serialized_%s" % name, serializer.dumps(value))
return value
def wrap(f):
def getter(self):
return getattr(
self,
"_serializable_%s" % f.__name__,
None
) or f(self)
def setter(self, value):
setattr(self, "_serializable_%s" % f.__name__, value)
# TODO(s1z): Do we need to serialize in real time or when dumping?
setattr(
self, "_serialized_%s" % f.__name__, serializer.dumps(value)
)
def deleter(self):
delattr(self, "_serializable_%s" % f.__name__)
return JProperty(
getter, setter, deleter, value_type, serializer, null, j
)
return wrap
class MetaSerializable(type):
def __new__(cls, name, bases, attrs):
new_cls = super(MetaSerializable, cls).__new__(cls, name, bases, attrs)
bases_serializable = {}
for base in bases:
if hasattr(base, "_serializable"):
bases_serializable.update(base._serializable)
new_cls._serializable = bases_serializable
for k, v in attrs.items():
if isinstance(v, JProperty):
new_cls._serializable[k] = v
return new_cls
def __call__(cls, *args, **kwargs):
return type.__call__(cls, *args, **kwargs)
# TODO(s1z): Do we need to init default values ? I guess not
# if len(args) < 0 or len(kwargs) < 0:
# for k in obj._serializable:
# getattr(obj, k)
class JsonSerializable(object):
__metaclass__ = MetaSerializable
_serializable = {}
def _get_keys(self):
return self._serializable.keys()
def _get_value(self, key):
serialized = getattr(self, "_serialized_%s" % key, None)
return serialized if serialized is not None else getattr(self, key)
@classmethod
def _obj_dumps(cls, obj):
if isinstance(obj, JsonSerializable):
serialized = {}
for key in obj._get_keys():
p = cls._serializable.get(key, None) # type: JProperty
final_key = obj._unpythonize(key) if p.get_jsonize() else key
value = obj._get_value(key)
# TODO(s1z): Do we need to send if null ? Have to add setups
if value is not None:
serialized[final_key] = value
else:
if p is not None and p.get_null():
serialized[final_key] = None
else:
raise AttributeError("'%s':'%s'" % (key, value))
return serialized
@classmethod
def _dumps(cls, obj):
if type(obj) is list:
serialized = []
for data in obj:
serialized.append(cls._obj_dumps(data))
return serialized
return cls._obj_dumps(obj)
@classmethod
def _obj_loads(cls, data):
obj = cls.__new__(cls)
if isinstance(data, dict):
for k, v in data.items():
real_name = cls._pythonize(k)
p = cls._serializable.get(real_name, None)
if p is not None and isinstance(p, JProperty):
serializer = p.get_serializer()
real_value = serializer.loads(v)
setattr(obj, real_name, real_value)
Log.d(TAG, "_obj_loads: {'%s': '%s'}" % (
real_name, real_value
))
# TODO(s1z): Check if all required fields are set.
return obj
@classmethod
def _loads(cls, obj):
if type(obj) is list:
loaded = []
for data in obj:
loaded.append(cls._obj_loads(data))
return loaded
return cls._obj_loads(obj)
@staticmethod
def _unpythonize(s):
if isinstance(s, (str, unicode)) and len(s) > 0:
new_s = ''
for i in range(len(s)):
if i > 0 and s[i-1] == "_":
new_s += s[i].upper()
elif s[i] == "_":
pass
else:
new_s += s[i].lower()
return new_s
raise AttributeError("Incorrect type = '%s' or len < 1" % type(s))
@staticmethod
def _pythonize(s):
return ''.join(["_%s" % l.lower() if l.isupper() else l for l in s])
@classmethod
def to_json(cls, obj):
return json.dumps(obj, default=cls._dumps)
@classmethod
def from_json(cls, data):
return cls._loads(json.loads(data))
@classmethod
def dumps(cls, value):
return cls._dumps(value)
@classmethod
def loads(cls, value):
return cls._loads(value)
def to_string(self):
return "{}".format(self.to_json(self))
# -*- coding: utf-8 -*-
import simplejson as json
from .log import Log
TAG = "JsonUtil"
class JsonProcessingException(Exception):
pass
class JsonUtil(object):
@staticmethod
def to_json(obj):
if getattr(obj, "to_json", None) is not None:
return obj.__class__.to_json(obj)
raise Exception("%s is not serializable" % obj.__class__)
@staticmethod
def from_json(text, cls):
if getattr(cls, "from_json", None) is not None:
return cls.from_json(text)
raise JsonProcessingException("%s is not serializable" % cls)
@staticmethod
def json_loads(data, default=None):
try:
jdata = json.loads(data, strict=False)
if isinstance(jdata, dict) or isinstance(jdata, list):
return jdata
except Exception as e:
Log.e(TAG, "json_loads: Error = '%s'" % repr(e))
return default
@staticmethod
def json_dumps(*args, **kwargs):
try:
return json.dumps(*args, **kwargs)
except Exception as e:
Log.e(TAG, "json_dumps: Error = '%s'" % repr(e))
raise
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import traceback
import logging
class Log(object):
logger = logging.getLogger(__name__)
handler = logging.NullHandler()
handler.setLevel(logging.DEBUG)
logger.addHandler(handler)
@staticmethod
def log(level, tag, message, ex):
if ex is not None:
# exc_type, exc_value, exc_traceback = sys.exc_info()
message = "%s\n%s" % (message, traceback.format_exc())
msg = "{}::{}".format(tag, message)
Log.logger.log(level, msg)
@staticmethod
def d(tag, message, ex=None):
return Log.log(logging.DEBUG, tag, message, ex)
@staticmethod
def i(tag, message, ex=None):
return Log.log(logging.INFO, tag, message, ex)
@staticmethod
def w(tag, message, ex=None):
return Log.log(logging.WARN, tag, message, ex)
@staticmethod
def e(tag, message, ex=None):
return Log.log(logging.ERROR, tag, message, ex)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import print_function
from hashlib import new
# TODO(s1z): This was copied from newer version of python or django.
# TODO(s1z): Add the source where is was copied from.
try:
range = xrange
except ImportError:
pass
try:
# OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA
from _hashlib import pbkdf2_hmac
except ImportError:
import binascii
import struct
_trans_5C = b"".join(chr(x ^ 0x5C) for x in range(256))
_trans_36 = b"".join(chr(x ^ 0x36) for x in range(256))
def pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None):
"""Password based key derivation function 2 (PKCS #5 v2.0)
This Python implementations based on the hmac module about as fast
as OpenSSL's PKCS5_PBKDF2_HMAC for short passwords and much faster
for long passwords.
"""
if not isinstance(hash_name, str):
raise TypeError(hash_name)
if not isinstance(password, (bytes, bytearray)):
password = bytes(password)
if not isinstance(salt, (bytes, bytearray)):
salt = bytes(salt)
# Fast inline HMAC implementation
inner = new(hash_name)
outer = new(hash_name)
blocksize = getattr(inner, 'block_size', 64)
if len(password) > blocksize:
password = new(hash_name, password).digest()
password = password + b'\x00' * (blocksize - len(password))
inner.update(password.translate(_trans_36))
outer.update(password.translate(_trans_5C))
def prf(msg, inner=inner, outer=outer):
# PBKDF2_HMAC uses the password as key. We can re-use the same
# digest objects and just update copies to skip initialization.
icpy = inner.copy()
ocpy = outer.copy()
icpy.update(msg)
ocpy.update(icpy.digest())
return ocpy.digest()
if iterations < 1:
raise ValueError(iterations)
if dklen is None:
dklen = outer.digest_size
if dklen < 1:
raise ValueError(dklen)
hex_format_string = "%%0%ix" % (new(hash_name).digest_size * 2)
dkey = b''
loop = 1
while len(dkey) < dklen:
prev = prf(salt + struct.pack(b'>I', loop))
rkey = int(binascii.hexlify(prev), 16)
for i in range(iterations - 1):
prev = prf(prev)
rkey ^= int(binascii.hexlify(prev), 16)
loop += 1
dkey += binascii.unhexlify(hex_format_string % rkey)
return dkey[:dklen]
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import tornado.httpserver
import tornado.ioloop
import tornado.web
import logging
import settings as s
from info.s1z.utils.log import Log
from info.s1z import apsql as models
from handlers.main_handler import MainHandler
from utils.auth_manager import AuthManager
from utils.redis_client import RedisClient
TAG = "Main"
def main():
psql = models.Model.set_parameters(**s.POSTGRES)
redis = RedisClient(**s.REDIS)
initializer = dict(
relay=object(),
push=object(),
session=object(),
auth=AuthManager(redis)
)
application = tornado.web.Application([
(
r'^/session/(?P<device_id>\d+)/(?P<number>[0-9\+]{3,15})$',
MainHandler,
initializer
)
])
server = tornado.httpserver.HTTPServer(application)
server.listen(s.SERVER_PORT, s.SERVER_HOST)
server.start()
Log.d(TAG, "OpenRedPhoneServer is run")
if __name__ == "__main__":
io_loop = tornado.ioloop.IOLoop.current()
try:
logging_config = {
"format": "%(asctime)-23s %(levelname)8s: %(message)s",
"level": logging.DEBUG,
}
logging.basicConfig(**logging_config)
main()
io_loop.start()
except KeyboardInterrupt:
io_loop.stop()
tornado>=4.5.1
simplejson>=3.11.1
futures>=3.0.5
tornadis>=0.8.0
momoko>=2.2.4
django>=2.0.0
\ No newline at end of file
from __future__ import unicode_literals
SERVER_HOST = "0.0.0.0"
SERVER_PORT = 31337
POSTGRES = dict(
host="127.0.0.1",
port=5432,
login="login",
password="password",
db="db"
)
REDIS = dict(
host="localhost",
port=6379,
login=None,
password=None,
db=0
)
from __future__ import unicode_literals
import sys
import tornado.gen
from info.s1z.utils.log import Log
from info.s1z.utils.base_64 import Base64
from utils.redis_client import RedisClient
if sys.version_info[0] == 3:
unicode = str
TAG = "AuthManager"
class AuthManager(object):
def __init__(self, redis):
# type: (RedisClient) -> None
self.redis = redis
@tornado.gen.coroutine
def authenticate(self, headers):
# type: (dict) -> Account or None
for header_name in ["Authorization", "WWW-Authorization"]:
auth_data = headers.get(header_name, None)
if auth_data is not None:
try:
auth_type, token = auth_data.split(" ")
auth_method = {
"otp": self.auth_otp,
"basic": self.auth_basic
}.get(auth_type.lower(), None)
if auth_method is not None:
raise tornado.gen.Return((
yield auth_method(
Base64.decode(token).decode("utf-8")
)
))
except Exception as e:
Log.e(TAG, "authenticate error", e)
@tornado.gen.coroutine
def auth_basic(self, token):
# type: (unicode) -> Account or None
login, passw = token.split(":")
account = yield self.redis.get(login)
if account is not None:
pass
def auth_otp(self, token):
# type: (unicode) -> Account or None
login, passw, otp = token.split(":")
account = yield self.redis.get(login)
if account is not None:
pass
import tornadis
import tornado.gen
import concurrent.futures
from info.s1z.utils.log import Log
TAG = "RedisClient"
class RedisClient(object):
CONNECTION_TIMEOUT = 10 # type: int
host = None # type: bytes
port = None # type: int
login = None # type: bytes
password = None # type: bytes
db = None # type: int
is_running = None # type: bool
_client = None # type: tornadis.Client
def __init__(self, host=b"localhost", port=6379,
login=None, password=None, db=0):
self.host = host
self.port = port
self.login = login
self.password = password
self.db = db
self._client = None
def start(self):
self.is_running = True
if self._client is None:
self._client = tornadis.Client(
autoconnect=False,
password=self.password,
db=self.db,
**dict(
read_callback=self.on_connected_callback,
close_callback=self.on_close_callback,
host=self.host,
port=self.port,
tcp_nodelay=True,
connect_timeout=self.CONNECTION_TIMEOUT,
read_timeout=self.CONNECTION_TIMEOUT,
)
)
self.connect()
def stop(self):
self.is_running = False
self.disconnect()
@tornado.gen.coroutine
def connect(self):
if self._client is not None:
while not (yield self._client.connect()):
Log.e(TAG, "client connect error. retry after 1 sec")
yield tornado.gen.sleep(1)
raise tornado.gen.Return()
raise AttributeError("tornadis client is None!")
@tornado.gen.coroutine
def disconnect(self):
if self._client is not None and self._client.is_connected():
self._client.disconnect()
@tornado.gen.coroutine
def on_connected_callback(self):
Log.d(TAG, "on_connected_callback")
@tornado.gen.coroutine
def on_close_callback(self):
Log.d(TAG, "on_connected_callback")
if self.is_running is True:
self._client.disconnect()
yield self.connect()
@tornado.gen.coroutine
def set(self, key, value):
# type: (bytes, bytes) -> bool
if self._client.is_connected() and self.is_running is True:
result = yield self._client.call("SET", key, value)
if not isinstance(result, tornadis.ConnectionError):
raise tornado.gen.Return(True)
raise tornado.gen.Return(False)
@tornado.gen.coroutine
def setex(self, key, ttl, value):
# type: (bytes, int, bytes) -> bool
if self._client.is_connected() and self.is_running is True:
result = yield self._client.call("SETEX", key, ttl, value)
if not isinstance(result, tornadis.ConnectionError):
raise tornado.gen.Return(True)
raise tornado.gen.Return(False)
@tornado.gen.coroutine
def get(self, key):
# type: (bytes) -> bytes or None
if self._client.is_connected() and self.is_running is True:
result = yield self._client.call(b"GET", key)
if isinstance(result, tornadis.ConnectionError):
raise result
raise tornado.gen.Return(result)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import tornado.gen
import tornado.iostream
import tornado.httputil as httputil
from tornado.escape import native_str, utf8
from info.s1z.utils.log import Log
TAG = "StaticSocketConnection"
try:
range = xrange
except NameError:
pass
class StaticSocketConnection(object):
_handler = None
_stream = None
_server_terminated = False
_on_open_callbacks = []
_on_close_callbacks = []
is_closed = False
def __init__(self, handler):
# type: (StaticSocketHandler) -> None
self._handler = handler
self._stream = handler.request.connection.detach()
self._stream.set_close_callback(self.on_close)
def abort(self):
if not self.is_closed:
self.is_closed = True
self._handler = None
self._stream.close()
def clear_callbacks(self):
self._on_open_callbacks = []
self._on_close_callbacks = []
def add_on_open_callback(self, callback):
self._on_open_callbacks.append(callback)
def add_on_close_callback(self, callback):
self._on_close_callbacks.append(callback)
@staticmethod
def _execute_callbacks(queue, *args, **kwargs):
# type: (list, tuple, dict) -> None
for i in range(len(queue)):
callback = queue.pop()
try:
callback(*args, **kwargs)
except Exception as e:
Log.e(TAG, b"callback execute error", e)
Log.d(TAG, b"callbacks execution done")
def on_close(self, *args, **kwargs):
self._execute_callbacks(self._on_close_callbacks, *args, **kwargs)
def on_open(self, *args, **kwargs):
self._execute_callbacks(self._on_open_callbacks, *args, **kwargs)
def accept_connection(self):
if self._stream.closed():
return self.abort()
self.on_open(*self._handler.open_args, **self._handler.open_kwargs)
self._receive_data()
def write_headers(self, start_line, headers, chunk=None, callback=None):
lines = [b" ".join([b'%r' % x for x in [
start_line.version, start_line.code, start_line.reason
]])]
for name, value in headers.items():
lines.append(b"%r: %r" % (name, value))
if not self._stream.closed():
data = utf8(b"\r\n".join(lines) + b"\r\n\r\n")
self._stream.write(data)
@tornado.gen.coroutine
def _handle_request(self, data):
try:
start_line, headers = self.parse_headers(data)
request = httputil.HTTPServerRequest(
headers=headers,
start_line=httputil.parse_request_start_line(start_line)
)
deli = self._handler.application.find_handler(request)
getattr(
self._handler, request.method.lower(), self._handler.r404
)(*deli.path_args, **deli.path_kwargs)
except httputil.HTTPInputError as e:
Log.e(TAG, b"_handle_request error", e)
@tornado.gen.coroutine
def _handle_response(self, data):
try:
response = httputil.parse_response_start_line(data)
Log.d(TAG, b"_handle_response %r" % response)
except httputil.HTTPInputError as e:
Log.e(TAG, b"_handle_response error", e)
@tornado.gen.coroutine
def _handle_data(self, data):
if b"HTTP" in data[:4]:
# response
yield self._handle_response(data)
else:
# try read request
yield self._handle_request(data)
self._receive_data()
@tornado.gen.coroutine
def _receive_data(self):
if not self.is_closed:
try:
data_future = self._stream.read_until_regex(
b"\r?\n\r?\n",
max_bytes=65536
)
# TODO(s1z): Do we need a timeout here? I Think we need.
data = yield data_future
yield self._handle_data(data)
except tornado.iostream.StreamClosedError:
self.abort()
@staticmethod
def parse_headers(data):
data = native_str(data.decode('latin1')).lstrip(b"\r\n")
eol = data.find(b"\n")
start_line = data[:eol].rstrip(b"\r")
try:
headers = httputil.HTTPHeaders.parse(data[eol:])
except ValueError:
raise httputil.HTTPInputError(
b"Malformed HTTP headers: %r" % data
)
return start_line, headers
def __del__(self):
# Will not be invoke if there is a memory leak somewhere
# TODO(s1z): Remove it when everything is done
Log.d(TAG, "__del__ %s" % StaticSocketConnection.__name__)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment