skip to Main Content

Instructions

In this stage of this project I’m making, I need to add support for the ECHO command.

ECHO is a command like PING that’s used for testing and debugging. It accepts a single argument and returns it back as a RESP bulk string.

$ redis-cli PING # The command I implemented in previous stages
PONG
$ redis-cli ECHO hey # The command I will implement in this stage
hey

Tests

The tester will execute the program like this:

$ ./your_program.sh

It’ll then send an ECHO command with an argument to your server:

$ redis-cli ECHO hey

The tester will expect to receive $3\r\nhey\r\n as a response (that’s the string hey encoded as a RESP bulk string.

This is my solution:

import socket
import threading


def handle_client(conn, addr):
    print(f'Connected to {addr}')
    
    while True:
        data = conn.recv(1024)  # Read data as bytes
        if not data:  # If no data is received, exit the loop
            break
        
        request = data.decode()

        pong = b"+PONGrn"

        if request.lower().startswith("echo"):
            # Parse the message for the ECHO command
            parts = request.split("rn")
            if len(parts) >= 4:
                res_data = parts[3]  # Extract the actual argument for ECHO (the string after ECHO)
                content_len = len(res_data)  # Get the length of the argument
                # Correctly format the response as a bulk string
                response = f"${content_len}rn{res_data}rn"
                conn.send(response.encode())  # Send the response back
        else:
            # Default response for PING
            conn.send(b"+PONGrn")

    print(f"Connection to {addr} closed")
    conn.close()  # Close the connection with the client

def main():
    print("Logs from your program will appear here!")
    
    # Set up the server socket and start listening for connections
    server_socket = socket.create_server(("localhost", 6379), reuse_port=True)
    server_socket.listen()
    
    while True:
        # Accept a new client connection
        conn, addr = server_socket.accept()
        
        # Create and start a new thread to handle the client
        client_thread = threading.Thread(target=handle_client, args=(conn, addr))
        client_thread.start()

if __name__ == "__main__":
    main()

Error:

Initiating test run...

⚡ This is a turbo test run. https://codecrafters.io/turbo

Running tests. Logs should appear shortly...

[compile] Moved ./.codecrafters/run.sh → ./your_program.sh
[compile] Compilation successful.

[tester::#QQ0] Running tests for Stage #QQ0 (Implement the ECHO command)
[tester::#QQ0] $ ./your_program.sh
[your_program] Logs from your program will appear here!
[tester::#QQ0] $ redis-cli ECHO pineapple
[your_program] Connected to ('127.0.0.1', 39346)
[tester::#QQ0] Expected "pineapple", got "PONG"
[tester::#QQ0] Test failed (try setting 'debug: true' in your codecrafters.yml to see more details)

I think that the parser is unable to read the ECHO command, but I don’t know how to fix it. Any help would be appreciated.

2

Answers


  1. Chosen as BEST ANSWER

    The following is the answer to the question:

    import threading
    
    def parse_resp(data):
        """Parse RESP format data."""
        data = data.decode()
        if not data:
            return None, None
        
        # Handle array format (*2rn$4rnECHOrn$6rnorangern)
        if data[0] == '*':
            # Split into lines
            parts = data.split('rn')
            # First line contains number of arguments (*2)
            num_args = int(parts[0][1:])
            
            args = []
            i = 1
            for _ in range(num_args):
                # Skip the length indicator ($4)
                str_len = int(parts[i][1:])
                # Get the actual string value
                args.append(parts[i + 1])
                i += 2
                
            return args[0], ' '.join(args[1:]) if len(args) > 1 else ''
        
        return None, None
    
    def pong(data, conn):
        conn.send(b"+PONGrn")
    
    def echo(data, conn):
        # Format response in RESP format for bulk strings
        response = f"${len(data)}rn{data}rn"
        conn.send(response.encode())
    
    def handle_client(conn, addr):
        print(f'Connected to {addr}')
     
        commands = {
            "ECHO": echo,
            "PING": pong,
        }
    
        while True:
            try:
                data = conn.recv(1024)  # Read data as bytes
                if not data:  # If no data is received, exit the loop
                    break
                
                command, args = parse_resp(data)
                if command:
                    command_handler = commands.get(command.upper())
                    if command_handler:
                        command_handler(args, conn)
            except Exception as e:
                print(f"Error handling client: {e}")
                break
    
        print(f"Connection to {addr} closed")
        conn.close()
    
    def main():
        print("Logs from your program will appear here!")
        
        server_socket = socket.create_server(("localhost", 6379), reuse_port=True)
        server_socket.listen()
        
        while True:
            conn, addr = server_socket.accept()
            client_thread = threading.Thread(target=handle_client, args=(conn, addr))
            client_thread.start()
    
    if __name__ == "__main__":
        main()
    

  2. Define a function for each command and then split the input data on the first space character and call the appropriate function for the command passing the remaining data to the function.

    def pong(data, conn):
        conn.send(b"+PONGrn")
    
    def echo(data, conn):
        conn.send(f"${len(data)}rn{data}rn".encode())
    
    def handle_client(conn, addr):
        print(f'Connected to {addr}')
        
        commands = {
            "PING": pong,
            "ECHO": echo,
        }
    
        while True:
            data = conn.recv(1024)  # Read data as bytes
            if not data:  # If no data is received, exit the loop
                break
            
            name, _, extra_data = data.decode().rstrip().partition(" ")
            command = commands.get(name.upper(), None)
            if command:
                command(extra_data, conn)
    
        print(f"Connection to {addr} closed")
        conn.close()  # Close the connection with the client
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search