aboutsummaryrefslogtreecommitdiff
path: root/pa2_webserver/server.c
diff options
context:
space:
mode:
authorJack Sangdahl <[email protected]>2024-10-28 23:08:30 -0600
committerJack Sangdahl <[email protected]>2024-10-28 23:08:30 -0600
commit102d6be55e2e665b9c9c688725d1119a501139ef (patch)
tree124931deb31a04f0168eba56a418cff0ea82af26 /pa2_webserver/server.c
parentbe5c2aa696d3d3c499ab8817ee23bf5fe851363c (diff)
downloadecen5273-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.c214
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);
+}