summaryrefslogtreecommitdiff
path: root/backend/server.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backend/server.cpp')
-rw-r--r--backend/server.cpp360
1 files changed, 360 insertions, 0 deletions
diff --git a/backend/server.cpp b/backend/server.cpp
new file mode 100644
index 0000000..eb371d3
--- /dev/null
+++ b/backend/server.cpp
@@ -0,0 +1,360 @@
+#include <stdio.h>
+#include <wS2tcpip.h>
+#include <string.h>
+#include <assert.h>
+#include <time.h>
+
+#pragma comment (lib, "ws2_32.lib")
+
+#define WIN32_LEAN_AND_MEAN
+#define PORT 80
+
+enum HttpRequestType {
+ HttpRequestType_None,
+ HttpRequestType_Get
+};
+
+enum HttpRequestError {
+ HttpRequestError_None,
+ HttpRequestError_UnsupportedRequest,
+ HttpRequestError_NoBuffer,
+ HttpRequestError_ClientDisconnect
+};
+
+struct HeaderParseResult {
+ HttpRequestType requestType;
+ char resource[512];
+ HttpRequestError error = HttpRequestError_None;
+};
+
+void copyUntilStr(char* destination, char* source, const char* cmpstr) {
+ int index = 0;
+ char* ptr = source;
+ int cmpStrLen = strlen(cmpstr);
+ while (strncmp(ptr, cmpstr, cmpStrLen) != 0) {
+ destination[index++] = *ptr;
+ ptr++;
+ }
+
+ destination[index] = '\0';
+}
+
+int endsWith(const char *str, const char *suffix) {
+ size_t str_len = strlen(str);
+ size_t suffix_len = strlen(suffix);
+
+ return (str_len >= suffix_len) &&
+ (!memcmp(str + str_len - suffix_len, suffix, suffix_len));
+}
+
+int fixNewLines(char* str, int strSize) {
+ int removed = 0;
+ for(int i = 0; i < strSize; i++) {
+ if(str[i] == '\n') {
+ str[i] = '\r\n';
+ removed++;
+ }
+ }
+
+ return removed;
+}
+
+struct HeaderParser {
+ char buffer[4096];
+ int bufferPtr = 0;
+ int bufferSize = 0;
+ char line[512];
+ int lineIndex = 0 ;
+
+ bool tryReadLine() {
+ int startIndex = bufferPtr;
+ int endIndex = startIndex;
+ while (true) {
+ if (bufferPtr >= bufferSize) {
+ endIndex = bufferPtr;
+ break;
+ }
+
+ if (strncmp(&buffer[bufferPtr], "\r\n", 2) == 0) {
+ endIndex = bufferPtr;
+ bufferPtr += 2; // Move past the new line character
+ break;
+ }
+
+ bufferPtr++;
+ }
+
+ int lineLength = endIndex - startIndex;
+ if (lineLength == 0) {
+ return false;
+ }
+
+ assert(lineLength < 512 && lineLength > 0);
+ strncpy_s(line, &buffer[startIndex], lineLength);
+ line[lineLength] = '\0';
+ return true;
+ }
+
+ HeaderParseResult readHeader(SOCKET clientSocket) {
+ HeaderParseResult retval;
+ ZeroMemory(buffer, 4096);
+
+ // Wait for client to send data
+ bufferSize = recv(clientSocket, buffer, 4096, 0);
+ if (bufferSize == SOCKET_ERROR) {
+ printf("Error in receive. Quitting\n");
+ retval.error = HttpRequestError_NoBuffer;
+ return retval;
+ } else if (bufferSize == 0) {
+ printf("Client disconnected\n");
+ retval.error = HttpRequestError_ClientDisconnect;
+ return retval;
+ }
+
+ printf("Received message (%d bytes): %s", bufferSize, buffer);
+
+ while (tryReadLine()) {
+ if (lineIndex == 0) {
+ // Parse request, only supporting GETs for now
+ int linePtr = 0;
+ if (strncmp(line, "GET ", 3) == 0) {
+ retval.requestType = HttpRequestType_Get;
+ linePtr += 4; // Move past teh get
+ } else {
+ retval.error = HttpRequestError_UnsupportedRequest;
+ return retval;
+ }
+
+ copyUntilStr(retval.resource, &line[linePtr], " ");
+ printf("Get request on resource: %s\n", retval.resource);
+ }
+
+ lineIndex++;
+ }
+
+ return retval;
+ }
+};
+
+void getCurrentDateStr(char* text) {
+ time_t now = time(NULL);
+ sprintf(text, "%s GMT", ctime(&now));
+}
+
+enum FileType {
+ FileType_None,
+ FileType_HTML,
+ FileType_CSS,
+ FileType_JS
+};
+
+enum HttpStatusCode {
+ HttpStatusCode_OK = 200,
+ HttpStatusCode_BADREQUEST = 400,
+ HttpStatusCode_NOTFOUND = 404
+};
+
+void sendErrorMessage(SOCKET socket, HttpStatusCode status, const char* errorHtml) {
+ char timeStr[100];
+ char header[512];
+
+ ZeroMemory(timeStr, 100);
+ ZeroMemory(header, 100);
+
+ const char* errorMessage;
+ switch (status) {
+ case HttpStatusCode_BADREQUEST:
+ errorMessage = "Bad Request";
+ break;
+ case HttpStatusCode_NOTFOUND:
+ errorMessage = "Not found";
+ break;
+ default:
+ errorMessage = "Unknown";
+ break;
+ }
+
+ getCurrentDateStr(timeStr);
+ int contentLen = strlen(errorHtml);
+ sprintf(header, "HTTP/1.1 %d %s\r\nDate: %s\r\nContent-Type: text/html; charset=utf-8\r\nConnection: keep-alive\r\nContent-Length: %d\r\n\r\n", status, errorMessage, timeStr, contentLen);
+ send(socket, header, strlen(header), 0);
+
+ send(socket, errorHtml, contentLen, 0);
+}
+
+#define MAXBUFLEN 1000000
+
+int readFileToMemory(char* filepath, char source[MAXBUFLEN + 1]) {
+ FILE* file;
+ fopen_s(&file, filepath, "r+");
+
+ if(file == NULL) {
+ printf("Failed to read the file\n");
+ return -1;
+
+ }
+ size_t newLen = fread(source, sizeof(char), MAXBUFLEN, file);
+ if ( ferror( file ) != 0 ) {
+ fputs("Error reading file", stderr);
+ return -1;
+ }
+
+ fclose(file);
+ return newLen;
+}
+
+
+bool trySendFile(SOCKET clientSocket, char* filename) {
+ FILE* file;
+ char filePath[128];
+ sprintf(filePath, "../../frontend%s", filename);
+
+ char source[MAXBUFLEN + 1];
+ int fileSizeBytes = readFileToMemory(filePath, source);
+
+ if (fileSizeBytes <= 0) {
+ return false;
+ }
+
+
+ const char* contentType;
+ if (endsWith(filename, ".html")) {
+ contentType = "text/html";
+ } else if (endsWith(filename, ".js")) {
+ contentType = "application/javascript";
+ } else if (endsWith(filename, ".css")) {
+ contentType = "text/css";
+ } else if (endsWith(filename, ".ico")) {
+ contentType = "image/x-icon";
+ } else {
+ contentType = "text/plain";
+ }
+
+ char timeStr[100];
+ char header[512];
+
+ ZeroMemory(timeStr, 100);
+ ZeroMemory(header, 100);
+
+ getCurrentDateStr(timeStr);
+ sprintf(header, "HTTP/1.1 200 OK\r\nDate: %s\r\nContent-Type: %s; charset=utf-8\r\nConnection: keep-alive\r\nContent-Length: %d\r\n\r\n", timeStr, contentType, fileSizeBytes);
+ send(clientSocket, header, strlen(header), 0);
+
+
+ int bytesSent = 0;
+ int bytesRemaining = fileSizeBytes;
+ while (bytesSent < fileSizeBytes) {
+ int amountToSend = bytesRemaining >= 4096 ? 4096 : bytesRemaining; // Send 4K bytes at a time max
+ send(clientSocket, &source[bytesSent], amountToSend, 0);
+ bytesRemaining -= amountToSend;
+ bytesSent += amountToSend;
+ }
+
+ return true;
+}
+
+
+int main() {
+ // Initializing Winsock
+ WSADATA wsaData;
+ WORD version = MAKEWORD(2, 2);
+
+ int wsOK = WSAStartup(version, &wsaData);
+ if (wsOK != 0) {
+ printf("Cannot initialize winsock: %d\n", wsOK);
+ return EXIT_FAILURE;
+ }
+
+ // Creating a socket
+ SOCKET listeningSocket = socket(AF_INET, SOCK_STREAM, 0);
+ if (listeningSocket == INVALID_SOCKET) {
+ printf("Cannot create socket\n");
+ return EXIT_FAILURE;
+ }
+
+ // Bind an IP address and port to a socket
+ sockaddr_in hint = { 0 };
+ hint.sin_family = AF_INET;
+ hint.sin_port = htons(PORT); // Networking is big endian, while PCs are little endian
+ hint.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
+
+ bind(listeningSocket, (sockaddr*)&hint, sizeof(hint));
+
+ // Tell Winsock the socket is for listening
+ listen(listeningSocket, SOMAXCONN);
+
+ while (true) {
+
+ // Wait for a connection
+ sockaddr_in client;
+ int clientSize = sizeof(client);
+
+ SOCKET clientSocket = accept(listeningSocket, (sockaddr*)&client, &clientSize);
+ if (clientSocket == INVALID_SOCKET) {
+ printf("Cannot create the client socket\n");
+ return EXIT_FAILURE;
+ }
+
+ char host[NI_MAXHOST]; // Client's remote name
+ char service[NI_MAXSERV]; // Service (i.e. port) the client is connected on
+
+ ZeroMemory(host, NI_MAXHOST);
+ ZeroMemory(service, NI_MAXSERV);
+
+ if (getnameinfo((sockaddr*)&client, sizeof(client), host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0) {
+ printf("Host %s connected on port %s\n", host, service);
+ } else {
+ inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
+ printf("Host %s connected on port %d\n", host, ntohs(client.sin_port));
+ }
+
+ // Accept and echo message back to client
+ while (true) {
+ HeaderParser parser;
+ HeaderParseResult parseResult = parser.readHeader(clientSocket);
+ bool shouldBreak = false;
+
+ switch (parseResult.error) {
+ case HttpRequestError_ClientDisconnect: {
+ shouldBreak = true;
+ break;
+ }
+ case HttpRequestError_UnsupportedRequest: {
+ shouldBreak = true;
+ break;
+ }
+ }
+
+ if (shouldBreak) {
+ break;
+ }
+
+ switch (parseResult.requestType) {
+ case HttpRequestType_Get: {
+ if (strcmp(parseResult.resource, "/") == 0) {
+ trySendFile(clientSocket, (char*)"/index.html");
+ } else {
+ trySendFile(clientSocket, parseResult.resource);
+ }
+ shouldBreak = true;
+ break;
+ }
+ }
+
+ if (shouldBreak) {
+ break;
+ }
+ }
+
+ // Close the socket
+ closesocket(clientSocket);
+ }
+
+ // Close listening socket
+ closesocket(listeningSocket);
+
+ // Cleanup winsock
+ WSACleanup();
+
+ return EXIT_SUCCESS;
+} \ No newline at end of file