// HTTP Web Server // Jack Sangdahl // 09 October 2024 #include "server.h" int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s \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); }