Why does recv() ends up blocking even though there is still data to read?

57 Views Asked by At

So I was writing a server in C which you can use to server html/css/files. The server is able to parse the GET request and send an appropriate html file when the URL is "/". Now since my html file has both a styles.css and a javascript file, it should be able to send them both but instead it hangs at the recv() method AFTER it has send styles.css. So it isn't able to get the GET request for the javascript file. The browser keeps on loading and loading, waiting for the js file but my server blocks on recv().

What could be causing this behavior?

#include <errno.h>
#include <error.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h> // read(), write(), close()

#define MAX 80
#define PORT 8080
#define SA struct sockaddr
#define BUF_SIZE 2000
#define NOT_ACCEPTABLE 406
#define FILE_NOT_FOUND ENOENT
#define NOT_IMPLEMENTED 501
#define EXTRA_LEN 300
#define RED "\033[1;31m"
#define RESET "\033[0m"
#define DEBUG

enum mime_types{
  HTML,
  CSS,
  JS,
  PNG,
  JPEG,
  SVG,
  AVIF,
  AWAV,
  AWEBM,
  VWEBM
};

const char* content_types[] = {
  "text/html",
  "text/css",
  "text/javascript",
  "image/png",
  "image/jpeg",
  "image/svg+xml",
  "image/avif",
  "audio/wav",
  "audio/webm",
  "video/webm"
};

typedef struct _http_request_headers {
  char *host;
  char connection[15];
  char content_type[100];
  uint32_t content_length;
  char *accept[20];
  char *accept_language;
  char *accept_encoding;
} HttpRequestHeaders;

typedef struct _http_response_headers {
  char content_type[50];
  uint32_t content_length;
  char *content_encoding;
  char *connection;
} HttpResponseHeaders;

struct HttpRequest {
  char method[10]; // GET, POST, DELETE
  char *URL;
  char version[10];
  HttpRequestHeaders headers;
  char *body;
};
struct HttpResponse {
  char protocol_version[20];
  char status_code[4];
  char status_text[100];
  HttpResponseHeaders headers;
};

char buffer[BUF_SIZE];
const char *acceptable_headers[] = {
    "Host",   "Connection",      "Content-Type",   "Content-Length",
    "Accept", "Accept-Language", "Accept-Encoding"
    };

int concat(char *restrict, const char *restrict, size_t, size_t);
int parse(struct HttpRequest *request, char *message, int connfd);
int get_str(char *ptr, const char delim);
void update_fields(const char *m, HttpRequestHeaders *header, char *ptr);
void read_request(struct HttpRequest *request, int connfd);
void handle_request(struct HttpRequest *request, int connfd,
                    struct HttpResponse *response);
void send_all(int connfd, char *msg);
void handle_error(int err, int connfd);
char *read_file(char *filepath, FILE *fptr);
int get_content_type(char* __content_ptr, char* filepath);

// Driver function
int main() {
  int sockfd, connfd;
  struct sockaddr_in servaddr, cli;
  socklen_t len = sizeof(cli);

  // socket create and verification
  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd == -1) {
    printf("socket creation failed...\n");
    exit(0);
  } else
    printf("Socket successfully created..\n");
  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &(int){1}, sizeof(int)) < 0)
    perror("setsockopt(SO_REUSEADDR) failed");
  memset(&servaddr, 0, sizeof(servaddr));
  memset(&cli, 0, sizeof(cli));
  memset(buffer, 0, sizeof(buffer));

  // assign IP, PORT
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(PORT);

  // Binding newly created socket to given IP and verification
  if ((bind(sockfd, (SA *)&servaddr, sizeof(servaddr))) != 0) {
    printf("socket bind failed...\n");
    exit(0);
  } else
    printf("Socket successfully binded..\n");

  // Now server is ready to listen and verification
  if ((listen(sockfd, 5)) != 0) {
    printf("Listen failed...\n");
    exit(0);
  } else
    printf("Server listening..\n");

  // Accept the data packet from client and verification
  connfd = accept(sockfd, (SA *)&cli, &len);
  if (connfd < 0) {
    printf("server accept failed...\n");
    exit(0);
  } else
    printf("server accept the client...\n");

  while (1) {
    struct HttpRequest request;
    struct HttpResponse response;
    memset(&request, 0, sizeof(request));
    memset(&response,0,sizeof(response));
    read_request(&request, connfd);
#ifdef DEBUG
    printf("Handling request : \n");
#endif
    handle_request(&request, connfd, &response);
  }
}

int concat(char *restrict dst, const char *restrict src, size_t n,
           size_t size) {
  if (sizeof(dst) + sizeof(src) + sizeof(char) > size) {
    return -1;
  }
  strncat(dst, src, n);
  return 0;
}

