Эх сурвалжийг харах

add boost asio echo, http_client, http_server example

tanghai 15 жил өмнө
parent
commit
31987d9822

+ 137 - 0
src/experimental/echo/async_tcp_echo_server.cc

@@ -0,0 +1,137 @@
+//
+// async_tcp_echo_server.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <cstdlib>
+#include <iostream>
+#include <boost/bind.hpp>
+#include <boost/asio.hpp>
+
+using boost::asio::ip::tcp;
+
+class session
+{
+public:
+	session(boost::asio::io_service& io_service) :
+		socket_(io_service)
+	{
+	}
+
+	tcp::socket& socket()
+	{
+		return socket_;
+	}
+
+	void start()
+	{
+		socket_.async_read_some(boost::asio::buffer(data_, max_length),
+		        boost::bind(&session::handle_read, this,
+		                boost::asio::placeholders::error,
+		                boost::asio::placeholders::bytes_transferred));
+	}
+
+	void handle_read(const boost::system::error_code& error,
+	        size_t bytes_transferred)
+	{
+		if(!error)
+		{
+			boost::asio::async_write(socket_, boost::asio::buffer(data_,
+			        bytes_transferred), boost::bind(&session::handle_write,
+			        this, boost::asio::placeholders::error));
+		}
+		else
+		{
+			delete this;
+		}
+	}
+
+	void handle_write(const boost::system::error_code& error)
+	{
+		if(!error)
+		{
+			socket_.async_read_some(boost::asio::buffer(data_, max_length),
+			        boost::bind(&session::handle_read, this,
+			                boost::asio::placeholders::error,
+			                boost::asio::placeholders::bytes_transferred));
+		}
+		else
+		{
+			delete this;
+		}
+	}
+
+private:
+	tcp::socket socket_;
+	enum
+	{
+		max_length = 1024
+	};
+	char data_[max_length];
+};
+
+class server
+{
+public:
+	server(boost::asio::io_service& io_service, short port) :
+		io_service_(io_service), acceptor_(io_service, tcp::endpoint(tcp::v4(),
+		        port))
+	{
+		session* new_session = new session(io_service_);
+		acceptor_.async_accept(new_session->socket(), boost::bind(
+		        &server::handle_accept, this, new_session,
+		        boost::asio::placeholders::error));
+	}
+
+	void handle_accept(session* new_session,
+	        const boost::system::error_code& error)
+	{
+		if(!error)
+		{
+			new_session->start();
+			new_session = new session(io_service_);
+			acceptor_.async_accept(new_session->socket(), boost::bind(
+			        &server::handle_accept, this, new_session,
+			        boost::asio::placeholders::error));
+		}
+		else
+		{
+			delete new_session;
+		}
+	}
+
+private:
+	boost::asio::io_service& io_service_;
+	tcp::acceptor acceptor_;
+};
+
+int main(int argc, char* argv[])
+{
+	try
+	{
+		if(argc != 2)
+		{
+			std::cerr << "Usage: async_tcp_echo_server <port>\n";
+			return 1;
+		}
+
+		boost::asio::io_service io_service;
+
+		using namespace std;
+		// For atoi.
+		server s(io_service, atoi(argv[1]));
+
+		io_service.run();
+	}
+	catch(std::exception& e)
+	{
+		std::cerr << "Exception: " << e.what() << "\n";
+	}
+
+	return 0;
+}

+ 93 - 0
src/experimental/echo/async_udp_echo_server.cc

@@ -0,0 +1,93 @@
+//
+// async_udp_echo_server.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <cstdlib>
+#include <iostream>
+#include <boost/bind.hpp>
+#include <boost/asio.hpp>
+
+using boost::asio::ip::udp;
+
+class server
+{
+public:
+	server(boost::asio::io_service& io_service, short port) :
+		io_service_(io_service), socket_(io_service, udp::endpoint(udp::v4(),
+		        port))
+	{
+		socket_.async_receive_from(boost::asio::buffer(data_, max_length),
+		        sender_endpoint_, boost::bind(&server::handle_receive_from,
+		                this, boost::asio::placeholders::error,
+		                boost::asio::placeholders::bytes_transferred));
+	}
+
+	void handle_receive_from(const boost::system::error_code& error,
+	        size_t bytes_recvd)
+	{
+		if(!error && bytes_recvd > 0)
+		{
+			socket_.async_send_to(boost::asio::buffer(data_, bytes_recvd),
+			        sender_endpoint_, boost::bind(&server::handle_send_to,
+			                this, boost::asio::placeholders::error,
+			                boost::asio::placeholders::bytes_transferred));
+		}
+		else
+		{
+			socket_.async_receive_from(boost::asio::buffer(data_, max_length),
+			        sender_endpoint_, boost::bind(&server::handle_receive_from,
+			                this, boost::asio::placeholders::error,
+			                boost::asio::placeholders::bytes_transferred));
+		}
+	}
+
+	void handle_send_to(const boost::system::error_code& /*error*/, size_t /*bytes_sent*/)
+	{
+		socket_.async_receive_from(boost::asio::buffer(data_, max_length),
+		        sender_endpoint_, boost::bind(&server::handle_receive_from,
+		                this, boost::asio::placeholders::error,
+		                boost::asio::placeholders::bytes_transferred));
+	}
+
+private:
+	boost::asio::io_service& io_service_;
+	udp::socket socket_;
+	udp::endpoint sender_endpoint_;
+	enum
+	{
+		max_length = 1024
+	};
+	char data_[max_length];
+};
+
+int main(int argc, char* argv[])
+{
+	try
+	{
+		if(argc != 2)
+		{
+			std::cerr << "Usage: async_udp_echo_server <port>\n";
+			return 1;
+		}
+
+		boost::asio::io_service io_service;
+
+		using namespace std;
+		// For atoi.
+		server s(io_service, atoi(argv[1]));
+
+		io_service.run();
+	}
+	catch(std::exception& e)
+	{
+		std::cerr << "Exception: " << e.what() << "\n";
+	}
+
+	return 0;
+}

+ 63 - 0
src/experimental/echo/blocking_tcp_echo_client.cc

@@ -0,0 +1,63 @@
+//
+// blocking_tcp_echo_client.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <boost/asio.hpp>
+
+using boost::asio::ip::tcp;
+
+enum
+{
+	max_length = 1024
+};
+
+int main(int argc, char* argv[])
+{
+	try
+	{
+		if(argc != 3)
+		{
+			std::cerr << "Usage: blocking_tcp_echo_client <host> <port>\n";
+			return 1;
+		}
+
+		boost::asio::io_service io_service;
+
+		tcp::resolver resolver(io_service);
+		tcp::resolver::query query(tcp::v4(), argv[1], argv[2]);
+		tcp::resolver::iterator iterator = resolver.resolve(query);
+
+		tcp::socket s(io_service);
+		s.connect(*iterator);
+
+		using namespace std;
+		// For strlen.
+		std::cout << "Enter message: ";
+		char request[max_length];
+		std::cin.getline(request, max_length);
+		size_t request_length = strlen(request);
+		boost::asio::write(s, boost::asio::buffer(request, request_length));
+
+		char reply[max_length];
+		size_t reply_length = boost::asio::read(s, boost::asio::buffer(reply,
+		        request_length));
+		std::cout << "Reply is: ";
+		std::cout.write(reply, reply_length);
+		std::cout << "\n";
+	}
+	catch(std::exception& e)
+	{
+		std::cerr << "Exception: " << e.what() << "\n";
+	}
+
+	return 0;
+}

