My echo server works, but It's no passing the test

I’m stuck on Stage #CN2

Here are the logs when I push my code:

remote: [tester::#CN2] Sent bytes: "GET /echo/raspberry HTTP/1.1\r\nHost: localhost:4221\r\n\r\n"
remote: [your_program] line: GET /echo/raspberry HTTP/1.1
remote: [your_program] Request: GET /echo/raspberry HTTP/1.1
remote: [your_program]  -> else if
remote: [tester::#CN2] Failed to read response:
remote: [tester::#CN2] Received: "" (no content received)
remote: [tester::#CN2]            ^ error
remote: [tester::#CN2] Error: Expected: HTTP-version, Received: ""
remote: [tester::#CN2] Test failed

But when I do the same request on my computer I get it right:

➜ curl -v localhost:4221/echo/raspberry
*   Trying 127.0.0.1:4221...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 4221 (#0)
> GET /echo/raspberry HTTP/1.1
> Host: localhost:4221
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Length: 9
<
* Connection #0 to host localhost left intact
raspberry% 

This is my code, I add some printf for debuging:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#define PORT 4221
#define BUF_SIZE 512
#define TARGET_ECHO "/echo"

struct http_request {
    char *method;
    char *request_target;
    char *protocol;
};


int main() {
	// Disable output buffering
	setbuf(stdout, NULL);
 	setbuf(stderr, NULL);

	// You can use print statements as follows for debugging, they'll be visible when running tests.
	printf("Logs from your program will appear here!\n");

	// Uncomment this block to pass the first stage
	int server_fd, client_addr_len, client_fd;
	struct sockaddr_in client_addr;
    char buf[BUF_SIZE];
    ssize_t nread;
    char *request_line;
    struct http_request request;
    char *echo;
	
	server_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (server_fd == -1) {
		fprintf(stderr, "Socket creation failed: %s...\n", strerror(errno));
		exit(EXIT_FAILURE);
	}
	
	// Since the tester restarts your program quite often, setting SO_REUSEADDR
    // ensures that we don't run into 'Address already in use' errors
    int reuse = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
		fprintf(stderr, "SO_REUSEADDR failed: %s \n", strerror(errno));
		exit(EXIT_FAILURE);
	}

	struct sockaddr_in serv_addr = { .sin_family = AF_INET ,
									 .sin_port = htons(PORT),
									 .sin_addr = { htonl(INADDR_ANY) },
									};
	
	if (bind(server_fd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) != 0) {
		fprintf(stderr, "Bind failed: %s \n", strerror(errno));
		exit(EXIT_FAILURE);
	}
    
	int connection_backlog = 5;
	if (listen(server_fd, connection_backlog) != 0) {
		fprintf(stderr, "Listen failed: %s \n", strerror(errno));
		exit(EXIT_FAILURE);
	}
	
	printf("Waiting for a client to connect...\n");
	client_addr_len = sizeof(client_addr);

	char *response;
	client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &client_addr_len);
	if (client_fd < 0) {
		fprintf(stderr, "Accept failed: %s \n", strerror(errno));
		exit(EXIT_FAILURE);
	} 
	printf("Client connected\n");

    nread = recv(client_fd, buf, BUF_SIZE, 0);
    if (nread < 0) {
        fprintf(stderr, "Recv failed: %s \n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    // parsing the "request-line"
    request_line = strtok(buf, "\r\n");
    printf("line: %s\n", request_line);
    request.method = strtok(request_line, " ");    
    request.request_target = strtok(NULL, " ");    
    request.protocol = strtok(NULL, " ");    
    printf("Request: %s %s %s\n", request.method, request.request_target, request.protocol);
    if (strcmp(request.request_target, "/") == 0) {
		printf(" -> if\n");
		response = "HTTP/1.1 200 OK\r\n\r\n";
	} else if (strncmp(request.request_target, TARGET_ECHO, strlen(TARGET_ECHO)) == 0){
        printf(" -> else if\n");
		echo = strtok(request.request_target, "/");
        echo = strtok(NULL, "/");
        sprintf(response,
                "HTTP/1.1 200 OK\
\r\n\
Content-Type: text/plain\r\n\
Content-Length: %ld\r\n\
\r\n\
%s", strlen(echo), echo);
	} else {
		printf(" -> else\n");
		response = "HTTP/1.1 404 Not Found\r\n\r\n";	
    }
    printf("Response: %s\n", response);
	if(send(client_fd, response, strlen(response), 0) < 0) {
        fprintf(stderr, "Send failed: %s \n", strerror(errno));
    }

	close(client_fd);
	close(server_fd);

	return 0;
}

My printf("Response: %s\n", response); it’s never printing, so it’s like the program on the test never runs after sprintf statement.

Hi @adecora, it looks like you’ve got past this stage. Do you happen to remember what was wrong? Would love to see if we can improve the tester / instructions.

I was declaring response as a char pointer, in the previous step was enough because I assign literal strings to the pointer, but in here echo response is a template that uses sprintf on response.

    char *response;
    ...
    if (strcmp(request.request_target, "/") == 0) {
		printf(" -> if\n");
		response = "HTTP/1.1 200 OK\r\n\r\n";
	} else if (strncmp(request.request_target, TARGET_ECHO, strlen(TARGET_ECHO)) == 0){
        printf(" -> else if\n");
		echo = strtok(request.request_target, "/");
        echo = strtok(NULL, "/");
        printf("response (before sprintf) [%p]: %s\n", response, response);
        sprintf(response,
                "HTTP/1.1 200 OK\
\r\n\
Content-Type: text/plain\r\n\
Content-Length: %ld\r\n\
\r\n\
%s", strlen(echo), echo);
}

I have the bad lucky that when executing locally, response was on a safe memory section and cause no troubles instead return a segmentation fault. The solution it’s change response declaration as a buffer char response[512].

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.