diff options
author | Jack Sangdahl <[email protected]> | 2024-10-28 23:08:30 -0600 |
---|---|---|
committer | Jack Sangdahl <[email protected]> | 2024-10-28 23:08:30 -0600 |
commit | 102d6be55e2e665b9c9c688725d1119a501139ef (patch) | |
tree | 124931deb31a04f0168eba56a418cff0ea82af26 /pa2_webserver/server.c | |
parent | be5c2aa696d3d3c499ab8817ee23bf5fe851363c (diff) | |
download | ecen5273-102d6be55e2e665b9c9c688725d1119a501139ef.tar.gz ecen5273-102d6be55e2e665b9c9c688725d1119a501139ef.zip |
Programming Assignment 2: Web Server
Signed-off-by: Jack Sangdahl <[email protected]>
Diffstat (limited to 'pa2_webserver/server.c')
-rw-r--r-- | pa2_webserver/server.c | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/pa2_webserver/server.c b/pa2_webserver/server.c new file mode 100644 index 0000000..3ea2e3e --- /dev/null +++ b/pa2_webserver/server.c @@ -0,0 +1,214 @@ +// 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); +} |