From 398175de761b9c7294e4a59776575229339f6719 Mon Sep 17 00:00:00 2001 From: Ramsey Kant Date: Sat, 12 May 2012 02:16:51 -0400 Subject: [PATCH] Network functionality complete. Initial connection of Resource manager, HTTP request parsing, and response sending. Need to include HTTP1.1 expected headers --- HTTPServer.cpp | 258 ++++++++++++++++++++++++++++---------------- HTTPServer.h | 25 ++--- Makefile | 34 +++++- Resource.cpp | 2 +- ResourceManager.cpp | 27 +++-- ResourceManager.h | 4 +- main.cpp | 25 ++++- 7 files changed, 246 insertions(+), 129 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index d8850dc..1e680a3 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -9,7 +9,7 @@ HTTPServer::HTTPServer() { memset(&serverAddr, 0, sizeof(serverAddr)); // clear the struct keepRunning = false; - // Create a resource manager managing the base path ./ + // Create a resource manager managing the VIRTUAL base path ./ resMgr = new ResourceManager("./", true); // Instance clientMap, relates Socket Descriptor to pointer to Client object @@ -41,7 +41,7 @@ bool HTTPServer::initSocket(int port) { // Create a handle for the listening socket, TCP listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(listenSocket == INVALID_SOCKET) { - printf("Could not create socket!\n"); + cout << "Could not create socket!" << endl; return false; } @@ -52,14 +52,14 @@ bool HTTPServer::initSocket(int port) { // Bind: Assign the address to the socket if(bind(listenSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) { - printf("Failed to bind to the address!\n"); + cout << "Failed to bind to the address!" << endl; return false; } // Listen: Put the socket in a listening state, ready to accept connections // Accept a backlog of the OS Maximum connections in the queue if(listen(listenSocket, SOMAXCONN) != 0) { - printf("Failed to put the socket in a listening state\n"); + cout << "Failed to put the socket in a listening state" << endl; return false; } @@ -93,28 +93,68 @@ void HTTPServer::closeSockets() { listenSocket = INVALID_SOCKET; } +/** + * Accept Connection + * When a new connection is detected in runServer() this function is called. This attempts to accept the pending connection, instance a Client object, and add to the client Map + */ +void HTTPServer::acceptConnection() { + // Setup new client with prelim address info + sockaddr_in clientAddr; + int clientAddrLen = sizeof(clientAddr); + SOCKET clfd = INVALID_SOCKET; + + // Accept the pending connection and retrive the client descriptor + clfd = accept(listenSocket, (sockaddr*)&clientAddr, (socklen_t*)&clientAddrLen); + if(clfd == INVALID_SOCKET) + return; + + // Instance Client object + Client *cl = new Client(clfd, clientAddr); + + // Add to the master FD set + FD_SET(clfd, &fd_master); + + // If the new client's handle is greater than the max, set the new max + if(clfd > fdmax) + fdmax = clfd; + + // Add the client object to the client map + clientMap->insert(std::pair(clfd, cl)); + + // Print the client's IP on connect + cout << "[" << cl->getClientIP() << "] connected" << endl; +} + +/** + * Run Server + * Main server loop where the socket is initialized and the loop is started, checking for new messages or clients to be read with select() + * and handling them appropriately + */ void HTTPServer::runServer(int port) { // Initialize the socket and put it into a listening state if(!initSocket(port)) { - printf("Failed to start server.\n"); + cout << "Failed to start server." << endl; return; } + + cout << "Server started. Listening on port " << port << "..." << endl; // Processing loop while(keepRunning) { + usleep(1000); + // Copy master set into fd_read for processing fd_read = fd_master; // Populate read_fd set with client descriptors that are ready to be read - int selret = select(fdmax+1, &fd_read, NULL, NULL, NULL); - if(selret < 0) { + if(select(fdmax+1, &fd_read, NULL, NULL, NULL) < 0) { //printf("select failed!"); continue; } // Loop through all descriptors in the read_fd set and check to see if data needs to be processed for(int i = 0; i <= fdmax; i++) { - // If i isn't within the set of descriptors to be read, skip it + // Socket i isn't ready to be read (not in the read set), continue if(!FD_ISSET(i, &fd_read)) continue; @@ -132,38 +172,6 @@ void HTTPServer::runServer(int port) { closeSockets(); } -/** - * Accept Connection - * When a new connection is detected in runServer() this function is called. This attempts to accept the pending connection, instance a Client object, and add to the client Map - */ -void HTTPServer::acceptConnection() { - // Setup new client with prelim address info - sockaddr_in clientAddr; - int clientAddrLen = sizeof(clientAddr); - SOCKET clfd = INVALID_SOCKET; - - // Accept the pending connection and retrive the client descriptor - clfd = accept(listenSocket, (sockaddr*)&clientAddr, (socklen_t*)&clientAddrLen); - if(clfd == INVALID_SOCKET) - return; - - // Instance Client object - Client *cl = new Client(clfd, clientAddr); - - // Add to the master FD set - FD_SET(clfd, &fd_master); - - // If the new client's handle is greater than the max, set the new max - if(clfd > fdmax) - fdmax = clfd; - - // Add the client object to the client map - clientMap->insert(std::pair(clfd, cl)); - - // Print the client's IP on connect - printf("%s has connected\n", cl->getClientIP()); -} - /** * Get Client * Lookup client based on the socket descriptor number in the clientMap @@ -208,12 +216,20 @@ void HTTPServer::disconnectClient(Client *cl) { // http://www.yolinux.com/TUTORIALS/Sockets.html#TIPS +/** + * Handle Client + * Recieve data from a client that has indicated (via select()) that it has data waiting. Pass recv'd data to handleRequest() + * Also detect any errors in the state of the socket + * + * @param cl Pointer to Client that sent the data + */ void HTTPServer::handleClient(Client *cl) { if (cl == NULL) return; + HTTPRequest* req; size_t dataLen = 1300; - char *pData = new char[dataLen]; + char* pData = new char[dataLen]; // Receive data on the wire into pData /* TODO: Figure out what flags need to be set */ @@ -223,56 +239,45 @@ void HTTPServer::handleClient(Client *cl) { // Determine state of the client socket and act on it if(lenRecv == 0) { // Client closed the connection - printf("Client[%s] has opted to close the connection\n", cl->getClientIP()); + cout << "[" << cl->getClientIP() << "] has opted to close the connection" << endl; disconnectClient(cl); } else if(lenRecv < 0) { // Something went wrong with the connection // TODO: check perror() for the specific error message disconnectClient(cl); } else { - // Print the data the client sent (in ascii) - printf("%s: \n", cl->getClientIP()); - ascii_print(pData, (int)lenRecv); + // Data received + cout << "[" << cl->getClientIP() << "] " << lenRecv << " bytes received" << endl; - // Add the packet data to a string and pass to processRequest to serve the request - string r; - r.append(pData); - handleRequest(cl, r); + // Place the data in an HTTPRequest and pass it to handleRequest for processing + req = new HTTPRequest((byte*)pData, lenRecv); + handleRequest(cl, req); + delete req; } delete [] pData; } -void HTTPServer::sendResponse(Client *cl, HTTPResponse *res) { - size_t dataLen = 0; - char *sData = NULL; - std::string strResp; - - // Get the string response, allocate memory for the response - strResp = res->generateResponse(); - dataLen = strlen(strResp.c_str()); - sData = new char[dataLen]; - - // Send the data over the wire - send(cl->getSocket(), sData, dataLen, 0); - - // Delete the allocated space for the response - if(sData != NULL) - delete sData; -} - -void HTTPServer::handleRequest(Client *cl, string requestStr) { - // Create an HTTPRequest object to consume the requestStr - HTTPRequest *req = new HTTPRequest(requestStr); - if(req == NULL) - return; - - HTTPResponse *res = NULL; - - // If there was a parse error, report it and send the appropriate error in response - if(req->hasParseError()) { - printf("[%s] There was an error processing the request of type: %i\n", cl->getClientIP(), req->getMethod()); - return; +/** + * Handle Request + * Process an incoming request from a Client. Send request off to appropriate handler function + * that corresponds to an HTTP operation (GET, HEAD etc) :) + * + * @param cl Client object where request originated from + * @param req HTTPRequest object filled with raw packet data + */ +void HTTPServer::handleRequest(Client *cl, HTTPRequest* req) { + HTTPResponse* res = NULL; + bool dcClient = false; + + // Parse the request + // If there's an error, report it and send the appropriate error in response + if(!req->parse()) { + cout << "[" << cl->getClientIP() << "] There was an error processing the request of type: " << req->methodIntToStr(req->getMethod()) << endl; + cout << req->getParseError() << endl; + // TODO: Send appropriate HTTP error message + disconnectClient(cl); + return; } // Send the request to the correct handler function @@ -284,26 +289,97 @@ void HTTPServer::handleRequest(Client *cl, string requestStr) { res = handleGet(cl, req); break; default: - printf("[%s] Could not handle or determine request of type: %i\n", cl->getClientIP(), req->getMethod()); + cout << cl->getClientIP() << ": Could not handle or determine request of type " << req->methodIntToStr(req->getMethod()) << endl; break; } - - // Send the built response to the client - if(res != NULL) - sendResponse(cl, res); - - // Free memory consumed by req and response object - delete req; - if(res != NULL) - delete res; + + // If a response could not be built, send a 500 (internal server error) + if(res == NULL) { + res = new HTTPResponse(); + res->setStatus(Status(SERVER_ERROR)); + std::string body = res->getStatusStr(); + res->setData((byte*)body.c_str(), body.size()); + dcClient = true; + } + + // Send the built response to the client + sendResponse(cl, res, dcClient); + + delete res; } +/** + * Handle Get + * Process a GET request to provide the client with an appropriate response + * + * @param cl Client requesting the resource + * @param req State of the request + */ HTTPResponse* HTTPServer::handleGet(Client *cl, HTTPRequest *req) { - - return NULL; + HTTPResponse* res = NULL; + + // Check if the requested resource exists + std::string uri = req->getRequestUri(); + Resource* r = resMgr->getResource(uri); + if(r != NULL) { // Exists + + } else { // Not found + res = new HTTPResponse(); + res->setStatus(Status(NOT_FOUND)); + std::string body = res->getStatusStr(); + res->setData((byte*)body.c_str(), body.size()); + } + + return res; } +/** + * Handle Head + * Process a HEAD request to provide the client with an appropriate response + * + * @param cl Client requesting the resource + * @param req State of the request + */ HTTPResponse* HTTPServer::handleHead(Client *cl, HTTPRequest *req) { + HTTPResponse* res = NULL; + return NULL; } +/** + * Send Response + * Send HTTPResponse packet data to a particular Client + * + * @param cl Client to send data to + * @param buf ByteBuffer containing data to be sent + * @param disconnect Should the server disconnect the client after sending (Optional, default = false) + */ +void HTTPServer::sendResponse(Client* cl, HTTPResponse* res, bool disconnect) { + // Get raw data by creating the response (pData will be cleaned up by the response obj) + byte* pData = res->create(); + + // Retrieve sizes + size_t totalSent = 0, bytesLeft = res->size(), dataLen = res->size(); + ssize_t n = 0; + + // Solution to deal with partials sends...loop till totalSent matches dataLen + while(totalSent < dataLen) { + n = send(cl->getSocket(), pData+totalSent, bytesLeft, 0); + + // Client closed the connection + if(n < 0) { + cout << "[" << cl->getClientIP() << "] has disconnected." << endl; + disconnectClient(cl); + break; + } + + // Adjust byte count after a successful send + totalSent += n; + bytesLeft -= n; + } + + if(disconnect) + disconnectClient(cl); +} + + diff --git a/HTTPServer.h b/HTTPServer.h index 376e90a..dd1194c 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -35,31 +35,24 @@ class HTTPServer { bool initSocket(int port = 80); void closeSockets(); - // Debug - void ascii_print(char* data, int length) - { - for(int i = 0; i (); + memoryFileMap = new std::map(); refreshMemoryFS(); } } @@ -51,7 +51,7 @@ void ResourceManager::resetMemoryFS() { return; // Cleanup all Resource objects - map::const_iterator it; + std::map::const_iterator it; for(it = memoryFileMap->begin(); it != memoryFileMap->end(); ++it) { delete it->second; } @@ -71,8 +71,7 @@ void ResourceManager::refreshMemoryFS() { resetMemoryFS(); // Debug, load test resources - if(DEBUG) - loadTestMemory(); + loadTestMemory(); // Load all files from the disk FS: } @@ -82,8 +81,8 @@ void ResourceManager::loadTestMemory() { Resource *res1 = new Resource("/hey/test2.mres", "", true); Resource *res2 = new Resource("/hey/dir/blank.mres", "", true); - memoryFileMap->insert(pair(res1->getRelativeLocation(), res1)); - memoryFileMap->insert(pair(res2->getRelativeLocation(), res2)); + memoryFileMap->insert(std::pair(res1->getRelativeLocation(), res1)); + memoryFileMap->insert(std::pair(res2->getRelativeLocation(), res2)); } /** @@ -92,7 +91,7 @@ void ResourceManager::loadTestMemory() { * @param dirPath Relative directory path * @return String representation of the directory. Blank string if invalid directory */ -string ResourceManager::listDirectory(std::string dirPath) { +std::string ResourceManager::listDirectory(std::string dirPath) { std::string ret = ""; std::string tempPath; Resource *tempRec; @@ -105,12 +104,12 @@ string ResourceManager::listDirectory(std::string dirPath) { // Memory FS: // Build a list of all resources that have a URI that begins with dirPath if(memoryOnly) { - std::map::const_iterator it; + std::map::const_iterator it; for(it = memoryFileMap->begin(); it != memoryFileMap->end(); ++it) { tempPath = it->first; found = tempPath.find(dirPath); // Resource's URI falls within the directory search path (dirPath) - if(found != string::npos) { + if(found != std::string::npos) { // TODO: get properties of the in memory resource and provide them as part of the listing tempRec = it->second; @@ -155,28 +154,28 @@ Resource* ResourceManager::getResource(std::string uri) { // If using memory file system: if(memoryOnly) { - std::map::const_iterator it; + std::map::const_iterator it; it = memoryFileMap->find(uri); // If it isn't the element past the end (end()), then a resource was found if(it != memoryFileMap->end()) { res = it->second; } } else { // Otherwise check the disk - ifstream file; + std::ifstream file; long long len = 0; // Open file for reading as binary uri = diskBasePath + uri; - file.open(uri.c_str(), ios::binary); + file.open(uri.c_str(), std::ios::binary); // Return null if failed if(!file.is_open()) return NULL; // Get the length of the file - file.seekg(0, ios::end); + file.seekg(0, std::ios::end); len = file.tellg(); - file.seekg(0, ios::beg); + file.seekg(0, std::ios::beg); // Allocate memory for contents of file and read in the contents char *fdata = new char[len]; diff --git a/ResourceManager.h b/ResourceManager.h index ae0c3b9..40ad9cc 100644 --- a/ResourceManager.h +++ b/ResourceManager.h @@ -1,6 +1,8 @@ #ifndef _RESOURCEMANAGER_H_ #define _RESOURCEMANAGER_H_ +#include +#include #include #include @@ -36,7 +38,7 @@ class ResourceManager { void loadTestMemory(); // Provide a string rep of the directory listing - string listDirectory(std::string dirPath); + std::string listDirectory(std::string dirPath); // Load a file from disk or memory and return it's representation as a Resource object Resource* getResource(std::string uri); diff --git a/main.cpp b/main.cpp index 079fc21..f4cba3d 100644 --- a/main.cpp +++ b/main.cpp @@ -1,21 +1,40 @@ #include #include +#include "HTTPServer.h" #include "Testers.h" void handleSigPipe(int snum); +void handleSigQuit(int snum); + +HTTPServer* svr; // Ignore signals with this function void handleSigPipe(int snum) { return; } +void handleSigQuit(int snum) { + if(svr != NULL) + svr->stopServer(); +} + int main (int argc, const char * argv[]) { // Ignore SIGPIPE "Broken pipe" signals when socket connections are broken. - signal(SIGPIPE, handleSigpipe); - - testAll(); + signal(SIGPIPE, handleSigPipe); + + // Register termination signals to stop the server + signal(SIGABRT, &handleSigQuit); + signal(SIGINT, &handleSigQuit); + signal(SIGTERM, &handleSigQuit); + + // Instance and start the server + svr = new HTTPServer(); + svr->runServer(8080); + delete svr; + + //testAll(); return 0; }