Skip to content

Commit

Permalink
feat: parano & http_only on .handle/.serve too
Browse files Browse the repository at this point in the history
  • Loading branch information
manatlan committed Oct 4, 2023
1 parent fc6744d commit 975f141
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 88 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ Process live as long as the server live (TODO: a TIMEOUT will be back soon)
* 'parano mode' (can aes encrypt all communications between client & server ... to avoid mitm'proxies on ws/http interactions)
* auto reconnect websocket

### Instanciate

Like a classical starlette'app :

```python
from htagweb import AppServer
from yourcode import YourApp # <-- your htag class

app=AppServer( YourApp, ... )
if __name__=="__main__":
app.run()
```

You can use the following parameters :

#### debug (bool)

Expand All @@ -55,11 +69,15 @@ non-sense in http_only mode.
- When False: (default) interactions between front/ui and back are in clear text (json), readable by a MITM.
- When True: interactions will be encrypted (less readable by a MITM, TODO: will try to use public/private keys in future)

this parameter is available on `app.handle(request, obj, ... parano=True|False ...)` too, to override defaults !

#### http_only (bool)

- When False: (default) it will use websocket interactions (between front/ui and back), with auto-reconnect feature.
- When True: it will use http interactions (between front/ui and back). But "tag.update" feature will not be available.

this parameter is available on `app.handle(request, obj, ... http_only=True|False ...)` too, to override defaults !

#### session_factory (htagweb.sessions)

You can provide a Session Factory to handle the session in different modes.
Expand Down
12 changes: 3 additions & 9 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import json,asyncio,time,os

"""
Complex htag's app to test:
Complex htag's app for my tests purpose:
- a dynamic object (TagSession), which got a render method (new way)
- using tag.state (in session)
Expand Down Expand Up @@ -78,17 +78,11 @@ def test(o):
self <= Tag.button("B",_onclick=test)

async def handleJo(req):
return await req.app.handle(req,Jo)
return await req.app.handle(req,Jo,http_only=True,parano=True)

# With Web http runner provided by htag
#------------------------------------------------------
# from htag.runners import WebHTTP
# WebHTTP( App ).run()

# With htagweb.WebServer runner provided by htagweb
#------------------------------------------------------
from htagweb import SimpleServer,AppServer
app=AppServer( App ,parano=False,http_only=True)
app=AppServer( App )
app.add_route("/jo", handleJo )

if __name__=="__main__":
Expand Down
164 changes: 89 additions & 75 deletions htagweb/appserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class HRSocket(WebSocketEndpoint):

async def _sendback(self,websocket, txt:str) -> bool:
try:
if websocket.app.parano:
if self.is_parano:
seed = parano_seed( websocket.scope["uid"])
txt = crypto.encrypt(txt.encode(),seed)

Expand Down Expand Up @@ -152,6 +152,7 @@ async def on_connect(self, websocket):
uid=websocket.scope["uid"]
event=HrClient(uid,fqn).event_response+"_update"
#======================================================
self.is_parano="parano" in websocket.query_params.keys()

await websocket.accept()

Expand All @@ -162,7 +163,7 @@ async def on_receive(self, websocket, data):
fqn=websocket.path_params.get("fqn","")
uid=websocket.scope["uid"]

if websocket.app.parano:
if self.is_parano:
data = crypto.decrypt(data.encode(),parano_seed( uid )).decode()
data=json.loads(data)

Expand Down Expand Up @@ -208,15 +209,15 @@ async def lifespan(app):
class AppServer(Starlette):
def __init__(self,
obj:"htag.Tag class|fqn|None"=None,
session_factory:"sessions.MemDict|sessions.FileDict|sessions.FilePersistentDict|None"=None,
debug:bool=True,
ssl:bool=False,
parano:bool=False,
http_only:bool=False,
session_factory:"sessions.MemDict|sessions.FileDict|sessions.FilePersistentDict|None"=None,
):
self.ssl=ssl
self.parano=parano
self.http_only=http_only
self.parano = parano
self.http_only = http_only

if session_factory is None:
self.sesprovider = sessions.MemDict
Expand All @@ -242,112 +243,125 @@ def __init__(self,

if obj:
async def handleHome(request):
return await self.serve(request,obj)
return await self.handle(request,obj,recreate=False,http_only=http_only,parano=parano)
self.add_route( '/', handleHome )

# new method
async def handle(self, request, obj:"htag.Tag class|fqn", recreate:bool=False ) -> HTMLResponse:
return await self.serve(request,obj,recreate)
async def handle(self, request,
obj:"htag.Tag class|fqn",
recreate:bool=False,
http_only:"bool|None"=False,
parano:"bool|None"=False ) -> HTMLResponse:
return await self.serve(request,obj,recreate,http_only,parano)


# DEPRECATED
async def serve(self, request, obj:"htag.Tag class|fqn", force:bool=False ) -> HTMLResponse:
async def serve(self, request,
obj:"htag.Tag class|fqn",
force:bool=False,
http_only:"bool|None"=False,
parano:"bool|None"=False ) -> HTMLResponse:

# take default behaviour if not present
is_parano = self.parano if parano is None else parano
is_http_only = self.http_only if http_only is None else http_only


uid = request.scope["uid"]
args,kargs = commons.url2ak(str(request.url))
fqn=normalize(findfqn(obj))

if self.parano:
if is_parano:
seed = parano_seed( uid )

jstunnel = crypto.JSCRYPTO
jstunnel += f"\nvar _PARANO_='{seed}'\n"
jstunnel += "\nasync function _read_(x) {return await decrypt(x,_PARANO_)}\n"
jstunnel += "\nasync function _write_(x) {return await encrypt(x,_PARANO_)}\n"
jslib = crypto.JSCRYPTO
jslib += f"\nvar _PARANO_='{seed}'\n"
jslib += "\nasync function _read_(x) {return await decrypt(x,_PARANO_)}\n"
jslib += "\nasync function _write_(x) {return await encrypt(x,_PARANO_)}\n"
pparano="?parano"
else:
jstunnel = ""
jstunnel += "\nasync function _read_(x) {return x}\n"
jstunnel += "\nasync function _write_(x) {return x}\n"
jslib = ""
jslib += "\nasync function _read_(x) {return x}\n"
jslib += "\nasync function _write_(x) {return x}\n"
pparano=""


if self.http_only:
if is_http_only:
# interactions use HTTP POST
js = """
%(jstunnel)s
async function interact( o ) {
let body = await _write_(JSON.stringify(o));
let req=await window.fetch("/_/%(fqn)s",{method:"POST", body: body});
let actions=await req.text();
action( await _read_(actions) );
}
window.addEventListener('DOMContentLoaded', start );
""" % locals()
js = """%(jslib)s
async function interact( o ) {
let body = await _write_(JSON.stringify(o));
let req=await window.fetch("/_/%(fqn)s%(pparano)s",{method:"POST", body: body});
let actions=await req.text();
action( await _read_(actions) );
}
window.addEventListener('DOMContentLoaded', start );
""" % locals()
else:
# interactions use WS
protocol = "wss" if self.ssl else "ws"

js = """
%(jstunnel)s
async function interact( o ) {
_WS_.send( await _write_(JSON.stringify(o)) );
}
// instanciate the WEBSOCKET
let _WS_=null;
let retryms=500;
function connect() {
_WS_= new WebSocket("%(protocol)s://"+location.host+"/_/%(fqn)s");
_WS_.onopen=function(evt) {
console.log("** WS connected")
document.body.classList.remove("htagoff");
retryms=500;
start();
_WS_.onmessage = async function(e) {
let actions = await _read_(e.data)
action(actions)
};
}
_WS_.onclose = function(evt) {
console.log("** WS disconnected, retry in (ms):",retryms);
document.body.classList.add("htagoff");
setTimeout( function() {
connect();
retryms=retryms*2;
}, retryms);
};
}
connect();
""" % locals()

p = HrClient(uid,fqn,js,self.sesprovider.__name__,force=force)

args,kargs = commons.url2ak(str(request.url))
js = """%(jslib)s
async function interact( o ) {
_WS_.send( await _write_(JSON.stringify(o)) );
}
// instanciate the WEBSOCKET
let _WS_=null;
let retryms=500;
function connect() {
_WS_= new WebSocket("%(protocol)s://"+location.host+"/_/%(fqn)s%(pparano)s");
_WS_.onopen=function(evt) {
console.log("** WS connected")
document.body.classList.remove("htagoff");
retryms=500;
start();
_WS_.onmessage = async function(e) {
let actions = await _read_(e.data)
action(actions)
};
}
_WS_.onclose = function(evt) {
console.log("** WS disconnected, retry in (ms):",retryms);
document.body.classList.add("htagoff");
setTimeout( function() {
connect();
retryms=retryms*2;
}, retryms);
};
}
connect();
""" % locals()

p = HrClient(uid,fqn,js,self.sesprovider.__name__,recreate=force)
html=await p.start(*args,**kargs)
return HTMLResponse(html)

async def HRHttp(self,request) -> PlainTextResponse:
uid = request.scope["uid"]
fqn = request.path_params.get("fqn","")
is_parano="parano" in request.query_params.keys()
seed = parano_seed( uid )

p=HrClient(uid,fqn)
data = await request.body()

if self.parano:
if is_parano:
data = crypto.decrypt(data,seed).decode()

data=json.loads(data)
actions=await p.interact( oid=data["id"], method_name=data["method"], args=data["args"], kargs=data["kargs"], event=data.get("event") )
txt=json.dumps(actions)

if self.parano:
if is_parano:
txt = crypto.encrypt(txt.encode(),seed)

return PlainTextResponse(txt)
Expand Down
6 changes: 3 additions & 3 deletions htagweb/server/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
TIMEOUT=20 # sec to wait answer from redys server #TODO: set better

class HrClient:
def __init__(self,uid:str,fqn:str,js:str=None,sesprovidername=None,force=False):
def __init__(self,uid:str,fqn:str,js:str=None,sesprovidername=None,recreate=False):
""" !!!!!!!!!!!!!!!!!!!! if js|sesprovidername is None : can't do a start() !!!!!!!!!!!!!!!!!!!!!!"""
self.uid=uid
self.fqn=fqn
self.js=js
self.bus = redys.v2.AClient()
self.sesprovidername=sesprovidername
self.force=force
self.recreate=recreate

self.hid=f"{uid}_{fqn}"
self.event_response = f"response_{self.hid}"
Expand Down Expand Up @@ -57,7 +57,7 @@ async def start(self,*a,**k) -> str:
js=self.js,
init= (a,k),
sesprovidername=self.sesprovidername,
force=self.force,
force=self.recreate,
))

# wait 1st rendering
Expand Down
2 changes: 1 addition & 1 deletion test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async def test_base( server ):
html=await p.start()
assert html.startswith("<!DOCTYPE html><html>")

p=HrClient(uid,"test_hr:App","//",force=True)
p=HrClient(uid,"test_hr:App","//",recreate=True)
html=await p.start()
assert html.startswith("<!DOCTYPE html><html>")

Expand Down

0 comments on commit 975f141

Please sign in to comment.