server_udp_or_tcp.py 6.06 KB
#!/usr/bin/env python3

# cf https://docs.python.org/3/library/socketserver.html#socketserver-tcpserver-example
# https://www.linuxtopia.org/online_books/programming_books/python_programming/python_ch36s06.html

"""Socket Server implementation

To be used as a minimalist telescope simulator to which a socket client (SocketClient) can connect
"""

# Standard library imports
import os
import socketserver
import sys

# Third party imports
# None

# Local application imports
# None
#from .client_channel import printd
'''
sys.path.append('..')
from device_controller.abstract_component.device_simulator import getc, getp
'''


def printd(*args, **kwargs):
    if os.environ.get('PYROS_DEBUG', '0')=='1': print(*args, **kwargs)


# Very BASIC implementation
def make_answer_for_request_CMD_TO_UPPER(request_bytes):
    #raise NotImplementedError
    printd("(SIM serv) Request received is", request_bytes)

    # Convert to string
    request = request_bytes.decode("utf-8")
    #if len(request) < STAMP_LENGTH+2+1: raise UnknownCommandException(request)

    # Remove TERMINATOR
    #request = request[0:-1]

    # Remove leading stamp
    #stamp = request[0:STAMP_LENGTH]
    #command = request[STAMP_LENGTH:]
    command = request
    printd("(SIM serv) Command received is", repr(command))

    answer = 'ANSWER TO '+command.upper()
    #if command == ':GR#': answer = "15:01:49"
    #elif command == ':GD#': answer = "+12:29"
    #elif command == ':SG+00#': answer = "1"
    #elif command == ':GG#': answer = "+00"

    #full_answer_in_bytes = bytes(stamp + answer + '#' + TERMINATOR, "utf-8")
    full_answer_in_bytes = bytes(answer, "utf-8")
    #printd("request str upper is", str(request).upper())
    printd("(SIM serv) Answer sent is", full_answer_in_bytes)
    return full_answer_in_bytes


# Default BASIC function "make_answer_for_request" to be overriden by subclass
# Select the function we want to use by default
make_answer_for_request = make_answer_for_request_CMD_TO_UPPER


def get_SocketServer_UDP_TCP(myhost:str="localhost", myport:int=11110, PROTOCOL:str="TCP", func_make_answer_for_request=make_answer_for_request):

    socketserver_type = socketserver.UDPServer if PROTOCOL=="UDP" else socketserver.TCPServer
    socketserver_handler_type = socketserver.DatagramRequestHandler if PROTOCOL=="UDP" else socketserver.StreamRequestHandler

    
    """
    A classic version of request handler:
    """
    class _MyUDPorTCPHandler_classic(socketserver.BaseRequestHandler):
        # The request handler class for our server.
        # It is instantiated once per connection to the server, and must
        # override the handle() method to implement communication to the client
    
        def handle(self):
    
            # Get received data
            # - TCP
            if PROTOCOL == "TCP": 
                # For TCP, self.request is the TCP socket connected to the client
                data_rcv = self.request.recv(1024).strip()
            # - UDP
            else:
                # For UDP, self.request consists of a pair of data and client socket
                data_rcv = self.request[0].strip()
                socket_client = self.request[1]
            printd("(SIM serv) {} wrote:".format(self.client_address[0]))
            printd("(SIM serv)", data_rcv)
    
            # Send back the same data, but upper-cased
            # - TCP
            if PROTOCOL == "TCP": 
                self.request.sendall(data_rcv.upper())
            # - UDP
            # Since there is no connection, the client address must be given explicitly when sending data back via sendto().
            else:
                socket_client.sendto(data_rcv.upper(), self.client_address)

    """
    An alternative request handler class that makes use of streams 
    (file-like objects that simplify communication by providing the standard file interface):
    """
    #class MyTCPHandler(socketserver.StreamRequestHandler):
    #class MyUDPHandler(socketserver.DatagramRequestHandler):
    class _MyUDPorTCPHandler(socketserver_handler_type):

        
        def get_useful_data(self, data_received):
            return data_received
        
        '''
        def make_answer_for_request(self, request_bytes:bytes):
            ##return self.make_answer_for_request_Gemini(request_bytes)
            raise NotImplementedError
        '''



        # @override
        def handle(self):
    
            # 1) RECEIVE REQUEST from client
            # self.rfile is a file-like object created by the handler;
            # we can now use e.g. readline() instead of raw recv() calls
            #data_received = self.request.recv(1024)
            data_received = self.rfile.readline().strip() # data is "bytes" type
            data_useful = self.get_useful_data(data_received)
            #printd("data type is", type(data))
            #printd("\nFrom {}, received: {}".format(self.client_address[0], data_useful))
            printd(f"\n(SIM server_udp_or_tcp) From {self.client_address[0]}, received: {data_useful}\n")
    
            # 2) SEND REPLY to client
            # Likewise, self.wfile is a file-like object used to write back to the client
            #self.wfile.write(self.make_answer_for_request(data_useful))
            self.wfile.write(func_make_answer_for_request(data_useful))
            # inutile, ca plante car data est deja en bytes:
            #self.wfile.write(data.upper().encode())
    
    
    # Return a "classic" handler:
    #return socketserver_type((HOST, PORT), _MyUDPorTCPHandler_classic)

    # or a more "modern" handler:
    return socketserver_type((myhost, myport), _MyUDPorTCPHandler)



if __name__ == "__main__":
    #with socketserver_type((HOST, PORT), MyUDPorTCPHandler_classic) as myserver:
    '''
    with socketserver_type((HOST, PORT), MyUDPorTCPHandler) as myserver:
        myserver.serve_forever()
    '''
    #with get_SocketServer_UDP_TCP("localhost", 11110, "UDP", make_answer_for_request) as myserver:
    with get_SocketServer_UDP_TCP("localhost", 11110, "UDP") as myserver:
        # Handle requests in an infinite loop. Runs until the loop is broken with an exception
        myserver.serve_forever()