/* * Copyright 2012 Steven Gribble * * This file is part of the UW CSE 333 lecture code (333lec). * * 333lec is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 333lec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 333proj. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "HttpServer.h" extern int errno; HttpServer::HttpServer(std::string portnum) { // Do constructor-y stuff here. Listen(portnum); } void HttpServer::AcceptDispatch(HandleFn handle) { // Loop forever, receiving a client connection and doing // the HTTP parsing on it. while (1) { struct sockaddr_storage caddr; socklen_t caddr_len = sizeof(caddr); struct sockaddr *saddr = reinterpret_cast(&caddr); int client_fd = accept(listen_fd_, saddr, &caddr_len); if (client_fd < 0) { continue; } // Have a client connection. Handle it. std::string client_IP, client_DNS; unsigned short client_port; try { GetIPInfo(client_fd, true, &client_IP, &client_DNS); } catch(const std::exception &exc) { std:: cerr << exc.what() << std::endl; close(client_fd); continue; } if (saddr->sa_family == AF_INET) { client_port = htons((reinterpret_cast(saddr))->sin_port); } else { client_port = htons((reinterpret_cast(saddr))->sin6_port); } // Get the URL. std::string URL; try { URL = ParseURL(client_fd); } catch(const std::exception &exc) { std:: cerr << exc.what() << std::endl; close(client_fd); continue; } // Invoke the handler. handle(client_fd, URL, client_IP, client_port); close(client_fd); } } void HttpServer::Listen(std::string portnum) { // Populate the "hints" addrinfo structure for getaddrinfo(). // ("man addrinfo") struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; // allow IPv4 or IPv6 hints.ai_socktype = SOCK_STREAM; // stream hints.ai_flags = AI_PASSIVE; // use wildcard "INADDR_ANY" hints.ai_protocol = IPPROTO_TCP; // tcp protocol hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; // Use argv[1] as the string representation of our portnumber to // pass in to getaddrinfo(). getaddrinfo() returns a list of // address structures via the output parameter "result". struct addrinfo *result; int res = getaddrinfo(NULL, portnum.c_str(), &hints, &result); // Did addrinfo() fail? if (res != 0) { std::string err = "getaddrinfo() failed with "; err += gai_strerror(res); throw std::runtime_error(err); } // Loop through the returned address structures until we are able to // create a socket and bind to one. The address structures are // linked in a list through the "ai_next" field of result. We will // make sure to break out of the for loop, even if an error exists, // so that we can free the structure returned by getaddrinfo. listen_fd_ = -1; for (struct addrinfo *rp = result; rp != NULL; rp = rp->ai_next) { listen_fd_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (listen_fd_ == -1) { // Creating this socket failed. So, loop to the next returned // result and try again. continue; } // Configure the socket; we're setting a socket "option." In // particular, we set "SO_REUSEADDR", which tells the TCP stack // so make the port we bind to available again as soon as we // exit, rather than waiting for a few tens of seconds to recycle it. int optval = 1; if (setsockopt(listen_fd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) != 0) { close(listen_fd_); listen_fd_ = -1; continue; } // Try binding the socket to the address and port number returned // by getaddrinfo(). if (bind(listen_fd_, rp->ai_addr, rp->ai_addrlen) == 0) { // Bind worked! Get the information about what we bound to. try { GetIPInfo(listen_fd_, false, &server_addr_, &server_dns_); server_port_ = portnum; } catch(const std::exception &exc) { // GetIPInfo failed; loop around. std:: cerr << exc.what() << std::endl; close(listen_fd_); listen_fd_ = -1; continue; } break; } else { // The bind failed. Close the socket, then loop back around and // try the next address/port returned by getaddrinfo(). close(listen_fd_); listen_fd_ = -1; continue; } } // Free the structure returned by getaddrinfo(). freeaddrinfo(result); // If we failed to bind, throw an exception. if (listen_fd_ <= 0) { throw std::runtime_error("Couldn't bind to any addresses."); } // Success. Tell the OS that we want this to be a listening socket. if (listen(listen_fd_, SOMAXCONN) != 0) { close(listen_fd_); listen_fd_ = -1; std::string err = "Failed to mark socket as listening: "; err += strerror(errno); throw std::runtime_error(err); } // Done! } void HttpServer::GetIPInfo(int fd, bool peer, std::string *addr, std::string *dns) const { // Get the socket address information. struct sockaddr_storage fdaddr; socklen_t fdaddrlen = sizeof(fdaddr); if (!peer) { if (getsockname(fd, reinterpret_cast(&fdaddr), &fdaddrlen) != 0) { throw std::runtime_error("getsockname failed"); } } else { if (getpeername(fd, reinterpret_cast(&fdaddr), &fdaddrlen) != 0) { throw std::runtime_error("getsockname failed"); } } // Get the string representation struct sockaddr *saddr = reinterpret_cast(&fdaddr); if (saddr->sa_family == AF_INET) { // Print out the IPV4 address and port char astring[INET_ADDRSTRLEN]; struct sockaddr_in *in4 = reinterpret_cast(saddr); inet_ntop(AF_INET, &(in4->sin_addr), astring, INET_ADDRSTRLEN); *addr = astring; } else if (saddr->sa_family == AF_INET6) { // Print out the IPV6 address and port char astring[INET6_ADDRSTRLEN]; struct sockaddr_in6 *in6 = reinterpret_cast(saddr); inet_ntop(AF_INET, &(in6->sin6_addr), astring, INET6_ADDRSTRLEN); *addr = astring; } else { throw std::runtime_error("unknown IP protocol type"); } // Get the reverse DNS char hostname[1024]; // ought to be big enough. if (getnameinfo(saddr, fdaddrlen, hostname, 1024, NULL, 0, 0) != 0) { throw std::runtime_error("reverse DNS failed"); } *dns = hostname; } std::string HttpServer::ParseURL(int client_fd) { std::string data; // Keep reading from the client until either an error (in which case // throw an exception) or until the request terminator \r\n\r\n is // seen. char buf[1024]; while (1) { ssize_t res = read(client_fd, buf, 1023); if (res == -1) { if ((errno == EAGAIN) || (errno == EINTR)) continue; std::string err = "Couldn't read: "; err += strerror(errno); throw std::runtime_error(err); } // Append whatever we found, test for end of request. buf[res] = '\0'; data += buf; if (data.find("\r\n\r\n") != std::string::npos) break; // Make sure client didn't hang up on us. if (res == 0) { // Hung up, no full request. throw std::runtime_error("Client disconnected prior to full request"); } } // OK, pull out the first line. std::vector strs; boost::split(strs, data, boost::is_any_of("\r\n")); if (strs.size() < 1) { throw std::runtime_error("couldn't parse request"); } // Great, split that first line. std::vector comps; boost::split(comps, strs[0], boost::is_any_of(" \r\n")); if (comps.size() < 3) { throw std::runtime_error("couldn't parse request(2)"); } return comps[1]; }