Skip to content

Commit

Permalink
r0.9.20 : big change : graceful death
Browse files Browse the repository at this point in the history
  • Loading branch information
manatlan committed Jan 5, 2019
1 parent e6384fd commit 1a54a74
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 64 deletions.
3 changes: 3 additions & 0 deletions a_server.py
Expand Up @@ -10,6 +10,9 @@ class tchat(wuy.Server): # name the class as the web/<class_name>.html
def post(self,txt):
self.emit( "addTxt", txt) # emit an event to all clients (me too !)

def myexit(self):
self.exit()

if __name__=="__main__":
print("Open your browser (manually) to http://localhost:8080/tchat.html (as many as you want)")
tchat() # can't exit !
2 changes: 1 addition & 1 deletion web/index.html
Expand Up @@ -18,7 +18,7 @@
<button onclick="wuy.my_python_method(42,43).then( rep )">call a py method</button><br/>
<button onclick="wuy.my_python_method2(42).then( rep )">call a py method (which send a event)</button><br/>
<button onclick="wuy.emit('js_event','from JS').then( rep )">send an event to everybody (except me)</button><br/>
<button onclick="wuy.my_python_exit();window.close()">exit app (server)</button><br/>
<button onclick="wuy.my_python_exit()">exit app (server)</button><br/>
<button onclick="window.close()">exit app (client)</button><br/>
<a href="unknown">Quit by redirect to an unknown page</a>

Expand Down
2 changes: 2 additions & 0 deletions web/tchat.html
@@ -1,10 +1,12 @@
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script> <!-- With that : it works on IE11 !!! (and other older webbrowser) -->
<script src="wuy.js"></script>

<button style="float:right" onclick="wuy.myexit()">exit server</button>
<form onsubmit="wuy.post( this.txt.value );this.txt.value=''; return false">
<input name="txt" placeholder="type something here"/>
<input type="submit" value="ok"/>
</form>

<div id="txts"></div>

<script>
Expand Down
80 changes: 37 additions & 43 deletions wuy.py
Expand Up @@ -14,10 +14,11 @@
# https://github.com/manatlan/wuy
# #############################################################################

__version__="0.9.12"
__version__="0.9.20"

from aiohttp import web, WSCloseCode
from multidict import CIMultiDict
import concurrent
import aiohttp
import asyncio
import json,sys,os
Expand Down Expand Up @@ -62,6 +63,8 @@

# helpers
#############################################################
class ExitException(Exception): pass

