This commit is contained in:
2025-12-13 00:43:53 +00:00
parent febf779301
commit 28ad19b63c
2 changed files with 172 additions and 45 deletions
BIN
View File
Binary file not shown.
+172 -45
View File
@@ -20,7 +20,18 @@
#define BOUND_IP "10.4.78.8"
#define PORT 8000
#define DEBUG 0 // Controls Optional Debug Output
// 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
@@ -46,6 +57,7 @@
#define CACHE_MISS -1
#define CACHE_EXPIRED -2
#define CACHE_HIT 1
// Globals
struct cache_entry *cache[100];
@@ -125,7 +137,7 @@ struct req_http_start_line parse_req_http_start_line(char *start_line_unparsed)
memcpy(start_line.version, version_start, line_end);
start_line.version[line_end] = '\0';
if (DEBUG) {
if (DEBUG_MODE >= DEBUG_EXTENDED) {
printf("Received Message:\n");
printf(" %s %s %s\n", start_line.method, start_line.path, start_line.version);
}
@@ -171,7 +183,7 @@ int parse_http_headers(char *message_str, struct http_header *headers) {
current_header_unparsed[line_end - current_line] = '\0';
headers[header_count] = parse_http_header(current_header_unparsed);
if (DEBUG) {
if (DEBUG_MODE >= DEBUG_EXTENDED) {
printf(" %s: %s\n" , headers[header_count].field_name, headers[header_count].field_value);
}
header_count++;
@@ -280,7 +292,7 @@ struct http_forward_info process_for_relay(struct req_http_message received_mess
}
forward_info.new_message.header_count = new_header_count;
if (DEBUG) {
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);
@@ -355,7 +367,7 @@ struct res_http_start_line parse_res_http_start_line(char *start_line_unparsed)
start_line.reason_phrase[line_end] = '\0';
// Debug: Print All Parts
if (DEBUG) {
if (DEBUG_MODE >= DEBUG_EXTENDED) {
printf(" %s %s %s\n", start_line.version, start_line.status_code, start_line.reason_phrase);
}
@@ -395,7 +407,7 @@ struct res_http_message forward_message_await_response(struct http_forward_info
char message_str[MAX_MESSAGE_SIZE];
req_http_message_to_string(forward_info.new_message, message_str);
if (DEBUG) printf("FORWARD:\n%s\n", message_str);
if (DEBUG_MODE >= DEBUG_EXTENDED) printf("FORWARD:\n%s\n", message_str);
// Create TCP Socket to Origin Server
struct sockaddr_in sa;
@@ -405,18 +417,18 @@ struct res_http_message forward_message_await_response(struct http_forward_info
inet_pton(AF_INET, forward_info.host, &(sa.sin_addr));
int recv_size = connect(my_sock, (struct sockaddr *)&sa, sizeof(sa));
printf("CONNECT: Origin Server Connected\n");
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) printf("FORWARD: Message Sent to Origin Server\n");
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, 0)) > 0) {
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) {
@@ -426,19 +438,15 @@ struct res_http_message forward_message_await_response(struct http_forward_info
}
}
printf("Last recv returned: %d\n", bytes_received);
printf("Buffer: \n%s\n", response_buffer);
printf("Total Received: %d\n", total_received);
response_buffer[total_received] = '\0';
printf("RECEIVE: Response Received from Origin Server (%d bytes)\n", total_received);
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);
printf("DISCONNECT: Origin Server Disconnected\n");
if (DEBUG_MODE >= DEBUG_INFO) {printf("DISCONNECT: Origin Server Disconnected\n");}
return response_message;
}
@@ -456,21 +464,28 @@ int extract_max_age_from_headers(struct http_header *headers, int header_count)
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
int current_time = (int)time(NULL);
if (entry.timeSaved + entry.maxAge < current_time) {
return CACHE_EXPIRED;
}
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));
@@ -485,23 +500,78 @@ void save_response_to_cache(struct req_http_message req_message, struct res_http
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;
}
}
printf("[Info] Caching object with Max-Age = %d, ETag = \"%s\"\n", new_entry->maxAge, new_entry->ETag);
// Add to cache
cache[cacheCount] = new_entry;
// 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) {
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) printf("SETUP: Setup Sockets\n");
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) printf("SETUP: Socket Created\n");
if (DEBUG_MODE >= DEBUG_EXTENDED) printf("SETUP: Socket Created\n");
// Bind Sockets
server.sin_family = AF_INET;
@@ -515,48 +585,74 @@ int main(int argc, char *argv[]) {
perror("BIND FAILED");
return 1;
}
if (DEBUG) printf("SETUP: Socket Bound\n");
if (DEBUG_MODE >= DEBUG_EXTENDED) printf("SETUP: Socket Bound\n");
// Start Listening and Waiting for Connections
listen(serv_sock, 1);
if (DEBUG) printf("SETUP: Listening for Connections\n\n");
if (DEBUG_MODE >= DEBUG_EXTENDED) printf("SETUP: Listening for Connections\n\n");
while (1) {
// Accept Connection from Client
printf("WAIT: Waiting for incoming connections...\n");
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);
printf("CONNECT: Client Connected\n");
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);
printf("RECEIVE: Message Received\n");
if (DEBUG_MODE >= DEBUG_INFO) {printf("RECEIVE: Message Received\n");}
// Process Message From Client
printf("PROCESS: Processing Message\n");
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
printf("CACHE: Checking Cache for Request\n");
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_hit != CACHE_EXPIRED) {
printf("CACHE: HIT\n");
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 {
printf("CACHE: MISS/EXPIRED\n");
printf("CACHE Miss Type: %s\n", (cache_hit == CACHE_EXPIRED) ? "EXPIRED" : "MISS");
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)
printf("[Info] Object not in cache. Contacting server\n");
}
// Process Message for Relay
printf("PROCESS: Processing for Relay\n");
if (DEBUG_MODE >= DEBUG_INFO) {printf("PROCESS: Processing for Relay\n");}
struct http_forward_info relay_message = process_for_relay(received_message);
if (cache_hit == CACHE_EXPIRED) {
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");
@@ -566,15 +662,30 @@ int main(int argc, char *argv[]) {
}
// Forward Message to Destination Server
printf("FORWARD: Forwarding Message to %s\n", relay_message.host);
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
if (cache_hit == CACHE_EXPIRED && atoi(response.start_line.status_code) == 304) {
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) {
@@ -585,15 +696,31 @@ int main(int argc, char *argv[]) {
}
}
}
response = *(cache[cache_hit]->response);
}
if (DEBUG_MODE == DEBUG_ASSIGNMENT) {
printf("[Info] Returning cached object with 200 OK\n");
}
// Save Response to Cache
if (atoi(response.start_line.status_code) != 400) {
save_response_to_cache(received_message, response);
if (can_cache(response)) {
if (DEBUG_MODE == DEBUG_ASSIGNMENT) {
printf("[Info] Object is cacheable\n");
}
if (atoi(response.start_line.status_code) != 400) {
save_response_to_cache(received_message, response);
}
} else if (DEBUG_MODE == DEBUG_ASSIGNMENT) {
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;
@@ -606,14 +733,14 @@ int main(int argc, char *argv[]) {
total_response_length = strlen(response_str) - strlen(response.body) + response.body_length;
}
printf("RESPONSE: Response Sent Back to Client\n");
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);
printf("DISCONNECT: Client Disconnected\n\n");
if (DEBUG_MODE >= DEBUG_INFO) {printf("DISCONNECT: Client Disconnected\n\n");}
}
return 0;