Ejemplo n.º 1
0
 def inner_acceptor(handle, events, error):
     """Function that try to accept new connection."""
     if error:  # pragma: no cover
         logger.error(
             'Error handling new connection for'
             ' service %r: %s', service, strerror(error))
         return
     try:
         fd, addr = utils.accept_connection(listen_fd)
     except OSError as exc:
         if exc.errno not in NOTBLOCK:
             raise
         return
     try:
         # Setup socket.
         utils.set_nonblocking(fd)
         utils.set_sockopt(fd, socket.IPPROTO_TCP, socket.TCP_NODELAY,
                           1)
     except:
         os.close(fd)
         raise
     handle = TCP(loop)
     handle.open(fd)
     connection = self.Connection(producer, loop, handle, addr,
                                  on_close)
     connections.register(connection)
Ejemplo n.º 2
0
 def start(self, loop: Loop):
     """
     Start the Server on the loop
     SERVICE
     This is where the TCP Server binds to the event loop to start accepting new connections,
     it is listening on the given ip address and port.
     The default port is 8864
     :param loop: Loop to bind the server to
     """
     self.__logger.info(
         "Listening on {ip}:{port} for new connections".format(
             ip=self.__ip, port=self.__port))
     self.__handle = TCP(loop)
     # Listen on the provided ip and port
     self.__handle.bind((self.__ip, self.__port))
     # Register connection handler
     self.__handle.listen(self.on_connection)
Ejemplo n.º 3
0
    def start(self, loop: Loop) -> None:
        """
        Attach the client to the event loop
        CLIENT

        :param loop: Event Loop object to attach to
        """
        # Register the TCP client to connect to the server
        self.__tcp_handle = TCP(loop)
        self.__tcp_handle.connect((self.__ip, self.__port),
                                  self.__on_connection_start)

        # Register the interrupt handler to catch sigint
        self.__interrupt_handle = Signal(loop)
        self.__interrupt_handle.start(self.__on_signal, SIGINT)

        # Create the timeout handler
        self.__timeout_timer = Timer(loop)
Ejemplo n.º 4
0
def on_tcp_read(client: pyuv.TCP, data, error):
    if not data:
        logging.info("No data received, closing connection")
        client.close()
        clients.remove(client)
        return
    req = data.split(b'\r\n')[0].split(b'\x20')
    setup = ""
    output = ""
    logging.debug("Got a %s request", req[0])
    with open("index.html", "r") as fh:
        index = fh.read()
    if req[0] == b'POST':
        params = data.split(b'\r\n')[-1]
        source = params.split(b'&')[0].split(b'=')[1].decode("utf-8")
        source = replace_special_chars(source)
        with open(cpp_source_file, "w") as fh:
            logging.debug("Loading template file")
            fh.write(source)
        cmd_setup = ["docker", "build", "-t", "myapp", "sandbox"]
        cmd_run = ["docker", "run", "myapp"]
        try:
            setup = subprocess.check_output(cmd_setup).decode("utf-8")
        except Exception as e:
            setup = "Build failed: " + str(e)
        try:
            output = subprocess.check_output(cmd_run).decode("utf-8")
        except Exception as e:
            output = "Build failed: " + str(e)
    else:
        with open(os.path.join(root_path, "examples/helloworld.cpp")) as fh:
            source = fh.read()
    index = index.replace("<%source>", source)
    index = index.replace("<%setup>", setup)
    index = index.replace("<%output>", output)
    index = index.replace("<%source_html>", html_safe(source))
    client.write("HTTP/1.1 200 OK\r\n\r\n".encode("utf-8"))
    client.write(index.encode("utf-8"))
    client.write(b'\r\n')
    client.close()
    clients.remove(client)
Ejemplo n.º 5
0
 def __on_connection_start(self, handler: TCP, error: Optional[int]):
     """
     Called when the TCP connection is started
     :param handler: TCP Socket that is connected to the server
     :param error: An Error would be here if it failed to connect to the server
     """
     if error is not None:
         if error == -4095:
             self.__logger.warning("Got End of File from server")
             self.shutdown()
         else:
             self.__logger.error(
                 "Unknown TCP Error: {stringed}, errno: {no}".format(
                     stringed=errno.strerror(error), no=error))
             self.shutdown()
     else:
         self.__logger.info(
             "Connected to server at {ip}".format(ip=self.__ip))
         # Start Asynchronous Read on the TCP Socket
         handler.start_read(self.__on_data)
         # Send the Log In Message
         self.__send(Connect(1, self.__username, self.__password))
         self.__protocol_state = ProtocolState.IN_QUEUE
