first commit

This commit is contained in:
yumoqing 2019-07-10 17:34:45 +08:00
commit 16999f6b0b
22 changed files with 1434 additions and 0 deletions

1
README.md Normal file
View File

@ -0,0 +1 @@
# ahserver

5
ah.py Normal file
View File

@ -0,0 +1,5 @@
from ahserver.configuredServer import ConfiguredServer
if __name__ == '__main__':
server = ConfiguredServer()
server.run()

0
ahserver/__init__.py Executable file
View File

70
ahserver/auth_api.py Normal file
View File

@ -0,0 +1,70 @@
from aiohttp_auth import auth
from os import urandom
from aiohttp import web
import aiohttp_session
from aiohttp_session import get_session, session_middleware
from aiohttp_session.cookie_storage import EncryptedCookieStorage
class AuthAPI:
def setupAuth(self,app):
# setup session middleware in aiohttp fashion
storage = EncryptedCookieStorage(urandom(32))
aiohttp_session.setup(app, storage)
# Create an auth ticket mechanism that expires after 1 minute (60
# seconds), and has a randomly generated secret. Also includes the
# optional inclusion of the users IP address in the hash
policy = auth.SessionTktAuthentication(urandom(32), 60,
include_ip=True)
# setup aiohttp_auth.auth middleware in aiohttp fashion
auth.setup(app, policy)
app.middlewares.append(self.checkAuth)
app.router.add_route('POST','/login',self.login)
app.router.add_route('GET', '/logout', self.logout)
async def login(self,request):
params = await request.post()
user_id = params.get('user',None)
password = params.get('password',None)
from_path = params.get('from_path',None)
if self.checkUserPassword(user_id,password):
await auth.remember(request, user)
return web.HpptFound(from_path)
raise web.HTTPUnauthorized()
async def logout(self,request):
await auth.forget(request)
return web.REsponse(body='OK'.encode('utf-8'))
@web.middleware
async def checkAuth(self,request,handler):
path = request.path
print(f'*****{path} checkAuth called********')
if not await self.needAuth(path):
return await handler(request)
user = await auth.get_auth(request)
if user is None:
raise web.HTTPFound(f'/login_form?from_path={path}')
user_perms = await self.getUserPermission(user)
need_perm = await self.getPermissionNeed(path)
if need_perm in user_perms:
return await handler(request)
print(f'**{path} forbidden**')
raise web.HTTPForbidden()
async def needAuth(self,path):
if path in ['/','/header.tmpl','footer.tmpl','/login','/login_form','/index.tmpl',]:
return False
return False
async def getPermissionNeed(self,path):
return 'admin'
async def checkUserPassword(self,user_id,password):
return True
async def getUserPermissions(self,user):
return ['admin','view']

191
ahserver/baseProcessor.py Normal file
View File