def isFree(ip,port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
Expand Down Expand Up @@ -122,10 +125,10 @@ def path(f):
if hasattr(sys,"_MEIPASS"): # when freezed with pyinstaller ;-)
return os.path.join(sys._MEIPASS,f)
else:
return os.path.join(PATH,f)
return os.path.join(PATH,f)

def wlog(*l):
if isLog:
if isLog:
s=" ".join([str(i) for i in l])
if len(s)>200: s=s[:200]+"..."
print(s)
Expand Down Expand Up @@ -343,16 +346,12 @@ async def handleWeb(req): # serve all statics from web folder
else:
return web.Response(status=200,body='<script src="wuy.js"></script>\n'+html,content_type="text/html")



# classic serve static file or 404

## version<=0.9.5
## file = path( os.path.join( os.path.dirname(ressource),"web",os.path.basename(ressource)))

## version>0.9.5
file = path( os.path.join( "web",ressource) )


file = path( os.path.join( "web",ressource) )

if os.path.isfile(file):
wlog("- serve static file",file)
return web.FileResponse(file)
Expand Down Expand Up @@ -531,6 +530,8 @@ async def waitReturn( coroutine,uuid ):
try:
ret=await coroutine
m=dict(result=ret, uuid=uuid)
except concurrent.futures._base.CancelledError as e:
m=dict(error="task cancelled", uuid=uuid)
except Exception as e:
m=dict(error=str(e), uuid=uuid)
print("="*40,"in ASync",o["command"])
Expand All @@ -542,9 +543,12 @@ async def waitReturn( coroutine,uuid ):
continue # don't answer yet (the coroutine will do it)

r=dict(result = ret )
except ExitException as e:
await application.shutdown() # for an_app2 TODO: watch here more !
r=dict(error="Exit Server")
except Exception as e:
r=dict(error = str(e))
print("="*40,"on Recept",msg.data)
print("="*40,"Exception on Recept",msg.data)
print(traceback.format_exc().strip())
print("="*79)

Expand All @@ -557,26 +561,16 @@ async def waitReturn( coroutine,uuid ):
wlog("Socket disconnected",page)
instance._clients.remove( ws )

if instance._closeIfSocketClose: _exit(instance)
if instance._closeIfSocketClose:
await application.shutdown()
_exit(instance)
return ws

def _emit(instance, event,*args): # sync version of emit for py side !
asyncio.ensure_future( wsBroadcast( instance, event, args) )

def _exit(instance=None): # exit method
global application
async def handle_exception(task):
try:
# print("*** cancel",task)
await task.cancel()
except Exception:
pass

for task in asyncio.Task.all_tasks():
asyncio.ensure_future(handle_exception(task))

asyncio.get_event_loop().stop()
asyncio.set_event_loop(asyncio.new_event_loop()) # renew, so multiple start are availables

if instance and hasattr(instance,"_browser") and instance._browser:
del instance._browser
Expand All @@ -585,10 +579,12 @@ async def handle_exception(task):
application=None
wlog("exit")

# async def on_shutdown(app):
# for name,instance in currents.items():
# for ws in instance._clients:
# await ws.close(code=WSCloseCode.GOING_AWAY,message='Server shutdown')
async def on_shutdown(app):
for task in asyncio.all_tasks():
task.cancel()



# WUY routines
#############################################################
class Base:
Expand All @@ -600,16 +596,9 @@ class Base:
def __init__(self,log=True):
global isLog
isLog=log
# pc=os.path.dirname(inspect.getfile(self.__class__))
# if pc and pc!="." and pc!=PATH:
# pc=os.path.relpath(pc,PATH) # relpath from here
# if pc==".":
# self._name=self.__class__.__name__
# else:
# self._name=pc.replace("/",".").replace("\\",".")+"."+self.__class__.__name__
# else:

self._name=self.__class__.__name__

self._routes={n:v for n, v in inspect.getmembers(self, inspect.ismethod) if not v.__func__.__qualname__.startswith( ("Base.","Window.","Server."))}
self._routes.update( dict(set=self.set,get=self.get)) # add get/set config methods
if "init" in self._routes: del self._routes["init"] # ensure that a server-side init() is not exposed on client-side
Expand Down Expand Up @@ -649,15 +638,20 @@ def _start(cls,host,port,instances,appmode):
web.route("*",'/_/{url:.+}',handleProxy),
web.route("*",'/{path:.+}', handleWeb),
])
# application.on_shutdown.append(on_shutdown)
application.on_shutdown.append(on_shutdown)
try:
if appmode: # app-mode, don't shout "server started, Running on, ctrl-c"
web.run_app(application,host=host,port=port,print=lambda *a,**k: None)
else:
web.run_app(application,host=host,port=port)
except KeyboardInterrupt:
except concurrent.futures._base.CancelledError:
pass # die silently
except RuntimeError: # for tests stopping loop TODO check
_exit()

asyncio.set_event_loop(asyncio.new_event_loop()) # renew, so multiple start are availables


def emit(self,*a,**k): # emit available for all
_emit(self,*a,**k)

Expand All @@ -668,15 +662,15 @@ def request(self,req): #override to hook others web http requests
pass

def exit(self): # available for ALL !!!
_exit(self)
raise ExitException()

def set(self,key,value,file="config.json"):
c=JDict(file)
c.set(key,value)

def get(self,key=None,file="config.json"):
c=JDict(file)
return c.get(key)
return c.get(key)

