#include #include #include #include #include #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; }