@ -0,0 +1,191 @@
import os
import re
import json
import codecs
from aiohttp.web_request import Request
from aiohttp.web_response import Response, StreamResponse
from jinja2 import Template,Environment,BaseLoader
from appPublic.jsonConfig import getConfig
from appPublic.dictObject import DictObject
from appPublic.folderUtils import listFile
from .serverenv import ServerEnv
class ObjectCache:
def __init__(self):
self.cache = {}
def store(self,path,obj):
o = self.cache.get(path,None)
if o is not None:
try:
del o.cached_obj
except:
pass
o = DictObject()
o.cached_obj = obj
o.mtime = os.path.getmtime(path)
self.cache[path] = o
def get(self,path):
o = self.cache.get(path)
if o:
if os.path.getmtime(path) > o.mtime:
return None
return o.cached_obj
return None
class BaseProcessor:
@classmethod
def isMe(self,name):
return name=='base'
def __init__(self,path,resource):
self.path = path
self.resource = resource
self.retResponse = None
self.last_modified = os.path.getmtime(path)
self.content_length = os.path.getsize(path)
self.headers = {
'Content-Type': 'text/html',
'Content-Length': str(self.content_length),
'Accept-Ranges': 'bytes'
}
self.content = ''
async def handle(self,request):
config = getConfig()
await self.datahandle(request)
if self.retResponse is not None:
return self.retResponse
if type(self.content) == type({}):
self.content = json.dumps(self.content,
indent=4)
if type(self.content) == type([]):
self.content = json.dumps(self.content,
indent=4)
self.setheaders()
return Response(text=self.content,headers=self.headers)
async def datahandle(self,txt,request):
print('*******Error*************')
self.content=''
def setheaders(self):
self.headers['Content-Length'] = str(len(self.content))
class TemplateProcessor(BaseProcessor):
@classmethod
def isMe(self,name):
return name=='tmpl'
async def datahandle(self,request):
path = request.path
g = ServerEnv()
ns = DictObject()
ns.update(g)
ns.update(self.resource.y_env)
ns.request = request
ns.ref_real_path = self.path
te = g.tmpl_engine
self.content = te.render(path,**ns)
#self.content = await te.render_async(path,**ns)
def setheaders(self):
super(TemplateProcessor,self).setheaders()
if self.path.endswith('.tmpl.css'):
self.headers['Content-Type'] = 'text/css; utf-8'
elif self.path.endswith('.tmpl.js'):
self.headers['Content-Type'] = 'application/javascript ; utf-8'
else:
self.headers['Content-Type'] = 'text/html; utf-8'
class PythonScriptProcessor(BaseProcessor):
@classmethod
def isMe(self,name):
return name=='dspy'
def loadScript(self):
data = ''
with codecs.open(self.path,'rb','utf-8') as f:
data = f.read()
b= ''.join(data.split('\r'))
lines = b.split('\n')
lines = ['\t' + l for l in lines ]
txt = "async def myfunc(request,**ns):\n" + '\n'.join(lines)
return txt
async def datahandle(self,request):
g = ServerEnv()
lenv = {}
lenv.update(g)
lenv.update(self.resource.y_env)
if not g.get('dspy_cache',False):
g.dspy_cache = ObjectCache()
func = g.dspy_cache.get(self.path)
if not func:
txt = self.loadScript()
exec(txt,lenv,lenv)
func = lenv['myfunc']
print('func=',func)
g.dspy_cache.store(self.path,func)
self.content = await func(request,**lenv)
class MarkdownProcessor(BaseProcessor):
@classmethod
def isMe(self,name):
return name=='md'
async def datahandle(self,request:Request):
data = ''
with codecs.open(self.path,'rb','utf-8') as f:
data = f.read()
b = data
b = self.urlreplace(b,request)
ret = {
"__widget__":"markdown",
"data":{
"md_text":b
}
}
config = getConfig()
self.content = json.dumps(ret,indent=4)
def urlreplace(self,mdtxt,request):
def replaceURL(s):
p1 = '\[.*?\]\((.*?)\)'
url = re.findall(p1,s)[0]
txts = s.split(url)
url = self.resource.absUrl(request,url)
return url.join(txts)
p = '\[.*?\]\(.*?\)'
textarray = re.split(p,mdtxt)
links = re.findall(p,mdtxt)
newlinks = [ replaceURL(link) for link in links]
if len(links)>0:
mdtxt = ''
for i in range(len(newlinks)):
mdtxt = mdtxt + textarray[i]
mdtxt = mdtxt + newlinks[i]
mdtxt = mdtxt + textarray[i+1]
return mdtxt
def getProcessor(name):
return _getProcessor(BaseProcessor,name)
def _getProcessor(kclass,name):
for k in kclass.__subclasses__():
if not hasattr(k,'isMe'):
continue
if k.isMe(name):
return k
a = _getProcessor(k,name)
if a is not None:
return a
return None

View File

@ -0,0 +1,57 @@
import os,sys
import ssl
from aiohttp import web
from appPublic.folderUtils import ProgramPath
from appPublic.jsonConfig import getConfig
from sqlor.dbpools import DBPools
from .processorResource import ProcessorResource
from .auth_api import AuthAPI
from .myTE import setupTemplateEngine
from .globalEnv import initEnv
class ConfiguredServer:
def __init__(self):
pp = ProgramPath()
workdir = pp
if len(sys.argv) > 1:
workdir = sys.argv[1]
config = getConfig(workdir,{'workdir':workdir,'ProgramPath':pp})
if config.databases:
DBPools(config.databases)
initEnv()
setupTemplateEngine()
self.app = web.Application()
auth = AuthAPI()
auth.setupAuth(self.app)
self.configPath(config)
def run(self):
config = getConfig()
ssl_context = None
if config.website.ssl:
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain(config.website.ssl.crtfile,
config.website.ssl.keyfile)
web.run_app(self.app,host=config.website.host or '0.0.0.0',
port=config.website.port or 8080,
ssl_context=ssl_context)
def configPath(self,config):
for p,prefix in config.website.paths:
res = ProcessorResource(prefix,p,show_index=True,
follow_symlinks=True)
res.setProcessors(config.website.processors or {})
res.setIndexes(config.website.indexes or [])
self.app.router.register_resource(res)
def addProcessors(self,config,resource):
for subfix,processorname in config.website.processors:
resource.addProcessor(subfix,processorname)
return resource
def addIndexes(self,res,indexes):
res.indexes = indexes
return res

67
ahserver/dsProcessor.py Executable file
View File

