init
This commit is contained in:
18
.clang-format
Normal file
18
.clang-format
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
BasedOnStyle: Google
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 79
|
||||
TabWidth: 8
|
||||
ContinuationIndentWidth: 8
|
||||
UseTab: Never
|
||||
|
||||
PointerAlignment: Right
|
||||
DerivePointerAlignment: false
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignTrailingComments: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
IncludeBlocks: Regroup
|
||||
BreakBeforeBraces: Attach
|
||||
---
|
||||
27
Makefile
Normal file
27
Makefile
Normal file
@@ -0,0 +1,27 @@
|
||||
CC=gcc
|
||||
CFLAGS=-std=gnu99 -Wall -pedantic -I/local/courses/csse2310/include
|
||||
LIBCFLAGS=-fPIC $(CFLAGS)
|
||||
LDFLAGS=-L/local/courses/csse2310/lib -lcsse2310a4
|
||||
|
||||
.PHONY: all debug profile clean
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
all: dbclient dbserver libstringstore.so
|
||||
|
||||
debug: CFLAGS += -g
|
||||
debug: all
|
||||
|
||||
profile: CFLAGS += -pg -fprofile-arcs -ftest-coverage
|
||||
profile: all
|
||||
|
||||
dbserver: dbserver.c dbserver.h
|
||||
$(CC) $(CFLAGS) -pthread $(LDFLAGS) -lcsse2310a3 -lstringstore -o $@ $^
|
||||
|
||||
stringstore.o: stringstore.c /local/courses/csse2310/include/stringstore.h
|
||||
$(CC) $(LIBCFLAGS) -c $<
|
||||
|
||||
libstringstore.so: stringstore.o
|
||||
$(CC) -shared -o $@ stringstore.o
|
||||
|
||||
clean:
|
||||
rm -f dbclient dbserver libstringstore.so *.o
|
||||
151
dbclient.c
Normal file
151
dbclient.c
Normal file
@@ -0,0 +1,151 @@
|
||||
#include "dbclient.h"
|
||||
|
||||
char *construct_header(char *key, char *value) {
|
||||
char *header = NULL;
|
||||
|
||||
// If there is no value, make a get request, otherwise make a put request
|
||||
// with that value
|
||||
if (!value) {
|
||||
size_t needed_size = snprintf(NULL, 0, GET_REQUEST, key);
|
||||
header = (char *)malloc(needed_size + sizeof(char));
|
||||
sprintf(header, GET_REQUEST, key);
|
||||
} else {
|
||||
size_t body_size = strlen(value) * sizeof(char);
|
||||
size_t needed_size =
|
||||
snprintf(NULL, 0, PUT_REQUEST, key, body_size, value);
|
||||
header = (char *)malloc(needed_size + sizeof(char));
|
||||
sprintf(header, PUT_REQUEST, key, body_size, value);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
int handle_connect(char *port, int *fd) {
|
||||
struct addrinfo *ai = NULL;
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
|
||||
// Set hints
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
// Get address information
|
||||
int err;
|
||||
if ((err = getaddrinfo("localhost", port, &hints, &ai))) {
|
||||
freeaddrinfo(ai);
|
||||
fprintf(stderr, "%s\n", gai_strerror(err));
|
||||
return EXIT_ERR;
|
||||
}
|
||||
|
||||
// Connect to port
|
||||
*fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
// clang-format off
|
||||
if (connect(*fd, (struct sockaddr *)ai->ai_addr,
|
||||
sizeof(struct sockaddr))) {
|
||||
// clang-format on
|
||||
freeaddrinfo(ai);
|
||||
close(*fd);
|
||||
|
||||
fprintf(stderr, "dbclient: unable to connect to port %s\n", port);
|
||||
return EXIT_PORT_ERR;
|
||||
}
|
||||
freeaddrinfo(ai);
|
||||
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
int handle_response(FILE *recv, int valueSet) {
|
||||
HttpHeader **responseHeaders = NULL;
|
||||
memset(&responseHeaders, 0, sizeof(HttpHeader *));
|
||||
|
||||
int status;
|
||||
char *statusExplain = NULL;
|
||||
char *body = NULL;
|
||||
get_HTTP_response(recv, &status, &statusExplain, &responseHeaders, &body);
|
||||
|
||||
// Print the value only if GET and got status 200
|
||||
if (!valueSet && status == 200) {
|
||||
printf("%s\n", body);
|
||||
}
|
||||
|
||||
free_array_of_headers(responseHeaders);
|
||||
free(statusExplain);
|
||||
free(body);
|
||||
|
||||
if (status != 200 && valueSet) {
|
||||
return EXIT_PUT_ERR;
|
||||
} else if (status != 200) {
|
||||
return EXIT_GET_ERR;
|
||||
}
|
||||
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
int handle_connection(char *port, char *key, char *value) {
|
||||
int connectStatus = 0;
|
||||
int sendFd;
|
||||
if ((connectStatus = handle_connect(port, &sendFd))) {
|
||||
close(sendFd);
|
||||
return connectStatus;
|
||||
}
|
||||
|
||||
// Setup fd's for sending and receiving requests
|
||||
int recvFd = dup(sendFd);
|
||||
FILE *send = fdopen(sendFd, "w");
|
||||
FILE *recv = fdopen(recvFd, "r");
|
||||
|
||||
// Send Request
|
||||
char *header = construct_header(key, value);
|
||||
fprintf(send, "%s", header);
|
||||
fflush(send);
|
||||
fclose(send);
|
||||
free(header);
|
||||
close(sendFd);
|
||||
|
||||
// Get Response
|
||||
int valueSet = (value != NULL);
|
||||
int exitCode = handle_response(recv, valueSet);
|
||||
fclose(recv);
|
||||
close(recvFd);
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
int is_valid_key(char *key) {
|
||||
for (int i = 0; i < strlen(key); i++) {
|
||||
if (isspace(key[i])) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Usage: dbclient portnum key [value]\n");
|
||||
return EXIT_ERR;
|
||||
}
|
||||
|
||||
char *portnumArgument = strdup(argv[1]);
|
||||
char *keyArgument = strdup(argv[2]);
|
||||
char *valueArgument = NULL;
|
||||
|
||||
if (argc > 3) {
|
||||
valueArgument = strdup(argv[3]);
|
||||
}
|
||||
|
||||
if (!is_valid_key(keyArgument)) {
|
||||
fprintf(stderr, "dbclient: key must not contain spaces or newlines\n");
|
||||
return EXIT_ERR;
|
||||
}
|
||||
|
||||
int exitStatus =
|
||||
handle_connection(portnumArgument, keyArgument, valueArgument);
|
||||
|
||||
free(portnumArgument);
|
||||
free(keyArgument);
|
||||
free(valueArgument);
|
||||
|
||||
return exitStatus;
|
||||
}
|
||||
81
dbclient.h
Normal file
81
dbclient.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#ifndef DBCLIENT_H
|
||||
#define DBCLIENT_H
|
||||
#include <csse2310a4.h>
|
||||
#include <ctype.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define GET_REQUEST "GET /public/%s HTTP/1.1\r\n\r\n"
|
||||
#define PUT_REQUEST "PUT /public/%s HTTP/1.1\r\nContent-Length: %lu\r\n\r\n%s"
|
||||
|
||||
// Program exit codes
|
||||
enum ExitCodes {
|
||||
EXIT_OK = 0,
|
||||
EXIT_ERR = 1,
|
||||
EXIT_PORT_ERR = 2,
|
||||
EXIT_GET_ERR = 3,
|
||||
EXIT_PUT_ERR = 4
|
||||
};
|
||||
|
||||
/* construct_header()
|
||||
* ------------------
|
||||
* Constructs the needed header to be sent
|
||||
*
|
||||
* key: the to check or update
|
||||
* value: the value to assign to the key
|
||||
*
|
||||
* Returns: the header
|
||||
*/
|
||||
char *construct_header(char *key, char *value);
|
||||
|
||||
/* handle_connect()
|
||||
* ----------------
|
||||
* Handles the initial connection to the port given, updates the given
|
||||
* file descriptors pointer to the created one
|
||||
*
|
||||
* port: the port number or service to connect to
|
||||
* fd: the file descriptor that will be connected for the connection
|
||||
*
|
||||
* Returns: 1 if the address info couldnt be found, 2 if could not connect to
|
||||
* port, 0 if successful
|
||||
*/
|
||||
int handle_connect(char *port, int *fd);
|
||||
|
||||
/* handle_response()
|
||||
* -----------------
|
||||
* Handles the response given from the connected server
|
||||
*
|
||||
* recv: the fd that will be used for reading
|
||||
* valueSet: if the value argument was set
|
||||
*
|
||||
* Returns: 3 if GET failed, 4 if PUT failed, 0 if successful
|
||||
*/
|
||||
int handle_response(FILE *recv, int valueSet);
|
||||
|
||||
/* handle_connection()
|
||||
* -------------------
|
||||
* Handles the connection to the given port, sends a GET or PUT request
|
||||
* depending on if the value argument is not NULL
|
||||
*
|
||||
* port: the port to connect to
|
||||
* key: the key to retrieve or update
|
||||
* value: the value to update the supplied key to
|
||||
*/
|
||||
int handle_connection(char *port, char *key, char *value);
|
||||
|
||||
/* is_valid_key()
|
||||
* --------------
|
||||
* Checks if the given key is valid according to the spec
|
||||
*
|
||||
* key: the key to check if valid
|
||||
*
|
||||
* Returns: 0 if not valid, 1 if valid
|
||||
*/
|
||||
int is_valid_key(char *key);
|
||||
|
||||
#endif
|
||||
443
dbserver.c
Normal file
443
dbserver.c
Normal file
@@ -0,0 +1,443 @@
|
||||
#include "dbserver.h"
|
||||
|
||||
int get_nth_char_position(const char *s, int c, int n) {
|
||||
int charCount = 0;
|
||||
|
||||
for (int i = 0; i < strlen(s); i++) {
|
||||
if (s[i] == c && (++charCount) == n) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int get_valid_number(char *rawNumber) {
|
||||
if (strlen(rawNumber) == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < strlen(rawNumber); i++) {
|
||||
if (!isdigit(rawNumber[i])) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return atoi(rawNumber);
|
||||
}
|
||||
|
||||
char *get_authkey_from_file(char *filePath) {
|
||||
FILE *fin = NULL;
|
||||
if ((fin = fopen(filePath, "r")) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *line = NULL;
|
||||
if ((line = read_line(fin)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fclose(fin);
|
||||
return line;
|
||||
}
|
||||
|
||||
struct addrinfo *get_address_info(int port) {
|
||||
struct addrinfo *ai = 0;
|
||||
struct addrinfo hints;
|
||||
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
// Convert port to string for use in bind
|
||||
char portnum[6];
|
||||
sprintf(portnum, "%d", port);
|
||||
|
||||
// Get the address info
|
||||
int err;
|
||||
if ((err = getaddrinfo(NULL, portnum, &hints, &ai))) {
|
||||
freeaddrinfo(ai);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ai;
|
||||
}
|
||||
|
||||
int open_socket(int port) {
|
||||
struct addrinfo *ai = 0;
|
||||
if ((ai = get_address_info(port)) == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Bind socket to port
|
||||
int recvFd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
// Reuse ports immediately
|
||||
int optionValue = 1;
|
||||
// clang-format off
|
||||
if (setsockopt(recvFd, SOL_SOCKET, SO_REUSEADDR, &optionValue,
|
||||
sizeof(int)) < 0) {
|
||||
// clang-format on
|
||||
freeaddrinfo(ai);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Bind fd to socket
|
||||
if (bind(recvFd, (struct sockaddr *)ai->ai_addr, sizeof(struct sockaddr)) <
|
||||
0) {
|
||||
freeaddrinfo(ai);
|
||||
return -1;
|
||||
}
|
||||
freeaddrinfo(ai);
|
||||
|
||||
// Backlogged if not being used right away
|
||||
if (listen(recvFd, MAX_BACKLOG) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get port number and print to stdout
|
||||
struct sockaddr_in sin;
|
||||
socklen_t len = sizeof(sin);
|
||||
if (getsockname(recvFd, (struct sockaddr *)&sin, &len) == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
fprintf(stderr, "%d\n", ntohs(sin.sin_port));
|
||||
}
|
||||
|
||||
return recvFd;
|
||||
}
|
||||
|
||||
void serverstats_init() {
|
||||
serverStats =
|
||||
(struct ServerStatistics *)malloc(sizeof(struct ServerStatistics));
|
||||
|
||||
serverStats->currentConnections = 0;
|
||||
serverStats->completedConnections = 0;
|
||||
serverStats->authFails = 0;
|
||||
serverStats->gets = 0;
|
||||
serverStats->puts = 0;
|
||||
serverStats->deletes = 0;
|
||||
serverStats->connectionLock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||
}
|
||||
|
||||
int handle_listen(int port, int connections, char *authKey) {
|
||||
// Create a public (0) and private (1) stringstore
|
||||
StringStore *stringStores[2] = {NULL, NULL};
|
||||
stringStores[0] = stringstore_init();
|
||||
stringStores[1] = stringstore_init();
|
||||
|
||||
struct sockaddr_in fromAddress = {0};
|
||||
socklen_t fromAddressSize = sizeof(fromAddress);
|
||||
|
||||
int recvFd;
|
||||
if ((recvFd = open_socket(port)) < 0) {
|
||||
return EXIT_PORT_ERR;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
int fd = accept(
|
||||
recvFd, (struct sockaddr *)&fromAddress, &fromAddressSize);
|
||||
if (fd < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
char hostname[NI_MAXHOST];
|
||||
int err;
|
||||
// clang-format off
|
||||
if ((err = getnameinfo((struct sockaddr *)&fromAddress,
|
||||
fromAddressSize, hostname, NI_MAXHOST, NULL, 0, 0))) {
|
||||
// clang-format on
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct ConnectionInfo ci;
|
||||
memset(&ci, 0, sizeof(struct ConnectionInfo));
|
||||
ci.fd = fd;
|
||||
ci.connections = connections;
|
||||
ci.authKey = authKey;
|
||||
ci.stringStores = stringStores;
|
||||
|
||||
struct ConnectionInfo *ciPtr = malloc(sizeof(struct ConnectionInfo));
|
||||
*ciPtr = ci;
|
||||
|
||||
pthread_t threadId;
|
||||
pthread_create(&threadId, NULL, handle_connection, ciPtr);
|
||||
pthread_detach(threadId);
|
||||
}
|
||||
|
||||
free(stringStores[0]);
|
||||
free(stringStores[1]);
|
||||
close(recvFd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *process_response_get(char *key, StringStore *stringStore) {
|
||||
char *response = NULL;
|
||||
char *value = NULL;
|
||||
if ((value = (char *)stringstore_retrieve(stringStore, key)) == NULL) {
|
||||
response = construct_HTTP_response(404, "Not Found", NULL, NULL);
|
||||
} else {
|
||||
response = construct_HTTP_response(200, "OK", NULL, value);
|
||||
|
||||
pthread_mutex_lock(&(serverStats->connectionLock));
|
||||
serverStats->gets += 1;
|
||||
pthread_mutex_unlock(&(serverStats->connectionLock));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
char *process_response_put(char *key, char *value, StringStore *stringStore) {
|
||||
char *response = NULL;
|
||||
if (stringstore_add(stringStore, key, value) == 0) {
|
||||
response = construct_HTTP_response(
|
||||
500, "Internal Server Error", NULL, NULL);
|
||||
} else {
|
||||
response = construct_HTTP_response(200, "OK", NULL, NULL);
|
||||
|
||||
pthread_mutex_lock(&(serverStats->connectionLock));
|
||||
serverStats->puts += 1;
|
||||
pthread_mutex_unlock(&(serverStats->connectionLock));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
char *process_response_delete(char *key, StringStore *stringStore) {
|
||||
char *response = NULL;
|
||||
if (stringstore_delete(stringStore, key) == 0) {
|
||||
response = construct_HTTP_response(404, "Not Found", NULL, NULL);
|
||||
} else {
|
||||
response = construct_HTTP_response(200, "OK", NULL, NULL);
|
||||
|
||||
pthread_mutex_lock(&(serverStats->connectionLock));
|
||||
serverStats->deletes += 1;
|
||||
pthread_mutex_unlock(&(serverStats->connectionLock));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
int check_auth_key(HttpHeader **headers, char *authKey) {
|
||||
for (int i = 0; headers[i] != NULL; i++) {
|
||||
if (strcmp(headers[i]->name, "Authorization") == 0 &&
|
||||
strcmp(headers[i]->value, authKey) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *construct_response(
|
||||
struct RequestParams req, StringStore **stringStores) {
|
||||
char *response = NULL;
|
||||
|
||||
// Default to public string store
|
||||
StringStore *stringStore = stringStores[0];
|
||||
|
||||
if (req.path == NULL || req.key == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If the client is trying to access a private path
|
||||
if (strcmp(req.path, PRIVATE_PATH) == 0) {
|
||||
if (!check_auth_key(req.headers, req.authKey)) {
|
||||
// Client failed to auth
|
||||
pthread_mutex_lock(&(serverStats->connectionLock));
|
||||
serverStats->authFails += 1;
|
||||
pthread_mutex_unlock(&(serverStats->connectionLock));
|
||||
|
||||
return construct_HTTP_response(401, "Unauthorized", NULL, NULL);
|
||||
} else {
|
||||
stringStore = stringStores[1];
|
||||
}
|
||||
} else if (strcmp(req.path, PUBLIC_PATH) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strcmp(req.method, "GET") == 0) {
|
||||
response = process_response_get(req.key, stringStore);
|
||||
} else if (strcmp(req.method, "PUT") == 0) {
|
||||
response = process_response_put(req.key, req.value, stringStore);
|
||||
} else if (strcmp(req.method, "DELETE") == 0) {
|
||||
response = process_response_delete(req.key, stringStore);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
int get_path_key(char *address, char **path, char **key) {
|
||||
int keyStartPos = get_nth_char_position(address, '/', 2) + 1;
|
||||
if (keyStartPos <= 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
*path = (char *)malloc(sizeof(char) * (keyStartPos + 1));
|
||||
strncpy(*path, address, keyStartPos);
|
||||
|
||||
*key = (char *)malloc(
|
||||
sizeof(char) * ((strlen(address) - keyStartPos) + 1));
|
||||
strcpy(*key, &address[keyStartPos]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct RequestParams request_params_init() {
|
||||
struct RequestParams requestParams = {0};
|
||||
memset(&requestParams, 0, sizeof(struct RequestParams));
|
||||
requestParams.method = NULL;
|
||||
requestParams.path = NULL;
|
||||
requestParams.key = NULL;
|
||||
requestParams.value = NULL;
|
||||
requestParams.authKey = NULL;
|
||||
|
||||
requestParams.headers = NULL;
|
||||
memset(&(requestParams.headers), 0, sizeof(HttpHeader *));
|
||||
|
||||
return requestParams;
|
||||
}
|
||||
|
||||
int handle_connection_request(
|
||||
FILE *recv, FILE *send, char *authKey, StringStore **stringStores) {
|
||||
struct RequestParams req = request_params_init();
|
||||
|
||||
char *address = NULL;
|
||||
// clang-format off
|
||||
if (get_HTTP_request(recv, &(req.method), &address, &(req.headers),
|
||||
&(req.value)) == 1) {
|
||||
// clang-format on
|
||||
get_path_key(address, &(req.path), &(req.key));
|
||||
|
||||
req.authKey = authKey;
|
||||
|
||||
char *response = NULL;
|
||||
if ((response = construct_response(req, stringStores)) == NULL) {
|
||||
response = construct_HTTP_response(400, "Bad Request", NULL, NULL);
|
||||
}
|
||||
fprintf(send, response);
|
||||
fflush(send);
|
||||
|
||||
// Will only need to free in this branch
|
||||
free(req.method);
|
||||
free(address);
|
||||
free(req.value);
|
||||
|
||||
free(response);
|
||||
free(req.path);
|
||||
free(req.key);
|
||||
free_array_of_headers(req.headers);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void handle_client_connection(
|
||||
int sendFd, char *authKey, StringStore **stringStores) {
|
||||
int recvFd = dup(sendFd);
|
||||
FILE *send = fdopen(sendFd, "w");
|
||||
FILE *recv = fdopen(recvFd, "r");
|
||||
|
||||
// Loop until EOF or invalid response
|
||||
int err = 0;
|
||||
// clang-format off
|
||||
while ((err = handle_connection_request(
|
||||
recv, send, authKey, stringStores)) == 0) {
|
||||
// clang-format on
|
||||
;
|
||||
}
|
||||
fclose(recv);
|
||||
fclose(send);
|
||||
|
||||
close(recvFd);
|
||||
}
|
||||
|
||||
void *handle_connection(void *ciPtr) {
|
||||
struct ConnectionInfo ci = *(struct ConnectionInfo *)ciPtr;
|
||||
free(ciPtr);
|
||||
|
||||
// Lock connection count variable while updating
|
||||
pthread_mutex_lock(&(serverStats->connectionLock));
|
||||
serverStats->currentConnections += 1;
|
||||
pthread_mutex_unlock(&(serverStats->connectionLock));
|
||||
|
||||
int illegalConnection = 0;
|
||||
if (serverStats->currentConnections <= ci.connections ||
|
||||
ci.connections == 0) {
|
||||
// if within the max connections
|
||||
handle_client_connection(ci.fd, ci.authKey, ci.stringStores);
|
||||
} else {
|
||||
// send them away
|
||||
write(ci.fd, UNAVALIABLE_RESPONSE, strlen(UNAVALIABLE_RESPONSE));
|
||||
illegalConnection = 1;
|
||||
}
|
||||
close(ci.fd);
|
||||
|
||||
pthread_mutex_lock(&(serverStats->connectionLock));
|
||||
serverStats->currentConnections--;
|
||||
if (!illegalConnection) {
|
||||
serverStats->completedConnections++;
|
||||
}
|
||||
pthread_mutex_unlock(&(serverStats->connectionLock));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void print_server_stats(int s) {
|
||||
fprintf(stderr,
|
||||
"Connected clients:%d\nCompleted clients:%d\nAuth "
|
||||
"failures:%d\nGET "
|
||||
"operations:%d\nPUT operations:%d\nDELETE operations:%d\n",
|
||||
serverStats->currentConnections, serverStats->completedConnections,
|
||||
serverStats->authFails, serverStats->gets, serverStats->puts,
|
||||
serverStats->deletes);
|
||||
}
|
||||
|
||||
void setup_signal_handler(void) {
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = print_server_stats;
|
||||
sa.sa_flags = SA_RESTART;
|
||||
|
||||
sigaction(SIGHUP, &sa, 0);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
serverstats_init();
|
||||
setup_signal_handler();
|
||||
|
||||
if (argc < 3 || argc > 4) {
|
||||
fprintf(stderr, PROGRAM_USAGE);
|
||||
exit(EXIT_ERR);
|
||||
}
|
||||
|
||||
int connections = 0;
|
||||
if ((connections = get_valid_number(argv[2])) < 0) {
|
||||
fprintf(stderr, PROGRAM_USAGE);
|
||||
exit(EXIT_ERR);
|
||||
}
|
||||
|
||||
int portnum = 0;
|
||||
if (argv[3] && strlen(argv[3]) > 0) {
|
||||
portnum = get_valid_number(argv[3]);
|
||||
if (portnum != 0 && (portnum < PORT_MIN || portnum > PORT_MAX)) {
|
||||
fprintf(stderr, PROGRAM_USAGE);
|
||||
exit(EXIT_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
char *authKey = get_authkey_from_file(argv[1]);
|
||||
if (authKey == NULL) {
|
||||
fprintf(stderr, "dbserver: unable to read authentication string\n");
|
||||
exit(EXIT_FILE_ERR);
|
||||
}
|
||||
|
||||
int err;
|
||||
if ((err = handle_listen(portnum, connections, authKey)) > 0) {
|
||||
fprintf(stderr, "dbserver: unable to open socket for listening\n");
|
||||
free(authKey);
|
||||
exit(err);
|
||||
}
|
||||
|
||||
free(authKey);
|
||||
free(serverStats);
|
||||
}
|
||||
270
dbserver.h
Normal file
270
dbserver.h
Normal file
@@ -0,0 +1,270 @@
|
||||
#ifndef DBSERVER_H
|
||||
#define DBSERVER_H
|
||||
#include <csse2310a3.h>
|
||||
#include <csse2310a4.h>
|
||||
#include <ctype.h>
|
||||
#include <netdb.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stringstore.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define PORT_MIN 1024
|
||||
#define PORT_MAX 65535
|
||||
|
||||
#define MAX_BACKLOG 10
|
||||
|
||||
#define UNAVALIABLE_RESPONSE \
|
||||
"HTTP/1.1 503 Service Unavaliable\r\nContent-Length: 0\r\n\r\n"
|
||||
#define OK_RESPONSE "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
|
||||
#define ISR_RESPONSE \
|
||||
"HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n"
|
||||
|
||||
#define PROGRAM_USAGE "Usage: dbserver authfile connections [portnum]\n"
|
||||
|
||||
#define PUBLIC_PATH "/public/"
|
||||
#define PRIVATE_PATH "/private/"
|
||||
|
||||
// Program exit codes
|
||||
enum ExitCodes {
|
||||
EXIT_OK = 0,
|
||||
EXIT_ERR = 1,
|
||||
EXIT_FILE_ERR = 2,
|
||||
EXIT_PORT_ERR = 3
|
||||
};
|
||||
|
||||
// Server Statistics
|
||||
struct ServerStatistics {
|
||||
int currentConnections;
|
||||
int completedConnections;
|
||||
int authFails;
|
||||
int gets;
|
||||
int puts;
|
||||
int deletes;
|
||||
pthread_mutex_t connectionLock;
|
||||
};
|
||||
|
||||
// Connection information needed for threads
|
||||
struct ConnectionInfo {
|
||||
int fd;
|
||||
int connections;
|
||||
char *authKey;
|
||||
struct ConnectionThread *connectionThread;
|
||||
StringStore **stringStores;
|
||||
};
|
||||
|
||||
// Information regarding a client request
|
||||
struct RequestParams {
|
||||
char *method;
|
||||
char *path;
|
||||
char *key;
|
||||
char *value;
|
||||
char *authKey;
|
||||
HttpHeader **headers;
|
||||
};
|
||||
|
||||
// The server statistic instance, global as needed to be called by SIGHUP
|
||||
struct ServerStatistics *serverStats;
|
||||
|
||||
/* get_nth_char_position()
|
||||
* -----------------------
|
||||
* Get the character position of the nth occurrence of that character
|
||||
*
|
||||
* s: the string to search
|
||||
* c: the character to look for
|
||||
* n: the nth occurance to retrieve the position of
|
||||
*
|
||||
* Returns: the position of the nth ocurrance of the given character
|
||||
*/
|
||||
int get_nth_char_position(const char *s, int c, int n);
|
||||
|
||||
/* get_valid_number()
|
||||
* ------------------
|
||||
* Checks if a given number is an actual number, then returns it as an int
|
||||
*
|
||||
* rawNumber: the number to check and convert
|
||||
*
|
||||
* Returns: -1 if the number is not valid, the converted string if it is
|
||||
* valid
|
||||
*/
|
||||
int get_valid_number(char *rawNumber);
|
||||
|
||||
/* get_authkey_from_file()
|
||||
* -----------------------
|
||||
* Gets the first line from a file
|
||||
*
|
||||
* filePath: the path to the file
|
||||
*
|
||||
* Returns: NULL if the file does not exist or the first line does not
|
||||
* exist, the first line otherwise
|
||||
*/
|
||||
char *get_authkey_from_file(char *filePath);
|
||||
|
||||
/* get_address_info()
|
||||
* ------------------
|
||||
* Gets the required addrinfo for the given port
|
||||
*
|
||||
* port: the port to assign to
|
||||
*
|
||||
* Returns: the appropriate addrinfo
|
||||
*/
|
||||
struct addrinfo *get_address_info(int port);
|
||||
|
||||
/* open_socket()
|
||||
* -------------
|
||||
* Opens a socket for the given port holding the given connections
|
||||
*
|
||||
* port: the port to open a socket on
|
||||
* connections: the amount of connections to support
|
||||
*
|
||||
* Returns: -1 if failed to connect to socket, 0 if success
|
||||
*/
|
||||
int open_socket(int port);
|
||||
|
||||
/* serverstats_init()
|
||||
* -----------------------
|
||||
* Initializes the server statistics
|
||||
*
|
||||
* Returns: the newly initialised server stats
|
||||
*/
|
||||
void serverstats_init(void);
|
||||
|
||||
/* handle_listen()
|
||||
* ---------------
|
||||
* Opens a socket and deals with connections
|
||||
*
|
||||
* port: the port to open a socket on
|
||||
* connections: the amount of connections to support
|
||||
* authKey: the authorization key for the private db
|
||||
*
|
||||
* Returns: 3 if failed to connect to socket, 0 if success
|
||||
*/
|
||||
int handle_listen(int port, int connections, char *authKey);
|
||||
|
||||
/* process_response_get()
|
||||
* -----------------------
|
||||
* Gets the appropriate get response for the request from the client
|
||||
*
|
||||
* key: the key to get the value of from the db
|
||||
* stringStore: the db
|
||||
*
|
||||
* Returns: the appropriate response (200 with value or 404)
|
||||
*/
|
||||
char *process_response_get(char *key, StringStore *stringStore);
|
||||
|
||||
/* process_response_put()
|
||||
* -----------------------
|
||||
* Adds the appropriate value to the db and then gets the appropriate
|
||||
* response
|
||||
*
|
||||
* key: the key to update the value of in the db
|
||||
* value: the value to update the key with
|
||||
* stringStore: the db
|
||||
*
|
||||
* Returns: the appropiate response (200 or 500 if failed)
|
||||
*/
|
||||
char *process_response_put(char *key, char *value, StringStore *stringStore);
|
||||
|
||||
/* process_response_delete()
|
||||
* -----------------------
|
||||
* Deletes the given key from the db and returns the appropriate response
|
||||
*
|
||||
* key: the key to delete from the db
|
||||
* stringStore: the db
|
||||
*
|
||||
* Returns: the appropriate response (200 or 404)
|
||||
*/
|
||||
char *process_response_delete(char *key, StringStore *stringStore);
|
||||
|
||||
/* check_auth_key()
|
||||
* ----------------
|
||||
* Check the given authentication key with the given headers
|
||||
*
|
||||
* headers: the headers that may contain the authorization header
|
||||
* authKey: the auth key to check against the header
|
||||
*
|
||||
* Returns: 1 if the key matches, 0 otherwise
|
||||
*/
|
||||
int check_auth_key(HttpHeader **headers, char *authKey);
|
||||
|
||||
/* construct_response()
|
||||
* --------------------
|
||||
* Process the request and construct the needed response
|
||||
*
|
||||
* req: the request parameters
|
||||
* stringStores: the db to update keys
|
||||
*
|
||||
* Returns: the required response
|
||||
*/
|
||||
char *construct_response(struct RequestParams req, StringStore **stringStores);
|
||||
|
||||
/* get_path_key()
|
||||
* --------------
|
||||
* Gets the needed path and key from the supplied address
|
||||
*
|
||||
* address: the address given by the HTTP request
|
||||
* path: the pointer to the path variable to update
|
||||
* key: the pointer to the key variable to update
|
||||
*/
|
||||
int get_path_key(char *address, char **path, char **key);
|
||||
|
||||
/* request_params_init()
|
||||
* ---------------------
|
||||
* Initialise the requestParams struct
|
||||
*
|
||||
* Returns: the initialised requestParams struct
|
||||
*/
|
||||
struct RequestParams request_params_init(void);
|
||||
|
||||
/* handle_connection_request()
|
||||
* ---------------------------
|
||||
* Handles the clients request
|
||||
*
|
||||
* recv: the connection file to read from
|
||||
* send: the connection file to write to
|
||||
* authKey: the authorization key for the private db
|
||||
* stringStores: the arrays of dbs
|
||||
*/
|
||||
int handle_connection_request(
|
||||
FILE *recv, FILE *send, char *authKey, StringStore **stringStores);
|
||||
|
||||
/* handle_client_connection()
|
||||
* ---------------------------
|
||||
* Handles the connection through the given fd
|
||||
*
|
||||
* fd: the fd to send and recieve information for the connection on
|
||||
* authKey: the authorization key for the private db
|
||||
* stringStores: the array of dbs
|
||||
*/
|
||||
void handle_client_connection(
|
||||
int fd, char *authKey, StringStore **stringStores);
|
||||
|
||||
/* handle_connection()
|
||||
* -------------------
|
||||
* Thread function for handling connections to the server, if there are too
|
||||
* many connections the server will turn away the client
|
||||
*
|
||||
* ciPtr: the pointer to the connection information
|
||||
*/
|
||||
void *handle_connection(void *ciPtr);
|
||||
|
||||
/* print_server_stats()
|
||||
* --------------------
|
||||
* Prints the server statistics. used by SIGHUP handler
|
||||
*
|
||||
* s: the signal from the signal handler
|
||||
*/
|
||||
void print_server_stats(int s);
|
||||
|
||||
/* setup_signal_handler()
|
||||
* ----------------------
|
||||
* Sets up the signal handler for SIGHUP
|
||||
*/
|
||||
void setup_signal_handler(void);
|
||||
|
||||
#endif
|
||||
72
stringstore.c
Normal file
72
stringstore.c
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stringstore.h>
|
||||
|
||||
typedef struct StringStoreContents {
|
||||
char *key;
|
||||
char *value;
|
||||
} StringStoreContents;
|
||||
|
||||
struct StringStore {
|
||||
struct StringStoreContents *contents;
|
||||
int size;
|
||||
};
|
||||
|
||||
StringStore *stringstore_init(void) {
|
||||
StringStore *stringStore = (StringStore *)malloc(sizeof(StringStore));
|
||||
memset(stringStore, 0, sizeof(StringStore));
|
||||
stringStore->size = 0;
|
||||
stringStore->contents =
|
||||
(StringStoreContents *)malloc(sizeof(StringStoreContents));
|
||||
return stringStore;
|
||||
}
|
||||
|
||||
StringStore *stringstore_free(StringStore *store) {
|
||||
for (int i = 0; i < store->size; i++) {
|
||||
free(store->contents[i].key);
|
||||
free(store->contents[i].value);
|
||||
}
|
||||
free(store->contents);
|
||||
free(store);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int stringstore_add(StringStore *store, const char *key, const char *value) {
|
||||
stringstore_delete(store, key);
|
||||
|
||||
store->contents = (StringStoreContents *)realloc(
|
||||
store->contents, sizeof(StringStoreContents) * (store->size + 1));
|
||||
|
||||
if ((store->contents[store->size].key = strdup(key)) == NULL ||
|
||||
(store->contents[store->size].value = strdup(value)) == NULL) {
|
||||
return 0;
|
||||
}
|
||||
store->size++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *stringstore_retrieve(StringStore *store, const char *key) {
|
||||
for (int i = 0; i < store->size; i++) {
|
||||
if (strcmp(store->contents[i].key, key) == 0) {
|
||||
return store->contents[i].value;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int stringstore_delete(StringStore *store, const char *key) {
|
||||
for (int i = 0; i < store->size; i++) {
|
||||
if (strcmp(store->contents[i].key, key) == 0) {
|
||||
free(store->contents[i].key);
|
||||
free(store->contents[i].value);
|
||||
if ((i + 1) < store->size) {
|
||||
store->contents[i] = store->contents[i + 1];
|
||||
}
|
||||
store->size--;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user