Files
2025-12-14 00:31:53 +00:00

765 lines
29 KiB
C

/*
Final Project - Part 2
Nicholas Pease
COS440
*/
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
// 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;
}