// Server for Project 1, phase 2 of EE 122 Fall 2007. // // Written by Daniel Killebrew. // // The server // (1) parses an HTTP 1.0 request into method, URI and version, // (2) checks that the file exists and we have permission to read it // (3) sends the file to the client #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Client.h" using namespace std; bool verbose = false; // How big each chunk should be. unsigned int chunkSize = 0; // Introduce a type name to make our code clearer/less verbose. typedef vector::iterator vecIterator; // Attempts to send sendAmt of data from the given buffer. // Returns 1 on success of sending everything, 0 if an error occurs // (including the other side closing the socket). int sendAll(int sock, const char *buf, int sendAmt) { while (sendAmt > 0) { int justSent = send(sock, buf, sendAmt, 0); if (justSent < 0) return 0; sendAmt -= justSent; } return 1; } // Creates the server socket and sets it up for listening, returning its // descriptor. On error, generates a message and exits. int createServerSocket(unsigned short port) { if (port <= 0) { printf("server requires a port number > 0\n"); exit(1); } int listenSock = socket(PF_INET, SOCK_STREAM, 0); if (listenSock < 0) { perror("ERROR -- Incommunicable Error: couldn't create socket"); exit( -1); } // Reuse port when binding. This enables us to re-run the server // soon after it has previously exited and still use the same port // as before. If we don't specify this, then TCP in the kernel // will prevent us from reusing the port for a while, as a way // to make sure that no data for old connections that is still // somewhere in the network is incorrectly accepted by any new // connections. char yes = 1; if (setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("ERROR -- Incommunicable Error: couldn't set SO_REUSEADDR"); exit( -1); } // Set up what port we are binding to. struct sockaddr_in listenAddr; memset(&listenAddr, 0, sizeof(listenAddr)); listenAddr.sin_family = AF_INET; // a single byte, so no ordering issues listenAddr.sin_port = htons(port); // needs to be in network order listenAddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(listenSock, (sockaddr*) &listenAddr, sizeof(listenAddr)) < 0) { perror("ERROR -- Incommunicable Error: couldn't bind()"); exit( -1); } // Now that we're bound the socket to the port we care about, // tell the kernel that we're willing to accept new connections // on that port. We give it a "backlog" parameter of 32 - that's // how many new connections can be pending on it (i.e., they arrive // but we haven't yet accept()'d them) before the kernel will tell // new remote hosts attempting to connect "sorry, service not // available". if (listen(listenSock, 32) < 0) { perror("ERROR -- Incommunicable Error: couldn't listen()"); exit( -1); } return listenSock; } void acceptNewConnection(vector &activeSockets, int listenSock) { // The following socket address (which accept() will fill in) isn't // strictly necessary. We can instead pass in a nil pointer and // empty size. However, this isn't so clear from the accept() // manual page, so here we go ahead and set up a variable to receive // the socket address. struct sockaddr_in clientAddr; int size = sizeof(clientAddr); memset(&clientAddr, 0, size); int newSock; newSock = accept(listenSock, (sockaddr*) &clientAddr, (socklen_t*) &size); if (newSock < 0) { perror("ERROR -- Incommunicable Error: bad status from accept()"); exit( -1); } if (verbose) printf("Connection created, socket %d\n", newSock); // Set the socket to nonblocking sending mode so that we can send to // multiple clients at once. To do so, we first gather the current // control flags associated with the socket: int flags = fcntl(newSock, F_GETFL); // then add in the nonblocking flag ... flags |= O_NONBLOCK; // and update the flags to their new setting: fcntl(newSock, F_SETFL, flags); // Add the new socket to the vector of active sockets. activeSockets.push_back(Client(newSock)); // Ensure that the vector is ordered, so we can always find // the largest descriptor value quickly. sort(activeSockets.begin(), activeSockets.end()); } // Match a string against a extended regular expression, treating errors // as no match. // // Returns true for match, false for no match. bool match(const char *string, const char *pattern) { regex_t re; if (regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB) != 0) return false; int status = regexec(&re, string, (size_t) 0, NULL, 0); regfree(&re); return status == 0; } // Grab everything that's in the socket buffer. Place everything up to and // including the first CRLF/CRLF in the completed request buffer. // Leave data in incompleteBuffer until the blank line arrives. // // Returns: // 0 for socket closed // -1 for socket error // 1 for blank line hasn't arrived yet // >1 for the full request has arrived int consumeRequest(Client &client) { const int bufSize = 128; char buf[bufSize]; int amtRecvd = recv(client.socket, buf, bufSize - 1, 0); if (amtRecvd == 0) { if (verbose) printf("socket %d closed by client\n", client.socket); return 0; } else if (amtRecvd < 0) { perror("ERROR -- Incommunicable Error: bad status from recv()"); return -1; } // NUL-terminate the buffer. We left room for doing so by // passing bufSize - 1 to recv() rather than bufsize. buf[amtRecvd] = '\0'; // Append all bytes received. client.incompleteBuffer.append(buf, amtRecvd); // Search incomplete buffer for the blank line. string::size_type blankLineIndex = client.incompleteBuffer.find("\r\n\r\n"); if (blankLineIndex == string::npos) // Return 1 to signal blank line hasn't yet arrived. return 1; // Grab everything up to the blank line, put into complete buffer. // The constant 4 here comes from the length of CRLF/CRLF. client.completeRequest.assign(client.incompleteBuffer, 0, blankLineIndex + 4); // Remove the stuff we just grabbed from incompleteBuffer. client.incompleteBuffer.erase(0, blankLineIndex + 4); return client.completeRequest.size(); } // Process one instance of input from the client associated with the // given socket. // // Returns 1 if the socket should be closed/removed, 0 otherwise. int processClientInput(Client &client) { int bytes = consumeRequest(client); if ( bytes <= 0 ) // Closed, or an error. return 1; if ( bytes == 1 ) // Blank line has not arrived yet, need to wait for more. return 0; // Echo the line. printf("%s", client.completeRequest.c_str()); if (!client.processRequest()) return 1; if (!client.sending && !client.persistent) return 1; return 0; } // Process one set of new activity, which means arrivals of connections // from new client, and/or activity on existing connections. // // Returns 0 if the activity indicates the server should exit, non-zero // othewise. (Currently this is always non-zero.) int processNextActivity(vector &activeSockets, int listenSock) { // First we set up the set of descriptors we want to inspect // for new activity via select(). fd_set readset, writeset; FD_ZERO(&readset); FD_ZERO(&writeset); // Set readset or writeset for all sockets. for (unsigned int s = 0; s < activeSockets.size(); s++) { if (activeSockets[s].sending) FD_SET(activeSockets[s].socket, &writeset); else FD_SET(activeSockets[s].socket, &readset); } // Make a blocking call to select(). Here we take advantage of // the fact that activeSockets is ordered, so the highest-numbered // descriptor in it will always be the last. int highestFD = activeSockets.rbegin()->socket; int numReadySocks = select(highestFD + 1, &readset, &writeset, NULL, NULL); if (numReadySocks == -1) { perror("ERROR -- Incommunicable Error: bad status from select()"); exit( -1); } // Check all active sockets for activity, as long as we know // we have more to process. We do this using an "iterator" // that knows how to move through the vector; these are provided // by the STL. for (vecIterator i = activeSockets.begin(); i != activeSockets.end() && numReadySocks > 0; /*i is incremented within the for loop*/) { if (FD_ISSET(i->socket, &readset)) { // The socket is exhibiting activity. Remove it // from readset and the count of pending activity, // since we're about to process it. FD_CLR(i->socket, &readset); numReadySocks--; // since we're about to process it if (i->socket == listenSock) { acceptNewConnection(activeSockets, listenSock); // Note, we've now modified activeSockets, // so the vector has been rearranged. // Consequently, i is invalidated and we // need to be careful to start the iteration // over. i = activeSockets.begin(); } else { if (processClientInput(*i)) { // Done with the socket. int ret = close(i->socket); assert(ret==0); if (verbose) cout << "Closing socket " << i->socket << endl; // Remove this socket from vector, // assign iterator to next element // after the one that got deleted. i = activeSockets.erase(i); } else // move on to the next socket i++; } } else if (FD_ISSET(i->socket, &writeset)) { // The socket is exhibiting activity. Remove it // from writeset and the count of pending activity, // since we're about to process it. FD_CLR(i->socket, &writeset); numReadySocks--; // since we're about to process it if (i->sendFilePart()) { // Done with the socket. int ret = close(i->socket); assert(ret==0); if (verbose) cout << "Closing socket " << i->socket << endl; // Remove this socket from vector, // assign iterator to next element // after the one that got deleted. i = activeSockets.erase(i); } else i++; } else // move to next socket i++; } // Some sanity checking to make sure we consistently iterated // through all of the sockets with activity. assert(numReadySocks == 0); return 1; } int main(int argc, char **argv) { int option; while ((option=getopt(argc, argv, "vc:")) != -1) { if (option=='v') verbose=true; else if (option=='c') chunkSize = atoi(optarg); } if (argc < 2) { printf("usage is http_server [-v] [-c chunkSize] port\n"); exit(0); } else if (optind >= argc) { printf("please supply a port to listen on! e.g. http_server 80\n"); exit(0); } unsigned short listenPort = atoi(argv[optind]); int listenSock = createServerSocket(listenPort); if (verbose) printf("Listening on port %d\n", listenPort); // We need to track all of our active sockets in order to // handle multiple clients concurrently. We do so using a // vector sorted by the sockets' descriptors. This lets us // easily find the highest-numbered descriptor, which we'll // need when calling select(). vector activeSockets; activeSockets.push_back(Client(listenSock)); // Enter main server loop: accept new connections and process // any activity seen on existing connections. while (true) if ( !processNextActivity(activeSockets, listenSock) ) break; close(listenSock); return 0; }