diff options
Diffstat (limited to 'backend/server.cpp')
| -rw-r--r-- | backend/server.cpp | 360 |
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 |