@ -0,0 +1,67 @@
import codecs
import json
from appPublic.jsonConfig import getConfig
from appPublic.dictObject import DictObject
from .baseProcessor import BaseProcessor
from .serverenv import ServerEnv
class DataSourceProcessor(BaseProcessor):
@classmethod
def isMe(self,name):
return name=='ds'
def __init__(self,filename,k):
super(DataSourceProcessor,self).__init__(filename,k)
self.actions = {
'getdata':self.getData,
'pagingdata':self.getPagingData,
'arguments':self.getArgumentsDesc,
'resultFields':self.getDataDesc,
'gridlist':self.getGridlist,
}
self.g = ServerEnv()
def getData(self,dict_data,ns,request):pass
def getPagingData(self,dict_data,ns,request):pass
def getArgumentsDesc(self,dict_data,ns,request):pass
def getDataDesc(self,dict_data,ns,request):pass
def getGridlist(self,dict_data,ns,request):
ret = self.getDataDesc(dict_data,ns,request)
ffs = [ f for f in ret if f.get('frozen',False) ]
fs = [ f for f in ret if not f['frozen'] ]
[ f.update({'hide':True}) for f in ffs if f.get('listhide',False) ]
[ f.update({'hide':True}) for f in fs if f.get('listhide') ]
d = {
"iconCls":"icon-search",
"url":self.resource.absUrl(request,request.path + '?action=pagingdata'),
"view":"bufferview",
"options":{
"pageSize":50,
"pagination":False
}
}
d.update({'fields':fs})
if len(ffs)>0:
d.update({'ffields':ffs})
ret = {
"__ctmpl__":"datagrid",
"data":d
}
return ret
async def datahandle(self,request):
dict_data = {}
config = getConfig()
with codecs.open(self.path,'r',config.website.coding) as f:
b = f.read()
dict_data = json.loads(b)
ns = DictObject()
g = ServerEnv()
ns.update(g)
ns.update(self.resource.y_env)
ns.update(self.resource.getGetArgs(request))
act = ns.get('action','getdata')
action = self.actions.get(act)
self.content = action(dict_data,ns,request)

47
ahserver/filestorage.py Executable file
View File

@ -0,0 +1,47 @@
# fileUpload.py
import os
import time
import tempfile
import aiofile
from appPublic.folderUtils import _mkdir
from appPublic.jsonConfig import getConfig
class FileStorage:
def __init__(self):
config = getConfig()
self.root = config.filesroot or tempfile.gettempdir()
def realPath(self,path):
if path[0] == '/':
path = path[1:]
p = os.path.join(self.root,path)
return p
def _name2path(name):
name = os.path.basename(name)
paths=[191,193,197,199,97]
v = int(time.time()*1000000)
# b = name.encode('utf8') if not isinstance(name,bytes) else name
# v = int.from_bytes(b,byteorder='big',signed=False)
path = os.path.abspath(os.path.join(self.root,
v % paths[0],
v % paths[1],
v % paths[2],
v % paths[3],
v % paths[4],
name))
return path
async def save(name,read_data):
p = self.name2path(name)
_mkdir(os.path.dirname(p))
async with aiofile.open(p,mode='rb') as f:
while 1:
d = await read_data()
if not d:
break
await f.write(d)
return p[len(self.root):]

173
ahserver/globalEnv.py Executable file
View File

@ -0,0 +1,173 @@
# -*- coding:utf8 -*-
import os
import sys
import codecs
from urllib.parse import quote
import json
import random
import time
import datetime
from openpyxl import Workbook
from tempfile import mktemp
from appPublic.jsonConfig import getConfig
from appPublic.Singleton import GlobalEnv
from appPublic.argsConvert import ArgsConvert
from appPublic.timeUtils import str2Date,str2Datetime,curDatetime,getCurrentTimeStamp
from appPublic.folderUtils import folderInfo
from appPublic.uniqueID import setNode,getID
from appPublic.unicoding import unicoding,uDict,uObject
from appPublic.Singleton import SingletonDecorator
from sql.crud import _CRUD,CRUD
# from sql.sqlorAPI import DBPools,runSQL,runSQLPaging,runSQLIterator
# from sql.sqlorAPI import getTables,getTableFields,getTablePrimaryKey
# from sql.sqlorAPI import getTableForignKeys,runSQLResultFields
from sqlor.dbpools import DBPools
from .xlsxData import XLSXData
from .uriop import URIOp
from .serverenv import ServerEnv
def data2xlsx(rows,headers=None):
wb = Workbook()
ws = wb.active
i = 1
if headers is not None:
for j in range(len(headers)):
v = headers[j].title if headers[j].get('title',False) else headers[j].name
ws.cell(column=j+1,row=i,value=v)
i += 1
for r in rows:
for j in range(len(r)):
v = r[headers[j].name]
ws.cell(column=j+1,row=i,value=v)
i += 1
name = mktemp(suffix='.xlsx')
wb.save(filename = name)
wb.close()
return name
class FileOutZone(Exception):
def __init__(self,fp,*args,**kwargs):
super(FileOutZone,self).__init__(*args,**kwargs)
self.openfilename = fp
def __str__(self):
return self.openfilename + ': not allowed to open'
def openfile(url,m):
fp = abspath(url)
if fp is None:
print('openfile(',url,m,'),url is not match a file')
raise Exception('url can not mathc a file')
config = getConfig()
paths = [ os.path.abspath(p) for p in config.website.paths ]
fs = config.get('allow_folders',[])
fs = [ os.path.abspath(i) for i in fs + paths ]
r = False
for f in fs:
if fp.startswith(f):
r = True
break
if not r:
raise FileOutZone(fp)
return open(fp,m)
def isNone(a):
return a is None
def abspath(path):
config = getConfig()
paths = [ os.path.abspath(p) for p in config.website.paths ]
for root in paths:
p = root + path
if os.path.exists(root+path):
return p
return None
def appname():
config = getConfig()
try:
return config.license.app
except:
return "test app"
def request2ns(request):
ret = {}
for k,v in request.args.items():
if type(v) == type([]) and len(v) == 1:
ret[k] = v[0]
else :
ret[k] = v
ret = uObject(ret)
return ret
def configValue(ks):
config = getConfig()
try:
a = eval('config' + ks)
return a
except:
return None
def visualcoding():
return configValue('.website.visualcoding');
def file_download(request,path,name,coding='utf8'):
f = openfile(path,'rb')
b = f.read()
f.close()
fname = quote(name).encode(coding)
hah = b"attachment; filename=" + fname
# print('file head=',hah.decode(coding))
request.setHeader(b'Content-Disposition',hah)
request.setHeader(b'Expires',0)
request.setHeader(b'Cache-Control',b'must-revalidate, post-check=0, pre-check=0')
request.setHeader(b'Content-Transfer-Encoding',b'binary')
request.setHeader(b'Pragma',b'public')
request.setHeader(b'Content-Length',len(b))
request.write(b)
request.finish()
def initEnv():
pool = DBPools()
g = ServerEnv()
g.configValue = configValue
g.visualcoding = visualcoding
g.uriop = URIOp
g.isNone = isNone
g.json = json
g.int = int
g.str = str
g.float = float
g.type = type
g.ArgsConvert = ArgsConvert
g.time = time
g.datetime = datetime
g.random = random
g.str2date = str2Date
g.str2datetime = str2Datetime
g.curDatetime = curDatetime
g.uObject = uObject
g.uuid = getID
g.runSQL = pool.runSQL
g.runSQLPaging = pool.runSQLPaging
g.runSQLIterator = pool.runSQL
g.runSQLResultFields = pool.runSQLResultFields
g.getTables = pool.getTables
g.getTableFields = pool.getTableFields
g.getTablePrimaryKey = pool.getTablePrimaryKey
g.getTableForignKeys = pool.getTableForignKeys
g.folderInfo = folderInfo
g.abspath = abspath
g.request2ns = request2ns
g.CRUD = CRUD
g.data2xlsx = data2xlsx
g.xlsxdata = XLSXData
g.openfile = openfile