int parse(struct HttpRequest *request, char *message, int connfd) {
  int i = 0;
  char *ptr1 = message;
  int len = 0;
  
  if( strncmp(ptr1,"GET",3) == 0 ){
    strncpy(request->method,"GET",3);
    ptr1 += 4;
  }else{
    // Only GET requests are implemented for now. If there is any request other than GET
    // send NOT_IMPLEMENTED error to client
    // -1 as return value indicate falure in parsing, the caller must close the connfd.
    handle_error(NOT_IMPLEMENTED,connfd);
    return -1;
  }
  
  len = get_str(ptr1, ' ');
  request->URL = strndup(ptr1, len);
  

  ptr1 += len + 1;

  len = get_str(ptr1, '\r');
  strncpy(request->version, ptr1, len);

  ptr1 += len + 2; // \r\n

  if( strncasecmp(ptr1,"Host:",5) == 0 ){
    ptr1 += 6;
    len = get_str(ptr1, '\r');
    request->headers.host = strndup(ptr1,len);
    ptr1 += len + 2;
  }
  


  while (strncmp(ptr1, "\r\n", 2) != 0) {
    len = get_str(ptr1, ':');
    int present = 0;
    for (int i = 0; i < 7; i++) {
      if (strncmp(ptr1, acceptable_headers[i], len) == 0) {
        present = 1;
        ptr1 += len + 2;
        update_fields(acceptable_headers[i], &request->headers, ptr1);
        break;
      }
    }
    if (!present) {
      // printf("Cannot accept the current header.\n");
      len = get_str(ptr1, '\r');
      ptr1 += len + 2;
    }
  }
}

void update_fields(const char *m, HttpRequestHeaders *header, char *ptr1) {
  int len = 0;
  len = get_str(ptr1, '\r');
  if (strcmp(m, "Connection") == 0) {
    strncpy(header->connection, ptr1, len);
  } else if (strcmp(m, "Content-Type") == 0) {
    strncpy(header->content_type, ptr1, len);
  } else if (strcmp(m, "Content-Length") == 0) {
    char cpy[10];
    memset(cpy, 0, sizeof(cpy));
    strncpy(cpy, ptr1, len);
    header->content_length = atoi(cpy);
  } else if (strcmp(m, "Accept") == 0) {
    char *val = strndup(ptr1, len);
    char *token;
    char *delim = ",";

    token = strtok(val, delim);
    char **ptr2 = header->accept;
    while (token != NULL) {
      *ptr2 = strdup(token);
      ptr2++;

      token = strtok(NULL, delim);
    }
    *ptr2 = "\0";
  } else if (strcmp(m, "Accept-Language") == 0) {
    header->accept_language = strndup(ptr1, len);
  } else if (strcmp(m, "Accept-Encoding") == 0) {
    header->accept_encoding = strndup(ptr1, len);
  }
  ptr1 += len + 2;
}

int get_str(char *ptr, const char delim) {
  char *itr = ptr;
  while (*itr != delim)
    itr++;
  int len = itr - ptr;
  return len;
}

void read_request(struct HttpRequest *request, int connfd) {
  // assuming the http fileds are just 800 bytes
  // to-do change it to something better
  char message[2000];

  memset(message, 0, sizeof(message));
  int total_length = 0;
  int loop = 0;
  while (1) {
    // read from connfd and store it in buffer. It is a blocking system call.
    // to-do make it non-blocking
    
    printf("about to read bytes -> \n");
    printf("buffer: %s\n", buffer);
    ssize_t bytesRcvd = recv(connfd, buffer, BUF_SIZE - 1 , 0);
    printf("bytes read: %ld\n", bytesRcvd);
    if (bytesRcvd <= 0) {
      if (bytesRcvd == 0) {
        // bytesRcvd == 0 indicates that the client has gracefully closed the
        // connection
        printf("Client Closed Conenction\n");
        return;
      }
      // for now, exit due to error.
      // to-do -> change it later
      perror("recv() error");
    }

    buffer[bytesRcvd] = '\0';

    // concat it to the end of message.
    // if you are concating more than the size of message, exit with in error
    // can be done in a better way.
    if (concat(message, buffer, strlen(buffer), 6000) < 0) {
      fprintf(stderr, "buffer overflow!\n");
      exit(1);
    }
    // printf("BUFFER : %s",buffer);
    int len = strlen(buffer);
    total_length += len;
    
    // if the last line contains \n\r, we have recieved all the request message
    // at least in the cause of HTTP GET request
    //  last 4 bytes are \r\n\r\n then means the request is complete;
    if (strncmp(buffer + len - 4, "\r\n\r\n", 4) == 0) {
      break;
    }
  }
  #ifdef DEBUG
    //printf("request message   : \n%s\n", message);
  #endif
  parse(request, message, connfd);
}

