11import asyncio
2+ from dataclasses import dataclass
23import zmq
34import zmq .asyncio
45import logging
1415from ..param import Parameterized
1516from ..param .parameters import (Integer , IPAddress , ClassSelector , Selector , TypedList , String )
1617from .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
1819from .dataklasses import HTTPResource , ServerSentEvent
1920from .utils import get_default_logger
2021from .serializers import JSONSerializer
2122from .database import ThingInformation
2223from .zmq_message_brokers import AsyncZMQClient , MessageMappedZMQClientPool
2324from .handlers import RPCHandler , BaseHandler , EventHandler , ThingsHandler , StopHandler
2425from .schema_validators import BaseSchemaValidator , JsonSchemaValidator
26+ from .events import Event
2527from .eventloop import EventLoop
2628from .config import global_config
2729
2830
2931
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+
3046class HTTPServer (Parameterized ):
3147 """
3248 HTTP(s) server to route requests to ``Thing``.
@@ -63,7 +79,7 @@ class HTTPServer(Parameterized):
6379 Unlike pure CORS, the server resource is not even executed if the client is not
6480 an allowed client. if None any client is served.""" )
6581 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
6783 # network_interface = String(default='Ethernet',
6884 # doc="Currently there is no logic to detect the IP addresss (as externally visible) correctly, \
6985 # 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
138154 self ._zmq_protocol = ZMQ_PROTOCOLS .IPC
139155 self ._zmq_inproc_socket_context = None
140156 self ._zmq_inproc_event_context = None
157+ self ._local_rules = dict () # type: typing.Dict[str, typing.List[InteractionAffordance]]
141158
142159 @property
143160 def all_ok (self ) -> bool :
@@ -147,6 +164,9 @@ def all_ok(self) -> bool:
147164 f"{ self .address } :{ self .port } " ),
148165 self .log_level )
149166
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+
150170 self .app = Application (handlers = [
151171 (r'/remote-objects' , ThingsHandler , dict (request_handler = self .request_handler ,
152172 event_handler = self .event_handler )),
@@ -250,7 +270,7 @@ async def update_router_with_thing(self, client : AsyncZMQClient):
250270 # Just to avoid duplication of this call as we proceed at single client level and not message mapped level
251271 return
252272 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 } ." )
254274 while True :
255275 try :
256276 await client .handshake_complete ()
@@ -272,7 +292,13 @@ async def update_router_with_thing(self, client : AsyncZMQClient):
272292 )))
273293 elif http_resource ["what" ] == ResourceTypes .EVENT :
274294 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 (
276302 resource = resource ,
277303 validator = None ,
278304 owner = self
@@ -306,10 +332,11 @@ def __init__(
306332 to make RPCHandler work
307333 """
308334 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 } ." )
310336 break
311337 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 )} . " +
313340 "Trying again in 5 seconds" )
314341 await asyncio .sleep (5 )
315342
@@ -328,10 +355,39 @@ def __init__(
328355 raise_client_side_exception = True
329356 )
330357 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 )} . " +
332359 "Trying again in 5 seconds" )
333360 self .zmq_client_pool .poller .register (client .socket , zmq .POLLIN )
334361 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 )
335391
336392
337393__all__ = [
0 commit comments