44
ahserver/mdProcessor.py Executable file
View File

@ -0,0 +1,44 @@
import re
from appPublic.jsonConfig import getConfig
from WebServer.configuredResource import BaseProcessor
from WebServer.globalEnv import request2ns,absUrl
class MarkDownProcessor(BaseProcessor):
@classmethod
def isMe(self,name):
return name=='md'
content_type='webwidget/json'
def fileHandle(self,f,request):
b = f.read()
b = self.urlreplace(b,request)
ret = {
"__widget__":"markdown",
"data":{
"md_text":b
}
}
self.file_data = ret
return self.file_data
def urlreplace(self,mdtxt,request):
def replaceURL(s):
p1 = '\[.*?\]\((.*?)\)'
url = re.findall(p1,s)[0]
txts = s.split(url)
url = absUrl(request,url)
return url.join(txts)
p = '\[.*?\]\(.*?\)'
textarray = re.split(p,mdtxt)
links = re.findall(p,mdtxt)
newlinks = [ replaceURL(link) for link in links]
if len(links)>0:
mdtxt = ''
for i in range(len(newlinks)):
mdtxt = mdtxt + textarray[i]
mdtxt = mdtxt + newlinks[i]
mdtxt = mdtxt + textarray[i+1]
return mdtxt

57
ahserver/myTE.py Normal file
View File

@ -0,0 +1,57 @@
import os
import codecs
from appPublic.Singleton import SingletonDecorator
from appPublic.jsonConfig import getConfig
from jinja2 import Template,Environment, BaseLoader
from .serverenv import ServerEnv
from .url2file import Url2File, TmplUrl2File
class TmplLoader(BaseLoader, TmplUrl2File):
def __init__(self, paths, indexes, subffixes=['.tmpl'], inherit=False):
BaseLoader.__init__(self)
TmplUrl2File.__init__(self,paths,indexes=indexes,subffixes=subffixes, inherit=inherit)
def get_source(self,env: Environment,template: str):
config = getConfig()
coding = config.website.coding
fp = self.url2file(template)
if not os.path.isfile(fp):
raise TemplateNotFound(template)
mtime = os.path.getmtime(fp)
with codecs.open(fp,'r',coding) as f:
source = f.read()
return source,fp,lambda:mtime == os.path.getmtime(fp)
def join_path(self,name, parent):
return self.relatedurl(parent,name)
def list_templates(self):
return []
class TemplateEngine(Environment):
def __init__(self,loader=None):
Environment.__init__(self,loader=loader)
self.urlpaths = {}
self.loader = loader
def join_path(self,template: str, parent: str):
return self.loader.join_path(template, parent)
def render(self,name: str, **globals):
t = self.get_template(name,globals=globals)
return t.render(globals)
def setupTemplateEngine():
config = getConfig()
subffixes = [ i[0] for i in config.website.processors if i[1] == 'tmpl' ]
print(subffixes)
paths = [ os.path.abspath(p) for p,prefix in config.website.paths ]
loader = TmplLoader(paths,config.website.indexes,subffixes,inherit=True)
engine = TemplateEngine(loader)
g = ServerEnv()
g.tmpl_engine = engine

