765 lines
29 KiB
C
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;
|
|
}
|