+ 81 - 0
src/experimental/echo/blocking_tcp_echo_server.cc

@@ -0,0 +1,81 @@
+//
+// blocking_tcp_echo_server.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <cstdlib>
+#include <iostream>
+#include <boost/bind.hpp>
+#include <boost/smart_ptr.hpp>
+#include <boost/asio.hpp>
+#include <boost/thread.hpp>
+
+using boost::asio::ip::tcp;
+
+const int max_length = 1024;
+
+typedef boost::shared_ptr<tcp::socket> socket_ptr;
+
+void session(socket_ptr sock)
+{
+	try
+	{
+		for(;;)
+		{
+			char data[max_length];
+
+			boost::system::error_code error;
+			size_t length = sock->read_some(boost::asio::buffer(data), error);
+			if(error == boost::asio::error::eof)
+				break; // Connection closed cleanly by peer.
+			else if(error)
+				throw boost::system::system_error(error); // Some other error.
+
+			boost::asio::write(*sock, boost::asio::buffer(data, length));
+		}
+	}
+	catch(std::exception& e)
+	{
+		std::cerr << "Exception in thread: " << e.what() << "\n";
+	}
+}
+
+void server(boost::asio::io_service& io_service, short port)
+{
+	tcp::acceptor a(io_service, tcp::endpoint(tcp::v4(), port));
+	for(;;)
+	{
+		socket_ptr sock(new tcp::socket(io_service));
+		a.accept(*sock);
+		boost::thread t(boost::bind(session, sock));
+	}
+}
+
+int main(int argc, char* argv[])
+{
+	try
+	{
+		if(argc != 2)
+		{
+			std::cerr << "Usage: blocking_tcp_echo_server <port>\n";
+			return 1;
+		}
+
+		boost::asio::io_service io_service;
+
+		using namespace std;
+		// For atoi.
+		server(io_service, atoi(argv[1]));
+	}
+	catch(std::exception& e)
+	{
+		std::cerr << "Exception: " << e.what() << "\n";
+	}
+
+	return 0;
+}

+ 217 - 0
src/experimental/http_client/async_client.cc

@@ -0,0 +1,217 @@
+//
+// async_client.cpp
+// ~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <iostream>
+#include <istream>
+#include <ostream>
+#include <string>
+#include <boost/asio.hpp>
+#include <boost/bind.hpp>
+
+using boost::asio::ip::tcp;
+
+class client
+{
+public:
+	client(boost::asio::io_service& io_service, const std::string& server,
+	        const std::string& path) :
+		resolver_(io_service), socket_(io_service)
+	{
+		// Form the request. We specify the "Connection: close" header so that the
+		// server will close the socket after transmitting the response. This will
+		// allow us to treat all data up until the EOF as the content.
+		std::ostream request_stream(&request_);
+		request_stream << "GET " << path << " HTTP/1.0\r\n";
+		request_stream << "Host: " << server << "\r\n";
+		request_stream << "Accept: */*\r\n";
+		request_stream << "Connection: close\r\n\r\n";
+
+
+		// Start an asynchronous resolve to translate the server and service names
+		// into a list of endpoints.
+		tcp::resolver::query query(server, "http");
+		resolver_.async_resolve(query, boost::bind(&client::handle_resolve,
+		        this, boost::asio::placeholders::error,
+		        boost::asio::placeholders::iterator));
+	}
+
+private:
+	void handle_resolve(const boost::system::error_code& err,
+	        tcp::resolver::iterator endpoint_iterator)
+	{
+		if(!err)
+		{
+			// Attempt a connection to the first endpoint in the list. Each endpoint
+			// will be tried until we successfully establish a connection.
+			tcp::endpoint endpoint = *endpoint_iterator;
+			socket_.async_connect(endpoint, boost::bind(
+			        &client::handle_connect, this,
+			        boost::asio::placeholders::error, ++endpoint_iterator));
+		}
+		else
+		{
+			std::cout << "Error: " << err.message() << "\n";
+		}
+	}
+
+	void handle_connect(const boost::system::error_code& err,
+	        tcp::resolver::iterator endpoint_iterator)
+	{
+		if(!err)
+		{
+			// The connection was successful. Send the request.
+			boost::asio::async_write(socket_, request_, boost::bind(
+			        &client::handle_write_request, this,
+			        boost::asio::placeholders::error));
+		}
+		else if(endpoint_iterator != tcp::resolver::iterator())
+		{
+			// The connection failed. Try the next endpoint in the list.
+			socket_.close();
+			tcp::endpoint endpoint = *endpoint_iterator;
+			socket_.async_connect(endpoint, boost::bind(
+			        &client::handle_connect, this,
+			        boost::asio::placeholders::error, ++endpoint_iterator));
+		}
+		else
+		{
+			std::cout << "Error: " << err.message() << "\n";
+		}
+	}
+
+	void handle_write_request(const boost::system::error_code& err)
+	{
+		if(!err)
+		{
+			// Read the response status line.
+			boost::asio::async_read_until(socket_, response_, "\r\n",
+			        boost::bind(&client::handle_read_status_line, this,
+			                boost::asio::placeholders::error));
+		}
+		else
+		{
+			std::cout << "Error: " << err.message() << "\n";
+		}
+	}
+
+	void handle_read_status_line(const boost::system::error_code& err)
+	{
+		if(!err)
+		{
+			// Check that response is OK.
+			std::istream response_stream(&response_);
+			std::string http_version;
+			response_stream >> http_version;
+			unsigned int status_code;
+			response_stream >> status_code;
+			std::string status_message;
+			std::getline(response_stream, status_message);
+			if(!response_stream || http_version.substr(0, 5) != "HTTP/")
+			{
+				std::cout << "Invalid response\n";
+				return;
+			}
+			if(status_code != 200)
+			{
+				std::cout << "Response returned with status code ";
+				std::cout << status_code << "\n";
+				return;
+			}
+
+
+			// Read the response headers, which are terminated by a blank line.
+			boost::asio::async_read_until(socket_, response_, "\r\n\r\n",
+			        boost::bind(&client::handle_read_headers, this,
+			                boost::asio::placeholders::error));
+		}
+		else
+		{
+			std::cout << "Error: " << err << "\n";
+		}
+	}
+
+	void handle_read_headers(const boost::system::error_code& err)
+	{
+		if(!err)
+		{
+			// Process the response headers.
+			std::istream response_stream(&response_);
+			std::string header;
+			while(std::getline(response_stream, header) && header != "\r")
+				std::cout << header << "\n";
+			std::cout << "\n";
+
+
+			// Write whatever content we already have to output.
+			if(response_.size() > 0)
+				std::cout << &response_;
+
+
+			// Start reading remaining data until EOF.
+			boost::asio::async_read(socket_, response_,
+			        boost::asio::transfer_at_least(1), boost::bind(
+			                &client::handle_read_content, this,
+			                boost::asio::placeholders::error));
+		}
+		else
+		{
+			std::cout << "Error: " << err << "\n";
+		}
+	}
+
+	void handle_read_content(const boost::system::error_code& err)
+	{
+		if(!err)
+		{
+			// Write all of the data that has been read so far.
+			std::cout << &response_;
+
+
+			// Continue reading remaining data until EOF.
+			boost::asio::async_read(socket_, response_,
+			        boost::asio::transfer_at_least(1), boost::bind(
+			                &client::handle_read_content, this,
+			                boost::asio::placeholders::error));
+		}
+		else if(err != boost::asio::error::eof)
+		{
+			std::cout << "Error: " << err << "\n";
+		}
+	}
+
+	tcp::resolver resolver_;
+	tcp::socket socket_;
+	boost::asio::streambuf request_;
+	boost::asio::streambuf response_;
+};
+
+int main(int argc, char* argv[])
+{
+	try
+	{
+		if(argc != 3)
+		{
+			std::cout << "Usage: async_client <server> <path>\n";
+			std::cout << "Example:\n";
+			std::cout << "  async_client www.boost.org /LICENSE_1_0.txt\n";
+			return 1;
+		}
+
+		boost::asio::io_service io_service;
+		client c(io_service, argv[1], argv[2]);
+		io_service.run();
+	}
+	catch(std::exception& e)
+	{
+		std::cout << "Exception: " << e.what() << "\n";
+	}
+
+	return 0;
+}