View File

@ -0,0 +1,193 @@
import os
import re
from aiohttp.web_urldispatcher import StaticResource, _WebHandler, PathLike
from aiohttp.web_urldispatcher import Optional, _ExpectHandler
from aiohttp.web_urldispatcher import Path
from aiohttp.web_response import Response, StreamResponse
from aiohttp.web_exceptions import (
HTTPException,
HTTPExpectationFailed,
HTTPForbidden,
HTTPMethodNotAllowed,
HTTPNotFound,
)
from aiohttp.web_fileresponse import FileResponse
from aiohttp.web_request import Request
from aiohttp.web_response import Response, StreamResponse
from aiohttp.web_routedef import AbstractRouteDef
from appPublic.jsonConfig import getConfig
from appPublic.MiniI18N import getI18N
from appPublic.dictObject import DictObject
from .baseProcessor import getProcessor
from .xlsxdsProcessor import XLSXDataSourceProcessor
from .sqldsProcessor import SQLDataSourceProcessor
from .serverenv import ServerEnv
from .url2file import Url2File
from .filestorage import FileStorage
def multiDict2Dict(md):
ns = {}
for k,v in md.items():
ov = ns.get(k,None)
if ov is None:
ns[k] = v
elif type(ov) == type([]):
ov.append(v)
ns[k] = ov
else:
ns[k] = [ov,v]
return ns
def getHeaderLang(request):
al = request.headers.get('Accept-Language')
if al is None:
return 'en'
return al.split(',')[0]
def i18nDICT(request):
c = getConfig()
i18n = getI18N()
lang = getHeaderLang(request)
l = c.langMapping.get(lang,lang)
return json.dumps(i18n.getLangDict(l)).encode(c.website.coding)
class ProcessorResource(StaticResource):
def __init__(self, prefix: str, directory: PathLike,
*, name: Optional[str]=None,
expect_handler: Optional[_ExpectHandler]=None,
chunk_size: int=256 * 1024,
show_index: bool=False, follow_symlinks: bool=False,
append_version: bool=False)-> None:
super().__init__(prefix, directory,
name=name,
expect_handler=expect_handler,
chunk_size=chunk_size,
show_index=show_index,
follow_symlinks=follow_symlinks,
append_version=append_version)
gr = self._routes.get('GET')
self._routes.update({'POST':gr})
self.y_processors = []
self.y_prefix = prefix
self.y_directory = directory
self.y_indexes = []
self.y_env = DictObject()
def setProcessors(self, processors):
self.y_processors = processors
def setIndexes(self, indexes):
self.y_indexes = indexes
def abspath(self,path:str):
path = path[len(self.y_prefix):]
if len(path)>0 and path[0] == '/':
path = path[1:]
rp = os.path.join(self.y_directory , path)
real_path = os.path.abspath(rp)
return real_path
async def getPostData(self,request):
reader = await request.multipart()
if reader is None:
md = await request.post()
ns = multiDict2Dict(md)
return ns
ns = {}
while 1:
field = await reader.next()
if not field:
break
value = ''
if hasattr(field,'filename'):
saver = FileStorage()
value = await saver.save(field.filename,field.read_chunk)
else:
value = await field.read(decode=True)
ov = ns.get(field.name)
if ov:
if type(ov) == type([]):
ov.append(value)
else:
ov = [ov,value]
else:
ov = value
ns.update({field.name:ov})
return ns
async def _handle(self,request:Request) -> StreamResponse:
clientkeys = {
"iPhone":"iphone",
"iPad":"ipad",
"Android":"androidpad",
"Windows Phone":"winphone",
"Windows NT[.]*Win64; x64":"pc",
}
def i18nDICT():
c = getConfig()
g = ServerEnv()
if not g.get('myi18n',False):
g.myi18n = getI18N()
lang = getHeaderLang(request)
l = c.langMapping.get(lang,lang)
return json.dumps(g.myi18n.getLangDict(l))
def getClientType(request):
agent = request.headers.get('user-agent')
if type(agent)!=type('') and type(agent)!=type(b''):
return 'pc'
for k in clientkeys.keys():
m = re.findall(k,agent)
if len(m)>0:
return clientkeys[k]
return 'pc'
def serveri18n(s):
lang = getHeaderLang(request)
c = getConfig()
g = ServerEnv()
if not g.get('myi18n',False):
g.myi18n = getI18N()
l = c.langMapping.get(lang,lang)
return g.myi18n(s,l)
async def getArgs():
ns = DictObject()
if request.method == 'POST':
return await self.getPostData(request)
ns = multiDict2Dict(request.query)
return ns
print('** ret=',ret,request.query)
return ns
self.y_env.i18n = serveri18n
self.y_env.i18nDict = i18nDICT
self.y_env.terminalType = getClientType(request)
self.y_env.absurl = self.absUrl
self.y_env.abspath = self.abspath
self.y_env.request2ns = getArgs
self.y_env.resource = self
path = request.path
for word, handlername in self.y_processors:
if path.endswith(word):
Klass = getProcessor(handlername)
processor = Klass(self.abspath(path),self)
return await processor.handle(request)
print(f'path={path} handler by StaticResource..')
return await super()._handle(request)
def absUrl(self,request,url):
http='http://'
https='https://'
if url[:7] == http:
return url
if url[:8] == https:
return url
path = request.path
return self.relatedurl(path,url)

