/* Final Project - Part 2 Nicholas Pease COS440 */ #include #include #include #include #include #include #include #include #include #include #include #include // Program Settings #define BOUND_IP "10.4.78.8" #define PORT 8000 // The below settings controls the output of the program // DEBUG_NONE: No output at all // DEBUG_INFO: Basic Output (All the DEBUG from Pt.1 of the program, plus a few extra print statements) // DEBUG_EXTENDED: Extra Output I used for inspecting messages being constructed / sent. // DEBUG_ASSIGNMENT: Output for Assignment Submission (This is the default setting I will be turning this program in with) #define DEBUG_MODE DEBUG_ASSIGNMENT // Debug levels # define DEBUG_NONE 0 # define DEBUG_INFO 1 # define DEBUG_EXTENDED 2 # define DEBUG_ASSIGNMENT -1 // Constants #define REQ_HTTP_START_LINE_METHOD_LENGTH 8 #define REQ_HTTP_START_LINE_PATH_LENGTH 1024 #define REQ_HTTP_START_LINE_VERSION_LENGTH 16 #define REQ_HTTP_START_LINE_EXTRA_CHARS 4 #define REQ_HTTP_START_LINE_SEPARATOR ' ' #define REQ_HTTP_REQ_MESSAGE_BODY_LENGTH 8192 #define RES_HTTP_START_LINE_VERSION_LENGTH 16 #define RES_HTTP_START_LINE_STATUS_CODE_LENGTH 4 #define RES_HTTP_START_LINE_REASON_PHRASE_LENGTH 64 #define HTTP_HEADER_FIELD_SEPARATOR ": " #define HTTP_HEADER_FIELD_NAME_LENGTH 256 #define HTTP_HEADER_FIELD_VALUE_LENGTH 1024 #define HTTP_HEADER_LINE_ENDING "\r\n" #define HTTP_MESSAGE_MAX_HEADERS 100 #define HTTP_BODY_SEPARATOR "\r\n\r\n" #define MAX_MESSAGE_SIZE 20000 #define CACHE_MISS -1 #define CACHE_EXPIRED -2 #define CACHE_HIT 1 // Globals struct cache_entry *cache[100]; int cacheSize = 100; int cacheCount = 0; // Structs struct req_http_start_line { char method[REQ_HTTP_START_LINE_METHOD_LENGTH]; char path[REQ_HTTP_START_LINE_PATH_LENGTH]; char version[REQ_HTTP_START_LINE_VERSION_LENGTH]; }; struct res_http_start_line { char version[RES_HTTP_START_LINE_VERSION_LENGTH]; char status_code[RES_HTTP_START_LINE_STATUS_CODE_LENGTH]; char reason_phrase[RES_HTTP_START_LINE_REASON_PHRASE_LENGTH]; }; struct http_header { char field_name[HTTP_HEADER_FIELD_NAME_LENGTH]; char field_value[HTTP_HEADER_FIELD_VALUE_LENGTH]; }; struct req_http_message { struct req_http_start_line start_line; struct http_header headers[HTTP_MESSAGE_MAX_HEADERS]; int header_count; char body[REQ_HTTP_REQ_MESSAGE_BODY_LENGTH]; }; struct res_http_message { struct res_http_start_line start_line; struct http_header headers[HTTP_MESSAGE_MAX_HEADERS]; int header_count; char *body; int body_length; }; struct http_forward_info { char host[REQ_HTTP_START_LINE_PATH_LENGTH]; int port; char transport_protocol[5]; struct req_http_message new_message; }; struct cache_entry { int maxAge; int timeSaved; char ETag[REQ_HTTP_START_LINE_PATH_LENGTH]; char url[REQ_HTTP_START_LINE_PATH_LENGTH]; struct res_http_message *response; // AKA the body // Cache-Control, Content-Type, Content-Length are all saved in the response.headers array }; // Functions // Construct REQ Start Line struct req_http_start_line parse_req_http_start_line(char *start_line_unparsed) { struct req_http_start_line start_line; // Method int first_space_seperator = strchr(start_line_unparsed, REQ_HTTP_START_LINE_SEPARATOR) - start_line_unparsed; memcpy(start_line.method, start_line_unparsed, first_space_seperator); start_line.method[first_space_seperator] = '\0'; // Path char *path_start = start_line_unparsed + first_space_seperator + 1; int second_space_seperator = strchr(path_start, REQ_HTTP_START_LINE_SEPARATOR) - path_start; memcpy(start_line.path, path_start, second_space_seperator); start_line.path[second_space_seperator] = '\0'; // Version char *version_start = path_start + second_space_seperator + 1; int line_end = strchr(version_start, '\r') - version_start; memcpy(start_line.version, version_start, line_end); start_line.version[line_end] = '\0'; if (DEBUG_MODE >= DEBUG_EXTENDED) { printf("Received Message:\n"); printf(" %s %s %s\n", start_line.method, start_line.path, start_line.version); } return start_line; } // Construct Header from String struct http_header parse_http_header(char *header_str) { struct http_header header; // Header Field Name char *separator_pos = strstr(header_str, HTTP_HEADER_FIELD_SEPARATOR); int field_name_len = separator_pos - header_str; memcpy(header.field_name, header_str, field_name_len); header.field_name[field_name_len] = '\0'; // Header Field Value char *field_value_start = separator_pos + strlen(HTTP_HEADER_FIELD_SEPARATOR); int field_value_len = strlen(field_value_start); memcpy(header.field_value, field_value_start, field_value_len); header.field_value[field_value_len] = '\0'; return header; } // Parse HTTP Headers from Message String int parse_http_headers(char *message_str, struct http_header *headers) { // Find start of headers char *headers_start = strstr(message_str, HTTP_HEADER_LINE_ENDING) + 2; char *headers_end = strstr(headers_start, HTTP_BODY_SEPARATOR); // Parse each header line char *current_line = headers_start; int header_count = 0; while (current_line < headers_end && header_count < HTTP_MESSAGE_MAX_HEADERS) { char *line_end = strstr(current_line, HTTP_HEADER_LINE_ENDING); if (line_end == NULL || line_end > headers_end) break; char current_header_unparsed[HTTP_HEADER_FIELD_NAME_LENGTH + HTTP_HEADER_FIELD_VALUE_LENGTH + 4]; memcpy(current_header_unparsed, current_line, line_end - current_line); current_header_unparsed[line_end - current_line] = '\0'; headers[header_count] = parse_http_header(current_header_unparsed); if (DEBUG_MODE >= DEBUG_EXTENDED) { printf(" %s: %s\n" , headers[header_count].field_name, headers[header_count].field_value); } header_count++; current_line = line_end + strlen(HTTP_HEADER_LINE_ENDING); } return header_count; } // Get Content-Length from headers int get_content_length(struct res_http_message message) { for (int i = 0; i < message.header_count; i++) { if (strcmp(message.headers[i].field_name, "Content-Length") == 0) { return atoi(message.headers[i].field_value); } } return -1; } // Parse HTTP Body from Message String void parse_http_body(char *message_str, char *body) { // Find start of headers char *headers_start = strstr(message_str, HTTP_HEADER_LINE_ENDING) + 2; char *headers_end = strstr(headers_start, HTTP_BODY_SEPARATOR); // Parse body char *body_start = headers_end + strlen(HTTP_BODY_SEPARATOR); int body_len = strlen(body_start); memcpy(body, body_start, body_len); body[body_len] = '\0'; } // Parse HTTP Response Body void parse_http_response_body(char *message_str, struct res_http_message *message) { // Find start of headers and body char *headers_start = strstr(message_str, HTTP_HEADER_LINE_ENDING) + 2; char *headers_end = strstr(headers_start, HTTP_BODY_SEPARATOR); char *body_start = headers_end + strlen(HTTP_BODY_SEPARATOR); // Get Content-Length from headers int content_length = get_content_length(*message); if (strlen(body_start)) { // Use Content-Length to determine body size message->body_length = content_length; message->body = malloc(content_length + 1); memcpy(message->body, body_start, content_length); message->body[content_length] = '\0'; } else { message->body_length = 0; message->body = NULL; } } // Construct HTTP Message from String struct req_http_message create_req_http_message_from_string(char *message_str) { struct req_http_message message; // Parse Start Line int start_line_len = REQ_HTTP_START_LINE_METHOD_LENGTH + REQ_HTTP_START_LINE_PATH_LENGTH + REQ_HTTP_START_LINE_VERSION_LENGTH + REQ_HTTP_START_LINE_EXTRA_CHARS; char start_line_unparsed[start_line_len]; memcpy(start_line_unparsed, message_str, start_line_len); message.start_line = parse_req_http_start_line(start_line_unparsed); // Parse Headers message.header_count = parse_http_headers(message_str, message.headers); // Parse Body parse_http_body(message_str, message.body); return message; } // Process Message for Relay struct http_forward_info process_for_relay(struct req_http_message received_message) { struct http_forward_info forward_info; forward_info.new_message = received_message; // New Transport Protocol char *original_host = memcpy(received_message.start_line.path, received_message.start_line.path, strlen(received_message.start_line.path)); char *transport_protocol_end = strstr(original_host, "://") + 3; memcpy(forward_info.transport_protocol, original_host, transport_protocol_end - original_host); // New Host char *host_end = strchr(transport_protocol_end, ':'); char *port_end = strchr(host_end, '/'); memcpy(forward_info.host, transport_protocol_end, host_end - transport_protocol_end); forward_info.host[host_end - transport_protocol_end] = '\0'; // New Port forward_info.port = atoi(host_end + 1); // New Path char *entire_host_string_end = strchr(transport_protocol_end, '/'); memcpy(forward_info.new_message.start_line.path, entire_host_string_end, strlen(entire_host_string_end)); forward_info.new_message.start_line.path[strlen(entire_host_string_end)] = '\0'; // Strip out Proxy-Connection Header and replace with Connection: Close int new_header_count = 0; for (int i = 0; i < forward_info.new_message.header_count; i++) { if (strcmp(forward_info.new_message.headers[i].field_name, "Proxy-Connection") == 0) { // Replace Proxy-Connection with Connection: close strcpy(forward_info.new_message.headers[new_header_count].field_name, "Connection"); strcpy(forward_info.new_message.headers[new_header_count].field_value, "Close"); } else { // Keep the existing header forward_info.new_message.headers[new_header_count] = forward_info.new_message.headers[i]; } new_header_count++; } forward_info.new_message.header_count = new_header_count; if (DEBUG_MODE >= DEBUG_EXTENDED) { printf("%s %s %s\n", forward_info.new_message.start_line.method, forward_info.new_message.start_line.path, forward_info.new_message.start_line.version); for (int i = 0; i < forward_info.new_message.header_count; i++) { printf("%s %s\n", forward_info.new_message.headers[i].field_name,forward_info.new_message.headers[i].field_value); } } return forward_info; } // Convert HTTP REQ Message Struct to String void req_http_message_to_string(struct req_http_message message, char *message_str) { // Start Line sprintf(message_str, "%s %s %s%s", message.start_line.method, message.start_line.path, message.start_line.version, HTTP_HEADER_LINE_ENDING); // Headers for (int i = 0; i < message.header_count; i++) { strcat(message_str, message.headers[i].field_name); strcat(message_str, HTTP_HEADER_FIELD_SEPARATOR); strcat(message_str, message.headers[i].field_value); strcat(message_str, HTTP_HEADER_LINE_ENDING); } // End of Headers strcat(message_str, HTTP_HEADER_LINE_ENDING); // Body strcat(message_str, message.body); } // Convert HTTP RES Message Struct to String void res_http_message_to_string(struct res_http_message message, char *message_str) { // Start Line sprintf(message_str, "%s %s %s%s", message.start_line.version, message.start_line.status_code, message.start_line.reason_phrase, HTTP_HEADER_LINE_ENDING); // Headers for (int i = 0; i < message.header_count; i++) { strcat(message_str, message.headers[i].field_name); strcat(message_str, HTTP_HEADER_FIELD_SEPARATOR); strcat(message_str, message.headers[i].field_value); strcat(message_str, HTTP_HEADER_LINE_ENDING); } // End of Headers strcat(message_str, HTTP_HEADER_LINE_ENDING); // Body if (message.body_length > 0) { memcpy(message_str + strlen(message_str), message.body, message.body_length); } } // Construct RES Start Line struct res_http_start_line parse_res_http_start_line(char *start_line_unparsed) { struct res_http_start_line start_line; // Version int first_space_seperator = strchr(start_line_unparsed, REQ_HTTP_START_LINE_SEPARATOR) - start_line_unparsed; memcpy(start_line.version, start_line_unparsed, first_space_seperator); start_line.version[first_space_seperator] = '\0'; // Status Code char *status_code_start = start_line_unparsed + first_space_seperator + 1; int second_space_seperator = strchr(status_code_start, REQ_HTTP_START_LINE_SEPARATOR) - status_code_start; memcpy(start_line.status_code, status_code_start, second_space_seperator); start_line.status_code[second_space_seperator] = '\0'; // Reason Phrase char *reason_phrase_start = status_code_start + second_space_seperator + 1; int line_end = strchr(reason_phrase_start, '\r') - reason_phrase_start; memcpy(start_line.reason_phrase, reason_phrase_start, line_end); start_line.reason_phrase[line_end] = '\0'; // Debug: Print All Parts if (DEBUG_MODE >= DEBUG_EXTENDED) { printf(" %s %s %s\n", start_line.version, start_line.status_code, start_line.reason_phrase); } return start_line; } // Construct HTTP RES Message from String struct res_http_message create_res_http_message_from_string(char *message_str) { struct res_http_message message; // Parse Start Line int start_line_len = RES_HTTP_START_LINE_VERSION_LENGTH + RES_HTTP_START_LINE_STATUS_CODE_LENGTH + RES_HTTP_START_LINE_REASON_PHRASE_LENGTH + REQ_HTTP_START_LINE_EXTRA_CHARS; char start_line_unparsed[start_line_len]; memcpy(start_line_unparsed, message_str, start_line_len); message.start_line = parse_res_http_start_line(start_line_unparsed); // Parse Headers message.header_count = parse_http_headers(message_str, message.headers); // Parse Body parse_http_response_body(message_str, &message); return message; } // Forward Message and Await Response struct res_http_message forward_message_await_response(struct http_forward_info forward_info) { // Start with initial buffer size int buffer_size = MAX_MESSAGE_SIZE; char *response_buffer = malloc(buffer_size); if (!response_buffer) { perror("Failed to allocate response buffer"); exit(1); } // Convert HTTP Message Struct to String char message_str[MAX_MESSAGE_SIZE]; req_http_message_to_string(forward_info.new_message, message_str); if (DEBUG_MODE >= DEBUG_EXTENDED) printf("FORWARD:\n%s\n", message_str); // Create TCP Socket to Origin Server struct sockaddr_in sa; int my_sock = socket(AF_INET, SOCK_STREAM, 0); sa.sin_family = AF_INET; sa.sin_port = htons(forward_info.port); inet_pton(AF_INET, forward_info.host, &(sa.sin_addr)); int recv_size = connect(my_sock, (struct sockaddr *)&sa, sizeof(sa)); if (DEBUG_MODE >= DEBUG_INFO) {printf("CONNECT: Origin Server Connected\n");} // Send Message to Origin Server send(my_sock, message_str, strlen(message_str), 0); if (DEBUG_MODE >= DEBUG_EXTENDED) printf("FORWARD: Message Sent to Origin Server\n"); // Await Response from Origin Server with dynamic buffer expansion memset(response_buffer, 0, buffer_size); int total_received = 0; int bytes_received; while ((bytes_received = recv(my_sock, response_buffer + total_received, buffer_size - total_received - 1, MSG_WAITALL)) > 0) { total_received += bytes_received; // Check if we need to expand the buffer if (total_received >= buffer_size - 1000) { buffer_size *= 2; char *new_buffer = realloc(response_buffer, buffer_size); response_buffer = new_buffer; } } response_buffer[total_received] = '\0'; if (DEBUG_MODE >= DEBUG_INFO) {printf("RECEIVE: Response Received from Origin Server (%d bytes)\n", total_received);} struct res_http_message response_message = create_res_http_message_from_string(response_buffer); // Clean up free(response_buffer); close(my_sock); if (DEBUG_MODE >= DEBUG_INFO) {printf("DISCONNECT: Origin Server Disconnected\n");} return response_message; } // Pt2: Cache Functions int extract_max_age_from_headers(struct http_header *headers, int header_count) { for (int i = 0; i < header_count; i++) { if (strcmp(headers[i].field_name, "Cache-Control") == 0) { char *max_age_str = strstr(headers[i].field_value, "max-age="); if (max_age_str != NULL) { return atoi(max_age_str + 8); } } } return -1; // No max-age found } // Check Cache for Request and Return Index if Hit int check_cache_for_request(struct req_http_message req_message) { for (int i = 0; i < cacheCount; i++) { struct cache_entry entry = *cache[i]; if (strcmp(entry.url, req_message.start_line.path) == 0) { // Check if cache entry is still valid based on maxAge return i; // Returns index of cache hit } } return CACHE_MISS; } int check_cache_if_valid(int cache_index) { struct cache_entry entry = *cache[cache_index]; int current_time = (int)time(NULL); if (entry.timeSaved + entry.maxAge < current_time) { return CACHE_EXPIRED; } return 1; // Valid } // Save Response to Cache void save_response_to_cache(struct req_http_message req_message, struct res_http_message res_message) { // Setup cache entry struct cache_entry *new_entry = malloc(sizeof(struct cache_entry)); new_entry->response = malloc(sizeof(struct res_http_message)); // Copy RES Message into cache entry struct res_http_message copy = res_message; copy.body = malloc(res_message.body_length + 1); memcpy(copy.body, res_message.body, res_message.body_length); copy.body[res_message.body_length] = '\0'; *(new_entry->response) = copy; strcpy(new_entry->url, req_message.start_line.path); new_entry->maxAge = extract_max_age_from_headers(res_message.headers, res_message.header_count); new_entry->timeSaved = (int)time(NULL); // Extract and save ETag from response headers for (int i = 0; i < res_message.header_count; i++) { if (strcmp(res_message.headers[i].field_name, "ETag") == 0) { strcpy(new_entry->ETag, res_message.headers[i].field_value); break; } } if (DEBUG_MODE == DEBUG_ASSIGNMENT) { printf("[Info] Caching object with Max-Age = %d, ETag = \"%s\"\n", new_entry->maxAge, new_entry->ETag); } // Add to cache // Look in the cache for a free spot. Also can use first expired spot int placed = 0; for (int i = 0; i < cacheCount; i++) { struct cache_entry entry = *cache[i]; int current_time = (int)time(NULL); if (entry.timeSaved + entry.maxAge < current_time) { // Found expired spot, replace it cache[i] = new_entry; placed = 1; return; } } if (!placed) { cache[cacheCount] = new_entry; } cacheCount++; } // Check if Response can be Cached int can_cache(struct res_http_message message) { if (atoi(message.start_line.status_code) != 200) { return 0; // Only cache 200 OK responses } for (int i = 0; i < message.header_count; i++) { if (strcmp(message.headers[i].field_name, "Cache-Control") == 0) { if (strstr(message.headers[i].field_value, "no-store") != NULL) { return 0; // Cannot cache } } } return CACHE_HIT; // Can cache } // Debug Print Functions void print_req_http_message(struct req_http_message message) { printf("[Info] \t\t%s %s %s\n", message.start_line.method, message.start_line.path, message.start_line.version); for (int i = 0; i < message.header_count; i++) { printf("[Info] \t\t%s: %s\n", message.headers[i].field_name, message.headers[i].field_value); } } void print_res_http_message(struct res_http_message message) { printf("[Info] \t\t%s %s %s\n", message.start_line.version, message.start_line.status_code, message.start_line.reason_phrase); for (int i = 0; i < message.header_count; i++) { printf("[Info] \t\t%s: %s\n", message.headers[i].field_name, message.headers[i].field_value); } printf("[Info] \t\tBody Length: %d\n", message.body_length); } // Main int main(int argc, char *argv[]) { // Setup TCP Socket if (DEBUG_MODE >= DEBUG_EXTENDED) printf("SETUP: Setup Sockets\n"); int serv_sock, client_sock, read_size; struct sockaddr_in server, client; char client_message[MAX_MESSAGE_SIZE]; serv_sock = socket(AF_INET, SOCK_STREAM, 0); client_sock = socket(AF_INET, SOCK_STREAM, 0); if (DEBUG_MODE >= DEBUG_EXTENDED) printf("SETUP: Socket Created\n"); // Bind Sockets server.sin_family = AF_INET; server.sin_port = htons(PORT); inet_pton(AF_INET, BOUND_IP, &(server.sin_addr)); // This line of code helps to avoid the "Address already in use" error when restarting the server quickly setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &server, sizeof(server)); if (bind(serv_sock, (struct sockaddr *)&server, sizeof(server)) < 0) { perror("BIND FAILED"); return 1; } if (DEBUG_MODE >= DEBUG_EXTENDED) printf("SETUP: Socket Bound\n"); // Start Listening and Waiting for Connections listen(serv_sock, 1); if (DEBUG_MODE >= DEBUG_EXTENDED) printf("SETUP: Listening for Connections\n\n"); while (1) { // Accept Connection from Client if (DEBUG_MODE >= DEBUG_INFO) {printf("WAIT: Waiting for incoming connections...\n");} int c = sizeof(struct sockaddr_in); client_sock = accept(serv_sock, (struct sockaddr *)&client, &c); if (DEBUG_MODE >= DEBUG_INFO) {printf("CONNECT: Client Connected\n");} // Receive Message from Client memset(client_message, 0, sizeof(client_message)); read_size = recv(client_sock, client_message, sizeof(client_message), 0); if (DEBUG_MODE >= DEBUG_INFO) {printf("RECEIVE: Message Received\n");} // Process Message From Client if (DEBUG_MODE >= DEBUG_INFO) {printf("PROCESS: Processing Message\n");} struct req_http_message received_message = create_req_http_message_from_string(client_message); // DEBUG: Print Received Message if (DEBUG_MODE == DEBUG_ASSIGNMENT) { printf("[Info] [REQ] Received Message from Client:\n"); print_req_http_message(received_message); } // Determine if response comes from Cache or Origin if (DEBUG_MODE >= DEBUG_INFO) {printf("CACHE: Checking Cache for Request\n");} int cache_hit = check_cache_for_request(received_message); int cache_valid; if (cache_hit != CACHE_MISS) { cache_valid = check_cache_if_valid(cache_hit); } struct res_http_message response; if (cache_hit != CACHE_MISS && cache_valid != CACHE_EXPIRED) { if (DEBUG_MODE >= DEBUG_INFO) {printf("CACHE: HIT\n");} // Debug: Print Assignment Case 2 Message if (DEBUG_MODE == DEBUG_ASSIGNMENT) { printf("[Info] Cache Hit! Object in cache and fresh (Max-Age = %d, in cache %d)\n", cache[cache_hit]->maxAge, cache_hit); printf("[Info] Returning cached object\n"); } // Retrieve Response from Cache response = *(cache[cache_hit]->response); } else { if (DEBUG_MODE >= DEBUG_INFO) {printf("CACHE: MISS/EXPIRED\n");} if (DEBUG_MODE >= DEBUG_INFO) {printf("CACHE Miss Type: %s\n", (cache_valid == CACHE_EXPIRED) ? "EXPIRED" : "MISS");} // Debug: Print Assignment Case 1 and 2 Messages if (DEBUG_MODE == DEBUG_ASSIGNMENT && cache_valid == CACHE_EXPIRED) { // Cache Miss (Case 2) printf("[Info] Cache Miss! Object in cache but stale (Max-Age = %d, in cache %d)\n", cache[cache_hit]->maxAge, cache_hit); printf("[Info] Must revalidate with server using If-None-Match: \"%s\"\n", cache[cache_hit]->ETag); } else { // Cache Miss (Case 1) if (DEBUG_MODE == DEBUG_ASSIGNMENT) { printf("[Info] Object not in cache. Contacting server\n"); } } // Process Message for Relay if (DEBUG_MODE >= DEBUG_INFO) {printf("PROCESS: Processing for Relay\n");} struct http_forward_info relay_message = process_for_relay(received_message); if (cache_valid == CACHE_EXPIRED) { // Add If-None-Match Header with ETag struct http_header if_none_match_header; strcpy(if_none_match_header.field_name, "If-None-Match"); strcpy(if_none_match_header.field_value, cache[cache_hit]->ETag); relay_message.new_message.headers[relay_message.new_message.header_count] = if_none_match_header; relay_message.new_message.header_count++; } // Forward Message to Destination Server if (DEBUG_MODE >= DEBUG_INFO) {printf("FORWARD: Forwarding Message to %s\n", relay_message.host);} // DEBUG: Print Forwarded Message if (DEBUG_MODE == DEBUG_ASSIGNMENT) { printf("[Info] [REQ] Forwarded Message to Origin Server:\n"); print_req_http_message(relay_message.new_message); } response = forward_message_await_response(relay_message); // DEBUG: Print Received Response Message if (DEBUG_MODE == DEBUG_ASSIGNMENT) { printf("[Info] [RES] Received Message from Origin Server:\n"); print_res_http_message(response); printf("[Info] Response Status: %d %s\n", atoi(response.start_line.status_code), response.start_line.reason_phrase); } // If 304 Not Modified, retrieve from cache int was_304 = 0; if (cache_valid == CACHE_EXPIRED && atoi(response.start_line.status_code) == 304) { printf("[Info] Received 304 Not Modified from server\n"); int new_max_age = extract_max_age_from_headers(response.headers, response.header_count); cache[cache_hit]->maxAge = new_max_age; cache[cache_hit]->timeSaved = (int)time(NULL); // Update Cache-Control header in cache->response->headers from the string in response for (int i = 0; i < response.header_count; i++) { if (strcmp(response.headers[i].field_name, "Cache-Control") == 0) { for (int j = 0; j < cache[cache_hit]->response->header_count; j++) { if (strcmp(cache[cache_hit]->response->headers[j].field_name, "Cache-Control") == 0) { strcpy(cache[cache_hit]->response->headers[j].field_value, response.headers[i].field_value); } } } } response = *(cache[cache_hit]->response); if (DEBUG_MODE == DEBUG_ASSIGNMENT) { printf("[Info] Returning cached object with 200 OK\n"); } was_304 = 1; } // Save Response to Cache if (can_cache(response) && !was_304) { if (DEBUG_MODE == DEBUG_ASSIGNMENT) { printf("[Info] Object is cacheable\n"); } save_response_to_cache(received_message, response); } else if (DEBUG_MODE == DEBUG_ASSIGNMENT & !was_304) { printf("[Info] Object is not cacheable\n"); } } // DEBUG: Print Response Message if (DEBUG_MODE == DEBUG_ASSIGNMENT) { printf("[Info] [RES] Sending Response to Client:\n"); print_res_http_message(response); } // Send Response Back to Client int response_str_size = response.body_length + MAX_MESSAGE_SIZE; char *response_str = malloc(response_str_size); memset(response_str, 0, response_str_size); res_http_message_to_string(response, response_str); int total_response_length = strlen(response_str); if (response.body_length > 0) { total_response_length = strlen(response_str) - strlen(response.body) + response.body_length; } if (DEBUG_MODE >= DEBUG_INFO) {printf("RESPONSE: Response Sent Back to Client\n");} send(client_sock, response_str, total_response_length, 0); free(response_str); // Close Client Socket close(client_sock); if (DEBUG_MODE >= DEBUG_INFO) { printf("DISCONNECT: Client Disconnected\n\n"); } if (DEBUG_MODE == DEBUG_ASSIGNMENT) { printf("===============================================================================================\n"); } } return 0; }