ecen5273

ECEN5273 Coursework
git clone https://git.pngu.org/ecen5273
Log | Files | Refs | README

server.c (6522B)


// HTTP Web Server
// Jack Sangdahl
// 09 October 2024
#include "server.h"

int main(int argc, char **argv) {
  if (argc < 2) {
    fprintf(stderr, "usage: %s <port number>\n", argv[0]);
    return EXIT_FAILURE;
  }
  signal(SIGINT, handle_sigint);
  int port_num = atoi(argv[1]);
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  pthread_t tid;
  if (sockfd < 0) {
    perror("socket failed");
    return EXIT_FAILURE;
  }
  printf("socket created\n");
  struct sockaddr_in server_addr, client_addr;
  int server_addr_len = sizeof(server_addr);
  int client_addr_len = sizeof(client_addr);
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(port_num);
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  if (bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len)) {
    perror("binding failed");
    return EXIT_FAILURE;
  }
  printf("socket bound to address\n");
  if (listen(sockfd, SOMAXCONN)) {
    perror("listening failed");
    return EXIT_FAILURE;
  }
  printf("listening for connections...\n");
  while (1) {
    int *new_sockfd = (int *)malloc(sizeof(int));
    *new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr,
                         (socklen_t *)&client_addr_len);
    if (*new_sockfd < 0)
      perror("accepting client connection failed");
    else
      printf("new connection accepted\n");
    if (pthread_create(&tid, NULL, handle_connection, new_sockfd)) {
      free(new_sockfd);
      perror("creating thread failed");
    }
  }
  return EXIT_SUCCESS;
}

void *handle_connection(void *a) {
  int connection = *((int *)a);
  free(a);
  pthread_detach(pthread_self());
  printf("thread %lx executing\n", (unsigned long)pthread_self());
  static char read_data[DATA_SIZE];
  struct timeval timeout;
  fd_set read_fds;
  bool keep_alive;
  while (1) {
    timeout.tv_sec = KEEP_ALIVE_TIMEOUT;
    timeout.tv_usec = 0;
    FD_ZERO(&read_fds);
    FD_SET(connection, &read_fds);
    int activity = select(connection + 1, &read_fds, NULL, NULL, &timeout);
    if (activity < 0) {
      perror("select failed");
      close(connection);
      break;
    } else if (!activity) {
      printf("timeout expired, closing connection\n");
      close(connection);
      break;
    }
    if (FD_ISSET(connection, &read_fds)) {
      memset(read_data, 0, sizeof(read_data));
      int bytes_read = read(connection, read_data, DATA_SIZE);
      if (bytes_read < 0) {
        perror("reading client data failed");
        close(connection);
        break;
      }
      char *request = strtok(read_data, " ");
      char *filename = strtok(NULL, " ");
      char *version = strtok(NULL, "\r");
      char *conn_type = strtok(NULL, "\n");
      while (conn_type) {
        if (strcasestr(conn_type, "close")) {
          keep_alive = false;
          break;
        }
        if (strcasestr(conn_type, "keep-alive"))
          keep_alive = true;
        conn_type = strtok(NULL, "\n");
      }
      printf("received request\ntype: %s\nfilename: %s\nversion: "
             "%s\nconnection: %s\n",
             request, filename, version, keep_alive ? "keep-alive" : "close");
      if (handle_request(connection, filename, version, keep_alive)) {
        perror("handling request failed");
        close(connection);
        break;
      }
      if (!keep_alive) {
        close(connection);
        break;
      }
      printf("awaiting additional requests for %d seconds...\n",
             KEEP_ALIVE_TIMEOUT);
    }
  }
  return NULL;
}

int handle_request(int connection, char *filename, char *version,
                   bool keep_alive) {
  if (strcmp(version, "HTTP/1.0") && strcmp(version, "HTTP/1.1")) {
    handle_error(connection, 505);
    return -1;
  }
  char extension[DATA_SIZE] = "", filepath[DATA_SIZE] = "www";
  if (!strcmp(filename, "/")) {
    strcat(filepath, "/index.html");
    strcpy(extension, "html");
  } else {
    snprintf(filepath, DATA_SIZE, "www%s", filename);
    char *dot = strrchr(filename, '.');
    if (dot)
      strncpy(extension, dot + 1, sizeof(extension) - 1);
  }
  struct stat fd_status;
  if (stat(filepath, &fd_status)) {
    handle_error(connection, 404);
    return -1;
  }
  if (!(fd_status.st_mode & S_IROTH)) {
    handle_error(connection, 403);
    return -1;
  }
  FILE *fd = fopen(filepath, "rb");
  if (!fd) {
    handle_error(connection, 404);
    return -1;
  }
  long file_size = fd_status.st_size;
  char *file_content = (char *)malloc(file_size * sizeof(char));
  size_t n = fread(file_content, sizeof(char), file_size, fd);
  if ((long)n != file_size) {
    perror("reading file failed");
    free(file_content);
    fclose(fd);
    return -1;
  }
  fclose(fd);
  char content_type[CONTENT_TYPE_SIZE], header[DATA_SIZE];
  struct file_ext_map e[] = {{"html", "text/html"},
                             {"txt", "text/plain"},
                             {"png", "image/png"},
                             {"gif", "image/gif"},
                             {"jpg", "image/jpg"},
                             {"ico", "image/x-icon"},
                             {"css", "text/css"},
                             {"js", "application/javascript"},
                             {0, 0}};
  for (int i = 0; e[i].ext; i++)
    if (!strcmp(e[i].ext, extension))
      strcpy(content_type, e[i].type);
  snprintf(header, sizeof(header),
           "%s 200 OK\r\n"
           "Content-Type: %s\r\n"
           "Content-Length: %ld\r\n"
           "Connection: %s\r\n\r\n",
           version, content_type, file_size,
           keep_alive ? "keep-alive" : "close");
  size_t response_size = strlen(header) + file_size;
  char *response = (char *)malloc(response_size * sizeof(char));
  strcpy(response, header);
  memcpy(response + strlen(header), file_content, file_size);
  write(connection, response, response_size);
  free(file_content);
  free(response);
  return 0;
}

int handle_error(int connection, int error_code) {
  char response[DATA_SIZE];
  switch (error_code) {
  case 400:
    snprintf(response, sizeof(response), RESP_400);
    break;
  case 403:
    snprintf(response, sizeof(response), RESP_403);
    break;
  case 404:
    snprintf(response, sizeof(response), RESP_404);
    break;
  case 405:
    snprintf(response, sizeof(response), RESP_405);
    break;
  case 505:
    snprintf(response, sizeof(response), RESP_505);
    break;
  default:
    return -1;
  }
  write(connection, response, strlen(response));
  return 0;
}

void handle_sigint(int sig) {
  if (sig == SIGINT)
    exit(EXIT_SUCCESS);
}