25
ahserver/serverenv.py Normal file
View File

@ -0,0 +1,25 @@
from appPublic.Singleton import SingletonDecorator
from appPublic.dictObject import DictObject
@SingletonDecorator
class ServerEnv(DictObject):
pass
clientkeys = {
"iPhone":"iphone",
"iPad":"ipad",
"Android":"androidpad",
"Windows Phone":"winphone",
"Windows NT[.]*Win64; x64":"pc",
}
def getClientType(request):
agent = request.headers.get('user-agent')
for k in clientkeys.keys():
m = re.findall(k,agent)
if len(m)>0:
return clientkeys[k]
return 'pc'

75
ahserver/sqldsProcessor.py Executable file
View File

@ -0,0 +1,75 @@
import codecs
from .dsProcessor import DataSourceProcessor
from appPublic.jsonConfig import getConfig
from sqlor.dbpools import DBPools
import json
"""
sqlds file format:
{
"sqldesc":{
"sql_string":"select * from dbo.stock_daily_hist where stock_num=${stock_num}$ order by trade_date desc",
"db":"mydb",
"sortfield":"stock_date"
}
"arguments":[
{
"name":"stock_num",
"type":"str",
"iotype":"text",
"default":"600804"
}
],
"datadesc":[
{
}
]
}
"""
class SQLDataSourceProcessor(DataSourceProcessor):
@classmethod
def isMe(self,name):
return name=='sqlds'
def getArgumentsDesc(self,dict_data,ns,request):
desc = dict_data.get('arguments',None)
return desc
def getDataDesc(self,dict_data,ns,request):
pool = DBPools()
@pool.runSQLResultFields
def sql(dbname,NS):
sqldesc = dict_data.get('sqldesc')
# print('sql(),sqldesc=',sqldesc)
return sqldesc
rec = dict_data.get('datadesc',None)
if rec is None:
sqldesc = dict_data.get('sqldesc')
ns = dict_data.get('arguments',{})
rec = [ r for r in sql(sqldesc['db'],ns) if r['name']!='_row_id' ]
dict_data['datadesc'] = rec
f = codecs.open(self.src_file,'w',self.config.website.coding)
b = json.dumps(dict_data,indent=4)
f.write(b)
f.close()
return rec
def getData(self,dict_data,ns,request):
pool = DBPools()
@pool.runSQL
def sql(dbname,NS):
sqldesc = dict_data.get('sqldesc')
return sqldesc
db = dict_data['sqldesc']['db']
ret = [ i for i in sql(db,ns) ]
return ret
def getPagingData(self,dict_data,ns,request):
pool = DBPools()
@pool.runSQLPaging
def sql(dbname,NS):
sqldesc = dict_data.get('sqldesc')
return sqldesc
db = dict_data['sqldesc']['db']
ret = sql(db,ns)
return ret

83
ahserver/uriop.py Executable file
View File

@ -0,0 +1,83 @@
#
import os
import codecs
from appPublic.jsonConfig import getConfig
from appPublic.folderUtils import folderInfo
class URIopException(Exception):
def __init__(self,errtype,errmsg):
self.errtype = errtype
self.errmsg = errmsg
super(URIopException,self).init('errtype=%s,errmsg=%s' % (errtype,errmsg))
def __str__(self):
return 'errtype=%s,errmsg=%s' % (self.errtype,self.errmsg)
class URIOp(object):
def __init__(self):
self.conf = getConfig()
self.realPath = os.path.abspath(self.conf.website.root)
def abspath(self,uri=None):
p = self.conf.website.root
if uri is not None and len(uri)>0:
x = uri
if x[0] == '/':
x = x[1:]
p = os.path.join(p,*x.split('/'))
d = os.path.abspath(p)
if len(d) < len(self.realPath):
raise URIopException('url scope error',uri);
if d[:len(self.realPath)] != self.realPath:
raise URIopException('url scope error',uri);
return d
def fileList(self,uri=''):
r = [ i for i in folderInfo(self.realPath,uri) ]
for i in r:
if i['type']=='dir':
i['state'] = 'closed'
i['id'] = '_#_'.join(i['id'].split('/'))
ret={
'total':len(r),
'rows':r
}
return ret
def mkdir(self,at_uri,name):
p = self.abspath(at_uri)
p = os.path.join(p,name)
os.mkdir(p)
def rename(self,uri,newname):
p = self.abspath(uri)
dir = os.path.dirname(p)
np = os.path.join(p,newname)
os.rename(p,np)
def delete(self,uri):
p = self.abspath(uri)
os.remove(p)
def save(self,uri,data):
p = self.abspath(uri)
f = codecs.open(p,"w",self.conf.website.coding)
f.write(data)
f.close()
def read(self,uri):
p = self.abspath(uri)
f = codecs.open(p,"r",self.conf.website.coding)
b = f.read()
f.close()
return b
def write(self,uri,data):
p = self.abspath(uri)
f = codecs.open(p,"w",self.conf.website.coding)
f.write(data)
f.close()

