def acceptDownConnection(self): #print ("acceptDownConnection()") sock, addr = self.ListenSock.accept() stream = MessageStream(sock) down_node_addr = None msg = stream.recv(tmo=1.0) #print("DownLink: acceptDownConnection: HELLO message:", msg) if msg.startswith("HELLO "): try: cmd, down_node_id, ip, port = msg.split(None, 3) down_node_addr = (ip, int(port)) self.DownNodeID = down_node_id self.DownNodeAddress = down_node_addr stream.send("OK %s" % (self.Node.ID)) self.Node.downConnected(self.DownNodeID, self.DownNodeAddress) except: stream.close() stream = None else: stream.close() stream = None if stream is not None: with self: if self.DownStream is not None: #print("DownLink.acceptDownConnection: disconnecting existing down stream...") #print(" sending RECONNECT...") self.DownStream.send("RECONNECT %s %d" % down_node_addr) self.DownStream.close() #print(" old down stream closed") self.DownStream = stream self.DownNodeAddress = down_node_addr
def reset_per_wsmsg_state(self): ## members used for consuming and receiving self.inbound_protobuf_stream = ProtoBufStream() ## members for consuming direction self.last_sent_msgnum = 0 # msg sent counter (message num) self.max_msgnum_to_send = 0 # highest message number allowed to send (most recently received) self.outqueue = MessageStream(self.MAX_OUTQUEUE_SIZE) # small buffer of messages to send self.send_a_marker = False # has a marker been requested? self.prev_message_marker = None # marker for the most recently send message self.prev_message_id = None # id of the most recently send message ## members for producing direction self.last_rcvd_msgnum = 0 # the message number of the most recently received message self.last_acked_msgnum = 0 # the last message number we sent a status for self.max_messages_to_request = 200 # the max number of messages we want to get at a time (probably based on memory or driver limitations) self.driver_append_results = [] # list of results we got appending a message to the driver; this is ordered, where the first element corresponds to message number last_acked_msgnum+1 (messages we have already responded to are not stored here)
class StreamingMarconiServerProtocol(WebSocketServerProtocol): # method which contains some one-time set up def __init__(self): self.logger = logger self.logger.info(str(datetime.datetime.now()) + ": __init__ called (driver_class_name="+driver_class_name+")") self.wsmsg_count = 0 # count of messages self.msg_state = ServerMsgState.CLOSED self.create_driver_instance(driver_class_name,driver_args,loop) # create driver using the name and args from our command line self.SEND_WORK_CHECK_FREQ_SECS = 0.01 self.MAX_OUTQUEUE_SIZE = 100 # max num messages in outqueue self.reset_per_wsmsg_state() # install signal handler (server being stopped) loop.add_signal_handler(signal.SIGINT,self.handle_interrupt_signal) def create_driver_instance(self,driver_class_name,driver_args,event_loop): driver_module= import_module(driver_class_name) # import specified driver class if driver_module is None: self.logger.error("could not import module %s" % (driver_class_name)) exit(1) driver_class = getattr(driver_module,driver_class_name) if driver_module is None: self.logger.error("could not import class %s" % (driver_class_name)) exit(1) self.driver = driver_class(driver_args,event_loop) # method to do any set/reset of state that is needed before handing a new inbound message def reset_per_wsmsg_state(self): ## members used for consuming and receiving self.inbound_protobuf_stream = ProtoBufStream() ## members for consuming direction self.last_sent_msgnum = 0 # msg sent counter (message num) self.max_msgnum_to_send = 0 # highest message number allowed to send (most recently received) self.outqueue = MessageStream(self.MAX_OUTQUEUE_SIZE) # small buffer of messages to send self.send_a_marker = False # has a marker been requested? self.prev_message_marker = None # marker for the most recently send message self.prev_message_id = None # id of the most recently send message ## members for producing direction self.last_rcvd_msgnum = 0 # the message number of the most recently received message self.last_acked_msgnum = 0 # the last message number we sent a status for self.max_messages_to_request = 200 # the max number of messages we want to get at a time (probably based on memory or driver limitations) self.driver_append_results = [] # list of results we got appending a message to the driver; this is ordered, where the first element corresponds to message number last_acked_msgnum+1 (messages we have already responded to are not stored here) def on_new_pb_message(self, encoded_pbmessage): """Handles a encoded pbmessage that has arrived""" if (self.msg_state == ServerMsgState.CLOSED): print "internal error: got a pbmessage, but we are still in CLOSED state" exit(1) # TODO: better response to error elif (self.msg_state == ServerMsgState.CLOSING): print "internal error: got a pbmessage, but we are still in CLOSING state" exit(1) # TODO: better response to error elif (self.msg_state == ServerMsgState.NEW): self.on_new_pb_message_in_new_state(encoded_pbmessage) elif (self.msg_state == ServerMsgState.GETTING): self.on_new_pb_message_in_getting_state(encoded_pbmessage) elif (self.msg_state == ServerMsgState.APPENDING): self.on_new_pb_message_in_appending_state(encoded_pbmessage) else: print "internal error: in an unexpected state: " + self.msg_state exit(1) # TODO: better response to error def on_new_pb_message_in_new_state(self, encoded_pbmessage): try: sr = MarconiStreamingAPI_pb2.SetupRequest() sr.ParseFromString(encoded_pbmessage) # TODO: check if succ except: print "Unexpected error in parsing SetupRequest:", sys.exc_info()[0] exit(1) # TODO: better response to error (close wsmessage with an error) #raise self.logger.info("got SetupRequest: " + str(sr)) # look at SetupRequest and validate it is_get = sr.HasField('get') is_append = sr.HasField('append') if (is_get and is_append): print "SetupRequest is invalid, it cannot be both a get and append" exit(1) # TODO: better response to error (close wsmessage with an error) if (is_get): csr = sr.get self.on_new_get_consume_setup_request(csr) elif (is_append): psr = sr.append self.on_new_append_produce_setup_request(psr) else: print "SetupRequest is invalid, no operation requested" exit(1) # TODO: better response to error (close wsmessage with an error) def on_new_get_consume_setup_request(self, csr): # verify that the ConsumerSetupRequest has a queue name for field in ['queue_name','echo_requested','include_claimed','starting_marker']: if not csr.HasField(field): print "ConsumerSetupRequest is invalid, it is missing a: " + field exit(1) # TODO: better response to error (close wsmessage with an error) # TODO: verify user has access to named queue # tell driver to start adding messages to outqueue based on queue name, starting marker, echo requested, and include claimed self.driver.init_get_stream(self.outqueue,csr.queue_name,csr.starting_marker,csr.echo_requested,csr.include_claimed) self.msg_state= ServerMsgState.GETTING self.send_a_100_status_pbmessage() self.periodically_check_for_send_work() # start separate work to periodically check for new message in outqueue or for messages that should be sent def on_new_append_produce_setup_request(self, psr): if not psr.HasField('queue_name'): print "ConsumerSetupRequest is invalid, it is missing a queue_name" exit(1) # TODO: better response to error (close wsmessage with an error) # TODO: verify user has access to named queue # tell driver to get ready for append request based on queue name self.driver.prepare_for_append_stream(psr.queue_name) self.msg_state= ServerMsgState.APPENDING # create a ProduceSetupResponse with status message with 100 and with max_msgnum_to_send set to 0+max_messages_to_request psr = MarconiStreamingAPI_pb2.ProduceSetupResponse() self.fill_status_pbmessage(psr.status,100) psr.max_msgnum_to_send = 0 + self.max_messages_to_request # give our initial number of messages we can handle as a message number # send the ProduceSetupResponse self.send_a_pbmessage(psr) def on_new_pb_message_in_getting_state(self, encoded_pbmessage): try: cr = MarconiStreamingAPI_pb2.ConsumeRequest() cr.ParseFromString(encoded_pbmessage) # TODO: check if succ except: print "Unexpected error in parsing ConsumeRequest:", sys.exc_info()[0] exit(1) # TODO: better response to error (close wsmessage with an error) #raise self.logger.debug("got ConsumeRequest: " + str(cr)) send_marker = cr.send_marker if send_marker is not None and send_marker: # client wants us to send a marker when we can self.send_a_marker = True max_msgnum_to_send = cr.max_msgnum_to_send if max_msgnum_to_send is None: print "ConsumeRequest is invalid, it is missing a max_msgnum_to_send" exit(1) # TODO: better response to error (close wsmessage with an error) else: self.logger.debug("old max_msgnum_to_send=" + str(self.max_msgnum_to_send) + "; new=" + str(max_msgnum_to_send) + "; we last provided #" + str(self.last_sent_msgnum)) self.max_msgnum_to_send = max_msgnum_to_send # we may have been allowed more messages, so this a good time to try to send messages self.send_messages_now() # callback from Autobahn def on_new_pb_message_in_appending_state(self, encoded_pbmessage): # parse encoded pbmessage as a ProduceRequest as produce-request try: pr = MarconiStreamingAPI_pb2.ProduceRequest() pr.ParseFromString(encoded_pbmessage) # TODO: check if succ except: print "Unexpected error in parsing ProduceRequest:", sys.exc_info()[0] exit(1) # TODO: better response to error (close wsmessage with an error) #raise #self.logger.debug("got ProduceRequest: " + str(pr)) # validate produce request for field in ['ttl']: if not pr.HasField(field): print "ProduceRequest is invalid, it is missing a \"" + field + "\" field" exit(1) # TODO: better response to error (close wsmessage with an error; since we couldn't parse, we don't know what message number the client is on any more) if len(pr.payloads) == 0: print "ProduceRequest is invalid, the payloads field is empty" exit(1) # TODO: better response to error (might be able to ignore) elif len(pr.payloads) > self.max_messages_to_request: print "ProduceRequest is invalid, the there are more messages that we said we could handle (we always say %d max, but this ProduceRequest contained %d" % (self.max_messages_to_request, len(pr.payloads)) exit(1) # TODO: better response to error (close wsmessage with an error) # mark that we have received more messages self.last_rcvd_msgnum += len(pr.payloads) self.logger.debug("got %d payloads to append, so last_rcvd_msgnum is now %d" % (len(pr.payloads), self.last_rcvd_msgnum)) # give the driver each of the new messages and collect the results # we store the result for message number N in self.driver_append_results[N-last_acked_msgnum-1] for payload in pr.payloads: # possible TODO: switch to some other way of driver representing result back to us (e.g., abstract them from status codes and/or provide a way to get a specific reason phrase) status_code = self.driver.append(payload,pr.ttl) # append driver's response to driver_append_result self.driver_append_results.append(status_code) assert (self.last_acked_msgnum + len(self.driver_append_results)) == self.last_rcvd_msgnum # now share result of appends # possible TODO: in principle this feedback to the client could be asynchronously, but we do want that feedback to be quite timely while (self.last_acked_msgnum < self.last_rcvd_msgnum): self.logger.debug("last_acked_msgnum=%d, last_rcvd_msgnum=%d, |driver_append_results|=%d" % (self.last_acked_msgnum, self.last_rcvd_msgnum, len(self.driver_append_results))) # find the number of entries in driver_append_result that are identical (same status) to the first entry, left shifting off these entries status_to_report = self.driver_append_results.pop(0) # let's see how many consecutive entries have the same status to report last_msgnum_with_same_status = self.last_acked_msgnum + 1 while (last_msgnum_with_same_status < self.last_rcvd_msgnum) and (self.driver_append_results[0] == status_to_report): self.driver_append_results.pop(0) last_msgnum_with_same_status += 1 # create a ProduceResponse based on this status pr= MarconiStreamingAPI_pb2.ProduceResponse() self.fill_status_pbmessage(pr.status,status_to_report) pr.msgnum_status_is_thru = last_msgnum_with_same_status pr.max_msgnum_to_send = self.last_rcvd_msgnum + self.max_messages_to_request self.send_a_pbmessage(pr) self.last_acked_msgnum = last_msgnum_with_same_status self.logger.debug("last_acked_msgnum=%d, last_rcvd_msgnum=%d, |driver_append_results|=%d" % (self.last_acked_msgnum, self.last_rcvd_msgnum, len(self.driver_append_results))) assert len(self.driver_append_results) == 0 # verify no leftover responses assert (self.last_acked_msgnum + len(self.driver_append_results)) == self.last_rcvd_msgnum # wrap up anything going on for current wsmessage def wrap_up_current_wsmessage(self): self.logger.info("starting wrap_up_current_wsmessage()") if (self.msg_state == ServerMsgState.CLOSED): return elif (self.msg_state == ServerMsgState.CLOSING): return self.msg_state = ServerMsgState.CLOSING # set closing so we won't send more full messages to client, etc if (self.msg_state == ServerMsgState.GETTING): if self.send_a_marker: # we are still on the hook for sending a marker, so the send the one associated with the previous message self.send_a_bare_marker(self.prev_message_marker,self.prev_message_id) elif (self.msg_state == ServerMsgState.APPENDING): self.driver.cancel_append_stream() # all done with responses to the client for this wsmessage self.endMessage() # reset/clear per-wsmessage for consume-direction state including contents of outqueue self.reset_per_wsmsg_state() self.msg_state = ServerMsgState.CLOSED self.logger.info("done with wrap_up_current_wsmessage()") # create a Status pbmessage def fill_status_pbmessage(self,status,status_code,reason_phrase=None): status.status_code = status_code if reason_phrase is not None: status.reason_phrase = reason_phrase return status # create a Status pbmessage def create_status_pbmessage(self,status_code,reason_phrase=None): status = MarconiStreamingAPI_pb2.Status() self.fill_status_pbmessage(status,status_code,reason_phrase) return status def send_a_pbmessage(self,pbmessage): try: encoded_pbmessage = pbmessage.SerializeToString() pbStream = ProtoBufStream() len_prefix = pbStream.encode_length_prefix(len(encoded_pbmessage)) self.sendMessageFrame(len_prefix + encoded_pbmessage) # TODO: check if succ except: print "Unexpected error in sending response:", sys.exc_info()[0] exit(1) # TODO: better response to error def send_status_pbmessage(self,status_code,reason_phrase=None): status = self.create_status_pbmessage(status_code,reason_phrase) self.send_a_pbmessage(status) def send_a_100_status_pbmessage(self): self.send_status_pbmessage(100) # send a MessageAndMetadata to client containing just the given message marker and message ID def send_a_bare_marker(self,msg_marker,msg_id): msg = MarconiStreamingAPI_pb2.MessageAndMetadata() msg.marker = msg_marker msg.id = msg_id self.send_a_pbmessage(msg) # clear send_a_marker since have sent one self.send_a_marker = False # send messages in outqueue (subject to buffering) def buffered_send_messages(self): # TODO: implement buffering logic # return if outqueue is empty # return if last_sent_msgnum >= max_msgnum_to_send # do_send = false # do_send ||= size of outqueue > PREFERED_MIN_NUM_MSGS_TO_SEND # do_send ||= (now - enqueue time for first item in outqueue) > MAX_WAIT_TO_SEND # if do_send invoke send_messages_now self.send_messages_now() def send_messages_now(self): if self.msg_state != ServerMsgState.GETTING: # if we are not in GETTING state we should not be sending new messages return num_mess_to_send = min(self.max_msgnum_to_send-self.last_sent_msgnum, self.outqueue.space_used()) if num_mess_to_send == 0: return cr = MarconiStreamingAPI_pb2.ConsumeResponse() # TODO: when we add the buffering logic, we will need the enqueue time for each message in outqueue self.logger.debug("server interface send_messages_now(): about to grab %d messages from outqueue to send to client" % (num_mess_to_send)) for i in range(0,num_mess_to_send): msg = self.outqueue.remove_first_message() new_msg = cr.messages.add() new_msg.CopyFrom(msg) # msg is an MessageAndMetadata if i == num_mess_to_send: # last item we are going to add include_marker = self.send_a_marker self.send_a_marker = False self.prev_message_marker = msg.marker self.prev_message_id = msg.id else: include_marker = False if not include_marker: new_msg.ClearField('marker') status = cr.status status.status_code = 100 # self.logger.debug("server interface send_messages_now(): about to send %d messages to client" % (num_mess_to_send)) self.send_a_pbmessage(cr) self.last_sent_msgnum += num_mess_to_send # don't add a anOpen method as it seems to not allow the frame interface afterwards # def onOpen(self): # called by autobahn when a new wsmessage has been started by the client def onMessageBegin(self, isBinary): self.logger.debug("got new wsmessage from client") WebSocketServerProtocol.onMessageBegin(self, isBinary) # do one-time and pre-wsmesssage setup if (self.wsmsg_count is None): self.__init__() self.wsmsg_count += 1 if(self.msg_state != ServerMsgState.GETTING): self.msg_state = ServerMsgState.NEW # TODO: verify that isBinary # set up a message back to the client for our responses self.beginMessage(True) # called by autobahn when a new wsmessage frame data has arrived as part of the current inbound wsmessage def onMessageFrameBegin(self, length): WebSocketServerProtocol.onMessageFrameBegin(self, length) # called by autobahn when new data has arrived on the current wsmessage def onMessageFrameData(self, payload): # length = len(payload) self.logger.debug("server got: " + ':'.join(x.encode('hex') for x in payload)) self.inbound_protobuf_stream.append(payload) while (True): try: encoded_pbmessage = self.inbound_protobuf_stream.shift_first_pb() if encoded_pbmessage is None: break except: print "Error parsing protobuf out of data from client in this message; cannot continue with this wsmessage:", sys.exc_info()[0] exit(1) # TODO: better response to error self.on_new_pb_message(encoded_pbmessage) # called by autobahn at end of a wsmessage frame that is part of the current inbound wsmessage def onMessageFrameEnd(self): pass # called by autobahn when the client terminates a wsmessage def onMessageEnd(self): self.wrap_up_current_wsmessage() # check for work that needs to be done for sending messages (e.g., check if the outqueue has new messages or if messages in the outqueue have been waiting too long) and schedule this routine to run again after SEND_WORK_CHECK_FREQ_SECS def periodically_check_for_send_work(self): # self.logger.debug(str(datetime.datetime.now()) + ": periodically_check_for_send_work called") check_later = self.check_for_send_work() if check_later: loop.call_later(self.SEND_WORK_CHECK_FREQ_SECS, self.periodically_check_for_send_work) # check for work that needs to be done for sending messages (e.g., check if the outqueue has new messages or if messages in the outqueue have been waiting too long) # returns true iff we should check for messages again later def check_for_send_work(self): if (self.msg_state == ServerMsgState.GETTING): self.buffered_send_messages() return True else: self.logger.info("disabling calling check_for_send_work since not in GETTING state") return False def handle_interrupt_signal(self): self.logger.info("got interrupt signal") self.wrap_up_current_wsmessage() loop.stop()