+ 122 - 0
src/experimental/http_client/sync_client.cc

@@ -0,0 +1,122 @@
+//
+// sync_client.cpp
+// ~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <iostream>
+#include <istream>
+#include <ostream>
+#include <string>
+#include <boost/asio.hpp>
+
+using boost::asio::ip::tcp;
+
+int main(int argc, char* argv[])
+{
+	try
+	{
+		if(argc != 3)
+		{
+			std::cout << "Usage: sync_client <server> <path>\n";
+			std::cout << "Example:\n";
+			std::cout << "  sync_client www.boost.org /LICENSE_1_0.txt\n";
+			return 1;
+		}
+
+		boost::asio::io_service io_service;
+
+
+		// Get a list of endpoints corresponding to the server name.
+		tcp::resolver resolver(io_service);
+		tcp::resolver::query query(argv[1], "http");
+		tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
+		tcp::resolver::iterator end;
+
+
+		// Try each endpoint until we successfully establish a connection.
+		tcp::socket socket(io_service);
+		boost::system::error_code error = boost::asio::error::host_not_found;
+		while(error && endpoint_iterator != end)
+		{
+			socket.close();
+			socket.connect(*endpoint_iterator++, error);
+		}
+		if(error)
+			throw boost::system::system_error(error);
+
+
+		// Form the request. We specify the "Connection: close" header so that the
+		// server will close the socket after transmitting the response. This will
+		// allow us to treat all data up until the EOF as the content.
+		boost::asio::streambuf request;
+		std::ostream request_stream(&request);
+		request_stream << "GET " << argv[2] << " HTTP/1.0\r\n";
+		request_stream << "Host: " << argv[1] << "\r\n";
+		request_stream << "Accept: */*\r\n";
+		request_stream << "Connection: close\r\n\r\n";
+
+
+		// Send the request.
+		boost::asio::write(socket, request);
+
+
+		// Read the response status line.
+		boost::asio::streambuf response;
+		boost::asio::read_until(socket, response, "\r\n");
+
+
+		// Check that response is OK.
+		std::istream response_stream(&response);
+		std::string http_version;
+		response_stream >> http_version;
+		unsigned int status_code;
+		response_stream >> status_code;
+		std::string status_message;
+		std::getline(response_stream, status_message);
+		if(!response_stream || http_version.substr(0, 5) != "HTTP/")
+		{
+			std::cout << "Invalid response\n";
+			return 1;
+		}
+		if(status_code != 200)
+		{
+			std::cout << "Response returned with status code " << status_code
+			        << "\n";
+			return 1;
+		}
+
+		// Read the response headers, which are terminated by a blank line.
+		boost::asio::read_until(socket, response, "\r\n\r\n");
+
+
+		// Process the response headers.
+		std::string header;
+		while(std::getline(response_stream, header) && header != "\r")
+			std::cout << header << "\n";
+		std::cout << "\n";
+
+
+		// Write whatever content we already have to output.
+		if(response.size() > 0)
+			std::cout << &response;
+
+
+		// Read until EOF, writing data to output as we go.
+		while(boost::asio::read(socket, response,
+		        boost::asio::transfer_at_least(1), error))
+			std::cout << &response;
+		if(error != boost::asio::error::eof)
+			throw boost::system::system_error(error);
+	}
+	catch(std::exception& e)
+	{
+		std::cout << "Exception: " << e.what() << "\n";
+	}
+
+	return 0;
+}

+ 97 - 0
src/experimental/http_server/connection.cc

@@ -0,0 +1,97 @@
+//
+// connection.cpp
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <vector>
+#include <boost/bind.hpp>
+#include "experimental/http_server/connection.h"
+#include "experimental/http_server/connection_manager.h"
+#include "experimental/http_server/request_handler.h"
+
+namespace http_server {
+
+connection::connection(boost::asio::io_service& io_service,
+        connection_manager& manager, request_handler& handler) :
+	socket_(io_service), connection_manager_(manager),
+	        request_handler_(handler)
+{
+}
+
+boost::asio::ip::tcp::socket& connection::socket()
+{
+	return socket_;
+}
+
+void connection::start()
+{
+	socket_.async_read_some(boost::asio::buffer(buffer_), boost::bind(
+	        &connection::handle_read, shared_from_this(),
+	        boost::asio::placeholders::error,
+	        boost::asio::placeholders::bytes_transferred));
+}
+
+void connection::stop()
+{
+	socket_.close();
+}
+
+void connection::handle_read(const boost::system::error_code& e,
+        std::size_t bytes_transferred)
+{
+	if(!e)
+	{
+		boost::tribool result;
+		boost::tie(result, boost::tuples::ignore) = request_parser_.parse(
+		        request_, buffer_.data(), buffer_.data() + bytes_transferred);
+
+		if(result)
+		{
+			request_handler_.handle_request(request_, reply_);
+			boost::asio::async_write(socket_, reply_.to_buffers(), boost::bind(
+			        &connection::handle_write, shared_from_this(),
+			        boost::asio::placeholders::error));
+		}
+		else if(!result)
+		{
+			reply_ = reply::stock_reply(reply::bad_request);
+			boost::asio::async_write(socket_, reply_.to_buffers(), boost::bind(
+			        &connection::handle_write, shared_from_this(),
+			        boost::asio::placeholders::error));
+		}
+		else
+		{
+			socket_.async_read_some(boost::asio::buffer(buffer_), boost::bind(
+			        &connection::handle_read, shared_from_this(),
+			        boost::asio::placeholders::error,
+			        boost::asio::placeholders::bytes_transferred));
+		}
+	}
+	else if(e != boost::asio::error::operation_aborted)
+	{
+		connection_manager_.stop(shared_from_this());
+	}
+}
+
+void connection::handle_write(const boost::system::error_code& e)
+{
+	if(!e)
+	{
+		// Initiate graceful connection closure.
+		boost::system::error_code ignored_ec;
+		socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both,
+		        ignored_ec);
+	}
+
+	if(e != boost::asio::error::operation_aborted)
+	{
+		connection_manager_.stop(shared_from_this());
+	}
+}
+
+} // namespace http_server