Ejemplo n.º 6
0
    def on_connection(self, handle: TCP, error: Optional[int]):
        """
        Called when a new user connects to the server
        CONCURRENT
        This function is called every time a new user connects,
        and then passes off the TCP socket to a session object which handles it asynchronously
        Internally, the PYUV library is using select to pick between

        :param handle: The TCP Server
        :param error: Where an error would be if there was an error connecting
        """
        if error is not None:
            self.__logger.warning(
                "Got {error}, {errno} when client attempted to connect to server"
                .format(error=error, errno=errno.strerror(error)))
        else:
            new_connection = TCP(handle.loop)
            # Accept a connection
            handle.accept(new_connection)
            # Add it to internal connections and create a session
            session = self.__session_creator(new_connection)
            session.start()
            self.__connections.append(new_connection)
Ejemplo n.º 7
0
 def inner_acceptor(handle, events, error):
     """Function that try to accept new connection."""
     if error:  # pragma: no cover
         logger.error('Error handling new connection for'
                      ' service %r: %s', service, strerror(error))
         return
     try:
         fd, addr = utils.accept_connection(listen_fd)
     except OSError as exc:
         if exc.errno not in NOTBLOCK:
             raise
         return
     try:
         # Setup socket.
         utils.set_nonblocking(fd)
         utils.set_sockopt(fd, socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
     except:
         os.close(fd)
         raise
     handle = TCP(loop)
     handle.open(fd)
     connection = self.Connection(producer, loop, handle, addr, on_close)
     connections.register(connection)
Ejemplo n.º 8
0
def on_tcp_connection(server: pyuv.TCP, error):
    client = pyuv.TCP(server.loop)
    logging.info("Client connected")
    server.accept(client)
    clients.append(client)
    client.start_read(on_tcp_read)
Ejemplo n.º 9
0
class Client:
    """
    STATEFUL
    Represents a User playing checkers
    The .__protocol_state property shows what state of the protocol this client is currently in
    """
    def __init__(self, ip: str, port: int, username: bytes, password: bytes,
                 interface: Interface, logger: Logger) -> None:
        """
        Constructor for the Client class
        :param ip: The IP Address for the server to connect to
        :param port: The Port to connect to on the remove server
        :param username: Username to connect with
        :param password: Password to connect with
        :param interface: The User Interface object to allow a user to interface with the game
        :param logger: Logger to write messages to
        """
        # Private Properties
        self.__ip = ip
        self.__port = port
        self.__tcp_handle: TCP = None
        self.__interrupt_handle: Signal = None
        self.__username = username
        self.__password = password
        self.__protocol_state = ProtocolState.UNAUTHENTICATED
        self.__ui = interface
        self.__logger = logger
        # Timeout settings
        self.__in_timeout = False
        self.__timeout_buffer = b""
        self.__timeout_timer: Timer = None
        self.__timeout_duration = 1

    # Public Methods

    def start(self, loop: Loop) -> None:
        """
        Attach the client to the event loop
        CLIENT

        :param loop: Event Loop object to attach to
        """
        # Register the TCP client to connect to the server
        self.__tcp_handle = TCP(loop)
        self.__tcp_handle.connect((self.__ip, self.__port),
                                  self.__on_connection_start)

        # Register the interrupt handler to catch sigint
        self.__interrupt_handle = Signal(loop)
        self.__interrupt_handle.start(self.__on_signal, SIGINT)

        # Create the timeout handler
        self.__timeout_timer = Timer(loop)

    def shutdown(self) -> None:
        """
        Shut the client down by telling the server it is logging out (if it is logged in)
        and then closing the connection
        """
        self.__logger.info("Shutting Down...")
        if self.__protocol_state != ProtocolState.UNAUTHENTICATED:
            self.__send(LogOut())
        self.__close()

    # Private Methods

    def __send(self, msg: Message) -> None:
        """
        Encode and Send a message to the server
        :param msg: Message to send
        """
        self.__logger.debug("Sending {msg}, encoded as {raw}".format(
            msg=msg, raw=msg.encode()))
        self.__tcp_handle.write(msg.encode())

    def __close(self):
        """
        Close the connection to the server and shut down the signal handler
        """
        self.__tcp_handle.stop_read()
        self.__tcp_handle.shutdown()
        self.__interrupt_handle.close()

    # Event Handles

    def __on_connection_start(self, handler: TCP, error: Optional[int]):
        """
        Called when the TCP connection is started
        :param handler: TCP Socket that is connected to the server
        :param error: An Error would be here if it failed to connect to the server
        """
        if error is not None:
            if error == -4095:
                self.__logger.warning("Got End of File from server")
                self.shutdown()
            else:
                self.__logger.error(
                    "Unknown TCP Error: {stringed}, errno: {no}".format(
                        stringed=errno.strerror(error), no=error))
                self.shutdown()
        else:
            self.__logger.info(
                "Connected to server at {ip}".format(ip=self.__ip))
            # Start Asynchronous Read on the TCP Socket
            handler.start_read(self.__on_data)
            # Send the Log In Message
            self.__send(Connect(1, self.__username, self.__password))
            self.__protocol_state = ProtocolState.IN_QUEUE

    def __on_data(self, client: TCP, data: bytes, error: Optional[int]):
        """
        Called when the TCP socket receives data
        :param client: The TCP Socket that received Data
        :param data: Read Data
        :param error: An Error would be here if it failed to read data
        """
        if error is not None:
            if error == -4095:
                self.__logger.warning("Got End of File from server")
                self.shutdown()
            else:
                self.__logger.error(
                    "Unknown TCP Error: {stringed}, errno: {no}".format(
                        stringed=errno.strerror(error), no=error))
                self.shutdown()
        else:
            # Parse the Message
            if self.__timeout_buffer != b"":
                data = self.__timeout_buffer + data
                self.__timeout_buffer = b""
            try:
                # Clear out the timeout buffer
                if self.__in_timeout:
                    self.__timeout_timer.stop()
                    self.__in_timeout = False
                while data != b"":
                    msg = message_to_type(data).parse_and_decode(data)
                    self.__logger.debug(
                        "Got {message} in state {state}".format(
                            message=msg, state=self.__protocol_state.name))
                    data = data[msg.calc_size() + 1:]
                    # Dispatch to another function based on current DFA state
                    {
                        ProtocolState.UNAUTHENTICATED:
                        self.__msg_on_unauthenticated,
                        ProtocolState.IN_QUEUE: self.__msg_on_in_queue,
                        ProtocolState.PROCESSING_GAME_STATE:
                        self.__msg_on_processing_game_state,
                        ProtocolState.USER_MOVE: self.__msg_on_user_move,
                        ProtocolState.GAME_END: self.__msg_on_game_end
                    }[self.__protocol_state](msg)
            except NotEnoughData:

                def timeout(timer_handle: Timer):
                    self.__logger.critical(
                        "Got invalid incomplete message ({raw}) from server, closing connections"
                        .format(raw=self.__timeout_buffer))
                    self.shutdown()

                # If already in timeout, restart the timer
                if self.__in_timeout:
                    self.__timeout_timer.stop()
                # Append data to the buffer
                self.__timeout_buffer += data
                # Set in timeout
                self.__in_timeout = True
                # Start timer to close connection after timeout duration if needed
                self.__timeout_timer.start(timeout, self.__timeout_duration, 0)
            except InvalidType:
                self.__logger.critical(
                    "Got Invalid Message Type in message ({raw}) from server, closing connections"
                    .format(raw=data))
                self.shutdown()
            except StructError:
                self.__logger.critical(
                    "Got Invalid Message ({raw}) from server, closing connections"
                    .format(raw=data))
                self.shutdown()

    def __on_signal(self, sig_handler: Signal, signal: int) -> None:
        """
        Callback to be called when a signal is caught
        :param sig_handler: The Signal Handler that caught the signal
        :param signal: The signal that was caught
        """
        self.__logger.info("Received Signal {}".format(signal))
        self.shutdown()

    # State based event handlers
    # These functions implement the Client side of the DFA
    # All of the following functions are STATEFUL

    def __msg_on_unauthenticated(self, msg: Message) -> None:
        """
        Logic to do while in the Unauthenticated state
        :param msg: Message received
        """
        self.__logger.fatal(
            "Received invalid message of type {type} for state {state}".format(
                type=msg.__class__.__name__, state=self.__protocol_state.name))
        self.shutdown()

    def __msg_on_in_queue(self, msg: Message) -> None:
        """
        Logic to do while in the In Queue state
        :param msg: Message received
        """
        if isinstance(msg, InvalidLogin):
            # This is the edge of the DFA from In Queue -> Unauthenticated (Invalid Login)
            self.__protocol_state = ProtocolState.UNAUTHENTICATED
            bad_login_reasons = {
                InvalidLogin.Reasons.InvalidPassword: "******",
                InvalidLogin.Reasons.AlreadyLoggedIn:
                "User is already logged in",
                InvalidLogin.Reasons.AccountDoesNotExist: "User does not exist"
            }
            self.__ui.display_message("Invalid Login, {reason}".format(
                reason=bad_login_reasons[msg.reason]))
            # Request a new set of credentials from the user
            self.__username, self.__password = self.__ui.request_credentials()
            self.__send(Connect(1, self.__username, self.__password))
            self.__protocol_state = ProtocolState.IN_QUEUE
        elif isinstance(msg, InvalidVersion):
            # This is the edge of the DFA from In Queue -> Unauthenticated (Invalid Login)
            self.__ui.display_message(
                "The Server does not support your version of the client, it supports versions {lowest}-{highest}"
                .format(lowest=msg.lowest_supported_version,
                        highest=msg.highest_supported_version))
            self.shutdown()
        elif isinstance(msg, QueuePosition):
            # This is the edge of the DFA from In Queue -> In Queue (Queue Pos)
            self.__ui.show_queue_position(msg.queue_pos, msg.rating,
                                          msg.queue_size)
        elif isinstance(msg, GameStart):
            # This is the edge of the DFA from In Queue -> Processing Game State (Game Start)
            self.__ui.game_start(msg.opponent_name.decode("utf-8"),
                                 msg.opponent_rating)
            self.__protocol_state = ProtocolState.PROCESSING_GAME_STATE
        else:
            self.__logger.fatal(
                "Received invalid message of type {type} for state {state}".
                format(type=msg.__class__.__name__,
                       state=self.__protocol_state.name))
            self.shutdown()

    def __msg_on_processing_game_state(self, msg: Message):
        """
        Logic to do while in the Processing Game State state
        :param msg: Message received
        """
        if isinstance(msg, YourTurn):
            # This is the edge of the DFA from Processing Game State -> User Move (Your Turn)
            self.__protocol_state = ProtocolState.USER_MOVE
            # Check if this is the beginning of the game
            if msg.last_move == Move(0, 0, Direction.Negative,
                                     Direction.Negative):
                self.__ui.display_message("You go first!")
            else:
                self.__ui.display_message("Last Move was : {move}".format(
                    move=msg.last_move.__repr__()))
            self.__ui.display(msg.board)
            self.__send(MakeMove(self.__ui.get_move()))
            self.__protocol_state = ProtocolState.PROCESSING_GAME_STATE
        elif isinstance(msg, CompulsoryMove):
            # This is the edge of the DFA from Processing Game State -> Processing Game State (Compulsory Move)
            self.__ui.display_message(
                "Compulsory Move: {move}".format(move=msg.move.__repr__()))
            self.__ui.display(msg.board)
        elif isinstance(msg, InvalidMove):
            # This is the edge of the DFA from Processing Game State -> User Move (Invalid Move)
            self.__protocol_state = ProtocolState.USER_MOVE
            self.__ui.display_message(
                "{move} Is an invalid move".format(move=msg.move.__repr__()))
            self.__ui.display(msg.board)
            self.__send(MakeMove(self.__ui.get_move()))
            self.__protocol_state = ProtocolState.PROCESSING_GAME_STATE
        elif isinstance(msg, GameOver):
            # This is the edge of the DFA from Processing Game State -> Game End (Game Over)
            self.__protocol_state = ProtocolState.GAME_END
            self.__ui.game_over(msg.board, msg.old_rating, msg.new_rating,
                                msg.you_won != 0)
            if self.__ui.prompt_play_again():
                self.__protocol_state = ProtocolState.IN_QUEUE
                self.__send(ReQueue())
            else:
                self.shutdown()

        elif isinstance(msg, OpponentDisconnect):
            # This is the edge of the DFA from Processing Game State -> Game End (Opponent Disconnect)
            self.__protocol_state = ProtocolState.GAME_END
            self.__ui.opponent_left()
            if self.__ui.prompt_play_again():
                self.__protocol_state = ProtocolState.IN_QUEUE
                self.__send(ReQueue())
            else:
                self.shutdown()
        else:
            self.__logger.fatal(
                "Received invalid message of type {type} for state {state}".
                format(type=msg.__class__.__name__,
                       state=self.__protocol_state.name))
            self.shutdown()

    def __msg_on_user_move(self, msg: Message):
        """
        Logic to do while in the User Move state
        :param msg: Message received
        """
        if isinstance(msg, OpponentDisconnect):
            # This is the edge of the DFA from User Move -> Game End (Opponent Disconnect)
            self.__protocol_state = ProtocolState.GAME_END
            self.__ui.opponent_left()
            if self.__ui.prompt_play_again():
                self.__protocol_state = ProtocolState.IN_QUEUE
                self.__send(ReQueue())
            else:
                self.shutdown()
        else:
            self.__logger.fatal(
                "Received invalid message of type {type} for state {state}".
                format(type=msg.__class__.__name__,
                       state=self.__protocol_state.name))
            self.shutdown()

    def __msg_on_game_end(self, msg: Message):
        """
        Logic to do while in the Game End state
        :param msg: Message Received
        """
        self.__logger.fatal(
            "Received invalid message of type {type} for state {state}".format(
                type=msg.__class__.__name__, state=self.__protocol_state.name))
        self.shutdown()
Ejemplo n.º 10
0
class Server:
    """ The Server accepts incoming TCP connections and moves the TCP socket into a Session object. """
    def __init__(self, listen_ip: str, port: int,
                 session_creator: Callable[[TCP], Session], logger: Logger):
        """
        Create a server object and initialize private members
        :param listen_ip: IP Address to listen on for new connections
        :param port: Port to listen on for new connections
        :param session_creator: Callable to use to create new sessions from TCP sockets
        :param logger: Logger to log to
        """
        self.__ip = listen_ip
        self.__port = port
        self.__connections: List[TCP] = []
        self.__handle: TCP = None
        self.__session_creator = session_creator
        self.__logger = logger

    def on_connection(self, handle: TCP, error: Optional[int]):
        """
        Called when a new user connects to the server
        CONCURRENT
        This function is called every time a new user connects,
        and then passes off the TCP socket to a session object which handles it asynchronously
        Internally, the PYUV library is using select to pick between

        :param handle: The TCP Server
        :param error: Where an error would be if there was an error connecting
        """
        if error is not None:
            self.__logger.warning(
                "Got {error}, {errno} when client attempted to connect to server"
                .format(error=error, errno=errno.strerror(error)))
        else:
            new_connection = TCP(handle.loop)
            # Accept a connection
            handle.accept(new_connection)
            # Add it to internal connections and create a session
            session = self.__session_creator(new_connection)
            session.start()
            self.__connections.append(new_connection)

    def start(self, loop: Loop):
        """
        Start the Server on the loop
        SERVICE
        This is where the TCP Server binds to the event loop to start accepting new connections,
        it is listening on the given ip address and port.
        The default port is 8864
        :param loop: Loop to bind the server to
        """
        self.__logger.info(
            "Listening on {ip}:{port} for new connections".format(
                ip=self.__ip, port=self.__port))
        self.__handle = TCP(loop)
        # Listen on the provided ip and port
        self.__handle.bind((self.__ip, self.__port))
        # Register connection handler
        self.__handle.listen(self.on_connection)

    def close(self):
        """
        Must close all clients when disconnecting
        """
        self.__logger.info(
            "Stopped accepting new connections, closing all connections that are open"
        )
        [c.close() for c in self.__connections if c.active]
        if self.__handle is not None and self.__handle.active:
            self.__handle.close()