1
1
import asyncio
2
+ from dataclasses import dataclass
2
3
import zmq
3
4
import zmq .asyncio
4
5
import logging
14
15
from ..param import Parameterized
15
16
from ..param .parameters import (Integer , IPAddress , ClassSelector , Selector , TypedList , String )
16
17
from .constants import ZMQ_PROTOCOLS , CommonRPC , HTTPServerTypes , ResourceTypes , ServerMessage
17
- from .utils import get_IP_from_interface
18
+ from .utils import get_IP_from_interface , issubklass
18
19
from .dataklasses import HTTPResource , ServerSentEvent
19
20
from .utils import get_default_logger
20
21
from .serializers import JSONSerializer
21
22
from .database import ThingInformation
22
23
from .zmq_message_brokers import AsyncZMQClient , MessageMappedZMQClientPool
23
24
from .handlers import RPCHandler , BaseHandler , EventHandler , ThingsHandler , StopHandler
24
25
from .schema_validators import BaseSchemaValidator , JsonSchemaValidator
26
+ from .events import Event
25
27
from .eventloop import EventLoop
26
28
from .config import global_config
27
29
28
30
29
31
32
+
33
+ @dataclass
34
+ class InteractionAffordance :
35
+ URL_path : str
36
+ obj : Event # typing.Union[Property, Action, Event]
37
+ http_methods : typing .Tuple [str , typing .Optional [str ], typing .Optional [str ]]
38
+ handler : BaseHandler
39
+ kwargs : dict
40
+
41
+ def __eq__ (self , other : "InteractionAffordance" ) -> bool :
42
+ return self .obj == other .obj
43
+
44
+
45
+
30
46
class HTTPServer (Parameterized ):
31
47
"""
32
48
HTTP(s) server to route requests to ``Thing``.
@@ -63,7 +79,7 @@ class HTTPServer(Parameterized):
63
79
Unlike pure CORS, the server resource is not even executed if the client is not
64
80
an allowed client. if None any client is served.""" )
65
81
host = String (default = None , allow_None = True ,
66
- doc = "Host Server to subscribe to coordinate starting sequence of remote objects & web GUI" ) # type: str
82
+ doc = "Host Server to subscribe to coordinate starting sequence of things & web GUI" ) # type: str
67
83
# network_interface = String(default='Ethernet',
68
84
# doc="Currently there is no logic to detect the IP addresss (as externally visible) correctly, \
69
85
# therefore please send the network interface name to retrieve the IP. If a DNS server is present, \
@@ -138,6 +154,7 @@ def __init__(self, things : typing.List[str], *, port : int = 8080, address : st
138
154
self ._zmq_protocol = ZMQ_PROTOCOLS .IPC
139
155
self ._zmq_inproc_socket_context = None
140
156
self ._zmq_inproc_event_context = None
157
+ self ._local_rules = dict () # type: typing.Dict[str, typing.List[InteractionAffordance]]
141
158
142
159
@property
143
160
def all_ok (self ) -> bool :
@@ -147,6 +164,9 @@ def all_ok(self) -> bool:
147
164
f"{ self .address } :{ self .port } " ),
148
165
self .log_level )
149
166
167
+ if self ._zmq_protocol == ZMQ_PROTOCOLS .INPROC and (self ._zmq_inproc_socket_context is None or self ._zmq_inproc_event_context is None ):
168
+ raise ValueError ("Inproc socket context is not provided. Logic Error." )
169
+
150
170
self .app = Application (handlers = [
151
171
(r'/remote-objects' , ThingsHandler , dict (request_handler = self .request_handler ,
152
172
event_handler = self .event_handler )),
@@ -250,7 +270,7 @@ async def update_router_with_thing(self, client : AsyncZMQClient):
250
270
# Just to avoid duplication of this call as we proceed at single client level and not message mapped level
251
271
return
252
272
self ._lost_things [client .instance_name ] = client
253
- self .logger .info (f"attempting to update router with remote object { client .instance_name } ." )
273
+ self .logger .info (f"attempting to update router with thing { client .instance_name } ." )
254
274
while True :
255
275
try :
256
276
await client .handshake_complete ()
@@ -272,7 +292,13 @@ async def update_router_with_thing(self, client : AsyncZMQClient):
272
292
)))
273
293
elif http_resource ["what" ] == ResourceTypes .EVENT :
274
294
resource = ServerSentEvent (** http_resource )
275
- handlers .append ((instruction , self .event_handler , dict (
295
+ if resource .class_name in self ._local_rules and any (ia .obj ._obj_name == resource .obj_name for ia in self ._local_rules [resource .class_name ]):
296
+ for ia in self ._local_rules [resource .class_name ]:
297
+ if ia .obj ._obj_name == resource .obj_name :
298
+ handlers .append ((f'/{ client .instance_name } { ia .URL_path } ' , ia .handler , dict (resource = resource , validator = None ,
299
+ owner = self , ** ia .kwargs )))
300
+ else :
301
+ handlers .append ((instruction , self .event_handler , dict (
276
302
resource = resource ,
277
303
validator = None ,
278
304
owner = self
@@ -306,10 +332,11 @@ def __init__(
306
332
to make RPCHandler work
307
333
"""
308
334
self .app .wildcard_router .add_rules (handlers )
309
- self .logger .info (f"updated router with remote object { client .instance_name } ." )
335
+ self .logger .info (f"updated router with thing { client .instance_name } ." )
310
336
break
311
337
except Exception as ex :
312
- self .logger .error (f"error while trying to update router with remote object - { str (ex )} . " +
338
+ print ("error" , ex )
339
+ self .logger .error (f"error while trying to update router with thing - { str (ex )} . " +
313
340
"Trying again in 5 seconds" )
314
341
await asyncio .sleep (5 )
315
342
@@ -328,10 +355,39 @@ def __init__(
328
355
raise_client_side_exception = True
329
356
)
330
357
except Exception as ex :
331
- self .logger .error (f"error while trying to update remote object with HTTP server details - { str (ex )} . " +
358
+ self .logger .error (f"error while trying to update thing with HTTP server details - { str (ex )} . " +
332
359
"Trying again in 5 seconds" )
333
360
self .zmq_client_pool .poller .register (client .socket , zmq .POLLIN )
334
361
self ._lost_things .pop (client .instance_name )
362
+
363
+
364
+ def add_event (self , URL_path : str , event : Event , handler : typing .Optional [BaseHandler ] = None ,
365
+ ** kwargs ) -> None :
366
+ """
367
+ Add an event to be served by HTTP server
368
+
369
+ Parameters
370
+ ----------
371
+ URL_path : str
372
+ URL path to access the event
373
+ event : Event
374
+ Event to be served
375
+ handler : BaseHandler, optional
376
+ custom handler for the event
377
+ kwargs : dict
378
+ additional keyword arguments to be passed to the handler's __init__
379
+ """
380
+ if not isinstance (event , Event ):
381
+ raise TypeError ("event should be of type Event" )
382
+ if not issubklass (handler , BaseHandler ):
383
+ raise TypeError ("handler should be subclass of BaseHandler" )
384
+ if event .owner .__name__ not in self ._local_rules :
385
+ self ._local_rules [event .owner .__name__ ] = []
386
+ obj = InteractionAffordance (URL_path = URL_path , obj = event ,
387
+ http_methods = ('GET' ,), handler = handler or self .event_handler ,
388
+ kwargs = kwargs )
389
+ if obj not in self ._local_rules [event .owner .__name__ ]:
390
+ self ._local_rules [event .owner .__name__ ].append (obj )
335
391
336
392
337
393
__all__ = [
0 commit comments