85
ahserver/url2file.py Normal file
View File

@ -0,0 +1,85 @@
import os
class Url2File:
def __init__(self,paths: list,indexes: list, inherit: bool=False):
self.paths = paths
self.indexes = indexes
self.inherit = inherit
def realurl(self,url):
items = url.split('/')
items = [ i for i in items if i != '.' ]
while '..' in items:
for i,v in enumerate(items):
if v=='..' and i > 0:
del items[i]
del items[i-1]
break
return '/'.join(items)
def isFolder(self,url: str):
for r in self.paths:
rp = r + url
real_path = os.path.abspath(rp)
if os.path.isdir(real_path):
return True
return False
def defaultIndex(self,url: str):
for p in self.indexes:
rp = url + '/' + p
r = self.url2file(rp)
if r is not None:
return r
return None
def url2file(self,url: str):
if url[-1] == '/':
url = url[:-1]
if self.isFolder(url):
return self.defaultIndex(url)
for r in self.paths:
f = r + url
real_path = os.path.abspath(f)
if os.path.isfile(real_path):
return f
if not self.inherit:
return None
items = url.split('/')
if len(items) > 2:
del items[-2]
url = '/'.join(items)
return self.url2file(url)
return None
def relatedurl(self,url: str, name: str):
if url[-1] == '/':
url = url[:-1]
if not self.isFolder(url):
items = url.split('/')
del items[-1]
url = '/'.join(items)
url = url + '/' + name
return self.realurl(url)
def relatedurl2file(self,url: str, name: str):
url = self.relatedurl(url,name)
return self.url2file(url)
class TmplUrl2File(Url2File):
def __init__(self,paths,indexes, subffixes=['.tmpl'],inherit=False):
Url2File.__init__(self,paths,indexes=indexes,inherit=inherit)
self.subffixes = subffixes
def list_tmpl(self):
ret = []
for rp in self.paths:
p = os.path.abspath(rp)
[ ret.append(i) for i in listFile(p,suffixs=self.subffixes,rescursive=True) ]
return sorted(ret)

11
ahserver/wwProcessor.py Executable file
View File

@ -0,0 +1,11 @@
from appPublic.jsonConfig import getConfig
from WebServer.configuredResource import BaseProcessor
from WebServer.globalEnv import request2ns,absUrl
class WebWidgetProcessor(BaseProcessor):
@classmethod
def isMe(self,name):
return name=='ww'

128
ahserver/xlsxData.py Executable file
View File

@ -0,0 +1,128 @@
from openpyxl import load_workbook
import json
"""
xlsxds file format:
{
"xlsxfile":"./data.xlsx",
"data_from":7,
"data_sheet":"Sheet1",
"label_at",1,
"name_at":null,
"datatype_at":2,
"ioattrs_at":3,
"listhide_at":4,
"inputhide_at":5,
"frozen_at":6
}
"""
class XLSXData:
def __init__(self,path,desc):
self.desc = desc
self.xlsxfile = path
self.workbook = load_workbook(self.xlsxfile)
self.ws = self.workbook[self.desc['data_sheet']]
def getBaseFieldsInfo(self):
ws = self.workbook[self.desc['data_sheet']]
ret = []
for y in range(1,ws.max_column+1):
r = {
'name':self._fieldName(ws,y),
'label':self._fieldLabel(ws,y),
'type':self._fieldType(ws,y),
'listhide':self._isListHide(ws,y),
'inputhide':self._isInputHide(ws,y),
'frozen':self._isFrozen(ws,y)
}
r.update(self._fieldIOattrs(ws,y))
ret.append(r)
return ret
def _fieldName(self,ws,i):
x = self.desc.get('name_at')
if x is not None:
return ws.cell(x,i).value
return 'f' + str(i)
def _fieldLabel(self,ws,i):
x = self.desc.get('label_at',1)
if x is not None:
return ws.cell(x,i).value
return 'f' + str(i)
def _fieldType(self,ws,i):
x = self.desc.get('datatype_at')
if x is not None:
return ws.cell(x,i).value
return 'str'
def _fieldIOattrs(self,ws,i):
x = self.desc.get('ioattrs_at')
if x is not None:
t = ws.cell(x,i).value
if t is not None:
try:
return json.loads(t,'utf-8')
except Exception as e:
print('xlsxData.py:field=',i,'t=',t,'error')
return {}
def _isFrozen(self,ws,i):
x = self.desc.get('frozen_at')
if x is not None:
t = ws.cell(x,y).value
if t == 'Y' or t == 'y':
return True
return False
def _isListHide(self,ws,i):
x = self.desc.get('listhide_at')
if x is not None:
t = ws.cell(x,i).value
if t == 'Y' or t == 'y':
return True
return False
def _isInputHide(self,ws,i):
x = self.desc.get('inputhide_at')
if x is not None:
t = ws.cell(x,i).value
if t == 'Y' or t == 'y':
return True
return False
def getPeriodData(self,min_r,max_r):
ws = self.ws
rows = []
assert(min_r >= self.desc.get('data_from',2))
if max_r > ws.max_row:
max_r = ws.max_row + 1;
if min_r <= max_r:
x = min_r;
while x < max_r:
d = {}
for y in range(1,ws.max_column+1):
name = self._fieldName(ws,y)
d.update({name:ws.cell(column=y,row=x).value})
rows.append(d)
x = x + 1
return rows
def getArgumentsDesc(self,ns,request):
return None
def getData(self,ns):
ws = self.ws
min_r = self.desc.get('data_from',2)
return self.getPeriodData(min_r,ws.max_row + 1)
def getPagingData(self,ns):
rows = int(ns.get('rows',50))
page = int(ns.get('page',1))
d1 = self.desc.get('data_from',2)
min_r = (page - 1) * rows + d1
max_r = page * rows + d1 + 1
rows = self.getPeriodData(min_r,max_r)
ret = {
'total':self.ws.max_row - d1,
'rows':rows
}
return ret