void handle_request(struct HttpRequest *request, int connfd,
                    struct HttpResponse *response) {

  if (strcmp(request->method, "GET") == 0) {
    //currently, only GET method is implemented 

    char *filepath = ++request->URL; // the url is of the form /something. ++ added to increase it 
                                    // by 1 so it becomes 'something'
    if (*(filepath) == '\0') {
      filepath = "index.html";
    }
    

    FILE *fptr;
    fptr = fopen(filepath, "r");

    if (fptr == NULL) {
      // currently, it only handles file not found error.
      handle_error(errno, connfd);
      errno = 0;
      return;
    }
    char *page = read_file(filepath, fptr);
    int content_length = strlen(page);

    strncpy(response->protocol_version, request->version,
            strlen(request->version));
    strncpy(response->status_code, "200", 3);
    strncpy(response->status_text, "OK", 2);
    response->headers.content_length = strlen(page);
    
    // if( get_content_type(response->headers.content_type,filepath) != 0 ){
    //   handle_error(NOT_ACCEPTABLE,connfd);
    //   return ;
    // }

    char *response_message =
        (char *)calloc(content_length + EXTRA_LEN, sizeof(char));

    snprintf(response_message, content_length + EXTRA_LEN,
             "%s %s %s\r\n"
             "Content-Length: %d\r\n"
             "Content-Type: %s\r\n"
             "\r\n",
             response->protocol_version, response->status_code, response->status_text, 
             content_length,
             response->headers.content_type);
    

    int len = strlen(response_message);
    strcat(response_message, page);

    send_all(connfd, response_message);

    #ifdef DEBUG
      printf("response message: \n%s\n", response_message);
    #endif

    free(response_message);
  }else{
    handle_error(NOT_IMPLEMENTED,connfd);
  }
}

void send_all(int connfd, char *msg) {
  int left = strlen(msg);
  while (left) {
    ssize_t bytesSnd = send(connfd, msg, left, 0);
    if (bytesSnd <= 0) {
      perror("send()");
    }
    left -= bytesSnd;
    msg += bytesSnd;
  }
}

char *read_file(char *filepath, FILE *fptr) {
  int i = 0;
  char *page = (char *)malloc(sizeof(char) * 100);
  int size = 100;

  while (1) {
    int ch = fgetc(fptr);
    if (ch == EOF)
      break;
    if (i < size) {
      page[i] = ch;
    } else {
      // if our arr is full, reallocate it with size twice of its current size
      page = (char *)realloc(page, 2 * size);
      size *= 2;
      page[i] = ch; // assign the currently read character;
    }
    i++;
  }
  page[i] = '\0';
  
  return page;
}

void handle_error(int err, int connfd) {
  if (err == FILE_NOT_FOUND) {
    FILE *fptr = fopen("error_html/error_404.html", "r");
    char *page = read_file("error_404.html", fptr);
    int content_length = strlen(page);

    char *error_reply = (char *)calloc(content_length + 300, sizeof(char));

    snprintf(error_reply, content_length + 300,
             "HTTP/1.1 404 Not Found\r\nContent-Length: %d\r\nContent-Type: "
             "text/html\r\n\r\n",
             content_length);
    strcat(error_reply, page);

    send_all(connfd, error_reply);
    #ifdef DEBUG
      printf(RED "error message: " RESET "\n%s",error_reply);
    #endif
    free(error_reply);
  }
  if( err == NOT_ACCEPTABLE ){
    char* error_reply = "HTTP/1.1 406 Not Acceptable\r\nContent-Length: 0\r\n\r\n";
    send_all(connfd,error_reply);
    #ifdef DEBUG
      printf(RED "error message: " RESET "\n%s",error_reply);
    #endif
  }
  if( err == NOT_IMPLEMENTED ){
    char* error_reply = "HTTP/1.1 501 Not Implemented\r\nContent-Length: 0\r\n\r\n";
    send_all(connfd,error_reply);

    #ifdef DEBUG
      printf(RED "error message: " RESET "\n%s",error_reply);
    #endif
  }
}

// can do better. 
// TO-DO Implement a map instead.
int get_content_type(char* cont_ptr, char* filename){
  const char* p = filename;
  while( *p != '.' ) p++;
  ++p;
  
  if( strcmp(p,"html") == 0 ){
    strcpy(cont_ptr,content_types[HTML]);
    return 0;
  }
  if( strcmp(p, "css") == 0 ){
    strcpy(cont_ptr,content_types[CSS]);
    return 0;
  }
  if( strcmp(p,"js") == 0 ){
    strcpy(cont_ptr,content_types[JS]);
    return 0;
  }
  if( strcmp(p,"png") == 0 ){
    strcpy(cont_ptr,content_types[PNG]);
    return 0;
  }
  if( strcmp(p,"jpeg") == 0 ){
    strcpy(cont_ptr, content_types[JPEG]);
    return 0;
  }
  return 1;

}

Sorry for the long code. Have been working on it for a while and been getting frustrated. Any help would be appreciated.

0

There are 0 best solutions below