+ 80 - 0
src/experimental/http_server/connection.h

@@ -0,0 +1,80 @@
+//
+// connection.hpp
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef EXPERIMENTAL_HTTP_SERVER_CONNECTION_H
+#define EXPERIMENTAL_HTTP_SERVER_CONNECTION_H
+
+#include <boost/asio.hpp>
+#include <boost/array.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "experimental/http_server/reply.h"
+#include "experimental/http_server/request.h"
+#include "experimental/http_server/request_handler.h"
+#include "experimental/http_server/request_parser.h"
+
+namespace http_server {
+
+class connection_manager;
+
+/// Represents a single connection from a client.
+class connection: public boost::enable_shared_from_this<connection>,
+        private boost::noncopyable
+{
+public:
+	/// Construct a connection with the given io_service.
+	explicit connection(boost::asio::io_service& io_service,
+	        connection_manager& manager, request_handler& handler);
+
+	/// Get the socket associated with the connection.
+	boost::asio::ip::tcp::socket& socket();
+
+	/// Start the first asynchronous operation for the connection.
+	void start();
+
+	/// Stop all asynchronous operations associated with the connection.
+	void stop();
+
+private:
+	/// Handle completion of a read operation.
+	void handle_read(const boost::system::error_code& e,
+	        std::size_t bytes_transferred);
+
+	/// Handle completion of a write operation.
+	void handle_write(const boost::system::error_code& e);
+
+	/// Socket for the connection.
+	boost::asio::ip::tcp::socket socket_;
+
+	/// The manager for this connection.
+	connection_manager& connection_manager_;
+
+	/// The handler used to process the incoming request.
+	request_handler& request_handler_;
+
+	/// Buffer for incoming data.
+	boost::array<char, 8192> buffer_;
+
+	/// The incoming request.
+	request request_;
+
+	/// The parser for the incoming request.
+	request_parser request_parser_;
+
+	/// The reply to be sent back to the client.
+	reply reply_;
+};
+
+typedef boost::shared_ptr<connection> connection_ptr;
+
+} // namespace http_server
+
+#endif // EXPERIMENTAL_HTTP_SERVER_CONNECTION_H

+ 36 - 0
src/experimental/http_server/connection_manager.cc

@@ -0,0 +1,36 @@
+//
+// connection_manager.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <algorithm>
+#include <boost/bind.hpp>
+#include "experimental/http_server/connection_manager.h"
+
+namespace http_server {
+
+void connection_manager::start(connection_ptr c)
+{
+	connections_.insert(c);
+	c->start();
+}
+
+void connection_manager::stop(connection_ptr c)
+{
+	connections_.erase(c);
+	c->stop();
+}
+
+void connection_manager::stop_all()
+{
+	std::for_each(connections_.begin(), connections_.end(), boost::bind(
+	        &connection::stop, _1));
+	connections_.clear();
+}
+
+} // namespace http_server

+ 41 - 0
src/experimental/http_server/connection_manager.h

@@ -0,0 +1,41 @@
+//
+// connection_manager.hpp
+// ~~~~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef EXPERIMENTAL_HTTP_SERVER_CONNECTION_MANAGER_H
+#define EXPERIMENTAL_HTTP_SERVER_CONNECTION_MANAGER_H
+
+#include <set>
+#include <boost/noncopyable.hpp>
+#include "experimental/http_server/connection.h"
+
+namespace http_server {
+
+/// Manages open connections so that they may be cleanly stopped when the server
+/// needs to shut down.
+class connection_manager: private boost::noncopyable
+{
+public:
+	/// Add the specified connection to the manager and start it.
+	void start(connection_ptr c);
+
+	/// Stop the specified connection.
+	void stop(connection_ptr c);
+
+	/// Stop all connections.
+	void stop_all();
+
+private:
+	/// The managed connections.
+	std::set<connection_ptr> connections_;
+};
+
+} // namespace http_server
+
+#endif // EXPERIMENTAL_HTTP_SERVER_CONNECTION_MANAGER_H

+ 26 - 0
src/experimental/http_server/header.h

@@ -0,0 +1,26 @@
+//
+// header.hpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef EXPERIMENTAL_HTTP_SERVER_HEADER_H
+#define EXPERIMENTAL_HTTP_SERVER_HEADER_H
+
+#include <string>
+
+namespace http_server {
+
+struct header
+{
+	std::string name;
+	std::string value;
+};
+
+} // namespace http_server
+
+#endif // EXPERIMENTAL_HTTP_SERVER_HEADER_H

+ 39 - 0
src/experimental/http_server/mime_types.cc

@@ -0,0 +1,39 @@
+//
+// mime_types.cpp
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "experimental/http_server/mime_types.h"
+
+namespace http_server {
+namespace mime_types {
+
+struct mapping
+{
+	const char* extension;
+	const char* mime_type;
+} mappings[] = { { "gif", "image/gif" }, { "htm", "text/html" }, { "html",
+        "text/html" }, { "jpg", "image/jpeg" }, { "png", "image/png" },
+        { 0, 0 } // Marks end of list.
+};
+
+std::string extension_to_type(const std::string& extension)
+{
+	for(mapping* m = mappings; m->extension; ++m)
+	{
+		if(m->extension == extension)
+		{
+			return m->mime_type;
+		}
+	}
+
+	return "text/plain";
+}
+
+} // namespace mime_types
+} // namespace http_server

+ 25 - 0
src/experimental/http_server/mime_types.h

@@ -0,0 +1,25 @@
+//
+// mime_types.hpp
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef EXPERIMENTAL_HTTP_SERVER_HTTP_MIME_TYPES_H
+#define EXPERIMENTAL_HTTP_SERVER_HTTP_MIME_TYPES_H
+
+#include <string>
+
+namespace http_server {
+namespace mime_types {
+
+/// Convert a file extension into a MIME type.
+std::string extension_to_type(const std::string& extension);
+
+} // namespace mime_types
+} // namespace http_server
+
+#endif // EXPERIMENTAL_HTTP_SERVER_HTTP_MIME_TYPES_H

+ 74 - 0
src/experimental/http_server/posix_main.cc