51
ahserver/xlsxdsProcessor.py Executable file
View File

@ -0,0 +1,51 @@
import codecs
from openpyxl import load_workbook
from appPublic.jsonConfig import getConfig
from .dsProcessor import DataSourceProcessor
from .xlsxData import XLSXData
"""
xlsxds file format:
{
"xlsxfile":"./data.xlsx",
"data_from":7,
"data_sheet":"Sheet1",
"label_at",1,
"name_at":null,
"datatype_at":2,
"ioattrs":3,
"listhide_at":4,
"inputhide_at":5,
"frozen_at":6
}
"""
class XLSXDataSourceProcessor(DataSourceProcessor):
@classmethod
def isMe(self,name):
return name=='xlsxds'
def getArgumentsDesc(self,dict_data,ns,request):
return None
def getDataDesc(self,dict_data,ns,request):
path = dict_data.get('xlsxfile',None)
self.xlsxdata = XLSXData(self.g.abspath(self.g.absurl(request,path)),dict_data)
ret = self.xlsxdata.getBaseFieldsInfo(ns)
return ret
def getData(self,dict_data,ns,request):
path = dict_data.get('xlsxfile',None)
self.xlsxdata = XLSXData(self.g.abspath(self.g.absurl(request,path)),dict_data)
ret = self.xlsxdata.getData(ns)
return ret
def getPagingData(self,dict_data,ns,request):
path = dict_data.get('xlsxfile',None)
self.xlsxdata = XLSXData(self.g.abspath(ns.absurl(request,path)),dict_data)
ret = self.xlsxdata.getPagingData(ns)
return ret

71
conf/config.json Normal file
View File

@ -0,0 +1,71 @@
{
"debug":true,
"databases":{
"aiocfae":{
"driver":"aiomysql",
"async_mode":true,
"coding":"utf8",
"dbname":"cfae",
"kwargs":{
"user":"test",
"db":"cfae",
"password":"test123",
"host":"localhost"
}
},
"cfae":{
"driver":"mysql.connector",
"coding":"utf8",
"dbname":"cfae",
"kwargs":{
"user":"test",
"db":"cfae",
"password":"test123",
"host":"localhost"
}
}
},
"website":{
"paths":[
["$[workdir]$/../usedpkgs/antd","/antd"],
["$[workdir]$/../wolon",""]
],
"host":"0.0.0.0",
"port":8080,
"coding":"utf-8",
"ssl_gg":{
"crtfile":"$[workdir]$/conf/www.bsppo.com.pem",
"keyfile":"$[workdir]$/conf/www.bsppo.com.key"
},
"indexes":[
"index.html",
"index.tmpl",
"index.dspy",
"index.md"
],
"visualcoding":{
"default_root":"/samples/vc/test",
"userroot":{
"ymq":"/samples/vc/ymq",
"root":"/samples/vc/root"
},
"jrjpath":"/samples/vc/default"
},
"processors":[
[".xlsxds","xlsxds"],
[".sqlds","sqlds"],
[".tmpl.js","tmpl"],
[".tmpl.css","tmpl"],
[".html.tmpl","tmpl"],
[".tmpl","tmpl"],
[".dspy","dspy"],
[".md","md"]
]
},
"langMapping":{
"zh-Hans-CN":"zh-cn",
"zh-CN":"zh-cn",
"en-us":"en",
"en-US":"en"
}
}

0
i18n/en/msg.txt Normal file
View File

0
i18n/zh-cn/msg.txt Normal file
View File