#include #include #include #include #include #include "stringmanip.h" #pragma comment (lib, "ws2_32.lib") #define WIN32_LEAN_AND_MEAN #define PORT 80 #define NUM_CLIENT_THREADS 8 void handleClient(SOCKET clientSocket); DWORD WINAPI runConnection(LPVOID lpParam); struct ClientConnection { bool isActive = false; SOCKET clientSocket; }; struct ClientConnectionThreadArray { ClientConnection connectionList[NUM_CLIENT_THREADS]; DWORD threadIdArray[NUM_CLIENT_THREADS]; HANDLE threadArray[NUM_CLIENT_THREADS]; void start() { for (int tIdx = 0; tIdx < NUM_CLIENT_THREADS; tIdx++) { threadArray[tIdx] = CreateThread( NULL, 0, runConnection, &connectionList[tIdx], 0, &threadIdArray[tIdx] ); } } void run(SOCKET clientSocket) { bool found = false; while (!found) { for (int connIdx = 0; connIdx < NUM_CLIENT_THREADS; connIdx++) { if (!connectionList[connIdx].isActive) { connectionList[connIdx].clientSocket = clientSocket; connectionList[connIdx].isActive = true; found = true; break; } } if (!found) { Sleep(50); } } } }; 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); ClientConnectionThreadArray threadManager; threadManager.start(); 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)); } threadManager.run(clientSocket); } // Close listening socket closesocket(listeningSocket); // Cleanup winsock WSACleanup(); return EXIT_SUCCESS; } 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; }; 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; } }; 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], bool isBinary) { FILE* file; fopen_s(&file, filepath, isBinary ? "rb" : "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; } 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; } bool trySendFile(SOCKET clientSocket, char* filename) { char filePath[128]; sprintf(filePath, "../../frontend%s", filename); bool isBinary = false; if (endsWith(filename, ".ico")) { isBinary = true; } char source[MAXBUFLEN + 1]; int fileSizeBytes = readFileToMemory(filePath, source, isBinary); 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; } void handleClient(SOCKET clientSocket) { 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); } DWORD WINAPI runConnection(LPVOID lpParam) { ClientConnection* connection = (ClientConnection*)lpParam; while (true) { Sleep(100); if (connection->isActive) { handleClient(connection->clientSocket); connection->isActive = false; } } }