def _select_loop(self): # efficient I/O multiplexing self._selector = selectors.DefaultSelector() # register server socket file descriptor self._selector.register( self._server_fd, selectors.EVENT_READ, (self._accept_client, None)) try: while True: events = self._selector.select() for key, mask in events: # accept new client callback, namespace = key.data try: if namespace is None: callback(key.fileobj) else: callback(key.fileobj, namespace) except exceptions.ExitWrite: # on client/server closed pass # clean all socket write buffer self._clean_write_queue() except KeyboardInterrupt: # when start debug mode listen Ctrl-C logger.info('<Ctrl + C> Bye, Never BUG') exit() except Exception as e: logger.error('Fatal Error occurs for {}'.format(repr(e))) raise
def register_controller(self, namespace, controller_name): exceptions.raise_parameter_error('namespace', str, namespace) if not issubclass(controller_name, base_controller.BaseController): raise exceptions.ParameterError( 'handlers must be derived with WebSocketHandlerProtocol') self._router.register(namespace, 'controller', controller_name) logger.info("Controller: {namespace} => {controller}".format( namespace=namespace, controller=controller_name))
def register_default_handler(self, class_object): if not issubclass(class_object, handler.WebSocketHandlerProtocol): raise exceptions.ParameterError( 'handlers must be derived with WebSocketHandlerProtocol') logger.info('Default handler: {}'.format(class_object)) self._router.register_default('handler', class_object) @functools.wraps(class_object) def _handler_wrapper(*args, **kwargs): return class_object(*args, **kwargs) return _handler_wrapper
def _decorator_wrapper(class_object): if not issubclass(class_object, handler.WebSocketHandlerProtocol): raise exceptions.ParameterError( 'handlers must be derived with WebSocketHandlerProtocol') self._router.register(namespace, 'handler', class_object) class_object.__namespace__ = namespace logger.info("Handler: '{namespace}' => {handler}".format( namespace=namespace, handler=class_object)) @functools.wraps(class_object) def _handler_wrapper(*args, **kwargs): return class_object(*args, **kwargs) return _handler_wrapper
def run_forever(self): # Start deamon on background super(WebSocketServerBase, self).run_forever() # Create server socket file descriptor self._server_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Set socket option, REUSEADDR = True self._server_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Set using non-block socket self._server_fd.setblocking(False) # Bind server listen port self._server_fd.bind(self._server_address) # Max connect queue size self._server_fd.listen(WebSocketServer.LISTEN_SIZE) logger.info('Server running in {}:{}'.format(*self._server_address)) # enter the main loop self._select_loop()
def _start_deamon(self): if not self._debug and (os.name == 'nt' or not hasattr(os, 'fork')): raise exceptions.DeamonError('Windows does not support fork') # double fork create a deamon try: pid = os.fork() # fork #1 if pid > 0: # parent exit exit() except OSError as e: raise exceptions.FatalError( 'Fork #1 error occurs, reason({})'.format(e)) os.chdir('/') os.setsid() os.umask(0) try: pid = os.fork() # fork #2 if pid > 0: # parent exit exit() except OSError as e: raise exceptions.FatalError( 'Fork #2 error occurs, reason({})'.format(e)) # redirect all std file descriptor sys.stdout.flush() sys.stderr.flush() _stdin = open(self._stdin, 'r') _stdout = open(self._stdout, 'a') # if require non-buffer, open mode muse be `b` _stderr = open(self._stderr, 'wb+', buffering=0) os.dup2(_stdin.fileno(), sys.stdin.fileno()) os.dup2(_stdout.fileno(), sys.stdout.fileno()) os.dup2(_stderr.fileno(), sys.stderr.fileno()) # terminal signal signal.signal(signal.SIGTERM, self._signal_handler) # kill signal signal.signal(signal.SIGILL, self._signal_handler) # system interrupt signal.signal(signal.SIGINT, signal.SIG_IGN) # register function at exit atexit.register(self._remove_pid_file) # write pid file with open(self._pid_file, 'w') as fd: fd.write('{pid}\n'.format(pid=os.getpid())) logger.info('Daemon has been started')
def _socket_ready_receive(self, socket_fd, namespace): try: controller = \ self._client_list[namespace][socket_fd] # type: BaseController controller.ready_receive() except exceptions.ConnectClosed as e: # from server close if self._close_information[socket_fd][0] is None: # TODO. handler send close-frame if e.args[0][0] == 1000: # client first send close-frame self._close_information[socket_fd] = ('client', False) else: self._close_information[socket_fd] = \ (self._close_information[socket_fd][0], True) self._close_client(socket_fd, namespace) if e.args[0][0] != 1000: logger.info('Server active close-frame, reason({})'.format( e.args[0][0])) self._write_queue[socket_fd].append(ws_frame.generate_close_frame( extra_data=e.args[0][1], errno=e.args[0][0]))
def verify_frame(self, frame: ws_frame.FrameBase): # MUST be 0 unless an extension is negotiated that defines meanings # for non-zero values. If a nonzero value is received and none of # the negotiated extensions defines the meaning of such a nonzero # value, the receiving endpoint MUST _Fail the WebSocket # Connection_. if frame.flag_rsv1 or frame.flag_rsv2 or frame.flag_rsv3: logger.info('{} send frame invalid'.format(self._client_name)) return False # As control frames cannot be fragmented, an intermediary MUST NOT # attempt to change the fragmentation of a control frame. if frame.flag_opcode >= 8 and frame.flag_fin != 1: logger.info('{} send frame invalid'.format(self._client_name)) return False # All control frames MUST have a payload length of 125 bytes or less # and MUST NOT be fragmented. if frame.flag_opcode >= 0x08 and frame.payload_data_length > 125: logger.info('{} send frame invalid'.format(self._client_name)) return False # An unfragmented message consists of a single frame with the FIN # bit set and an opcode other than 0. if frame.flag_fin == 1 and frame.flag_opcode == 0: logger.info('{} send frame invalid'.format(self._client_name)) return False # a client MUST mask all frames that it sends to the server. The server # MUST close the connection upon receiving a frame that is not masked. if frame.flag_mask == 1 and frame.mask_key is False: # In this case, a server MAY send a Close frame with a status code # of 1002 logger.info('{} send frame invalid'.format(self._client_name)) return False return True
def _accept_http_handshake(self, socket_fd): _tcp_stream = \ self._client_list['default'][socket_fd] # type:tcp_stream.TCPStream # receive data from kernel tcp buffer pos = _tcp_stream.find_buffer(b'\r\n\r\n') if pos is -1: return http_request = http_message.factory_http_message( _tcp_stream.feed_buffer(pos)) # Verify http request is correct support_extension = tuple() try: support_extension_list = \ http_verifier.verify_request( socket_fd.getpeername(), http_request) support_extension = ( b'Sec-WebSocket-Extensions', b','.join(map( lambda x: generic.to_bytes(x), support_extension_list))) if not support_extension[1]: support_extension = None except exceptions.HttpVerifierError: # verify error occurs, return 403 Forbidden http_response = http_message.HttpResponse( 403, (b'X-Forbidden-Reason', b'http-options-invalid')) # write directly to the data, and then close the connection self._socket_ready_write( socket_fd, http_response, http_request.url_path) # close connection self._close_client(socket_fd, 'default') logger.debug('Request: {}'.format(repr(http_request))) # TODO. chunk header-field if 'Content-Length' in http_request.header: logger.info('Request has payload, length = {}'.format( http_request.header.get_value('Content-Length'))) # buffer length is too short if _tcp_stream.get_buffer_length() < \ http_request.header.get_value('Content-Length'): return # drop payload data _tcp_stream.feed_buffer(http_request['Content-Length'].value) ws_key = http_request.header.get_value('Sec-WebSocket-Key') # Optionally, other header fields, such as those used to send # cookies or request authentication to a server. http_response = http_message.HttpResponse( 101, *( (b'Upgrade', b'websocket'), (b'Connection', b'Upgrade'), (b'Sec-WebSocket-Accept', ws_utils.ws_accept_key(ws_key)), support_extension ) ) try: namespace = generic.to_string(http_request.url_path) # get handler or default handler _handler = self._router.solution(namespace, 'handler')(socket_fd) # get controller or default controller controller_name = self._router.solution(namespace, 'controller') # initial controller if namespace not in self._client_list: self._client_list[namespace] = dict() self._client_list[namespace][socket_fd] = controller_name( self._client_list['default'].pop(socket_fd), self._write_queue[socket_fd].append, _handler) # send http-handshake-response self._write_queue[socket_fd].append(http_response) # notification handler connect event response = _handler.on_connect() # send connect message if hasattr(response, 'pack'): self._write_queue[socket_fd].append(response) elif hasattr(response, 'generate_frame'): self._write_queue[socket_fd].append(response.generate_frame) # modify selector data self._selector.modify(socket_fd, selectors.EVENT_READ, (self._socket_ready_receive, namespace)) except exceptions.ParameterError: raise exceptions.FatalError('handler not found')
def register_default_controller(self, controller_name): if not issubclass(controller_name, base_controller.BaseController): raise exceptions.ParameterError( 'handlers must be derived with WebSocketHandlerProtocol') logger.info('Default controller: {}'.format(controller_name)) self._router.register_default('controller', controller_name)
def _remove_pid_file(self): logger.info('Daemon has exited') if os.path.exists(self._pid_file): os.remove(self._pid_file)
def _signal_handler(self, signum, frame): logger.info('Daemon receive an exit signal({}: {})'.format( signum, frame)) self._remove_pid_file() exit()