class Window(Base):
size=True # or a tuple (wx,wy)
Expand Down
54 changes: 34 additions & 20 deletions wuy_tests.py
Expand Up @@ -16,7 +16,7 @@ class UnitTests(wuy.Window):
"""
<meta charset="utf-8" />
I am
I am
<script>
document.write(wuy.iam);
Expand Down Expand Up @@ -158,11 +158,11 @@ def testEmit(self):

async def atestError(self):
a=12/0
return "ok"
return "ok"

def testError(self):
a=12/0
return "ok"
return "ok"

async def testWuyRequest(self):
return (await wuy.request("http://localhost:%s/UnitTests.html" % self._port)).content
Expand Down Expand Up @@ -240,7 +240,11 @@ def test_a_window_render_html(self):
class aeff(wuy.Window):
size=(100,100)
def init(self):
asyncio.get_event_loop().call_later(2, self.exit)
asyncio.ensure_future(self.willQuit())

async def willQuit(self): #TODO: self.exit() would be better than stopping the loop
await asyncio.sleep(1)
asyncio.get_event_loop().stop()

if os.path.isfile("web/aeff.html"): os.unlink("web/aeff.html")
aeff()
Expand All @@ -254,20 +258,29 @@ def jo2():
class saeff(wuy.Server,More):
size=(100,100)
def init(self):
asyncio.get_event_loop().call_later(2, self.exit)
asyncio.ensure_future(self.willQuit())

async def willQuit(self): #TODO: self.exit() would be better than stopping the loop
await asyncio.sleep(1)
asyncio.get_event_loop().stop()

def jo1():
pass

x=saeff()
self.assertTrue( "jo1" in x._routes.keys())
self.assertTrue( "jo2" in x._routes.keys())
self.assertTrue( "jo1" in x._routes.keys())
self.assertTrue( "jo2" in x._routes.keys())

def test_a_double_window(self):
class aeff(wuy.Window):
"test double open"
size=(100,100)
def init(self):
asyncio.get_event_loop().call_later(2, self.exit)
asyncio.ensure_future(self.willQuit())

async def willQuit(self): #TODO: self.exit() would be better than stopping the loop
await asyncio.sleep(1)
asyncio.get_event_loop().stop()

aeff()
aeff()
Expand All @@ -276,22 +289,29 @@ def test_a_server(self):
class saeff(wuy.Server):
"I'm a server"
def init(self):
asyncio.get_event_loop().call_later(2, self.exit)
asyncio.ensure_future(self.willQuit())

async def willQuit(self): #TODO: self.exit() would be better than stopping the loop
await asyncio.sleep(1)
asyncio.get_event_loop().stop()
saeff()
self.assertEqual( len(wuy.currents),1) # there was one instance

def test_2_server(self):
class saeff1(wuy.Server):
"I'm a server, and the quitter"
"I'm a server"
def init(self):
asyncio.get_event_loop().call_later(2, self.exit)
asyncio.ensure_future(self.willQuit())

async def willQuit(self): #TODO: self.exit() would be better than stopping the loop
await asyncio.sleep(1)
asyncio.get_event_loop().stop()
class saeff2(wuy.Server):
"I'm a server, and I will killed by saeff1"
pass
wuy.Server.run()
self.assertEqual( len(wuy.currents),2) # there were 2 instances

# @only
def test_a_window(self): # <--- main tests here
ut=UnitTests(log=True,val="mémé",iam="local chrome")

Expand All @@ -300,7 +320,7 @@ def test_a_window(self): # <--- main tests here
self.assertEqual( len([ok for ok,*_ in ut.tests if ok]),JSTEST) # 23 tests ok

def test_cef_if_present(self):
import pkgutil
import pkgutil
if pkgutil.find_loader("cefpython3"):
try:
old=wuy.ChromeApp
Expand All @@ -315,13 +335,6 @@ def test_cef_if_present(self):
else:
print("***WARNING*** : cefpython3 not present, no tests !")

# def test_a_windows(self):
# s = 'hello world'
# self.assertEqual(s.split(), ['hello', 'world'])
# # check that s.split fails when the separator is not a string
# with self.assertRaises(TypeError):
# s.split(2)


if __name__=="__main__":
if ONLYs:
Expand All @@ -333,3 +346,4 @@ def load_tests(loader, tests, pattern):
return suite

unittest.main( )

0 comments on commit 1a54a74

Please sign in to comment.