@@ -0,0 +1,74 @@
+//
+// posix_main.cc
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <iostream>
+#include <string>
+#include <boost/asio.hpp>
+#include <boost/thread.hpp>
+#include <boost/bind.hpp>
+#include "experimental/http_server/server.h"
+
+#include <pthread.h>
+#include <signal.h>
+
+int main(int argc, char* argv[])
+{
+	try
+	{
+		// Check command line arguments.
+		if(argc != 4)
+		{
+			std::cerr << "Usage: http_server <address> <port> <doc_root>\n";
+			std::cerr << "  For IPv4, try:\n";
+			std::cerr << "    receiver 0.0.0.0 80 .\n";
+			std::cerr << "  For IPv6, try:\n";
+			std::cerr << "    receiver 0::0 80 .\n";
+			return 1;
+		}
+
+		// Block all signals for background thread.
+		sigset_t new_mask;
+		sigfillset(&new_mask);
+		sigset_t old_mask;
+		pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask);
+
+
+		// Run server in background thread.
+		http::server::server s(argv[1], argv[2], argv[3]);
+		boost::thread t(boost::bind(&http::server::server::run, &s));
+
+
+		// Restore previous signals.
+		pthread_sigmask(SIG_SETMASK, &old_mask, 0);
+
+
+		// Wait for signal indicating time to shut down.
+		sigset_t wait_mask;
+		sigemptyset(&wait_mask);
+		sigaddset(&wait_mask, SIGINT);
+		sigaddset(&wait_mask, SIGQUIT);
+		sigaddset(&wait_mask, SIGTERM);
+		pthread_sigmask(SIG_BLOCK, &wait_mask, 0);
+		int sig = 0;
+		sigwait(&wait_mask, &sig);
+
+
+		// Stop the server.
+		s.stop();
+		t.join();
+	}
+	catch(std::exception& e)
+	{
+		std::cerr << "exception: " << e.what() << "\n";
+	}
+
+	return 0;
+}
+

+ 225 - 0
src/experimental/http_server/reply.cc

@@ -0,0 +1,225 @@
+//
+// reply.cpp
+// ~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <string>
+#include <boost/lexical_cast.hpp>
+#include "experimental/http_server/reply.h"
+
+namespace http_server {
+
+namespace status_strings {
+
+const std::string ok = "HTTP/1.0 200 OK\r\n";
+const std::string created = "HTTP/1.0 201 Created\r\n";
+const std::string accepted = "HTTP/1.0 202 Accepted\r\n";
+const std::string no_content = "HTTP/1.0 204 No Content\r\n";
+const std::string multiple_choices = "HTTP/1.0 300 Multiple Choices\r\n";
+const std::string moved_permanently = "HTTP/1.0 301 Moved Permanently\r\n";
+const std::string moved_temporarily = "HTTP/1.0 302 Moved Temporarily\r\n";
+const std::string not_modified = "HTTP/1.0 304 Not Modified\r\n";
+const std::string bad_request = "HTTP/1.0 400 Bad Request\r\n";
+const std::string unauthorized = "HTTP/1.0 401 Unauthorized\r\n";
+const std::string forbidden = "HTTP/1.0 403 Forbidden\r\n";
+const std::string not_found = "HTTP/1.0 404 Not Found\r\n";
+const std::string internal_server_error =
+        "HTTP/1.0 500 Internal Server Error\r\n";
+const std::string not_implemented = "HTTP/1.0 501 Not Implemented\r\n";
+const std::string bad_gateway = "HTTP/1.0 502 Bad Gateway\r\n";
+const std::string service_unavailable = "HTTP/1.0 503 Service Unavailable\r\n";
+
+boost::asio::const_buffer to_buffer(reply::status_type status)
+{
+	switch(status)
+	{
+		case reply::ok:
+			return boost::asio::buffer(ok);
+		case reply::created:
+			return boost::asio::buffer(created);
+		case reply::accepted:
+			return boost::asio::buffer(accepted);
+		case reply::no_content:
+			return boost::asio::buffer(no_content);
+		case reply::multiple_choices:
+			return boost::asio::buffer(multiple_choices);
+		case reply::moved_permanently:
+			return boost::asio::buffer(moved_permanently);
+		case reply::moved_temporarily:
+			return boost::asio::buffer(moved_temporarily);
+		case reply::not_modified:
+			return boost::asio::buffer(not_modified);
+		case reply::bad_request:
+			return boost::asio::buffer(bad_request);
+		case reply::unauthorized:
+			return boost::asio::buffer(unauthorized);
+		case reply::forbidden:
+			return boost::asio::buffer(forbidden);
+		case reply::not_found:
+			return boost::asio::buffer(not_found);
+		case reply::internal_server_error:
+			return boost::asio::buffer(internal_server_error);
+		case reply::not_implemented:
+			return boost::asio::buffer(not_implemented);
+		case reply::bad_gateway:
+			return boost::asio::buffer(bad_gateway);
+		case reply::service_unavailable:
+			return boost::asio::buffer(service_unavailable);
+		default:
+			return boost::asio::buffer(internal_server_error);
+	}
+}
+
+} // namespace status_strings
+
+namespace misc_strings {
+
+const char name_value_separator[] = { ':', ' ' };
+const char crlf[] = { '\r', '\n' };
+
+} // namespace misc_strings
+
+std::vector<boost::asio::const_buffer> reply::to_buffers()
+{
+	std::vector < boost::asio::const_buffer > buffers;
+	buffers.push_back(status_strings::to_buffer(status));
+	for(std::size_t i = 0; i < headers.size(); ++i)
+	{
+		header& h = headers[i];
+		buffers.push_back(boost::asio::buffer(h.name));
+		buffers.push_back(boost::asio::buffer(
+		        misc_strings::name_value_separator));
+		buffers.push_back(boost::asio::buffer(h.value));
+		buffers.push_back(boost::asio::buffer(misc_strings::crlf));
+	}
+	buffers.push_back(boost::asio::buffer(misc_strings::crlf));
+	buffers.push_back(boost::asio::buffer(content));
+	return buffers;
+}
+
+namespace stock_replies {
+
+const char ok[] = "";
+const char created[] = "<html>"
+	"<head><title>Created</title></head>"
+	"<body><h1>201 Created</h1></body>"
+	"</html>";
+const char accepted[] = "<html>"
+	"<head><title>Accepted</title></head>"
+	"<body><h1>202 Accepted</h1></body>"
+	"</html>";
+const char no_content[] = "<html>"
+	"<head><title>No Content</title></head>"
+	"<body><h1>204 Content</h1></body>"
+	"</html>";
+const char multiple_choices[] = "<html>"
+	"<head><title>Multiple Choices</title></head>"
+	"<body><h1>300 Multiple Choices</h1></body>"
+	"</html>";
+const char moved_permanently[] = "<html>"
+	"<head><title>Moved Permanently</title></head>"
+	"<body><h1>301 Moved Permanently</h1></body>"
+	"</html>";
+const char moved_temporarily[] = "<html>"
+	"<head><title>Moved Temporarily</title></head>"
+	"<body><h1>302 Moved Temporarily</h1></body>"
+	"</html>";
+const char not_modified[] = "<html>"
+	"<head><title>Not Modified</title></head>"
+	"<body><h1>304 Not Modified</h1></body>"
+	"</html>";
+const char bad_request[] = "<html>"
+	"<head><title>Bad Request</title></head>"
+	"<body><h1>400 Bad Request</h1></body>"
+	"</html>";
+const char unauthorized[] = "<html>"
+	"<head><title>Unauthorized</title></head>"
+	"<body><h1>401 Unauthorized</h1></body>"
+	"</html>";
+const char forbidden[] = "<html>"
+	"<head><title>Forbidden</title></head>"
+	"<body><h1>403 Forbidden</h1></body>"
+	"</html>";
+const char not_found[] = "<html>"
+	"<head><title>Not Found</title></head>"
+	"<body><h1>404 Not Found</h1></body>"
+	"</html>";
+const char internal_server_error[] = "<html>"
+	"<head><title>Internal Server Error</title></head>"
+	"<body><h1>500 Internal Server Error</h1></body>"
+	"</html>";
+const char not_implemented[] = "<html>"
+	"<head><title>Not Implemented</title></head>"
+	"<body><h1>501 Not Implemented</h1></body>"
+	"</html>";
+const char bad_gateway[] = "<html>"
+	"<head><title>Bad Gateway</title></head>"
+	"<body><h1>502 Bad Gateway</h1></body>"
+	"</html>";
+const char service_unavailable[] = "<html>"
+	"<head><title>Service Unavailable</title></head>"
+	"<body><h1>503 Service Unavailable</h1></body>"
+	"</html>";
+
+std::string to_string(reply::status_type status)
+{
+	switch(status)
+	{
+		case reply::ok:
+			return ok;
+		case reply::created:
+			return created;
+		case reply::accepted:
+			return accepted;
+		case reply::no_content:
+			return no_content;
+		case reply::multiple_choices:
+			return multiple_choices;
+		case reply::moved_permanently:
+			return moved_permanently;
+		case reply::moved_temporarily:
+			return moved_temporarily;
+		case reply::not_modified:
+			return not_modified;
+		case reply::bad_request:
+			return bad_request;
+		case reply::unauthorized:
+			return unauthorized;
+		case reply::forbidden:
+			return forbidden;
+		case reply::not_found:
+			return not_found;
+		case reply::internal_server_error:
+			return internal_server_error;
+		case reply::not_implemented:
+			return not_implemented;
+		case reply::bad_gateway:
+			return bad_gateway;
+		case reply::service_unavailable:
+			return service_unavailable;
+		default:
+			return internal_server_error;
+	}
+}
+
+} // namespace stock_replies
+
+reply reply::stock_reply(reply::status_type status)
+{
+	reply rep;
+	rep.status = status;
+	rep.content = stock_replies::to_string(status);
+	rep.headers.resize(2);
+	rep.headers[0].name = "Content-Length";
+	rep.headers[0].value = boost::lexical_cast<std::string>(rep.content.size());
+	rep.headers[1].name = "Content-Type";
+	rep.headers[1].value = "text/html";
+	return rep;
+}
+
+} // namespace http_server

