/*
* 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];
}