///////////////////////////////////////////////////////////////////////// // MsgServer.cpp - Demonstrates simple one-way HTTP style messaging // // and file transfer // // // // Jim Fawcett, CSE687 - Object Oriented Design, Spring 2016 // // Application: OOD Project #4 // // Platform: Visual Studio 2015, Dell XPS 8900, Windows 10 pro // ///////////////////////////////////////////////////////////////////////// /* * This package implements a server that receives HTTP style messages and * files from multiple concurrent clients and simply displays the messages * and stores files. * * It's purpose is to provide a very simple illustration of how to use * the Socket Package provided for Project #4. */ /* * Required Files: * MsgClient.cpp, MsgServer.cpp * HttpMessage.h, HttpMessage.cpp * Cpp11-BlockingQueue.h * Sockets.h, Sockets.cpp * FileSystem.h, FileSystem.cpp * Logger.h, Logger.cpp * Utilities.h, Utilities.cpp */ /* * ToDo: * - pull the receiving code into a Receiver class * - Receiver should own a BlockingQueue, exposed through a * public method: * HttpMessage msg = Receiver.GetMessage() * - You will start the Receiver instance like this: * Receiver rcvr("localhost:8080"); * ClientHandler ch; * rcvr.start(ch); */ #include "../HttpMessage/HttpMessage.h" #include "../Sockets/Sockets.h" #include "../FileSystem/FileSystem.h" #include "../Logger/Cpp11-BlockingQueue.h" #include "../Logger/Logger.h" #include "../Utilities/Utilities.h" #include #include using Show = StaticLogger<1>; using namespace Utilities; ///////////////////////////////////////////////////////////////////// // ClientHandler class ///////////////////////////////////////////////////////////////////// // - instances of this class are passed by reference to a SocketListener // - when the listener returns from Accept with a socket it creates an // instance of this class to manage communication with the client. // - You no longer need to be careful using data members of this class // because each client handler thread gets its own copy of this // instance so you won't get unwanted sharing. // - I changed the SocketListener semantics to pass // instances of this class by value for version 5.2. // - that means that all ClientHandlers need copy semantics. // class ClientHandler { public: ClientHandler(BlockingQueue& msgQ) : msgQ_(msgQ) {} void operator()(Socket socket); private: bool connectionClosed_; HttpMessage readMessage(Socket& socket); bool readFile(const std::string& filename, size_t fileSize, Socket& socket); BlockingQueue& msgQ_; }; //----< this defines processing to frame messages >------------------ HttpMessage ClientHandler::readMessage(Socket& socket) { connectionClosed_ = false; HttpMessage msg; // read message attributes while (true) { std::string attribString = socket.recvString('\n'); if (attribString.size() > 1) { HttpMessage::Attribute attrib = HttpMessage::parseAttribute(attribString); msg.addAttribute(attrib); } else { break; } } // If client is done, connection breaks and recvString returns empty string if (msg.attributes().size() == 0) { connectionClosed_ = true; return msg; } // read body if POST - all messages in this demo are POSTs if (msg.attributes()[0].first == "POST") { // is this a file message? std::string filename = msg.findValue("file"); if (filename != "") { size_t contentSize; std::string sizeString = msg.findValue("content-length"); if (sizeString != "") contentSize = Converter::toValue(sizeString); else return msg; readFile(filename, contentSize, socket); } if (filename != "") { // construct message body msg.removeAttribute("content-length"); std::string bodyString = "" + filename + ""; std::string sizeString = Converter::toString(bodyString.size()); msg.addAttribute(HttpMessage::Attribute("content-length", sizeString)); msg.addBody(bodyString); } else { // read message body size_t numBytes = 0; size_t pos = msg.findAttribute("content-length"); if (pos < msg.attributes().size()) { numBytes = Converter::toValue(msg.attributes()[pos].second); Socket::byte* buffer = new Socket::byte[numBytes + 1]; socket.recv(numBytes, buffer); buffer[numBytes] = '\0'; std::string msgBody(buffer); msg.addBody(msgBody); delete[] buffer; } } } return msg; } //----< read a binary file from socket and save >-------------------- /* * This function expects the sender to have already send a file message, * and when this function is running, continuosly send bytes until * fileSize bytes have been sent. */ bool ClientHandler::readFile(const std::string& filename, size_t fileSize, Socket& socket) { std::string fqname = "../TestFiles/" + filename + ".snt"; FileSystem::File file(fqname); file.open(FileSystem::File::out, FileSystem::File::binary); if (!file.isGood()) { /* * This error handling is incomplete. The client will continue * to send bytes, but if the file can't be opened, then the server * doesn't gracefully collect and dump them as it should. That's * an exercise left for students. */ Show::write("\n\n can't open file " + fqname); return false; } const size_t BlockSize = 2048; Socket::byte buffer[BlockSize]; size_t bytesToRead; while (true) { if (fileSize > BlockSize) bytesToRead = BlockSize; else bytesToRead = fileSize; socket.recv(bytesToRead, buffer); FileSystem::Block blk; for (size_t i = 0; i < bytesToRead; ++i) blk.push_back(buffer[i]); file.putBlock(blk); if (fileSize < BlockSize) break; fileSize -= BlockSize; } file.close(); return true; } //----< receiver functionality is defined by this function >--------- void ClientHandler::operator()(Socket socket) { while (true) { HttpMessage msg = readMessage(socket); if (connectionClosed_ || msg.bodyString() == "quit") { Show::write("\n\n clienthandler thread is terminating"); break; } msgQ_.enQ(msg); } } //----< test stub >-------------------------------------------------- int main() { ::SetConsoleTitle(L"HttpMessage Server - Runs Forever"); Show::attach(&std::cout); Show::start(); Show::title("\n HttpMessage Server started"); BlockingQueue msgQ; try { SocketSystem ss; SocketListener sl(8080, Socket::IP6); ClientHandler cp(msgQ); sl.start(cp); /* * Since this is a server the loop below never terminates. * We could easily change that by sending a distinguished * message for shutdown. */ while (true) { HttpMessage msg = msgQ.deQ(); Show::write("\n\n server recvd message with body contents:\n" + msg.bodyString()); } } catch (std::exception& exc) { Show::write("\n Exeception caught: "); std::string exMsg = "\n " + std::string(exc.what()) + "\n\n"; Show::write(exMsg); } }