+ 62 - 0
src/experimental/http_server/reply.h

@@ -0,0 +1,62 @@
+//
+// reply.hpp
+// ~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef EXPERIMENTAL_HTTP_SERVER_HTTP_REPLY_H
+#define EXPERIMENTAL_HTTP_SERVER_HTTP_REPLY_H
+
+#include <string>
+#include <vector>
+#include <boost/asio.hpp>
+#include "experimental/http_server/header.h"
+
+namespace http_server {
+
+/// A reply to be sent to a client.
+struct reply
+{
+	/// The status of the reply.
+	enum status_type
+	{
+		ok = 200,
+		created = 201,
+		accepted = 202,
+		no_content = 204,
+		multiple_choices = 300,
+		moved_permanently = 301,
+		moved_temporarily = 302,
+		not_modified = 304,
+		bad_request = 400,
+		unauthorized = 401,
+		forbidden = 403,
+		not_found = 404,
+		internal_server_error = 500,
+		not_implemented = 501,
+		bad_gateway = 502,
+		service_unavailable = 503
+	} status;
+
+	/// The headers to be included in the reply.
+	std::vector<header> headers;
+
+	/// The content to be sent in the reply.
+	std::string content;
+
+	/// Convert the reply into a vector of buffers. The buffers do not own the
+	/// underlying memory blocks, therefore the reply object must remain valid and
+	/// not be changed until the write operation has completed.
+	std::vector<boost::asio::const_buffer> to_buffers();
+
+	/// Get a stock reply.
+	static reply stock_reply(status_type status);
+};
+
+} // namespace http_server
+
+#endif // EXPERIMENTAL_HTTP_SERVER_HTTP_REPLY_H

+ 32 - 0
src/experimental/http_server/request.h

@@ -0,0 +1,32 @@
+//
+// request.hpp
+// ~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef EXPERIMENTAL_HTTP_SERVER_HTTP_REQUEST_H
+#define EXPERIMENTAL_HTTP_SERVER_HTTP_REQUEST_H
+
+#include <string>
+#include <vector>
+#include "experimental/http_server/header.h"
+
+namespace http_server {
+
+/// A request received from a client.
+struct request
+{
+	std::string method;
+	std::string uri;
+	int http_version_major;
+	int http_version_minor;
+	std::vector<header> headers;
+};
+
+} // namespace http_server
+
+#endif // EXPERIMENTAL_HTTP_SERVER_HTTP_REQUEST_H

+ 120 - 0
src/experimental/http_server/request_handler.cc

@@ -0,0 +1,120 @@
+//
+// request_handler.cpp
+// ~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <boost/lexical_cast.hpp>
+#include "experimental/http_server/request_handler.h"
+#include "experimental/http_server/mime_types.h"
+#include "experimental/http_server/reply.h"
+#include "experimental/http_server/request.h"
+
+namespace http_server {
+
+request_handler::request_handler(const std::string& doc_root) :
+	doc_root_(doc_root)
+{
+}
+
+void request_handler::handle_request(const request& req, reply& rep)
+{
+	// Decode url to path.
+	std::string request_path;
+	if(!url_decode(req.uri, request_path))
+	{
+		rep = reply::stock_reply(reply::bad_request);
+		return;
+	}
+
+	// Request path must be absolute and not contain "..".
+	if(request_path.empty() || request_path[0] != '/'
+	        || request_path.find("..") != std::string::npos)
+	{
+		rep = reply::stock_reply(reply::bad_request);
+		return;
+	}
+
+	// If path ends in slash (i.e. is a directory) then add "index.html".
+	if(request_path[request_path.size() - 1] == '/')
+	{
+		request_path += "index.html";
+	}
+
+	// Determine the file extension.
+	std::size_t last_slash_pos = request_path.find_last_of("/");
+	std::size_t last_dot_pos = request_path.find_last_of(".");
+	std::string extension;
+	if(last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos)
+	{
+		extension = request_path.substr(last_dot_pos + 1);
+	}
+
+	// Open the file to send back.
+	std::string full_path = doc_root_ + request_path;
+	std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);
+	if(!is)
+	{
+		rep = reply::stock_reply(reply::not_found);
+		return;
+	}
+
+	// Fill out the reply to be sent to the client.
+	rep.status = reply::ok;
+	char buf[512];
+	while(is.read(buf, sizeof(buf)).gcount() > 0)
+		rep.content.append(buf, is.gcount());
+	rep.headers.resize(2);
+	rep.headers[0].name = "Content-Length";
+	rep.headers[0].value = boost::lexical_cast<std::string>(rep.content.size());
+	rep.headers[1].name = "Content-Type";
+	rep.headers[1].value = mime_types::extension_to_type(extension);
+}
+
+bool request_handler::url_decode(const std::string& in, std::string& out)
+{
+	out.clear();
+	out.reserve(in.size());
+	for(std::size_t i = 0; i < in.size(); ++i)
+	{
+		if(in[i] == '%')
+		{
+			if(i + 3 <= in.size())
+			{
+				int value = 0;
+				std::istringstream is(in.substr(i + 1, 2));
+				if(is >> std::hex >> value)
+				{
+					out += static_cast<char> (value);
+					i += 2;
+				}
+				else
+				{
+					return false;
+				}
+			}
+			else
+			{
+				return false;
+			}
+		}
+		else if(in[i] == '+')
+		{
+			out += ' ';
+		}
+		else
+		{
+			out += in[i];
+		}
+	}
+	return true;
+}
+
+} // namespace http_server

+ 43 - 0
src/experimental/http_server/request_handler.h

@@ -0,0 +1,43 @@
+//
+// request_handler.hpp
+// ~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef EXPERIMENTAL_HTTP_SERVER_HTTP_REQUEST_HANDLER_H
+#define EXPERIMENTAL_HTTP_SERVER_HTTP_REQUEST_HANDLER_H
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace http_server {
+
+struct reply;
+struct request;
+
+/// The common handler for all incoming requests.
+class request_handler: private boost::noncopyable
+{
+public:
+	/// Construct with a directory containing files to be served.
+	explicit request_handler(const std::string& doc_root);
+
+	/// Handle a request and produce a reply.
+	void handle_request(const request& req, reply& rep);
+
+private:
+	/// The directory containing the files to be served.
+	std::string doc_root_;
+
+	/// Perform URL-decoding on a string. Returns false if the encoding was
+	/// invalid.
+	static bool url_decode(const std::string& in, std::string& out);
+};
+
+} // namespace http_server
+
+#endif // EXPERIMENTAL_HTTP_SERVER_HTTP_REQUEST_HANDLER_H

+ 343 - 0
src/experimental/http_server/request_parser.cc

@@ -0,0 +1,343 @@
+//
+// request_parser.cpp
+// ~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "experimental/http_server/request_parser.h"
+#include "experimental/http_server/request.h"
+
+namespace http_server {
+
+request_parser::request_parser() :
+	state_(method_start)
+{
+}
+
+void request_parser::reset()
+{
+	state_ = method_start;
+}
+
+boost::tribool request_parser::consume(request& req, char input)
+{
+	switch(state_)
+	{
+		case method_start:
+			if(!is_char(input) || is_ctl(input) || is_tspecial(input))
+			{
+				return false;
+			}
+			else
+			{
+				state_ = method;
+				req.method.push_back(input);
+				return boost::indeterminate;
+			}
+		case method:
+			if(input == ' ')
+			{
+				state_ = uri;
+				return boost::indeterminate;
+			}
+			else if(!is_char(input) || is_ctl(input) || is_tspecial(input))
+			{
+				return false;
+			}
+			else
+			{
+				req.method.push_back(input);
+				return boost::indeterminate;
+			}
+		case uri_start:
+			if(is_ctl(input))
+			{
+				return false;
+			}
+			else
+			{
+				state_ = uri;
+				req.uri.push_back(input);
+				return boost::indeterminate;
+			}
+		case uri:
+			if(input == ' ')
+			{
+				state_ = http_version_h;
+				return boost::indeterminate;
+			}
+			else if(is_ctl(input))
+			{
+				return false;
+			}
+			else
+			{
+				req.uri.push_back(input);
+				return boost::indeterminate;
+			}
+		case http_version_h:
+			if(input == 'H')
+			{
+				state_ = http_version_t_1;
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case http_version_t_1:
+			if(input == 'T')
+			{
+				state_ = http_version_t_2;
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case http_version_t_2:
+			if(input == 'T')
+			{
+				state_ = http_version_p;
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case http_version_p:
+			if(input == 'P')
+			{
+				state_ = http_version_slash;
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case http_version_slash:
+			if(input == '/')
+			{
+				req.http_version_major = 0;
+				req.http_version_minor = 0;
+				state_ = http_version_major_start;
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case http_version_major_start:
+			if(is_digit(input))
+			{
+				req.http_version_major = req.http_version_major * 10 + input
+				        - '0';
+				state_ = http_version_major;
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case http_version_major:
+			if(input == '.')
+			{
+				state_ = http_version_minor_start;
+				return boost::indeterminate;
+			}
+			else if(is_digit(input))
+			{
+				req.http_version_major = req.http_version_major * 10 + input
+				        - '0';
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case http_version_minor_start:
+			if(is_digit(input))
+			{
+				req.http_version_minor = req.http_version_minor * 10 + input
+				        - '0';
+				state_ = http_version_minor;
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case http_version_minor:
+			if(input == '\r')
+			{
+				state_ = expecting_newline_1;
+				return boost::indeterminate;
+			}
+			else if(is_digit(input))
+			{
+				req.http_version_minor = req.http_version_minor * 10 + input
+				        - '0';
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case expecting_newline_1:
+			if(input == '\n')
+			{
+				state_ = header_line_start;
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case header_line_start:
+			if(input == '\r')
+			{
+				state_ = expecting_newline_3;
+				return boost::indeterminate;
+			}
+			else if(!req.headers.empty() && (input == ' ' || input == '\t'))
+			{
+				state_ = header_lws;
+				return boost::indeterminate;
+			}
+			else if(!is_char(input) || is_ctl(input) || is_tspecial(input))
+			{
+				return false;
+			}
+			else
+			{
+				req.headers.push_back(header());
+				req.headers.back().name.push_back(input);
+				state_ = header_name;
+				return boost::indeterminate;
+			}
+		case header_lws:
+			if(input == '\r')
+			{
+				state_ = expecting_newline_2;
+				return boost::indeterminate;
+			}
+			else if(input == ' ' || input == '\t')
+			{
+				return boost::indeterminate;
+			}
+			else if(is_ctl(input))
+			{
+				return false;
+			}
+			else
+			{
+				state_ = header_value;
+				req.headers.back().value.push_back(input);
+				return boost::indeterminate;
+			}
+		case header_name:
+			if(input == ':')
+			{
+				state_ = space_before_header_value;
+				return boost::indeterminate;
+			}
+			else if(!is_char(input) || is_ctl(input) || is_tspecial(input))
+			{
+				return false;
+			}
+			else
+			{
+				req.headers.back().name.push_back(input);
+				return boost::indeterminate;
+			}
+		case space_before_header_value:
+			if(input == ' ')
+			{
+				state_ = header_value;
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case header_value:
+			if(input == '\r')
+			{
+				state_ = expecting_newline_2;
+				return boost::indeterminate;
+			}
+			else if(is_ctl(input))
+			{
+				return false;
+			}
+			else
+			{
+				req.headers.back().value.push_back(input);
+				return boost::indeterminate;
+			}
+		case expecting_newline_2:
+			if(input == '\n')
+			{
+				state_ = header_line_start;
+				return boost::indeterminate;
+			}
+			else
+			{
+				return false;
+			}
+		case expecting_newline_3:
+			return (input == '\n');
+		default:
+			return false;
+	}
+}
+
+bool request_parser::is_char(int c)
+{
+	return c >= 0 && c <= 127;
+}
+
+bool request_parser::is_ctl(int c)
+{
+	return (c >= 0 && c <= 31) || (c == 127);
+}
+
+bool request_parser::is_tspecial(int c)
+{
+	switch(c)
+	{
+		case '(':
+		case ')':
+		case '<':
+		case '>':
+		case '@':
+		case ',':
+		case ';':
+		case ':':
+		case '\\':
+		case '"':
+		case '/':
+		case '[':
+		case ']':
+		case '?':
+		case '=':
+		case '{':
+		case '}':
+		case ' ':
+		case '\t':
+			return true;
+		default:
+			return false;
+	}
+}
+
+bool request_parser::is_digit(int c)
+{
+	return c >= '0' && c <= '9';
+}
+
+} // namespace http_server

+ 94 - 0
src/experimental/http_server/request_parser.h

@@ -0,0 +1,94 @@
+//
+// request_parser.hpp
+// ~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef EXPERIMENTAL_HTTP_SERVER_HTTP_REQUEST_PARSER_H
+#define EXPERIMENTAL_HTTP_SERVER_HTTP_REQUEST_PARSER_H
+
+#include <boost/logic/tribool.hpp>
+#include <boost/tuple/tuple.hpp>
+
+namespace http_server {
+
+struct request;
+
+/// Parser for incoming requests.
+class request_parser
+{
+public:
+	/// Construct ready to parse the request method.
+	request_parser();
+
+	/// Reset to initial parser state.
+	void reset();
+
+	/// Parse some data. The tribool return value is true when a complete request
+	/// has been parsed, false if the data is invalid, indeterminate when more
+	/// data is required. The InputIterator return value indicates how much of the
+	/// input has been consumed.
+	template<typename InputIterator>
+	boost::tuple<boost::tribool, InputIterator> parse(request& req,
+	        InputIterator begin, InputIterator end)
+	{
+		while(begin != end)
+		{
+			boost::tribool result = consume(req, *begin++);
+			if(result || !result)
+				return boost::make_tuple(result, begin);
+		}
+		boost::tribool result = boost::indeterminate;
+		return boost::make_tuple(result, begin);
+	}
+
+private:
+	/// Handle the next character of input.
+	boost::tribool consume(request& req, char input);
+
+	/// Check if a byte is an HTTP character.
+	static bool is_char(int c);
+
+	/// Check if a byte is an HTTP control character.
+	static bool is_ctl(int c);
+
+	/// Check if a byte is defined as an HTTP tspecial character.
+	static bool is_tspecial(int c);
+
+	/// Check if a byte is a digit.
+	static bool is_digit(int c);
+
+	/// The current state of the parser.
+	enum state
+	{
+		method_start,
+		method,
+		uri_start,
+		uri,
+		http_version_h,
+		http_version_t_1,
+		http_version_t_2,
+		http_version_p,
+		http_version_slash,
+		http_version_major_start,
+		http_version_major,
+		http_version_minor_start,
+		http_version_minor,
+		expecting_newline_1,
+		header_line_start,
+		header_lws,
+		header_name,
+		space_before_header_value,
+		header_value,
+		expecting_newline_2,
+		expecting_newline_3
+	} state_;
+};
+
+} // namespace http_server
+
+#endif // EXPERIMENTAL_HTTP_SERVER_HTTP_REQUEST_PARSER_H

+ 71 - 0
src/experimental/http_server/server.cc

@@ -0,0 +1,71 @@
+//
+// server.cc
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/bind.hpp>
+#include "experimental/http_server/server.h"
+
+namespace http_server {
+
+server::server(const std::string& address, const std::string& port,
+        const std::string& doc_root) :
+	io_service_(), acceptor_(io_service_), connection_manager_(),
+	        new_connection_(new connection(io_service_, connection_manager_,
+	                request_handler_)), request_handler_(doc_root)
+{
+	// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
+	boost::asio::ip::tcp::resolver resolver(io_service_);
+	boost::asio::ip::tcp::resolver::query query(address, port);
+	boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
+	acceptor_.open(endpoint.protocol());
+	acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
+	acceptor_.bind(endpoint);
+	acceptor_.listen();
+	acceptor_.async_accept(new_connection_->socket(), boost::bind(
+	        &server::handle_accept, this, boost::asio::placeholders::error));
+}
+
+void server::run()
+{
+	// The io_service::run() call will block until all asynchronous operations
+	// have finished. While the server is running, there is always at least one
+	// asynchronous operation outstanding: the asynchronous accept call waiting
+	// for new incoming connections.
+	io_service_.run();
+}
+
+void server::stop()
+{
+	// Post a call to the stop function so that server::stop() is safe to call
+	// from any thread.
+	io_service_.post(boost::bind(&server::handle_stop, this));
+}
+
+void server::handle_accept(const boost::system::error_code& e)
+{
+	if(!e)
+	{
+		connection_manager_.start(new_connection_);
+		new_connection_.reset(new connection(io_service_, connection_manager_,
+		        request_handler_));
+		acceptor_.async_accept(new_connection_->socket(), boost::bind(
+		        &server::handle_accept, this, boost::asio::placeholders::error));
+	}
+}
+
+void server::handle_stop()
+{
+	// The server is stopped by cancelling all outstanding asynchronous
+	// operations. Once all operations have finished the io_service::run() call
+	// will exit.
+	acceptor_.close();
+	connection_manager_.stop_all();
+}
+
+} // namespace http_server

+ 63 - 0
src/experimental/http_server/server.h

@@ -0,0 +1,63 @@
+//
+// server.hpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef EXPERIMENTAL_HTTP_SERVER_HTTP_SERVER_H
+#define EXPERIMENTAL_HTTP_SERVER_HTTP_SERVER_H
+
+#include <boost/asio.hpp>
+#include <string>
+#include <boost/noncopyable.hpp>
+#include "experimental/http_server/connection.h"
+#include "experimental/http_server/connection_manager.h"
+#include "experimental/http_server/request_handler.h"
+
+namespace http_server {
+
+/// The top-level class of the HTTP server.
+class server: private boost::noncopyable
+{
+public:
+	/// Construct the server to listen on the specified TCP address and port, and
+	/// serve up files from the given directory.
+	explicit server(const std::string& address, const std::string& port,
+	        const std::string& doc_root);
+
+	/// Run the server's io_service loop.
+	void run();
+
+	/// Stop the server.
+	void stop();
+
+private:
+	/// Handle completion of an asynchronous accept operation.
+	void handle_accept(const boost::system::error_code& e);
+
+	/// Handle a request to stop the server.
+	void handle_stop();
+
+	/// The io_service used to perform asynchronous operations.
+	boost::asio::io_service io_service_;
+
+	/// Acceptor used to listen for incoming connections.
+	boost::asio::ip::tcp::acceptor acceptor_;
+
+	/// The connection manager which owns all live connections.
+	connection_manager connection_manager_;
+
+	/// The next connection to be accepted.
+	connection_ptr new_connection_;
+
+	/// The handler for all incoming requests.
+	request_handler request_handler_;
+};
+
+} // namespace http_server
+
+#endif // EXPERIMENTAL_HTTP_SERVER_HTTP_SERVER_H