[DEV] add v1.76.0

This commit is contained in:
2021-10-05 21:37:46 +02:00
parent a97e9ae7d4
commit d0115b733d
45133 changed files with 4744437 additions and 1026325 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,285 @@
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/aligned_storage.hpp>
#include <boost/array.hpp>
#include <boost/bind/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
// Class to manage the memory to be used for handler-based custom allocation.
// It contains a single block of memory which may be returned for allocation
// requests. If the memory is in use when an allocation request is made, the
// allocator delegates allocation to the global heap.
class handler_memory
: private boost::noncopyable
{
public:
handler_memory()
: in_use_(false)
{
}
void* allocate(std::size_t size)
{
if (!in_use_ && size < storage_.size)
{
in_use_ = true;
return storage_.address();
}
else
{
return ::operator new(size);
}
}
void deallocate(void* pointer)
{
if (pointer == storage_.address())
{
in_use_ = false;
}
else
{
::operator delete(pointer);
}
}
private:
// Storage space used for handler-based custom memory allocation.
boost::aligned_storage<1024> storage_;
// Whether the handler-based custom allocation storage has been used.
bool in_use_;
};
// The allocator to be associated with the handler objects. This allocator only
// needs to satisfy the C++11 minimal allocator requirements, plus rebind when
// targeting C++03.
template <typename T>
class handler_allocator
{
public:
typedef T value_type;
explicit handler_allocator(handler_memory& mem)
: memory_(mem)
{
}
template <typename U>
handler_allocator(const handler_allocator<U>& other)
: memory_(other.memory_)
{
}
template <typename U>
struct rebind
{
typedef handler_allocator<U> other;
};
bool operator==(const handler_allocator& other) const
{
return &memory_ == &other.memory_;
}
bool operator!=(const handler_allocator& other) const
{
return &memory_ != &other.memory_;
}
T* allocate(std::size_t n) const
{
return static_cast<T*>(memory_.allocate(sizeof(T) * n));
}
void deallocate(T* p, std::size_t /*n*/) const
{
return memory_.deallocate(p);
}
//private:
// The underlying memory.
handler_memory& memory_;
};
// Wrapper class template for handler objects to allow handler memory
// allocation to be customised. The allocator_type typedef and get_allocator()
// member function are used by the asynchronous operations to obtain the
// allocator. Calls to operator() are forwarded to the encapsulated handler.
template <typename Handler>
class custom_alloc_handler
{
public:
typedef handler_allocator<Handler> allocator_type;
custom_alloc_handler(handler_memory& m, Handler h)
: memory_(m),
handler_(h)
{
}
allocator_type get_allocator() const
{
return allocator_type(memory_);
}
template <typename Arg1>
void operator()(Arg1 arg1)
{
handler_(arg1);
}
template <typename Arg1, typename Arg2>
void operator()(Arg1 arg1, Arg2 arg2)
{
handler_(arg1, arg2);
}
private:
handler_memory& memory_;
Handler handler_;
};
// Helper function to wrap a handler object to add custom allocation.
template <typename Handler>
inline custom_alloc_handler<Handler> make_custom_alloc_handler(
handler_memory& m, Handler h)
{
return custom_alloc_handler<Handler>(m, h);
}
class session
: public boost::enable_shared_from_this<session>
{
public:
session(boost::asio::io_context& io_context)
: socket_(io_context)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
socket_.async_read_some(boost::asio::buffer(data_),
make_custom_alloc_handler(handler_memory_,
boost::bind(&session::handle_read,
shared_from_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),
make_custom_alloc_handler(handler_memory_,
boost::bind(&session::handle_write,
shared_from_this(),
boost::asio::placeholders::error)));
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_),
make_custom_alloc_handler(handler_memory_,
boost::bind(&session::handle_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
}
private:
// The socket used to communicate with the client.
tcp::socket socket_;
// Buffer used to store data received from the client.
boost::array<char, 1024> data_;
// The memory to use for handler-based custom memory allocation.
handler_memory handler_memory_;
};
typedef boost::shared_ptr<session> session_ptr;
class server
{
public:
server(boost::asio::io_context& io_context, short port)
: io_context_(io_context),
acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
{
session_ptr new_session(new session(io_context_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session_ptr new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
}
new_session.reset(new session(io_context_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
private:
boost::asio::io_context& io_context_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_context io_context;
using namespace std; // For atoi.
server s(io_context, atoi(argv[1]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,131 @@
//
// reference_counted.cpp
// ~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <vector>
using boost::asio::ip::tcp;
// A reference-counted non-modifiable buffer class.
class shared_const_buffer
{
public:
// Construct from a std::string.
explicit shared_const_buffer(const std::string& data)
: data_(new std::vector<char>(data.begin(), data.end())),
buffer_(boost::asio::buffer(*data_))
{
}
// Implement the ConstBufferSequence requirements.
typedef boost::asio::const_buffer value_type;
typedef const boost::asio::const_buffer* const_iterator;
const boost::asio::const_buffer* begin() const { return &buffer_; }
const boost::asio::const_buffer* end() const { return &buffer_ + 1; }
private:
boost::shared_ptr<std::vector<char> > data_;
boost::asio::const_buffer buffer_;
};
class session
: public boost::enable_shared_from_this<session>
{
public:
session(boost::asio::io_context& io_context)
: socket_(io_context)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
using namespace std; // For time_t, time and ctime.
time_t now = time(0);
shared_const_buffer buffer(ctime(&now));
boost::asio::async_write(socket_, buffer,
boost::bind(&session::handle_write, shared_from_this()));
}
void handle_write()
{
}
private:
// The socket used to communicate with the client.
tcp::socket socket_;
};
typedef boost::shared_ptr<session> session_ptr;
class server
{
public:
server(boost::asio::io_context& io_context, short port)
: io_context_(io_context),
acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
{
session_ptr new_session(new session(io_context_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session_ptr new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
}
new_session.reset(new session(io_context_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
private:
boost::asio::io_context& io_context_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: reference_counted <port>\n";
return 1;
}
boost::asio::io_context io_context;
using namespace std; // For atoi.
server s(io_context, atoi(argv[1]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,178 @@
//
// chat_client.cpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 <deque>
#include <iostream>
#include <boost/bind/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread/thread.hpp>
#include "chat_message.hpp"
using boost::asio::ip::tcp;
typedef std::deque<chat_message> chat_message_queue;
class chat_client
{
public:
chat_client(boost::asio::io_context& io_context,
const tcp::resolver::results_type& endpoints)
: io_context_(io_context),
socket_(io_context)
{
boost::asio::async_connect(socket_, endpoints,
boost::bind(&chat_client::handle_connect, this,
boost::asio::placeholders::error));
}
void write(const chat_message& msg)
{
boost::asio::post(io_context_,
boost::bind(&chat_client::do_write, this, msg));
}
void close()
{
boost::asio::post(io_context_,
boost::bind(&chat_client::do_close, this));
}
private:
void handle_connect(const boost::system::error_code& error)
{
if (!error)
{
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.data(), chat_message::header_length),
boost::bind(&chat_client::handle_read_header, this,
boost::asio::placeholders::error));
}
}
void handle_read_header(const boost::system::error_code& error)
{
if (!error && read_msg_.decode_header())
{
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
boost::bind(&chat_client::handle_read_body, this,
boost::asio::placeholders::error));
}
else
{
do_close();
}
}
void handle_read_body(const boost::system::error_code& error)
{
if (!error)
{
std::cout.write(read_msg_.body(), read_msg_.body_length());
std::cout << "\n";
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.data(), chat_message::header_length),
boost::bind(&chat_client::handle_read_header, this,
boost::asio::placeholders::error));
}
else
{
do_close();
}
}
void do_write(chat_message msg)
{
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(msg);
if (!write_in_progress)
{
boost::asio::async_write(socket_,
boost::asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
boost::bind(&chat_client::handle_write, this,
boost::asio::placeholders::error));
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
write_msgs_.pop_front();
if (!write_msgs_.empty())
{
boost::asio::async_write(socket_,
boost::asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
boost::bind(&chat_client::handle_write, this,
boost::asio::placeholders::error));
}
}
else
{
do_close();
}
}
void do_close()
{
socket_.close();
}
private:
boost::asio::io_context& io_context_;
tcp::socket socket_;
chat_message read_msg_;
chat_message_queue write_msgs_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: chat_client <host> <port>\n";
return 1;
}
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
tcp::resolver::results_type endpoints = resolver.resolve(argv[1], argv[2]);
chat_client c(io_context, endpoints);
boost::thread t(boost::bind(&boost::asio::io_context::run, &io_context));
char line[chat_message::max_body_length + 1];
while (std::cin.getline(line, chat_message::max_body_length + 1))
{
using namespace std; // For strlen and memcpy.
chat_message msg;
msg.body_length(strlen(line));
memcpy(msg.body(), line, msg.body_length());
msg.encode_header();
c.write(msg);
}
c.close();
t.join();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,93 @@
//
// chat_message.hpp
// ~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 CHAT_MESSAGE_HPP
#define CHAT_MESSAGE_HPP
#include <cstdio>
#include <cstdlib>
#include <cstring>
class chat_message
{
public:
enum { header_length = 4 };
enum { max_body_length = 512 };
chat_message()
: body_length_(0)
{
}
const char* data() const
{
return data_;
}
char* data()
{
return data_;
}
size_t length() const
{
return header_length + body_length_;
}
const char* body() const
{
return data_ + header_length;
}
char* body()
{
return data_ + header_length;
}
size_t body_length() const
{
return body_length_;
}
void body_length(size_t new_length)
{
body_length_ = new_length;
if (body_length_ > max_body_length)
body_length_ = max_body_length;
}
bool decode_header()
{
using namespace std; // For strncat and atoi.
char header[header_length + 1] = "";
strncat(header, data_, header_length);
body_length_ = atoi(header);
if (body_length_ > max_body_length)
{
body_length_ = 0;
return false;
}
return true;
}
void encode_header()
{
using namespace std; // For sprintf and memcpy.
char header[header_length + 1] = "";
sprintf(header, "%4d", static_cast<int>(body_length_));
memcpy(data_, header, header_length);
}
private:
char data_[header_length + max_body_length];
size_t body_length_;
};
#endif // CHAT_MESSAGE_HPP

View File

@@ -0,0 +1,249 @@
//
// chat_server.cpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 <cstdlib>
#include <deque>
#include <iostream>
#include <list>
#include <set>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include "chat_message.hpp"
using boost::asio::ip::tcp;
//----------------------------------------------------------------------
typedef std::deque<chat_message> chat_message_queue;
//----------------------------------------------------------------------
class chat_participant
{
public:
virtual ~chat_participant() {}
virtual void deliver(const chat_message& msg) = 0;
};
typedef boost::shared_ptr<chat_participant> chat_participant_ptr;
//----------------------------------------------------------------------
class chat_room
{
public:
void join(chat_participant_ptr participant)
{
participants_.insert(participant);
std::for_each(recent_msgs_.begin(), recent_msgs_.end(),
boost::bind(&chat_participant::deliver,
participant, boost::placeholders::_1));
}
void leave(chat_participant_ptr participant)
{
participants_.erase(participant);
}
void deliver(const chat_message& msg)
{
recent_msgs_.push_back(msg);
while (recent_msgs_.size() > max_recent_msgs)
recent_msgs_.pop_front();
std::for_each(participants_.begin(), participants_.end(),
boost::bind(&chat_participant::deliver,
boost::placeholders::_1, boost::ref(msg)));
}
private:
std::set<chat_participant_ptr> participants_;
enum { max_recent_msgs = 100 };
chat_message_queue recent_msgs_;
};
//----------------------------------------------------------------------
class chat_session
: public chat_participant,
public boost::enable_shared_from_this<chat_session>
{
public:
chat_session(boost::asio::io_context& io_context, chat_room& room)
: socket_(io_context),
room_(room)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
room_.join(shared_from_this());
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.data(), chat_message::header_length),
boost::bind(
&chat_session::handle_read_header, shared_from_this(),
boost::asio::placeholders::error));
}
void deliver(const chat_message& msg)
{
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(msg);
if (!write_in_progress)
{
boost::asio::async_write(socket_,
boost::asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
boost::bind(&chat_session::handle_write, shared_from_this(),
boost::asio::placeholders::error));
}
}
void handle_read_header(const boost::system::error_code& error)
{
if (!error && read_msg_.decode_header())
{
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
boost::bind(&chat_session::handle_read_body, shared_from_this(),
boost::asio::placeholders::error));
}
else
{
room_.leave(shared_from_this());
}
}
void handle_read_body(const boost::system::error_code& error)
{
if (!error)
{
room_.deliver(read_msg_);
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.data(), chat_message::header_length),
boost::bind(&chat_session::handle_read_header, shared_from_this(),
boost::asio::placeholders::error));
}
else
{
room_.leave(shared_from_this());
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
write_msgs_.pop_front();
if (!write_msgs_.empty())
{
boost::asio::async_write(socket_,
boost::asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
boost::bind(&chat_session::handle_write, shared_from_this(),
boost::asio::placeholders::error));
}
}
else
{
room_.leave(shared_from_this());
}
}
private:
tcp::socket socket_;
chat_room& room_;
chat_message read_msg_;
chat_message_queue write_msgs_;
};
typedef boost::shared_ptr<chat_session> chat_session_ptr;
//----------------------------------------------------------------------
class chat_server
{
public:
chat_server(boost::asio::io_context& io_context,
const tcp::endpoint& endpoint)
: io_context_(io_context),
acceptor_(io_context, endpoint)
{
start_accept();
}
void start_accept()
{
chat_session_ptr new_session(new chat_session(io_context_, room_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&chat_server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(chat_session_ptr session,
const boost::system::error_code& error)
{
if (!error)
{
session->start();
}
start_accept();
}
private:
boost::asio::io_context& io_context_;
tcp::acceptor acceptor_;
chat_room room_;
};
typedef boost::shared_ptr<chat_server> chat_server_ptr;
typedef std::list<chat_server_ptr> chat_server_list;
//----------------------------------------------------------------------
int main(int argc, char* argv[])
{
try
{
if (argc < 2)
{
std::cerr << "Usage: chat_server <port> [<port> ...]\n";
return 1;
}
boost::asio::io_context io_context;
chat_server_list servers;
for (int i = 1; i < argc; ++i)
{
using namespace std; // For atoi.
tcp::endpoint endpoint(tcp::v4(), atoi(argv[i]));
chat_server_ptr server(new chat_server(io_context, endpoint));
servers.push_back(server);
}
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,204 @@
//
// posix_chat_client.cpp
// ~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/array.hpp>
#include <boost/bind/bind.hpp>
#include <boost/asio.hpp>
#include "chat_message.hpp"
#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
using boost::asio::ip::tcp;
namespace posix = boost::asio::posix;
class posix_chat_client
{
public:
posix_chat_client(boost::asio::io_context& io_context,
const tcp::resolver::results_type& endpoints)
: socket_(io_context),
input_(io_context, ::dup(STDIN_FILENO)),
output_(io_context, ::dup(STDOUT_FILENO)),
input_buffer_(chat_message::max_body_length)
{
boost::asio::async_connect(socket_, endpoints,
boost::bind(&posix_chat_client::handle_connect, this,
boost::asio::placeholders::error));
}
private:
void handle_connect(const boost::system::error_code& error)
{
if (!error)
{
// Read the fixed-length header of the next message from the server.
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.data(), chat_message::header_length),
boost::bind(&posix_chat_client::handle_read_header, this,
boost::asio::placeholders::error));
// Read a line of input entered by the user.
boost::asio::async_read_until(input_, input_buffer_, '\n',
boost::bind(&posix_chat_client::handle_read_input, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
void handle_read_header(const boost::system::error_code& error)
{
if (!error && read_msg_.decode_header())
{
// Read the variable-length body of the message from the server.
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
boost::bind(&posix_chat_client::handle_read_body, this,
boost::asio::placeholders::error));
}
else
{
close();
}
}
void handle_read_body(const boost::system::error_code& error)
{
if (!error)
{
// Write out the message we just received, terminated by a newline.
static char eol[] = { '\n' };
boost::array<boost::asio::const_buffer, 2> buffers = {{
boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
boost::asio::buffer(eol) }};
boost::asio::async_write(output_, buffers,
boost::bind(&posix_chat_client::handle_write_output, this,
boost::asio::placeholders::error));
}
else
{
close();
}
}
void handle_write_output(const boost::system::error_code& error)
{
if (!error)
{
// Read the fixed-length header of the next message from the server.
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.data(), chat_message::header_length),
boost::bind(&posix_chat_client::handle_read_header, this,
boost::asio::placeholders::error));
}
else
{
close();
}
}
void handle_read_input(const boost::system::error_code& error,
std::size_t length)
{
if (!error)
{
// Write the message (minus the newline) to the server.
write_msg_.body_length(length - 1);
input_buffer_.sgetn(write_msg_.body(), length - 1);
input_buffer_.consume(1); // Remove newline from input.
write_msg_.encode_header();
boost::asio::async_write(socket_,
boost::asio::buffer(write_msg_.data(), write_msg_.length()),
boost::bind(&posix_chat_client::handle_write, this,
boost::asio::placeholders::error));
}
else if (error == boost::asio::error::not_found)
{
// Didn't get a newline. Send whatever we have.
write_msg_.body_length(input_buffer_.size());
input_buffer_.sgetn(write_msg_.body(), input_buffer_.size());
write_msg_.encode_header();
boost::asio::async_write(socket_,
boost::asio::buffer(write_msg_.data(), write_msg_.length()),
boost::bind(&posix_chat_client::handle_write, this,
boost::asio::placeholders::error));
}
else
{
close();
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
// Read a line of input entered by the user.
boost::asio::async_read_until(input_, input_buffer_, '\n',
boost::bind(&posix_chat_client::handle_read_input, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
close();
}
}
void close()
{
// Cancel all outstanding asynchronous operations.
socket_.close();
input_.close();
output_.close();
}
private:
tcp::socket socket_;
posix::stream_descriptor input_;
posix::stream_descriptor output_;
chat_message read_msg_;
chat_message write_msg_;
boost::asio::streambuf input_buffer_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: posix_chat_client <host> <port>\n";
return 1;
}
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
tcp::resolver::results_type endpoints = resolver.resolve(argv[1], argv[2]);
posix_chat_client c(io_context, endpoints);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
#else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
int main() {}
#endif // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)

View File

@@ -0,0 +1,137 @@
//
// async_tcp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class session
{
public:
session(boost::asio::io_context& io_context)
: socket_(io_context)
{
}
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));
}
private:
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;
}
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_context& io_context, short port)
: io_context_(io_context),
acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
{
start_accept();
}
private:
void start_accept()
{
session* new_session = new session(io_context_);
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();
}
else
{
delete new_session;
}
start_accept();
}
boost::asio::io_context& io_context_;
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_context io_context;
using namespace std; // For atoi.
server s(io_context, atoi(argv[1]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,92 @@
//
// async_udp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::udp;
class server
{
public:
server(boost::asio::io_context& io_context, short port)
: socket_(io_context, 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:
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_context io_context;
using namespace std; // For atoi.
server s(io_context, atoi(argv[1]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,59 @@
//
// blocking_tcp_echo_client.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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_context io_context;
tcp::resolver resolver(io_context);
tcp::resolver::results_type endpoints =
resolver.resolve(tcp::v4(), argv[1], argv[2]);
tcp::socket s(io_context);
boost::asio::connect(s, endpoints);
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;
}

View File

@@ -0,0 +1,80 @@
//
// blocking_tcp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/bind.hpp>
#include <boost/smart_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/thread/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_context& io_context, unsigned short port)
{
tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port));
for (;;)
{
socket_ptr sock(new tcp::socket(io_context));
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_context io_context;
using namespace std; // For atoi.
server(io_context, atoi(argv[1]));
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,59 @@
//
// blocking_udp_echo_client.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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::udp;
enum { max_length = 1024 };
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: blocking_udp_echo_client <host> <port>\n";
return 1;
}
boost::asio::io_context io_context;
udp::socket s(io_context, udp::endpoint(udp::v4(), 0));
udp::resolver resolver(io_context);
udp::resolver::results_type endpoints =
resolver.resolve(udp::v4(), argv[1], argv[2]);
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);
s.send_to(boost::asio::buffer(request, request_length), *endpoints.begin());
char reply[max_length];
udp::endpoint sender_endpoint;
size_t reply_length = s.receive_from(
boost::asio::buffer(reply, max_length), sender_endpoint);
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;
}

View File

@@ -0,0 +1,53 @@
//
// blocking_udp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio.hpp>
using boost::asio::ip::udp;
enum { max_length = 1024 };
void server(boost::asio::io_context& io_context, unsigned short port)
{
udp::socket sock(io_context, udp::endpoint(udp::v4(), port));
for (;;)
{
char data[max_length];
udp::endpoint sender_endpoint;
size_t length = sock.receive_from(
boost::asio::buffer(data, max_length), sender_endpoint);
sock.send_to(boost::asio::buffer(data, length), sender_endpoint);
}
}
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: blocking_udp_echo_server <port>\n";
return 1;
}
boost::asio::io_context io_context;
using namespace std; // For atoi.
server(io_context, atoi(argv[1]));
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,190 @@
//
// daemon.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio/io_context.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/array.hpp>
#include <boost/bind/bind.hpp>
#include <ctime>
#include <iostream>
#include <syslog.h>
#include <unistd.h>
using boost::asio::ip::udp;
class udp_daytime_server
{
public:
udp_daytime_server(boost::asio::io_context& io_context)
: socket_(io_context, udp::endpoint(udp::v4(), 13))
{
start_receive();
}
private:
void start_receive()
{
socket_.async_receive_from(
boost::asio::buffer(recv_buffer_), remote_endpoint_,
boost::bind(&udp_daytime_server::handle_receive,
this, boost::placeholders::_1));
}
void handle_receive(const boost::system::error_code& ec)
{
if (!ec)
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
std::string message = ctime(&now);
boost::system::error_code ignored_ec;
socket_.send_to(boost::asio::buffer(message),
remote_endpoint_, 0, ignored_ec);
}
start_receive();
}
udp::socket socket_;
udp::endpoint remote_endpoint_;
boost::array<char, 1> recv_buffer_;
};
int main()
{
try
{
boost::asio::io_context io_context;
// Initialise the server before becoming a daemon. If the process is
// started from a shell, this means any errors will be reported back to the
// user.
udp_daytime_server server(io_context);
// Register signal handlers so that the daemon may be shut down. You may
// also want to register for other signals, such as SIGHUP to trigger a
// re-read of a configuration file.
boost::asio::signal_set signals(io_context, SIGINT, SIGTERM);
signals.async_wait(
boost::bind(&boost::asio::io_context::stop, &io_context));
// Inform the io_context that we are about to become a daemon. The
// io_context cleans up any internal resources, such as threads, that may
// interfere with forking.
io_context.notify_fork(boost::asio::io_context::fork_prepare);
// Fork the process and have the parent exit. If the process was started
// from a shell, this returns control to the user. Forking a new process is
// also a prerequisite for the subsequent call to setsid().
if (pid_t pid = fork())
{
if (pid > 0)
{
// We're in the parent process and need to exit.
//
// When the exit() function is used, the program terminates without
// invoking local variables' destructors. Only global variables are
// destroyed. As the io_context object is a local variable, this means
// we do not have to call:
//
// io_context.notify_fork(boost::asio::io_context::fork_parent);
//
// However, this line should be added before each call to exit() if
// using a global io_context object. An additional call:
//
// io_context.notify_fork(boost::asio::io_context::fork_prepare);
//
// should also precede the second fork().
exit(0);
}
else
{
syslog(LOG_ERR | LOG_USER, "First fork failed: %m");
return 1;
}
}
// Make the process a new session leader. This detaches it from the
// terminal.
setsid();
// A process inherits its working directory from its parent. This could be
// on a mounted filesystem, which means that the running daemon would
// prevent this filesystem from being unmounted. Changing to the root
// directory avoids this problem.
chdir("/");
// The file mode creation mask is also inherited from the parent process.
// We don't want to restrict the permissions on files created by the
// daemon, so the mask is cleared.
umask(0);
// A second fork ensures the process cannot acquire a controlling terminal.
if (pid_t pid = fork())
{
if (pid > 0)
{
exit(0);
}
else
{
syslog(LOG_ERR | LOG_USER, "Second fork failed: %m");
return 1;
}
}
// Close the standard streams. This decouples the daemon from the terminal
// that started it.
close(0);
close(1);
close(2);
// We don't want the daemon to have any standard input.
if (open("/dev/null", O_RDONLY) < 0)
{
syslog(LOG_ERR | LOG_USER, "Unable to open /dev/null: %m");
return 1;
}
// Send standard output to a log file.
const char* output = "/tmp/asio.daemon.out";
const int flags = O_WRONLY | O_CREAT | O_APPEND;
const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
if (open(output, flags, mode) < 0)
{
syslog(LOG_ERR | LOG_USER, "Unable to open output file %s: %m", output);
return 1;
}
// Also send standard error to the same log file.
if (dup(1) < 0)
{
syslog(LOG_ERR | LOG_USER, "Unable to dup output descriptor: %m");
return 1;
}
// Inform the io_context that we have finished becoming a daemon. The
// io_context uses this opportunity to create any internal file descriptors
// that need to be private to the new process.
io_context.notify_fork(boost::asio::io_context::fork_child);
// The io_context can now be used normally.
syslog(LOG_INFO | LOG_USER, "Daemon started");
io_context.run();
syslog(LOG_INFO | LOG_USER, "Daemon stopped");
}
catch (std::exception& e)
{
syslog(LOG_ERR | LOG_USER, "Exception: %s", e.what());
std::cerr << "Exception: " << e.what() << std::endl;
}
}

View File

@@ -0,0 +1,161 @@
//
// process_per_connection.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/write.hpp>
#include <boost/array.hpp>
#include <boost/bind/bind.hpp>
#include <cstdlib>
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using boost::asio::ip::tcp;
class server
{
public:
server(boost::asio::io_context& io_context, unsigned short port)
: io_context_(io_context),
signal_(io_context, SIGCHLD),
acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
socket_(io_context)
{
start_signal_wait();
start_accept();
}
private:
void start_signal_wait()
{
signal_.async_wait(boost::bind(&server::handle_signal_wait, this));
}
void handle_signal_wait()
{
// Only the parent process should check for this signal. We can determine
// whether we are in the parent by checking if the acceptor is still open.
if (acceptor_.is_open())
{
// Reap completed child processes so that we don't end up with zombies.
int status = 0;
while (waitpid(-1, &status, WNOHANG) > 0) {}
start_signal_wait();
}
}
void start_accept()
{
acceptor_.async_accept(socket_,
boost::bind(&server::handle_accept, this, boost::placeholders::_1));
}
void handle_accept(const boost::system::error_code& ec)
{
if (!ec)
{
// Inform the io_context that we are about to fork. The io_context cleans
// up any internal resources, such as threads, that may interfere with
// forking.
io_context_.notify_fork(boost::asio::io_context::fork_prepare);
if (fork() == 0)
{
// Inform the io_context that the fork is finished and that this is the
// child process. The io_context uses this opportunity to create any
// internal file descriptors that must be private to the new process.
io_context_.notify_fork(boost::asio::io_context::fork_child);
// The child won't be accepting new connections, so we can close the
// acceptor. It remains open in the parent.
acceptor_.close();
// The child process is not interested in processing the SIGCHLD signal.
signal_.cancel();
start_read();
}
else
{
// Inform the io_context that the fork is finished (or failed) and that
// this is the parent process. The io_context uses this opportunity to
// recreate any internal resources that were cleaned up during
// preparation for the fork.
io_context_.notify_fork(boost::asio::io_context::fork_parent);
socket_.close();
start_accept();
}
}
else
{
std::cerr << "Accept error: " << ec.message() << std::endl;
start_accept();
}
}
void start_read()
{
socket_.async_read_some(boost::asio::buffer(data_),
boost::bind(&server::handle_read, this,
boost::placeholders::_1, boost::placeholders::_2));
}
void handle_read(const boost::system::error_code& ec, std::size_t length)
{
if (!ec)
start_write(length);
}
void start_write(std::size_t length)
{
boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
boost::bind(&server::handle_write, this, boost::placeholders::_1));
}
void handle_write(const boost::system::error_code& ec)
{
if (!ec)
start_read();
}
boost::asio::io_context& io_context_;
boost::asio::signal_set signal_;
tcp::acceptor acceptor_;
tcp::socket socket_;
boost::array<char, 1024> data_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: process_per_connection <port>\n";
return 1;
}
boost::asio::io_context io_context;
using namespace std; // For atoi.
server s(io_context, atoi(argv[1]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
}
}

View File

@@ -0,0 +1,204 @@
//
// async_client.cpp
// ~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/bind.hpp>
using boost::asio::ip::tcp;
class client
{
public:
client(boost::asio::io_context& io_context,
const std::string& server, const std::string& path)
: resolver_(io_context),
socket_(io_context)
{
// 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.
resolver_.async_resolve(server, "http",
boost::bind(&client::handle_resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::results));
}
private:
void handle_resolve(const boost::system::error_code& err,
const tcp::resolver::results_type& endpoints)
{
if (!err)
{
// Attempt a connection to each endpoint in the list until we
// successfully establish a connection.
boost::asio::async_connect(socket_, endpoints,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Error: " << err.message() << "\n";
}
}
void handle_connect(const boost::system::error_code& err)
{
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
{
std::cout << "Error: " << err.message() << "\n";
}
}
void handle_write_request(const boost::system::error_code& err)
{
if (!err)
{
// Read the response status line. The response_ streambuf will
// automatically grow to accommodate the entire line. The growth may be
// limited by passing a maximum size to the streambuf constructor.
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_context io_context;
client c(io_context, argv[1], argv[2]);
io_context.run();
}
catch (std::exception& e)
{
std::cout << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,106 @@
//
// sync_client.cpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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_context io_context;
// Get a list of endpoints corresponding to the server name.
tcp::resolver resolver(io_context);
tcp::resolver::results_type endpoints = resolver.resolve(argv[1], "http");
// Try each endpoint until we successfully establish a connection.
tcp::socket socket(io_context);
boost::asio::connect(socket, endpoints);
// 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. The response streambuf will automatically
// grow to accommodate the entire line. The growth may be limited by passing
// a maximum size to the streambuf constructor.
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.
boost::system::error_code error;
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;
}

View File

@@ -0,0 +1,99 @@
//
// connection.cpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "connection.hpp"
#include <vector>
#include <boost/bind/bind.hpp>
#include "connection_manager.hpp"
#include "request_handler.hpp"
namespace http {
namespace server {
connection::connection(boost::asio::io_context& io_context,
connection_manager& manager, request_handler& handler)
: socket_(io_context),
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 server
} // namespace http

View File

@@ -0,0 +1,83 @@
//
// connection.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_CONNECTION_HPP
#define HTTP_CONNECTION_HPP
#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 "reply.hpp"
#include "request.hpp"
#include "request_handler.hpp"
#include "request_parser.hpp"
namespace http {
namespace 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_context.
explicit connection(boost::asio::io_context& io_context,
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 server
} // namespace http
#endif // HTTP_CONNECTION_HPP

View File

@@ -0,0 +1,38 @@
//
// connection_manager.cpp
// ~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "connection_manager.hpp"
#include <algorithm>
#include <boost/bind/bind.hpp>
namespace http {
namespace 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, boost::placeholders::_1));
connections_.clear();
}
} // namespace server
} // namespace http

View File

@@ -0,0 +1,44 @@
//
// connection_manager.hpp
// ~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_CONNECTION_MANAGER_HPP
#define HTTP_CONNECTION_MANAGER_HPP
#include <set>
#include <boost/noncopyable.hpp>
#include "connection.hpp"
namespace http {
namespace 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 server
} // namespace http
#endif // HTTP_CONNECTION_MANAGER_HPP

View File

@@ -0,0 +1,28 @@
//
// header.hpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_HEADER_HPP
#define HTTP_HEADER_HPP
#include <string>
namespace http {
namespace server {
struct header
{
std::string name;
std::string value;
};
} // namespace server
} // namespace http
#endif // HTTP_HEADER_HPP

View File

@@ -0,0 +1,44 @@
//
// main.cpp
// ~~~~~~~~
//
// Copyright (c) 2003-2021 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/bind/bind.hpp>
#include "server.hpp"
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;
}
// Initialise the server.
http::server::server s(argv[1], argv[2], argv[3]);
// Run the server until stopped.
s.run();
}
catch (std::exception& e)
{
std::cerr << "exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,46 @@
//
// mime_types.cpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "mime_types.hpp"
namespace http {
namespace 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 server
} // namespace http

View File

@@ -0,0 +1,27 @@
//
// mime_types.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_MIME_TYPES_HPP
#define HTTP_MIME_TYPES_HPP
#include <string>
namespace http {
namespace 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 server
} // namespace http
#endif // HTTP_MIME_TYPES_HPP

View File

@@ -0,0 +1,256 @@
//
// reply.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "reply.hpp"
#include <string>
#include <boost/lexical_cast.hpp>
namespace http {
namespace 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 server
} // namespace http

View File

@@ -0,0 +1,64 @@
//
// reply.hpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_REPLY_HPP
#define HTTP_REPLY_HPP
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include "header.hpp"
namespace http {
namespace 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 server
} // namespace http
#endif // HTTP_REPLY_HPP

View File

@@ -0,0 +1,34 @@
//
// request.hpp
// ~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_REQUEST_HPP
#define HTTP_REQUEST_HPP
#include <string>
#include <vector>
#include "header.hpp"
namespace http {
namespace 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 server
} // namespace http
#endif // HTTP_REQUEST_HPP

View File

@@ -0,0 +1,122 @@
//
// request_handler.cpp
// ~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "request_handler.hpp"
#include <fstream>
#include <sstream>
#include <string>
#include <boost/lexical_cast.hpp>
#include "mime_types.hpp"
#include "reply.hpp"
#include "request.hpp"
namespace http {
namespace 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 server
} // namespace http

View File

@@ -0,0 +1,46 @@
//
// request_handler.hpp
// ~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_REQUEST_HANDLER_HPP
#define HTTP_REQUEST_HANDLER_HPP
#include <string>
#include <boost/noncopyable.hpp>
namespace http {
namespace 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 server
} // namespace http
#endif // HTTP_REQUEST_HANDLER_HPP

View File

@@ -0,0 +1,315 @@
//
// request_parser.cpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "request_parser.hpp"
#include "request.hpp"
namespace http {
namespace 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:
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 server
} // namespace http

View File

@@ -0,0 +1,95 @@
//
// request_parser.hpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_REQUEST_PARSER_HPP
#define HTTP_REQUEST_PARSER_HPP
#include <boost/logic/tribool.hpp>
#include <boost/tuple/tuple.hpp>
namespace http {
namespace 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,
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 server
} // namespace http
#endif // HTTP_REQUEST_PARSER_HPP

View File

@@ -0,0 +1,94 @@
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "server.hpp"
#include <boost/bind/bind.hpp>
#include <signal.h>
namespace http {
namespace server {
server::server(const std::string& address, const std::string& port,
const std::string& doc_root)
: io_context_(),
signals_(io_context_),
acceptor_(io_context_),
connection_manager_(),
new_connection_(),
request_handler_(doc_root)
{
// Register to handle the signals that indicate when the server should exit.
// It is safe to register for the same signal multiple times in a program,
// provided all registration for the specified signal is made through Asio.
signals_.add(SIGINT);
signals_.add(SIGTERM);
#if defined(SIGQUIT)
signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)
signals_.async_wait(boost::bind(&server::handle_stop, this));
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(io_context_);
boost::asio::ip::tcp::endpoint endpoint =
*resolver.resolve(address, port).begin();
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
acceptor_.listen();
start_accept();
}
void server::run()
{
// The io_context::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_context_.run();
}
void server::start_accept()
{
new_connection_.reset(new connection(io_context_,
connection_manager_, request_handler_));
acceptor_.async_accept(new_connection_->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error));
}
void server::handle_accept(const boost::system::error_code& e)
{
// Check whether the server was stopped by a signal before this completion
// handler had a chance to run.
if (!acceptor_.is_open())
{
return;
}
if (!e)
{
connection_manager_.start(new_connection_);
}
start_accept();
}
void server::handle_stop()
{
// The server is stopped by cancelling all outstanding asynchronous
// operations. Once all operations have finished the io_context::run() call
// will exit.
acceptor_.close();
connection_manager_.stop_all();
}
} // namespace server
} // namespace http

View File

@@ -0,0 +1,69 @@
//
// server.hpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER_HPP
#define HTTP_SERVER_HPP
#include <boost/asio.hpp>
#include <string>
#include <boost/noncopyable.hpp>
#include "connection.hpp"
#include "connection_manager.hpp"
#include "request_handler.hpp"
namespace http {
namespace 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_context loop.
void run();
private:
/// Initiate an asynchronous accept operation.
void start_accept();
/// 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_context used to perform asynchronous operations.
boost::asio::io_context io_context_;
/// The signal_set is used to register for process termination notifications.
boost::asio::signal_set signals_;
/// 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 server
} // namespace http
#endif // HTTP_SERVER_HPP

View File

@@ -0,0 +1,93 @@
//
// connection.cpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "connection.hpp"
#include <vector>
#include <boost/bind/bind.hpp>
#include "request_handler.hpp"
namespace http {
namespace server2 {
connection::connection(boost::asio::io_context& io_context,
request_handler& handler)
: socket_(io_context),
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::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));
}
}
// If an error occurs then no new asynchronous operations are started. This
// means that all shared_ptr references to the connection object will
// disappear and the object will be destroyed automatically after this
// handler returns. The connection class's destructor closes the socket.
}
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);
}
// No new asynchronous operations are started. This means that all shared_ptr
// references to the connection object will disappear and the object will be
// destroyed automatically after this handler returns. The connection class's
// destructor closes the socket.
}
} // namespace server2
} // namespace http

View File

@@ -0,0 +1,75 @@
//
// connection.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER2_CONNECTION_HPP
#define HTTP_SERVER2_CONNECTION_HPP
#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 "reply.hpp"
#include "request.hpp"
#include "request_handler.hpp"
#include "request_parser.hpp"
namespace http {
namespace server2 {
/// 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_context.
explicit connection(boost::asio::io_context& io_context,
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();
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 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 server2
} // namespace http
#endif // HTTP_SERVER2_CONNECTION_HPP

View File

@@ -0,0 +1,28 @@
//
// header.hpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER2_HEADER_HPP
#define HTTP_SERVER2_HEADER_HPP
#include <string>
namespace http {
namespace server2 {
struct header
{
std::string name;
std::string value;
};
} // namespace server2
} // namespace http
#endif // HTTP_SERVER2_HEADER_HPP

View File

@@ -0,0 +1,71 @@
//
// io_context_pool.cpp
// ~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "server.hpp"
#include <stdexcept>
#include <boost/thread/thread.hpp>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
namespace http {
namespace server2 {
io_context_pool::io_context_pool(std::size_t pool_size)
: next_io_context_(0)
{
if (pool_size == 0)
throw std::runtime_error("io_context_pool size is 0");
// Give all the io_contexts work to do so that their run() functions will not
// exit until they are explicitly stopped.
for (std::size_t i = 0; i < pool_size; ++i)
{
io_context_ptr io_context(new boost::asio::io_context);
io_contexts_.push_back(io_context);
work_.push_back(boost::asio::require(io_context->get_executor(),
boost::asio::execution::outstanding_work.tracked));
}
}
void io_context_pool::run()
{
// Create a pool of threads to run all of the io_contexts.
std::vector<boost::shared_ptr<boost::thread> > threads;
for (std::size_t i = 0; i < io_contexts_.size(); ++i)
{
boost::shared_ptr<boost::thread> thread(new boost::thread(
boost::bind(&boost::asio::io_context::run, io_contexts_[i])));
threads.push_back(thread);
}
// Wait for all threads in the pool to exit.
for (std::size_t i = 0; i < threads.size(); ++i)
threads[i]->join();
}
void io_context_pool::stop()
{
// Explicitly stop all io_contexts.
for (std::size_t i = 0; i < io_contexts_.size(); ++i)
io_contexts_[i]->stop();
}
boost::asio::io_context& io_context_pool::get_io_context()
{
// Use a round-robin scheme to choose the next io_context to use.
boost::asio::io_context& io_context = *io_contexts_[next_io_context_];
++next_io_context_;
if (next_io_context_ == io_contexts_.size())
next_io_context_ = 0;
return io_context;
}
} // namespace server2
} // namespace http

View File

@@ -0,0 +1,56 @@
//
// io_context_pool.hpp
// ~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER2_IO_SERVICE_POOL_HPP
#define HTTP_SERVER2_IO_SERVICE_POOL_HPP
#include <boost/asio.hpp>
#include <list>
#include <vector>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
namespace http {
namespace server2 {
/// A pool of io_context objects.
class io_context_pool
: private boost::noncopyable
{
public:
/// Construct the io_context pool.
explicit io_context_pool(std::size_t pool_size);
/// Run all io_context objects in the pool.
void run();
/// Stop all io_context objects in the pool.
void stop();
/// Get an io_context to use.
boost::asio::io_context& get_io_context();
private:
typedef boost::shared_ptr<boost::asio::io_context> io_context_ptr;
/// The pool of io_contexts.
std::vector<io_context_ptr> io_contexts_;
/// The work-tracking executors that keep the io_contexts running.
std::list<boost::asio::any_io_executor> work_;
/// The next io_context to use for a connection.
std::size_t next_io_context_;
};
} // namespace server2
} // namespace http
#endif // HTTP_SERVER2_IO_SERVICE_POOL_HPP

View File

@@ -0,0 +1,46 @@
//
// main.cpp
// ~~~~~~~~
//
// Copyright (c) 2003-2021 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/bind/bind.hpp>
#include <boost/lexical_cast.hpp>
#include "server.hpp"
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 5)
{
std::cerr << "Usage: http_server <address> <port> <threads> <doc_root>\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver 0.0.0.0 80 1 .\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver 0::0 80 1 .\n";
return 1;
}
// Initialise the server.
std::size_t num_threads = boost::lexical_cast<std::size_t>(argv[3]);
http::server2::server s(argv[1], argv[2], argv[4], num_threads);
// Run the server until stopped.
s.run();
}
catch (std::exception& e)
{
std::cerr << "exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,46 @@
//
// mime_types.cpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "mime_types.hpp"
namespace http {
namespace server2 {
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 server2
} // namespace http

View File

@@ -0,0 +1,27 @@
//
// mime_types.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER2_MIME_TYPES_HPP
#define HTTP_SERVER2_MIME_TYPES_HPP
#include <string>
namespace http {
namespace server2 {
namespace mime_types {
/// Convert a file extension into a MIME type.
std::string extension_to_type(const std::string& extension);
} // namespace mime_types
} // namespace server2
} // namespace http
#endif // HTTP_SERVER2_MIME_TYPES_HPP

View File

@@ -0,0 +1,256 @@
//
// reply.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "reply.hpp"
#include <string>
#include <boost/lexical_cast.hpp>
namespace http {
namespace server2 {
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 server2
} // namespace http

View File

@@ -0,0 +1,64 @@
//
// reply.hpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER2_REPLY_HPP
#define HTTP_SERVER2_REPLY_HPP
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include "header.hpp"
namespace http {
namespace server2 {
/// 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 server2
} // namespace http
#endif // HTTP_SERVER2_REPLY_HPP

View File

@@ -0,0 +1,34 @@
//
// request.hpp
// ~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER2_REQUEST_HPP
#define HTTP_SERVER2_REQUEST_HPP
#include <string>
#include <vector>
#include "header.hpp"
namespace http {
namespace server2 {
/// 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 server2
} // namespace http
#endif // HTTP_SERVER2_REQUEST_HPP

View File

@@ -0,0 +1,122 @@
//
// request_handler.cpp
// ~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "request_handler.hpp"
#include <fstream>
#include <sstream>
#include <string>
#include <boost/lexical_cast.hpp>
#include "mime_types.hpp"
#include "reply.hpp"
#include "request.hpp"
namespace http {
namespace server2 {
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 server2
} // namespace http

View File

@@ -0,0 +1,46 @@
//
// request_handler.hpp
// ~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER2_REQUEST_HANDLER_HPP
#define HTTP_SERVER2_REQUEST_HANDLER_HPP
#include <string>
#include <boost/noncopyable.hpp>
namespace http {
namespace server2 {
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 server2
} // namespace http
#endif // HTTP_SERVER2_REQUEST_HANDLER_HPP

View File

@@ -0,0 +1,315 @@
//
// request_parser.cpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "request_parser.hpp"
#include "request.hpp"
namespace http {
namespace server2 {
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:
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 server2
} // namespace http

View File

@@ -0,0 +1,95 @@
//
// request_parser.hpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER2_REQUEST_PARSER_HPP
#define HTTP_SERVER2_REQUEST_PARSER_HPP
#include <boost/logic/tribool.hpp>
#include <boost/tuple/tuple.hpp>
namespace http {
namespace server2 {
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,
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 server2
} // namespace http
#endif // HTTP_SERVER2_REQUEST_PARSER_HPP

View File

@@ -0,0 +1,77 @@
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "server.hpp"
#include <boost/bind/bind.hpp>
namespace http {
namespace server2 {
server::server(const std::string& address, const std::string& port,
const std::string& doc_root, std::size_t io_context_pool_size)
: io_context_pool_(io_context_pool_size),
signals_(io_context_pool_.get_io_context()),
acceptor_(io_context_pool_.get_io_context()),
new_connection_(),
request_handler_(doc_root)
{
// Register to handle the signals that indicate when the server should exit.
// It is safe to register for the same signal multiple times in a program,
// provided all registration for the specified signal is made through Asio.
signals_.add(SIGINT);
signals_.add(SIGTERM);
#if defined(SIGQUIT)
signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)
signals_.async_wait(boost::bind(&server::handle_stop, this));
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(acceptor_.get_executor());
boost::asio::ip::tcp::endpoint endpoint =
*resolver.resolve(address, port).begin();
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
acceptor_.listen();
start_accept();
}
void server::run()
{
io_context_pool_.run();
}
void server::start_accept()
{
new_connection_.reset(new connection(
io_context_pool_.get_io_context(), request_handler_));
acceptor_.async_accept(new_connection_->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error));
}
void server::handle_accept(const boost::system::error_code& e)
{
if (!e)
{
new_connection_->start();
}
start_accept();
}
void server::handle_stop()
{
io_context_pool_.stop();
}
} // namespace server2
} // namespace http

View File

@@ -0,0 +1,68 @@
//
// server.hpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER2_SERVER_HPP
#define HTTP_SERVER2_SERVER_HPP
#include <boost/asio.hpp>
#include <string>
#include <vector>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include "connection.hpp"
#include "io_context_pool.hpp"
#include "request_handler.hpp"
namespace http {
namespace server2 {
/// 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, std::size_t io_context_pool_size);
/// Run the server's io_context loop.
void run();
private:
/// Initiate an asynchronous accept operation.
void start_accept();
/// 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 pool of io_context objects used to perform asynchronous operations.
io_context_pool io_context_pool_;
/// The signal_set is used to register for process termination notifications.
boost::asio::signal_set signals_;
/// Acceptor used to listen for incoming connections.
boost::asio::ip::tcp::acceptor acceptor_;
/// The next connection to be accepted.
connection_ptr new_connection_;
/// The handler for all incoming requests.
request_handler request_handler_;
};
} // namespace server2
} // namespace http
#endif // HTTP_SERVER2_SERVER_HPP

View File

@@ -0,0 +1,94 @@
//
// connection.cpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "connection.hpp"
#include <vector>
#include <boost/bind/bind.hpp>
#include "request_handler.hpp"
namespace http {
namespace server3 {
connection::connection(boost::asio::io_context& io_context,
request_handler& handler)
: strand_(boost::asio::make_strand(io_context)),
socket_(strand_),
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::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));
}
}
// If an error occurs then no new asynchronous operations are started. This
// means that all shared_ptr references to the connection object will
// disappear and the object will be destroyed automatically after this
// handler returns. The connection class's destructor closes the socket.
}
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);
}
// No new asynchronous operations are started. This means that all shared_ptr
// references to the connection object will disappear and the object will be
// destroyed automatically after this handler returns. The connection class's
// destructor closes the socket.
}
} // namespace server3
} // namespace http

View File

@@ -0,0 +1,78 @@
//
// connection.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER3_CONNECTION_HPP
#define HTTP_SERVER3_CONNECTION_HPP
#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 "reply.hpp"
#include "request.hpp"
#include "request_handler.hpp"
#include "request_parser.hpp"
namespace http {
namespace server3 {
/// 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_context.
explicit connection(boost::asio::io_context& io_context,
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();
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);
/// Strand to ensure the connection's handlers are not called concurrently.
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
/// Socket for the connection.
boost::asio::ip::tcp::socket socket_;
/// 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 server3
} // namespace http
#endif // HTTP_SERVER3_CONNECTION_HPP

View File

@@ -0,0 +1,28 @@
//
// header.hpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER3_HEADER_HPP
#define HTTP_SERVER3_HEADER_HPP
#include <string>
namespace http {
namespace server3 {
struct header
{
std::string name;
std::string value;
};
} // namespace server3
} // namespace http
#endif // HTTP_SERVER3_HEADER_HPP

View File

@@ -0,0 +1,46 @@
//
// main.cpp
// ~~~~~~~~
//
// Copyright (c) 2003-2021 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/bind/bind.hpp>
#include <boost/lexical_cast.hpp>
#include "server.hpp"
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 5)
{
std::cerr << "Usage: http_server <address> <port> <threads> <doc_root>\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver 0.0.0.0 80 1 .\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver 0::0 80 1 .\n";
return 1;
}
// Initialise the server.
std::size_t num_threads = boost::lexical_cast<std::size_t>(argv[3]);
http::server3::server s(argv[1], argv[2], argv[4], num_threads);
// Run the server until stopped.
s.run();
}
catch (std::exception& e)
{
std::cerr << "exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,46 @@
//
// mime_types.cpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "mime_types.hpp"
namespace http {
namespace server3 {
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 server3
} // namespace http

View File

@@ -0,0 +1,27 @@
//
// mime_types.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER3_MIME_TYPES_HPP
#define HTTP_SERVER3_MIME_TYPES_HPP
#include <string>
namespace http {
namespace server3 {
namespace mime_types {
/// Convert a file extension into a MIME type.
std::string extension_to_type(const std::string& extension);
} // namespace mime_types
} // namespace server3
} // namespace http
#endif // HTTP_SERVER3_MIME_TYPES_HPP

View File

@@ -0,0 +1,256 @@
//
// reply.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "reply.hpp"
#include <string>
#include <boost/lexical_cast.hpp>
namespace http {
namespace server3 {
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 server3
} // namespace http

View File

@@ -0,0 +1,64 @@
//
// reply.hpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER3_REPLY_HPP
#define HTTP_SERVER3_REPLY_HPP
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include "header.hpp"
namespace http {
namespace server3 {
/// 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 server3
} // namespace http
#endif // HTTP_SERVER3_REPLY_HPP

View File

@@ -0,0 +1,34 @@
//
// request.hpp
// ~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER3_REQUEST_HPP
#define HTTP_SERVER3_REQUEST_HPP
#include <string>
#include <vector>
#include "header.hpp"
namespace http {
namespace server3 {
/// 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 server3
} // namespace http
#endif // HTTP_SERVER3_REQUEST_HPP

View File

@@ -0,0 +1,122 @@
//
// request_handler.cpp
// ~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "request_handler.hpp"
#include <fstream>
#include <sstream>
#include <string>
#include <boost/lexical_cast.hpp>
#include "mime_types.hpp"
#include "reply.hpp"
#include "request.hpp"
namespace http {
namespace server3 {
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 server3
} // namespace http

View File

@@ -0,0 +1,46 @@
//
// request_handler.hpp
// ~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER3_REQUEST_HANDLER_HPP
#define HTTP_SERVER3_REQUEST_HANDLER_HPP
#include <string>
#include <boost/noncopyable.hpp>
namespace http {
namespace server3 {
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 server3
} // namespace http
#endif // HTTP_SERVER3_REQUEST_HANDLER_HPP

View File

@@ -0,0 +1,315 @@
//
// request_parser.cpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "request_parser.hpp"
#include "request.hpp"
namespace http {
namespace server3 {
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:
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 server3
} // namespace http

View File

@@ -0,0 +1,95 @@
//
// request_parser.hpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER3_REQUEST_PARSER_HPP
#define HTTP_SERVER3_REQUEST_PARSER_HPP
#include <boost/logic/tribool.hpp>
#include <boost/tuple/tuple.hpp>
namespace http {
namespace server3 {
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,
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 server3
} // namespace http
#endif // HTTP_SERVER3_REQUEST_PARSER_HPP

View File

@@ -0,0 +1,90 @@
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "server.hpp"
#include <boost/thread/thread.hpp>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <vector>
namespace http {
namespace server3 {
server::server(const std::string& address, const std::string& port,
const std::string& doc_root, std::size_t thread_pool_size)
: thread_pool_size_(thread_pool_size),
signals_(io_context_),
acceptor_(io_context_),
new_connection_(),
request_handler_(doc_root)
{
// Register to handle the signals that indicate when the server should exit.
// It is safe to register for the same signal multiple times in a program,
// provided all registration for the specified signal is made through Asio.
signals_.add(SIGINT);
signals_.add(SIGTERM);
#if defined(SIGQUIT)
signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)
signals_.async_wait(boost::bind(&server::handle_stop, this));
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(io_context_);
boost::asio::ip::tcp::endpoint endpoint =
*resolver.resolve(address, port).begin();
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
acceptor_.listen();
start_accept();
}
void server::run()
{
// Create a pool of threads to run all of the io_contexts.
std::vector<boost::shared_ptr<boost::thread> > threads;
for (std::size_t i = 0; i < thread_pool_size_; ++i)
{
boost::shared_ptr<boost::thread> thread(new boost::thread(
boost::bind(&boost::asio::io_context::run, &io_context_)));
threads.push_back(thread);
}
// Wait for all threads in the pool to exit.
for (std::size_t i = 0; i < threads.size(); ++i)
threads[i]->join();
}
void server::start_accept()
{
new_connection_.reset(new connection(io_context_, request_handler_));
acceptor_.async_accept(new_connection_->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error));
}
void server::handle_accept(const boost::system::error_code& e)
{
if (!e)
{
new_connection_->start();
}
start_accept();
}
void server::handle_stop()
{
io_context_.stop();
}
} // namespace server3
} // namespace http

View File

@@ -0,0 +1,70 @@
//
// server.hpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER3_SERVER_HPP
#define HTTP_SERVER3_SERVER_HPP
#include <boost/asio.hpp>
#include <string>
#include <vector>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include "connection.hpp"
#include "request_handler.hpp"
namespace http {
namespace server3 {
/// 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, std::size_t thread_pool_size);
/// Run the server's io_context loop.
void run();
private:
/// Initiate an asynchronous accept operation.
void start_accept();
/// 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 number of threads that will call io_context::run().
std::size_t thread_pool_size_;
/// The io_context used to perform asynchronous operations.
boost::asio::io_context io_context_;
/// The signal_set is used to register for process termination notifications.
boost::asio::signal_set signals_;
/// Acceptor used to listen for incoming connections.
boost::asio::ip::tcp::acceptor acceptor_;
/// The next connection to be accepted.
connection_ptr new_connection_;
/// The handler for all incoming requests.
request_handler request_handler_;
};
} // namespace server3
} // namespace http
#endif // HTTP_SERVER3_SERVER_HPP

View File

@@ -0,0 +1,122 @@
//
// file_handler.cpp
// ~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "file_handler.hpp"
#include <fstream>
#include <sstream>
#include <string>
#include <boost/lexical_cast.hpp>
#include "mime_types.hpp"
#include "reply.hpp"
#include "request.hpp"
namespace http {
namespace server4 {
file_handler::file_handler(const std::string& doc_root)
: doc_root_(doc_root)
{
}
void file_handler::operator()(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 file_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 server4
} // namespace http

View File

@@ -0,0 +1,44 @@
//
// file_handler.hpp
// ~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER4_FILE_HANDLER_HPP
#define HTTP_SERVER4_FILE_HANDLER_HPP
#include <string>
namespace http {
namespace server4 {
struct reply;
struct request;
/// The common handler for all incoming requests.
class file_handler
{
public:
/// Construct with a directory containing files to be served.
explicit file_handler(const std::string& doc_root);
/// Handle a request and produce a reply.
void operator()(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 server4
} // namespace http
#endif // HTTP_SERVER4_FILE_HANDLER_HPP

View File

@@ -0,0 +1,28 @@
//
// header.hpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER4_HEADER_HPP
#define HTTP_SERVER4_HEADER_HPP
#include <string>
namespace http {
namespace server4 {
struct header
{
std::string name;
std::string value;
};
} // namespace server4
} // namespace http
#endif // HTTP_SERVER4_HEADER_HPP

View File

@@ -0,0 +1,58 @@
//
// main.cpp
// ~~~~~~~~
//
// Copyright (c) 2003-2021 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 <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <signal.h>
#include "server.hpp"
#include "file_handler.hpp"
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;
}
boost::asio::io_context io_context;
// Launch the initial server coroutine.
http::server4::server(io_context, argv[1], argv[2],
http::server4::file_handler(argv[3]))();
// Wait for signals indicating time to shut down.
boost::asio::signal_set signals(io_context);
signals.add(SIGINT);
signals.add(SIGTERM);
#if defined(SIGQUIT)
signals.add(SIGQUIT);
#endif // defined(SIGQUIT)
signals.async_wait(boost::bind(
&boost::asio::io_context::stop, &io_context));
// Run the server.
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,46 @@
//
// mime_types.cpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "mime_types.hpp"
namespace http {
namespace server4 {
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 server4
} // namespace http

View File

@@ -0,0 +1,27 @@
//
// mime_types.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER4_MIME_TYPES_HPP
#define HTTP_SERVER4_MIME_TYPES_HPP
#include <string>
namespace http {
namespace server4 {
namespace mime_types {
/// Convert a file extension into a MIME type.
std::string extension_to_type(const std::string& extension);
} // namespace mime_types
} // namespace server4
} // namespace http
#endif // HTTP_SERVER4_MIME_TYPES_HPP

View File

@@ -0,0 +1,256 @@
//
// reply.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "reply.hpp"
#include <string>
#include <boost/lexical_cast.hpp>
namespace http {
namespace server4 {
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 server4
} // namespace http

View File

@@ -0,0 +1,64 @@
//
// reply.hpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER4_REPLY_HPP
#define HTTP_SERVER4_REPLY_HPP
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include "header.hpp"
namespace http {
namespace server4 {
/// 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 server4
} // namespace http
#endif // HTTP_SERVER4_REPLY_HPP

View File

@@ -0,0 +1,46 @@
//
// request.hpp
// ~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER4_REQUEST_HPP
#define HTTP_SERVER4_REQUEST_HPP
#include <string>
#include <vector>
#include "header.hpp"
namespace http {
namespace server4 {
/// A request received from a client.
struct request
{
/// The request method, e.g. "GET", "POST".
std::string method;
/// The requested URI, such as a path to a file.
std::string uri;
/// Major version number, usually 1.
int http_version_major;
/// Minor version number, usually 0 or 1.
int http_version_minor;
/// The headers included with the request.
std::vector<header> headers;
/// The optional content sent with the request.
std::string content;
};
} // namespace server4
} // namespace http
#endif // HTTP_SERVER4_REQUEST_HPP

View File

@@ -0,0 +1,226 @@
//
// request_parser.cpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "request_parser.hpp"
#include <algorithm>
#include <cctype>
#include <boost/lexical_cast.hpp>
#include "request.hpp"
namespace http {
namespace server4 {
// Enable the pseudo-keywords reenter, yield and fork.
#include <boost/asio/yield.hpp>
std::string request_parser::content_length_name_ = "Content-Length";
boost::tribool request_parser::consume(request& req, char c)
{
reenter (this)
{
req.method.clear();
req.uri.clear();
req.http_version_major = 0;
req.http_version_minor = 0;
req.headers.clear();
req.content.clear();
content_length_ = 0;
// Request method.
while (is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != ' ')
{
req.method.push_back(c);
yield return boost::indeterminate;
}
if (req.method.empty())
return false;
// Space.
if (c != ' ') return false;
yield return boost::indeterminate;
// URI.
while (!is_ctl(c) && c != ' ')
{
req.uri.push_back(c);
yield return boost::indeterminate;
}
if (req.uri.empty()) return false;
// Space.
if (c != ' ') return false;
yield return boost::indeterminate;
// HTTP protocol identifier.
if (c != 'H') return false;
yield return boost::indeterminate;
if (c != 'T') return false;
yield return boost::indeterminate;
if (c != 'T') return false;
yield return boost::indeterminate;
if (c != 'P') return false;
yield return boost::indeterminate;
// Slash.
if (c != '/') return false;
yield return boost::indeterminate;
// Major version number.
if (!is_digit(c)) return false;
while (is_digit(c))
{
req.http_version_major = req.http_version_major * 10 + c - '0';
yield return boost::indeterminate;
}
// Dot.
if (c != '.') return false;
yield return boost::indeterminate;
// Minor version number.
if (!is_digit(c)) return false;
while (is_digit(c))
{
req.http_version_minor = req.http_version_minor * 10 + c - '0';
yield return boost::indeterminate;
}
// CRLF.
if (c != '\r') return false;
yield return boost::indeterminate;
if (c != '\n') return false;
yield return boost::indeterminate;
// Headers.
while ((is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != '\r')
|| (c == ' ' || c == '\t'))
{
if (c == ' ' || c == '\t')
{
// Leading whitespace. Must be continuation of previous header's value.
if (req.headers.empty()) return false;
while (c == ' ' || c == '\t')
yield return boost::indeterminate;
}
else
{
// Start the next header.
req.headers.push_back(header());
// Header name.
while (is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != ':')
{
req.headers.back().name.push_back(c);
yield return boost::indeterminate;
}
// Colon and space separates the header name from the header value.
if (c != ':') return false;
yield return boost::indeterminate;
if (c != ' ') return false;
yield return boost::indeterminate;
}
// Header value.
while (is_char(c) && !is_ctl(c) && c != '\r')
{
req.headers.back().value.push_back(c);
yield return boost::indeterminate;
}
// CRLF.
if (c != '\r') return false;
yield return boost::indeterminate;
if (c != '\n') return false;
yield return boost::indeterminate;
}
// CRLF.
if (c != '\r') return false;
yield return boost::indeterminate;
if (c != '\n') return false;
// Check for optional Content-Length header.
for (std::size_t i = 0; i < req.headers.size(); ++i)
{
if (headers_equal(req.headers[i].name, content_length_name_))
{
try
{
content_length_ =
boost::lexical_cast<std::size_t>(req.headers[i].value);
}
catch (boost::bad_lexical_cast&)
{
return false;
}
}
}
// Content.
while (req.content.size() < content_length_)
{
yield return boost::indeterminate;
req.content.push_back(c);
}
}
return true;
}
// Disable the pseudo-keywords reenter, yield and fork.
#include <boost/asio/unyield.hpp>
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';
}
bool request_parser::tolower_compare(char a, char b)
{
return std::tolower(a) == std::tolower(b);
}
bool request_parser::headers_equal(const std::string& a, const std::string& b)
{
if (a.length() != b.length())
return false;
return std::equal(a.begin(), a.end(), b.begin(),
&request_parser::tolower_compare);
}
} // namespace server4
} // namespace http

View File

@@ -0,0 +1,78 @@
//
// request_parser.hpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER4_REQUEST_PARSER_HPP
#define HTTP_SERVER4_REQUEST_PARSER_HPP
#include <string>
#include <boost/logic/tribool.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/asio/coroutine.hpp>
namespace http {
namespace server4 {
struct request;
/// Parser for incoming requests.
class request_parser : boost::asio::coroutine
{
public:
/// 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:
/// The name of the content length header.
static std::string content_length_name_;
/// Content length as decoded from headers. Defaults to 0.
std::size_t content_length_;
/// 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);
/// Check if two characters are equal, without regard to case.
static bool tolower_compare(char a, char b);
/// Check whether the two request header names match.
bool headers_equal(const std::string& a, const std::string& b);
};
} // namespace server4
} // namespace http
#endif // HTTP_SERVER4_REQUEST_PARSER_HPP

View File

@@ -0,0 +1,122 @@
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 "server.hpp"
#include "request.hpp"
#include "reply.hpp"
namespace http {
namespace server4 {
server::server(boost::asio::io_context& io_context,
const std::string& address, const std::string& port,
boost::function<void(const request&, reply&)> request_handler)
: request_handler_(request_handler)
{
tcp::resolver resolver(io_context);
boost::asio::ip::tcp::endpoint endpoint =
*resolver.resolve(address, port).begin();
acceptor_.reset(new tcp::acceptor(io_context, endpoint));
}
// Enable the pseudo-keywords reenter, yield and fork.
#include <boost/asio/yield.hpp>
void server::operator()(boost::system::error_code ec, std::size_t length)
{
// In this example we keep the error handling code in one place by
// hoisting it outside the coroutine. An alternative approach would be to
// check the value of ec after each yield for an asynchronous operation.
if (!ec)
{
// On reentering a coroutine, control jumps to the location of the last
// yield or fork. The argument to the "reenter" pseudo-keyword can be a
// pointer or reference to an object of type coroutine.
reenter (this)
{
// Loop to accept incoming connections.
do
{
// Create a new socket for the next incoming connection.
socket_.reset(new tcp::socket(acceptor_->get_executor()));
// Accept a new connection. The "yield" pseudo-keyword saves the current
// line number and exits the coroutine's "reenter" block. We use the
// server coroutine as the completion handler for the async_accept
// operation. When the asynchronous operation completes, the io_context
// invokes the function call operator, we "reenter" the coroutine, and
// then control resumes at the following line.
yield acceptor_->async_accept(*socket_, *this);
// We "fork" by cloning a new server coroutine to handle the connection.
// After forking we have a parent coroutine and a child coroutine. Both
// parent and child continue execution at the following line. They can
// be distinguished using the functions coroutine::is_parent() and
// coroutine::is_child().
fork server(*this)();
// The parent continues looping to accept the next incoming connection.
// The child exits the loop and processes the connection.
} while (is_parent());
// Create the objects needed to receive a request on the connection.
buffer_.reset(new boost::array<char, 8192>);
request_.reset(new request);
// Loop until a complete request (or an invalid one) has been received.
do
{
// Receive some more data. When control resumes at the following line,
// the ec and length parameters reflect the result of the asynchronous
// operation.
yield socket_->async_read_some(boost::asio::buffer(*buffer_), *this);
// Parse the data we just received.
boost::tie(valid_request_, boost::tuples::ignore)
= request_parser_.parse(*request_,
buffer_->data(), buffer_->data() + length);
// An indeterminate result means we need more data, so keep looping.
} while (boost::indeterminate(valid_request_));
// Create the reply object that will be sent back to the client.
reply_.reset(new reply);
if (valid_request_)
{
// A valid request was received. Call the user-supplied function object
// to process the request and compose a reply.
request_handler_(*request_, *reply_);
}
else
{
// The request was invalid.
*reply_ = reply::stock_reply(reply::bad_request);
}
// Send the reply back to the client.
yield boost::asio::async_write(*socket_, reply_->to_buffers(), *this);
// Initiate graceful connection closure.
socket_->shutdown(tcp::socket::shutdown_both, ec);
}
}
// If an error occurs then the coroutine is not reentered. Consequently, no
// new asynchronous operations are started. This means that all shared_ptr
// references will disappear and the resources associated with the coroutine
// will be destroyed automatically after this function call returns.
}
// Disable the pseudo-keywords reenter, yield and fork.
#include <boost/asio/unyield.hpp>
} // namespace server4
} // namespace http

View File

@@ -0,0 +1,73 @@
//
// server.hpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 HTTP_SERVER4_SERVER_HPP
#define HTTP_SERVER4_SERVER_HPP
#include <boost/asio.hpp>
#include <string>
#include <boost/array.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include "request_parser.hpp"
namespace http {
namespace server4 {
struct request;
struct reply;
/// The top-level coroutine of the HTTP server.
class server : boost::asio::coroutine
{
public:
/// Construct the server to listen on the specified TCP address and port, and
/// serve up files from the given directory.
explicit server(boost::asio::io_context& io_context,
const std::string& address, const std::string& port,
boost::function<void(const request&, reply&)> request_handler);
/// Perform work associated with the server.
void operator()(
boost::system::error_code ec = boost::system::error_code(),
std::size_t length = 0);
private:
typedef boost::asio::ip::tcp tcp;
/// The user-supplied handler for all incoming requests.
boost::function<void(const request&, reply&)> request_handler_;
/// Acceptor used to listen for incoming connections.
boost::shared_ptr<tcp::acceptor> acceptor_;
/// The current connection from a client.
boost::shared_ptr<tcp::socket> socket_;
/// Buffer for incoming data.
boost::shared_ptr<boost::array<char, 8192> > buffer_;
/// The incoming request.
boost::shared_ptr<request> request_;
/// Whether the request is valid or not.
boost::tribool valid_request_;
/// The parser for the incoming request.
request_parser request_parser_;
/// The reply to be sent back to the client.
boost::shared_ptr<reply> reply_;
};
} // namespace server4
} // namespace http
#endif // HTTP_SERVER4_SERVER_HPP

View File

@@ -0,0 +1,94 @@
//
// icmp_header.hpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 ICMP_HEADER_HPP
#define ICMP_HEADER_HPP
#include <istream>
#include <ostream>
#include <algorithm>
// ICMP header for both IPv4 and IPv6.
//
// The wire format of an ICMP header is:
//
// 0 8 16 31
// +---------------+---------------+------------------------------+ ---
// | | | | ^
// | type | code | checksum | |
// | | | | |
// +---------------+---------------+------------------------------+ 8 bytes
// | | | |
// | identifier | sequence number | |
// | | | v
// +-------------------------------+------------------------------+ ---
class icmp_header
{
public:
enum { echo_reply = 0, destination_unreachable = 3, source_quench = 4,
redirect = 5, echo_request = 8, time_exceeded = 11, parameter_problem = 12,
timestamp_request = 13, timestamp_reply = 14, info_request = 15,
info_reply = 16, address_request = 17, address_reply = 18 };
icmp_header() { std::fill(rep_, rep_ + sizeof(rep_), 0); }
unsigned char type() const { return rep_[0]; }
unsigned char code() const { return rep_[1]; }
unsigned short checksum() const { return decode(2, 3); }
unsigned short identifier() const { return decode(4, 5); }
unsigned short sequence_number() const { return decode(6, 7); }
void type(unsigned char n) { rep_[0] = n; }
void code(unsigned char n) { rep_[1] = n; }
void checksum(unsigned short n) { encode(2, 3, n); }
void identifier(unsigned short n) { encode(4, 5, n); }
void sequence_number(unsigned short n) { encode(6, 7, n); }
friend std::istream& operator>>(std::istream& is, icmp_header& header)
{ return is.read(reinterpret_cast<char*>(header.rep_), 8); }
friend std::ostream& operator<<(std::ostream& os, const icmp_header& header)
{ return os.write(reinterpret_cast<const char*>(header.rep_), 8); }
private:
unsigned short decode(int a, int b) const
{ return (rep_[a] << 8) + rep_[b]; }
void encode(int a, int b, unsigned short n)
{
rep_[a] = static_cast<unsigned char>(n >> 8);
rep_[b] = static_cast<unsigned char>(n & 0xFF);
}
unsigned char rep_[8];
};
template <typename Iterator>
void compute_checksum(icmp_header& header,
Iterator body_begin, Iterator body_end)
{
unsigned int sum = (header.type() << 8) + header.code()
+ header.identifier() + header.sequence_number();
Iterator body_iter = body_begin;
while (body_iter != body_end)
{
sum += (static_cast<unsigned char>(*body_iter++) << 8);
if (body_iter != body_end)
sum += static_cast<unsigned char>(*body_iter++);
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
header.checksum(static_cast<unsigned short>(~sum));
}
#endif // ICMP_HEADER_HPP

View File

@@ -0,0 +1,102 @@
//
// ipv4_header.hpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 IPV4_HEADER_HPP
#define IPV4_HEADER_HPP
#include <algorithm>
#include <boost/asio/ip/address_v4.hpp>
// Packet header for IPv4.
//
// The wire format of an IPv4 header is:
//
// 0 8 16 31
// +-------+-------+---------------+------------------------------+ ---
// | | | | | ^
// |version|header | type of | total length in bytes | |
// | (4) | length| service | | |
// +-------+-------+---------------+-+-+-+------------------------+ |
// | | | | | | |
// | identification |0|D|M| fragment offset | |
// | | |F|F| | |
// +---------------+---------------+-+-+-+------------------------+ |
// | | | | |
// | time to live | protocol | header checksum | 20 bytes
// | | | | |
// +---------------+---------------+------------------------------+ |
// | | |
// | source IPv4 address | |
// | | |
// +--------------------------------------------------------------+ |
// | | |
// | destination IPv4 address | |
// | | v
// +--------------------------------------------------------------+ ---
// | | ^
// | | |
// / options (if any) / 0 - 40
// / / bytes
// | | |
// | | v
// +--------------------------------------------------------------+ ---
class ipv4_header
{
public:
ipv4_header() { std::fill(rep_, rep_ + sizeof(rep_), 0); }
unsigned char version() const { return (rep_[0] >> 4) & 0xF; }
unsigned short header_length() const { return (rep_[0] & 0xF) * 4; }
unsigned char type_of_service() const { return rep_[1]; }
unsigned short total_length() const { return decode(2, 3); }
unsigned short identification() const { return decode(4, 5); }
bool dont_fragment() const { return (rep_[6] & 0x40) != 0; }
bool more_fragments() const { return (rep_[6] & 0x20) != 0; }
unsigned short fragment_offset() const { return decode(6, 7) & 0x1FFF; }
unsigned int time_to_live() const { return rep_[8]; }
unsigned char protocol() const { return rep_[9]; }
unsigned short header_checksum() const { return decode(10, 11); }
boost::asio::ip::address_v4 source_address() const
{
boost::asio::ip::address_v4::bytes_type bytes
= { { rep_[12], rep_[13], rep_[14], rep_[15] } };
return boost::asio::ip::address_v4(bytes);
}
boost::asio::ip::address_v4 destination_address() const
{
boost::asio::ip::address_v4::bytes_type bytes
= { { rep_[16], rep_[17], rep_[18], rep_[19] } };
return boost::asio::ip::address_v4(bytes);
}
friend std::istream& operator>>(std::istream& is, ipv4_header& header)
{
is.read(reinterpret_cast<char*>(header.rep_), 20);
if (header.version() != 4)
is.setstate(std::ios::failbit);
std::streamsize options_length = header.header_length() - 20;
if (options_length < 0 || options_length > 40)
is.setstate(std::ios::failbit);
else
is.read(reinterpret_cast<char*>(header.rep_) + 20, options_length);
return is;
}
private:
unsigned short decode(int a, int b) const
{ return (rep_[a] << 8) + rep_[b]; }
unsigned char rep_[60];
};
#endif // IPV4_HEADER_HPP

View File

@@ -0,0 +1,163 @@
//
// ping.cpp
// ~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio.hpp>
#include <boost/bind/bind.hpp>
#include <istream>
#include <iostream>
#include <ostream>
#include "icmp_header.hpp"
#include "ipv4_header.hpp"
using boost::asio::ip::icmp;
using boost::asio::steady_timer;
namespace chrono = boost::asio::chrono;
class pinger
{
public:
pinger(boost::asio::io_context& io_context, const char* destination)
: resolver_(io_context), socket_(io_context, icmp::v4()),
timer_(io_context), sequence_number_(0), num_replies_(0)
{
destination_ = *resolver_.resolve(icmp::v4(), destination, "").begin();
start_send();
start_receive();
}
private:
void start_send()
{
std::string body("\"Hello!\" from Asio ping.");
// Create an ICMP header for an echo request.
icmp_header echo_request;
echo_request.type(icmp_header::echo_request);
echo_request.code(0);
echo_request.identifier(get_identifier());
echo_request.sequence_number(++sequence_number_);
compute_checksum(echo_request, body.begin(), body.end());
// Encode the request packet.
boost::asio::streambuf request_buffer;
std::ostream os(&request_buffer);
os << echo_request << body;
// Send the request.
time_sent_ = steady_timer::clock_type::now();
socket_.send_to(request_buffer.data(), destination_);
// Wait up to five seconds for a reply.
num_replies_ = 0;
timer_.expires_at(time_sent_ + chrono::seconds(5));
timer_.async_wait(boost::bind(&pinger::handle_timeout, this));
}
void handle_timeout()
{
if (num_replies_ == 0)
std::cout << "Request timed out" << std::endl;
// Requests must be sent no less than one second apart.
timer_.expires_at(time_sent_ + chrono::seconds(1));
timer_.async_wait(boost::bind(&pinger::start_send, this));
}
void start_receive()
{
// Discard any data already in the buffer.
reply_buffer_.consume(reply_buffer_.size());
// Wait for a reply. We prepare the buffer to receive up to 64KB.
socket_.async_receive(reply_buffer_.prepare(65536),
boost::bind(&pinger::handle_receive, this, boost::placeholders::_2));
}
void handle_receive(std::size_t length)
{
// The actual number of bytes received is committed to the buffer so that we
// can extract it using a std::istream object.
reply_buffer_.commit(length);
// Decode the reply packet.
std::istream is(&reply_buffer_);
ipv4_header ipv4_hdr;
icmp_header icmp_hdr;
is >> ipv4_hdr >> icmp_hdr;
// We can receive all ICMP packets received by the host, so we need to
// filter out only the echo replies that match the our identifier and
// expected sequence number.
if (is && icmp_hdr.type() == icmp_header::echo_reply
&& icmp_hdr.identifier() == get_identifier()
&& icmp_hdr.sequence_number() == sequence_number_)
{
// If this is the first reply, interrupt the five second timeout.
if (num_replies_++ == 0)
timer_.cancel();
// Print out some information about the reply packet.
chrono::steady_clock::time_point now = chrono::steady_clock::now();
chrono::steady_clock::duration elapsed = now - time_sent_;
std::cout << length - ipv4_hdr.header_length()
<< " bytes from " << ipv4_hdr.source_address()
<< ": icmp_seq=" << icmp_hdr.sequence_number()
<< ", ttl=" << ipv4_hdr.time_to_live()
<< ", time="
<< chrono::duration_cast<chrono::milliseconds>(elapsed).count()
<< std::endl;
}
start_receive();
}
static unsigned short get_identifier()
{
#if defined(BOOST_ASIO_WINDOWS)
return static_cast<unsigned short>(::GetCurrentProcessId());
#else
return static_cast<unsigned short>(::getpid());
#endif
}
icmp::resolver resolver_;
icmp::endpoint destination_;
icmp::socket socket_;
steady_timer timer_;
unsigned short sequence_number_;
chrono::steady_clock::time_point time_sent_;
boost::asio::streambuf reply_buffer_;
std::size_t num_replies_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: ping <host>" << std::endl;
#if !defined(BOOST_ASIO_WINDOWS)
std::cerr << "(You may need to run this program as root.)" << std::endl;
#endif
return 1;
}
boost::asio::io_context io_context;
pinger p(io_context, argv[1]);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
}
}

View File

@@ -0,0 +1,171 @@
//
// prioritised_handlers.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio.hpp>
#include <boost/function.hpp>
#include <iostream>
#include <queue>
using boost::asio::ip::tcp;
class handler_priority_queue : public boost::asio::execution_context
{
public:
void add(int priority, boost::function<void()> function)
{
handlers_.push(queued_handler(priority, function));
}
void execute_all()
{
while (!handlers_.empty())
{
queued_handler handler = handlers_.top();
handler.execute();
handlers_.pop();
}
}
class executor
{
public:
executor(handler_priority_queue& q, int p)
: context_(q), priority_(p)
{
}
handler_priority_queue& context() const
{
return context_;
}
template <typename Function, typename Allocator>
void dispatch(const Function& f, const Allocator&) const
{
context_.add(priority_, f);
}
template <typename Function, typename Allocator>
void post(const Function& f, const Allocator&) const
{
context_.add(priority_, f);
}
template <typename Function, typename Allocator>
void defer(const Function& f, const Allocator&) const
{
context_.add(priority_, f);
}
void on_work_started() const {}
void on_work_finished() const {}
bool operator==(const executor& other) const
{
return &context_ == &other.context_ && priority_ == other.priority_;
}
bool operator!=(const executor& other) const
{
return !operator==(other);
}
private:
handler_priority_queue& context_;
int priority_;
};
template <typename Handler>
boost::asio::executor_binder<Handler, executor>
wrap(int priority, Handler handler)
{
return boost::asio::bind_executor(executor(*this, priority), handler);
}
private:
class queued_handler
{
public:
queued_handler(int p, boost::function<void()> f)
: priority_(p), function_(f)
{
}
void execute()
{
function_();
}
friend bool operator<(const queued_handler& a,
const queued_handler& b)
{
return a.priority_ < b.priority_;
}
private:
int priority_;
boost::function<void()> function_;
};
std::priority_queue<queued_handler> handlers_;
};
//----------------------------------------------------------------------
void high_priority_handler(const boost::system::error_code& /*ec*/)
{
std::cout << "High priority handler\n";
}
void middle_priority_handler(const boost::system::error_code& /*ec*/)
{
std::cout << "Middle priority handler\n";
}
void low_priority_handler()
{
std::cout << "Low priority handler\n";
}
int main()
{
boost::asio::io_context io_context;
handler_priority_queue pri_queue;
// Post a completion handler to be run immediately.
boost::asio::post(io_context, pri_queue.wrap(0, low_priority_handler));
// Start an asynchronous accept that will complete immediately.
tcp::endpoint endpoint(boost::asio::ip::address_v4::loopback(), 0);
tcp::acceptor acceptor(io_context, endpoint);
tcp::socket server_socket(io_context);
acceptor.async_accept(server_socket,
pri_queue.wrap(100, high_priority_handler));
tcp::socket client_socket(io_context);
client_socket.connect(acceptor.local_endpoint());
// Set a deadline timer to expire immediately.
boost::asio::steady_timer timer(io_context);
timer.expires_at(boost::asio::steady_timer::time_point::min());
timer.async_wait(pri_queue.wrap(42, middle_priority_handler));
while (io_context.run_one())
{
// The custom invocation hook adds the handlers to the priority queue
// rather than executing them from within the poll_one() call.
while (io_context.poll_one())
;
pri_queue.execute_all();
}
return 0;
}

View File

@@ -0,0 +1,44 @@
//
// daytime_client.cpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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>
using boost::asio::ip::tcp;
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: daytime_client <host>" << std::endl;
return 1;
}
tcp::iostream s(argv[1], "daytime");
if (!s)
{
std::cout << "Unable to connect: " << s.error().message() << std::endl;
return 1;
}
std::string line;
std::getline(s, line);
std::cout << line << std::endl;
}
catch (std::exception& e)
{
std::cout << "Exception: " << e.what() << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,51 @@
//
// daytime_server.cpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 <ctime>
#include <iostream>
#include <string>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
int main()
{
try
{
boost::asio::io_context io_context;
tcp::endpoint endpoint(tcp::v4(), 13);
tcp::acceptor acceptor(io_context, endpoint);
for (;;)
{
tcp::iostream stream;
boost::system::error_code ec;
acceptor.accept(stream.socket(), ec);
if (!ec)
{
stream << make_daytime_string();
}
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,91 @@
//
// http_client.cpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/ip/tcp.hpp>
using boost::asio::ip::tcp;
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cout << "Usage: http_client <server> <path>\n";
std::cout << "Example:\n";
std::cout << " http_client www.boost.org /LICENSE_1_0.txt\n";
return 1;
}
boost::asio::ip::tcp::iostream s;
// The entire sequence of I/O operations must complete within 60 seconds.
// If an expiry occurs, the socket is automatically closed and the stream
// becomes bad.
s.expires_after(boost::asio::chrono::seconds(60));
// Establish a connection to the server.
s.connect(argv[1], "http");
if (!s)
{
std::cout << "Unable to connect: " << s.error().message() << "\n";
return 1;
}
// Send 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.
s << "GET " << argv[2] << " HTTP/1.0\r\n";
s << "Host: " << argv[1] << "\r\n";
s << "Accept: */*\r\n";
s << "Connection: close\r\n\r\n";
// By default, the stream is tied with itself. This means that the stream
// automatically flush the buffered output before attempting a read. It is
// not necessary not explicitly flush the stream at this point.
// Check that response is OK.
std::string http_version;
s >> http_version;
unsigned int status_code;
s >> status_code;
std::string status_message;
std::getline(s, status_message);
if (!s || 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;
}
// Process the response headers, which are terminated by a blank line.
std::string header;
while (std::getline(s, header) && header != "\r")
std::cout << header << "\n";
std::cout << "\n";
// Write the remaining data to output.
std::cout << s.rdbuf();
}
catch (std::exception& e)
{
std::cout << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,142 @@
//
// connect_pair.cpp
// ~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 <cctype>
#include <boost/asio.hpp>
#include <boost/thread/thread.hpp>
#include <boost/array.hpp>
#include <boost/bind/bind.hpp>
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
using boost::asio::local::stream_protocol;
class uppercase_filter
{
public:
uppercase_filter(boost::asio::io_context& io_context)
: socket_(io_context)
{
}
stream_protocol::socket& socket()
{
return socket_;
}
void start()
{
// Wait for request.
socket_.async_read_some(boost::asio::buffer(data_),
boost::bind(&uppercase_filter::handle_read,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_read(const boost::system::error_code& ec, std::size_t size)
{
if (!ec)
{
// Compute result.
for (std::size_t i = 0; i < size; ++i)
data_[i] = std::toupper(data_[i]);
// Send result.
boost::asio::async_write(socket_, boost::asio::buffer(data_, size),
boost::bind(&uppercase_filter::handle_write,
this, boost::asio::placeholders::error));
}
else
{
throw boost::system::system_error(ec);
}
}
void handle_write(const boost::system::error_code& ec)
{
if (!ec)
{
// Wait for request.
socket_.async_read_some(boost::asio::buffer(data_),
boost::bind(&uppercase_filter::handle_read,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
throw boost::system::system_error(ec);
}
}
stream_protocol::socket socket_;
boost::array<char, 512> data_;
};
void run(boost::asio::io_context* io_context)
{
try
{
io_context->run();
}
catch (std::exception& e)
{
std::cerr << "Exception in thread: " << e.what() << "\n";
std::exit(1);
}
}
int main()
{
try
{
boost::asio::io_context io_context;
// Create filter and establish a connection to it.
uppercase_filter filter(io_context);
stream_protocol::socket socket(io_context);
boost::asio::local::connect_pair(socket, filter.socket());
filter.start();
// The io_context runs in a background thread to perform filtering.
boost::thread thread(boost::bind(run, &io_context));
for (;;)
{
// Collect request from user.
std::cout << "Enter a string: ";
std::string request;
std::getline(std::cin, request);
// Send request to filter.
boost::asio::write(socket, boost::asio::buffer(request));
// Wait for reply from filter.
std::vector<char> reply(request.size());
boost::asio::read(socket, boost::asio::buffer(reply));
// Show reply to user.
std::cout << "Result: ";
std::cout.write(&reply[0], request.size());
std::cout << std::endl;
}
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
std::exit(1);
}
}
#else // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
# error Local sockets not available on this platform.
#endif // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)

View File

@@ -0,0 +1,62 @@
//
// stream_client.cpp
// ~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 <cstring>
#include <iostream>
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
using boost::asio::local::stream_protocol;
enum { max_length = 1024 };
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: iostream_client <file>\n";
return 1;
}
stream_protocol::endpoint ep(argv[1]);
stream_protocol::iostream s(ep);
if (!s)
{
std::cerr << "Unable to connect: " << s.error().message() << std::endl;
return 1;
}
using namespace std; // For strlen.
std::cout << "Enter message: ";
char request[max_length];
std::cin.getline(request, max_length);
size_t length = strlen(request);
s << request;
char reply[max_length];
s.read(reply, length);
std::cout << "Reply is: ";
std::cout.write(reply, length);
std::cout << "\n";
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
#else // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
# error Local sockets not available on this platform.
#endif // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)

View File

@@ -0,0 +1,61 @@
//
// stream_client.cpp
// ~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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>
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
using boost::asio::local::stream_protocol;
enum { max_length = 1024 };
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: stream_client <file>\n";
return 1;
}
boost::asio::io_context io_context;
stream_protocol::socket s(io_context);
s.connect(stream_protocol::endpoint(argv[1]));
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;
}
#else // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
# error Local sockets not available on this platform.
#endif // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)

View File

@@ -0,0 +1,141 @@
//
// stream_server.cpp
// ~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 <cstdio>
#include <iostream>
#include <boost/array.hpp>
#include <boost/bind/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
using boost::asio::local::stream_protocol;
class session
: public boost::enable_shared_from_this<session>
{
public:
session(boost::asio::io_context& io_context)
: socket_(io_context)
{
}
stream_protocol::socket& socket()
{
return socket_;
}
void start()
{
socket_.async_read_some(boost::asio::buffer(data_),
boost::bind(&session::handle_read,
shared_from_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,
shared_from_this(),
boost::asio::placeholders::error));
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_),
boost::bind(&session::handle_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
private:
// The socket used to communicate with the client.
stream_protocol::socket socket_;
// Buffer used to store data received from the client.
boost::array<char, 1024> data_;
};
typedef boost::shared_ptr<session> session_ptr;
class server
{
public:
server(boost::asio::io_context& io_context, const std::string& file)
: io_context_(io_context),
acceptor_(io_context, stream_protocol::endpoint(file))
{
session_ptr new_session(new session(io_context_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session_ptr new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
}
new_session.reset(new session(io_context_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
private:
boost::asio::io_context& io_context_;
stream_protocol::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: stream_server <file>\n";
std::cerr << "*** WARNING: existing file is removed ***\n";
return 1;
}
boost::asio::io_context io_context;
std::remove(argv[1]);
server s(io_context, argv[1]);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
#else // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
# error Local sockets not available on this platform.
#endif // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)

View File

@@ -0,0 +1,93 @@
//
// receiver.cpp
// ~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/bind/bind.hpp"
const short multicast_port = 30001;
class receiver
{
public:
receiver(boost::asio::io_context& io_context,
const boost::asio::ip::address& listen_address,
const boost::asio::ip::address& multicast_address)
: socket_(io_context)
{
// Create the socket so that multiple may be bound to the same address.
boost::asio::ip::udp::endpoint listen_endpoint(
listen_address, multicast_port);
socket_.open(listen_endpoint.protocol());
socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
socket_.bind(listen_endpoint);
// Join the multicast group.
socket_.set_option(
boost::asio::ip::multicast::join_group(multicast_address));
socket_.async_receive_from(
boost::asio::buffer(data_, max_length), sender_endpoint_,
boost::bind(&receiver::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)
{
std::cout.write(data_, bytes_recvd);
std::cout << std::endl;
socket_.async_receive_from(
boost::asio::buffer(data_, max_length), sender_endpoint_,
boost::bind(&receiver::handle_receive_from, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
private:
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint sender_endpoint_;
enum { max_length = 1024 };
char data_[max_length];
};
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: receiver <listen_address> <multicast_address>\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver 0.0.0.0 239.255.0.1\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver 0::0 ff31::8000:1234\n";
return 1;
}
boost::asio::io_context io_context;
receiver r(io_context,
boost::asio::ip::make_address(argv[1]),
boost::asio::ip::make_address(argv[2]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,98 @@
//
// sender.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 <sstream>
#include <string>
#include <boost/asio.hpp>
#include "boost/bind/bind.hpp"
const short multicast_port = 30001;
const int max_message_count = 10;
class sender
{
public:
sender(boost::asio::io_context& io_context,
const boost::asio::ip::address& multicast_address)
: endpoint_(multicast_address, multicast_port),
socket_(io_context, endpoint_.protocol()),
timer_(io_context),
message_count_(0)
{
std::ostringstream os;
os << "Message " << message_count_++;
message_ = os.str();
socket_.async_send_to(
boost::asio::buffer(message_), endpoint_,
boost::bind(&sender::handle_send_to, this,
boost::asio::placeholders::error));
}
void handle_send_to(const boost::system::error_code& error)
{
if (!error && message_count_ < max_message_count)
{
timer_.expires_after(boost::asio::chrono::seconds(1));
timer_.async_wait(
boost::bind(&sender::handle_timeout, this,
boost::asio::placeholders::error));
}
}
void handle_timeout(const boost::system::error_code& error)
{
if (!error)
{
std::ostringstream os;
os << "Message " << message_count_++;
message_ = os.str();
socket_.async_send_to(
boost::asio::buffer(message_), endpoint_,
boost::bind(&sender::handle_send_to, this,
boost::asio::placeholders::error));
}
}
private:
boost::asio::ip::udp::endpoint endpoint_;
boost::asio::ip::udp::socket socket_;
boost::asio::steady_timer timer_;
int message_count_;
std::string message_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: sender <multicast_address>\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " sender 239.255.0.1\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " sender ff31::8000:1234\n";
return 1;
}
boost::asio::io_context io_context;
sender s(io_context, boost::asio::ip::make_address(argv[1]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,240 @@
//
// third_party_lib.cpp
// ~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio.hpp>
#include <boost/array.hpp>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <iostream>
using boost::asio::ip::tcp;
namespace third_party_lib {
// Simulation of a third party library that wants to perform read and write
// operations directly on a socket. It needs to be polled to determine whether
// it requires a read or write operation, and notified when the socket is ready
// for reading or writing.
class session
{
public:
session(tcp::socket& socket)
: socket_(socket),
state_(reading)
{
}
// Returns true if the third party library wants to be notified when the
// socket is ready for reading.
bool want_read() const
{
return state_ == reading;
}
// Notify that third party library that it should perform its read operation.
void do_read(boost::system::error_code& ec)
{
if (std::size_t len = socket_.read_some(boost::asio::buffer(data_), ec))
{
write_buffer_ = boost::asio::buffer(data_, len);
state_ = writing;
}
}
// Returns true if the third party library wants to be notified when the
// socket is ready for writing.
bool want_write() const
{
return state_ == writing;
}
// Notify that third party library that it should perform its write operation.
void do_write(boost::system::error_code& ec)
{
if (std::size_t len = socket_.write_some(
boost::asio::buffer(write_buffer_), ec))
{
write_buffer_ = write_buffer_ + len;
state_ = boost::asio::buffer_size(write_buffer_) > 0 ? writing : reading;
}
}
private:
tcp::socket& socket_;
enum { reading, writing } state_;
boost::array<char, 128> data_;
boost::asio::const_buffer write_buffer_;
};
} // namespace third_party_lib
// The glue between asio's sockets and the third party library.
class connection
: public boost::enable_shared_from_this<connection>
{
public:
typedef boost::shared_ptr<connection> pointer;
static pointer create(const boost::asio::any_io_executor& ex)
{
return pointer(new connection(ex));
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
// Put the socket into non-blocking mode.
socket_.non_blocking(true);
start_operations();
}
private:
connection(const boost::asio::any_io_executor& ex)
: socket_(ex),
session_impl_(socket_),
read_in_progress_(false),
write_in_progress_(false)
{
}
void start_operations()
{
// Start a read operation if the third party library wants one.
if (session_impl_.want_read() && !read_in_progress_)
{
read_in_progress_ = true;
socket_.async_wait(tcp::socket::wait_read,
boost::bind(&connection::handle_read,
shared_from_this(),
boost::asio::placeholders::error));
}
// Start a write operation if the third party library wants one.
if (session_impl_.want_write() && !write_in_progress_)
{
write_in_progress_ = true;
socket_.async_wait(tcp::socket::wait_write,
boost::bind(&connection::handle_write,
shared_from_this(),
boost::asio::placeholders::error));
}
}
void handle_read(boost::system::error_code ec)
{
read_in_progress_ = false;
// Notify third party library that it can perform a read.
if (!ec)
session_impl_.do_read(ec);
// The third party library successfully performed a read on the socket.
// Start new read or write operations based on what it now wants.
if (!ec || ec == boost::asio::error::would_block)
start_operations();
// Otherwise, an error occurred. Closing the socket cancels any outstanding
// asynchronous read or write operations. The connection object will be
// destroyed automatically once those outstanding operations complete.
else
socket_.close();
}
void handle_write(boost::system::error_code ec)
{
write_in_progress_ = false;
// Notify third party library that it can perform a write.
if (!ec)
session_impl_.do_write(ec);
// The third party library successfully performed a write on the socket.
// Start new read or write operations based on what it now wants.
if (!ec || ec == boost::asio::error::would_block)
start_operations();
// Otherwise, an error occurred. Closing the socket cancels any outstanding
// asynchronous read or write operations. The connection object will be
// destroyed automatically once those outstanding operations complete.
else
socket_.close();
}
private:
tcp::socket socket_;
third_party_lib::session session_impl_;
bool read_in_progress_;
bool write_in_progress_;
};
class server
{
public:
server(boost::asio::io_context& io_context, unsigned short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
{
start_accept();
}
private:
void start_accept()
{
connection::pointer new_connection =
connection::create(acceptor_.get_executor());
acceptor_.async_accept(new_connection->socket(),
boost::bind(&server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
void handle_accept(connection::pointer new_connection,
const boost::system::error_code& error)
{
if (!error)
{
new_connection->start();
}
start_accept();
}
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: third_party_lib <port>\n";
return 1;
}
boost::asio::io_context io_context;
using namespace std; // For atoi.
server s(io_context, atoi(argv[1]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

View File

@@ -0,0 +1,192 @@
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/if.hpp>
#include <boost/shared_ptr.hpp>
#include <algorithm>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <string>
#include "protocol.hpp"
using namespace boost;
using boost::asio::ip::tcp;
using boost::asio::ip::udp;
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: client <host> <port>\n";
return 1;
}
using namespace std; // For atoi.
std::string host_name = argv[1];
std::string port = argv[2];
boost::asio::io_context io_context;
// Determine the location of the server.
tcp::resolver resolver(io_context);
tcp::endpoint remote_endpoint = *resolver.resolve(host_name, port).begin();
// Establish the control connection to the server.
tcp::socket control_socket(io_context);
control_socket.connect(remote_endpoint);
// Create a datagram socket to receive data from the server.
boost::shared_ptr<udp::socket> data_socket(
new udp::socket(io_context, udp::endpoint(udp::v4(), 0)));
// Determine what port we will receive data on.
udp::endpoint data_endpoint = data_socket->local_endpoint();
// Ask the server to start sending us data.
control_request start = control_request::start(data_endpoint.port());
boost::asio::write(control_socket, start.to_buffers());
unsigned long last_frame_number = 0;
for (;;)
{
// Receive 50 messages on the current data socket.
for (int i = 0; i < 50; ++i)
{
// Receive a frame from the server.
frame f;
data_socket->receive(f.to_buffers(), 0);
if (f.number() > last_frame_number)
{
last_frame_number = f.number();
std::cout << "\n" << f.payload();
}
}
// Time to switch to a new socket. To ensure seamless handover we will
// continue to receive packets using the old socket until data arrives on
// the new one.
std::cout << " Starting renegotiation";
// Create the new data socket.
boost::shared_ptr<udp::socket> new_data_socket(
new udp::socket(io_context, udp::endpoint(udp::v4(), 0)));
// Determine the new port we will use to receive data.
udp::endpoint new_data_endpoint = new_data_socket->local_endpoint();
// Ask the server to switch over to the new port.
control_request change = control_request::change(
data_endpoint.port(), new_data_endpoint.port());
boost::system::error_code control_result;
boost::asio::async_write(control_socket, change.to_buffers(),
(
lambda::var(control_result) = lambda::_1
));
// Try to receive a frame from the server on the new data socket. If we
// successfully receive a frame on this new data socket we can consider
// the renegotation complete. In that case we will close the old data
// socket, which will cause any outstanding receive operation on it to be
// cancelled.
frame f1;
boost::system::error_code new_data_socket_result;
new_data_socket->async_receive(f1.to_buffers(),
(
// Note: lambda::_1 is the first argument to the callback handler,
// which in this case is the error code for the operation.
lambda::var(new_data_socket_result) = lambda::_1,
lambda::if_(!lambda::_1)
[
// We have successfully received a frame on the new data socket,
// so we can close the old data socket. This will cancel any
// outstanding receive operation on the old data socket.
lambda::var(data_socket) = boost::shared_ptr<udp::socket>()
]
));
// This loop will continue until we have successfully completed the
// renegotiation (i.e. received a frame on the new data socket), or some
// unrecoverable error occurs.
bool done = false;
while (!done)
{
// Even though we're performing a renegotation, we want to continue
// receiving data as smoothly as possible. Therefore we will continue to
// try to receive a frame from the server on the old data socket. If we
// receive a frame on this socket we will interrupt the io_context,
// print the frame, and resume waiting for the other operations to
// complete.
frame f2;
done = true; // Let's be optimistic.
if (data_socket) // Might have been closed by new_data_socket's handler.
{
data_socket->async_receive(f2.to_buffers(), 0,
(
lambda::if_(!lambda::_1)
[
// We have successfully received a frame on the old data
// socket. Stop the io_context so that we can print it.
lambda::bind(&boost::asio::io_context::stop, &io_context),
lambda::var(done) = false
]
));
}
// Run the operations in parallel. This will block until all operations
// have finished, or until the io_context is interrupted. (No threads!)
io_context.restart();
io_context.run();
// If the io_context.run() was interrupted then we have received a frame
// on the old data socket. We need to keep waiting for the renegotation
// operations to complete.
if (!done)
{
if (f2.number() > last_frame_number)
{
last_frame_number = f2.number();
std::cout << "\n" << f2.payload();
}
}
}
// Since the loop has finished, we have either successfully completed
// the renegotation, or an error has occurred. First we'll check for
// errors.
if (control_result)
throw boost::system::system_error(control_result);
if (new_data_socket_result)
throw boost::system::system_error(new_data_socket_result);
// If we get here it means we have successfully started receiving data on
// the new data socket. This new data socket will be used from now on
// (until the next time we renegotiate).
std::cout << " Renegotiation complete";
data_socket = new_data_socket;
data_endpoint = new_data_endpoint;
if (f1.number() > last_frame_number)
{
last_frame_number = f1.number();
std::cout << "\n" << f1.payload();
}
}
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,156 @@
//
// protocol.hpp
// ~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 PORTHOPPER_PROTOCOL_HPP
#define PORTHOPPER_PROTOCOL_HPP
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <cstring>
#include <iomanip>
#include <string>
#include <strstream>
// This request is sent by the client to the server over a TCP connection.
// The client uses it to perform three functions:
// - To request that data start being sent to a given port.
// - To request that data is no longer sent to a given port.
// - To change the target port to another.
class control_request
{
public:
// Construct an empty request. Used when receiving.
control_request()
{
}
// Create a request to start sending data to a given port.
static const control_request start(unsigned short port)
{
return control_request(0, port);
}
// Create a request to stop sending data to a given port.
static const control_request stop(unsigned short port)
{
return control_request(port, 0);
}
// Create a request to change the port that data is sent to.
static const control_request change(
unsigned short old_port, unsigned short new_port)
{
return control_request(old_port, new_port);
}
// Get the old port. Returns 0 for start requests.
unsigned short old_port() const
{
std::istrstream is(data_, encoded_port_size);
unsigned short port = 0;
is >> std::setw(encoded_port_size) >> std::hex >> port;
return port;
}
// Get the new port. Returns 0 for stop requests.
unsigned short new_port() const
{
std::istrstream is(data_ + encoded_port_size, encoded_port_size);
unsigned short port = 0;
is >> std::setw(encoded_port_size) >> std::hex >> port;
return port;
}
// Obtain buffers for reading from or writing to a socket.
boost::array<boost::asio::mutable_buffer, 1> to_buffers()
{
boost::array<boost::asio::mutable_buffer, 1> buffers
= { { boost::asio::buffer(data_) } };
return buffers;
}
private:
// Construct with specified old and new ports.
control_request(unsigned short old_port_number,
unsigned short new_port_number)
{
std::ostrstream os(data_, control_request_size);
os << std::setw(encoded_port_size) << std::hex << old_port_number;
os << std::setw(encoded_port_size) << std::hex << new_port_number;
}
// The length in bytes of a control_request and its components.
enum
{
encoded_port_size = 4, // 16-bit port in hex.
control_request_size = encoded_port_size * 2
};
// The encoded request data.
char data_[control_request_size];
};
// This frame is sent from the server to subscribed clients over UDP.
class frame
{
public:
// The maximum allowable length of the payload.
enum { payload_size = 32 };
// Construct an empty frame. Used when receiving.
frame()
{
}
// Construct a frame with specified frame number and payload.
frame(unsigned long frame_number, const std::string& payload_data)
{
std::ostrstream os(data_, frame_size);
os << std::setw(encoded_number_size) << std::hex << frame_number;
os << std::setw(payload_size)
<< std::setfill(' ') << payload_data.substr(0, payload_size);
}
// Get the frame number.
unsigned long number() const
{
std::istrstream is(data_, encoded_number_size);
unsigned long frame_number = 0;
is >> std::setw(encoded_number_size) >> std::hex >> frame_number;
return frame_number;
}
// Get the payload data.
const std::string payload() const
{
return std::string(data_ + encoded_number_size, payload_size);
}
// Obtain buffers for reading from or writing to a socket.
boost::array<boost::asio::mutable_buffer, 1> to_buffers()
{
boost::array<boost::asio::mutable_buffer, 1> buffers
= { { boost::asio::buffer(data_) } };
return buffers;
}
private:
// The length in bytes of a frame and its components.
enum
{
encoded_number_size = 8, // Frame number in hex.
frame_size = encoded_number_size + payload_size
};
// The encoded frame data.
char data_[frame_size];
};
#endif // PORTHOPPER_PROTOCOL_HPP

View File

@@ -0,0 +1,187 @@
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <cmath>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <set>
#include "protocol.hpp"
using boost::asio::ip::tcp;
using boost::asio::ip::udp;
typedef boost::shared_ptr<tcp::socket> tcp_socket_ptr;
typedef boost::shared_ptr<boost::asio::steady_timer> timer_ptr;
typedef boost::shared_ptr<control_request> control_request_ptr;
class server
{
public:
// Construct the server to wait for incoming control connections.
server(boost::asio::io_context& io_context, unsigned short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
timer_(io_context),
udp_socket_(io_context, udp::endpoint(udp::v4(), 0)),
next_frame_number_(1)
{
// Start waiting for a new control connection.
tcp_socket_ptr new_socket(new tcp::socket(acceptor_.get_executor()));
acceptor_.async_accept(*new_socket,
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error, new_socket));
// Start the timer used to generate outgoing frames.
timer_.expires_after(boost::asio::chrono::milliseconds(100));
timer_.async_wait(boost::bind(&server::handle_timer, this));
}
// Handle a new control connection.
void handle_accept(const boost::system::error_code& ec, tcp_socket_ptr socket)
{
if (!ec)
{
// Start receiving control requests on the connection.
control_request_ptr request(new control_request);
boost::asio::async_read(*socket, request->to_buffers(),
boost::bind(&server::handle_control_request, this,
boost::asio::placeholders::error, socket, request));
}
// Start waiting for a new control connection.
tcp_socket_ptr new_socket(new tcp::socket(acceptor_.get_executor()));
acceptor_.async_accept(*new_socket,
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error, new_socket));
}
// Handle a new control request.
void handle_control_request(const boost::system::error_code& ec,
tcp_socket_ptr socket, control_request_ptr request)
{
if (!ec)
{
// Delay handling of the control request to simulate network latency.
timer_ptr delay_timer(
new boost::asio::steady_timer(acceptor_.get_executor()));
delay_timer->expires_after(boost::asio::chrono::seconds(2));
delay_timer->async_wait(
boost::bind(&server::handle_control_request_timer, this,
socket, request, delay_timer));
}
}
void handle_control_request_timer(tcp_socket_ptr socket,
control_request_ptr request, timer_ptr /*delay_timer*/)
{
// Determine what address this client is connected from, since
// subscriptions must be stored on the server as a complete endpoint, not
// just a port. We use the non-throwing overload of remote_endpoint() since
// it may fail if the socket is no longer connected.
boost::system::error_code ec;
tcp::endpoint remote_endpoint = socket->remote_endpoint(ec);
if (!ec)
{
// Remove old port subscription, if any.
if (unsigned short old_port = request->old_port())
{
udp::endpoint old_endpoint(remote_endpoint.address(), old_port);
subscribers_.erase(old_endpoint);
std::cout << "Removing subscription " << old_endpoint << std::endl;
}
// Add new port subscription, if any.
if (unsigned short new_port = request->new_port())
{
udp::endpoint new_endpoint(remote_endpoint.address(), new_port);
subscribers_.insert(new_endpoint);
std::cout << "Adding subscription " << new_endpoint << std::endl;
}
}
// Wait for next control request on this connection.
boost::asio::async_read(*socket, request->to_buffers(),
boost::bind(&server::handle_control_request, this,
boost::asio::placeholders::error, socket, request));
}
// Every time the timer fires we will generate a new frame and send it to all
// subscribers.
void handle_timer()
{
// Generate payload.
double x = next_frame_number_ * 0.2;
double y = std::sin(x);
int char_index = static_cast<int>((y + 1.0) * (frame::payload_size / 2));
std::string payload;
for (int i = 0; i < frame::payload_size; ++i)
payload += (i == char_index ? '*' : '.');
// Create the frame to be sent to all subscribers.
frame f(next_frame_number_++, payload);
// Send frame to all subscribers. We can use synchronous calls here since
// UDP send operations typically do not block.
std::set<udp::endpoint>::iterator j;
for (j = subscribers_.begin(); j != subscribers_.end(); ++j)
{
boost::system::error_code ec;
udp_socket_.send_to(f.to_buffers(), *j, 0, ec);
}
// Wait for next timeout.
timer_.expires_after(boost::asio::chrono::milliseconds(100));
timer_.async_wait(boost::bind(&server::handle_timer, this));
}
private:
// The acceptor used to accept incoming control connections.
tcp::acceptor acceptor_;
// The timer used for generating data.
boost::asio::steady_timer timer_;
// The socket used to send data to subscribers.
udp::socket udp_socket_;
// The next frame number.
unsigned long next_frame_number_;
// The set of endpoints that are subscribed.
std::set<udp::endpoint> subscribers_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_context io_context;
using namespace std; // For atoi.
server s(io_context, atoi(argv[1]));
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,125 @@
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <vector>
#include "connection.hpp" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include "stock.hpp"
namespace s11n_example {
/// Downloads stock quote information from a server.
class client
{
public:
/// Constructor starts the asynchronous connect operation.
client(boost::asio::io_context& io_context,
const std::string& host, const std::string& service)
: connection_(io_context.get_executor())
{
// Resolve the host name into an IP address.
boost::asio::ip::tcp::resolver resolver(io_context);
boost::asio::ip::tcp::resolver::query query(host, service);
boost::asio::ip::tcp::resolver::iterator endpoint_iterator =
resolver.resolve(query);
// Start an asynchronous connect operation.
boost::asio::async_connect(connection_.socket(), endpoint_iterator,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
/// Handle completion of a connect operation.
void handle_connect(const boost::system::error_code& e)
{
if (!e)
{
// Successfully established connection. Start operation to read the list
// of stocks. The connection::async_read() function will automatically
// decode the data that is read from the underlying socket.
connection_.async_read(stocks_,
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error));
}
else
{
// An error occurred. Log it and return. Since we are not starting a new
// operation the io_context will run out of work to do and the client will
// exit.
std::cerr << e.message() << std::endl;
}
}
/// Handle completion of a read operation.
void handle_read(const boost::system::error_code& e)
{
if (!e)
{
// Print out the data that was received.
for (std::size_t i = 0; i < stocks_.size(); ++i)
{
std::cout << "Stock number " << i << "\n";
std::cout << " code: " << stocks_[i].code << "\n";
std::cout << " name: " << stocks_[i].name << "\n";
std::cout << " open_price: " << stocks_[i].open_price << "\n";
std::cout << " high_price: " << stocks_[i].high_price << "\n";
std::cout << " low_price: " << stocks_[i].low_price << "\n";
std::cout << " last_price: " << stocks_[i].last_price << "\n";
std::cout << " buy_price: " << stocks_[i].buy_price << "\n";
std::cout << " buy_quantity: " << stocks_[i].buy_quantity << "\n";
std::cout << " sell_price: " << stocks_[i].sell_price << "\n";
std::cout << " sell_quantity: " << stocks_[i].sell_quantity << "\n";
}
}
else
{
// An error occurred.
std::cerr << e.message() << std::endl;
}
// Since we are not starting a new operation the io_context will run out of
// work to do and the client will exit.
}
private:
/// The connection to the server.
connection connection_;
/// The data received from the server.
std::vector<stock> stocks_;
};
} // namespace s11n_example
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 3)
{
std::cerr << "Usage: client <host> <port>" << std::endl;
return 1;
}
boost::asio::io_context io_context;
s11n_example::client client(io_context, argv[1], argv[2]);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,188 @@
//
// connection.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2021 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 SERIALIZATION_CONNECTION_HPP
#define SERIALIZATION_CONNECTION_HPP
#include <boost/asio.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/tuple/tuple.hpp>
#include <iomanip>
#include <string>
#include <sstream>
#include <vector>
namespace s11n_example {
/// The connection class provides serialization primitives on top of a socket.
/**
* Each message sent using this class consists of:
* @li An 8-byte header containing the length of the serialized data in
* hexadecimal.
* @li The serialized data.
*/
class connection
{
public:
/// Constructor.
connection(const boost::asio::executor& ex)
: socket_(ex)
{
}
/// Get the underlying socket. Used for making a connection or for accepting
/// an incoming connection.
boost::asio::ip::tcp::socket& socket()
{
return socket_;
}
/// Asynchronously write a data structure to the socket.
template <typename T, typename Handler>
void async_write(const T& t, Handler handler)
{
// Serialize the data first so we know how large it is.
std::ostringstream archive_stream;
boost::archive::text_oarchive archive(archive_stream);
archive << t;
outbound_data_ = archive_stream.str();
// Format the header.
std::ostringstream header_stream;
header_stream << std::setw(header_length)
<< std::hex << outbound_data_.size();
if (!header_stream || header_stream.str().size() != header_length)
{
// Something went wrong, inform the caller.
boost::system::error_code error(boost::asio::error::invalid_argument);
boost::asio::post(socket_.get_executor(), boost::bind(handler, error));
return;
}
outbound_header_ = header_stream.str();
// Write the serialized data to the socket. We use "gather-write" to send
// both the header and the data in a single write operation.
std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(outbound_header_));
buffers.push_back(boost::asio::buffer(outbound_data_));
boost::asio::async_write(socket_, buffers, handler);
}
/// Asynchronously read a data structure from the socket.
template <typename T, typename Handler>
void async_read(T& t, Handler handler)
{
// Issue a read operation to read exactly the number of bytes in a header.
void (connection::*f)(
const boost::system::error_code&,
T&, boost::tuple<Handler>)
= &connection::handle_read_header<T, Handler>;
boost::asio::async_read(socket_, boost::asio::buffer(inbound_header_),
boost::bind(f,
this, boost::asio::placeholders::error, boost::ref(t),
boost::make_tuple(handler)));
}
/// Handle a completed read of a message header. The handler is passed using
/// a tuple since boost::bind seems to have trouble binding a function object
/// created using boost::bind as a parameter.
template <typename T, typename Handler>
void handle_read_header(const boost::system::error_code& e,
T& t, boost::tuple<Handler> handler)
{
if (e)
{
boost::get<0>(handler)(e);
}
else
{
// Determine the length of the serialized data.
std::istringstream is(std::string(inbound_header_, header_length));
std::size_t inbound_data_size = 0;
if (!(is >> std::hex >> inbound_data_size))
{
// Header doesn't seem to be valid. Inform the caller.
boost::system::error_code error(boost::asio::error::invalid_argument);
boost::get<0>(handler)(error);
return;
}
// Start an asynchronous call to receive the data.
inbound_data_.resize(inbound_data_size);
void (connection::*f)(
const boost::system::error_code&,
T&, boost::tuple<Handler>)
= &connection::handle_read_data<T, Handler>;
boost::asio::async_read(socket_, boost::asio::buffer(inbound_data_),
boost::bind(f, this,
boost::asio::placeholders::error, boost::ref(t), handler));
}
}
/// Handle a completed read of message data.
template <typename T, typename Handler>
void handle_read_data(const boost::system::error_code& e,
T& t, boost::tuple<Handler> handler)
{
if (e)
{
boost::get<0>(handler)(e);
}
else
{
// Extract the data structure from the data just received.
try
{
std::string archive_data(&inbound_data_[0], inbound_data_.size());
std::istringstream archive_stream(archive_data);
boost::archive::text_iarchive archive(archive_stream);
archive >> t;
}
catch (std::exception& e)
{
// Unable to decode data.
boost::system::error_code error(boost::asio::error::invalid_argument);
boost::get<0>(handler)(error);
return;
}
// Inform caller that data has been received ok.
boost::get<0>(handler)(e);
}
}
private:
/// The underlying socket.
boost::asio::ip::tcp::socket socket_;
/// The size of a fixed length header.
enum { header_length = 8 };
/// Holds an outbound header.
std::string outbound_header_;
/// Holds the outbound data.
std::string outbound_data_;
/// Holds an inbound header.
char inbound_header_[header_length];
/// Holds the inbound data.
std::vector<char> inbound_data_;
};
typedef boost::shared_ptr<connection> connection_ptr;
} // namespace s11n_example
#endif // SERIALIZATION_CONNECTION_HPP

View File

@@ -0,0 +1,123 @@
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2021 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/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <vector>
#include "connection.hpp" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include "stock.hpp"
namespace s11n_example {
/// Serves stock quote information to any client that connects to it.
class server
{
public:
/// Constructor opens the acceptor and starts waiting for the first incoming
/// connection.
server(boost::asio::io_context& io_context, unsigned short port)
: acceptor_(io_context,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
// Create the data to be sent to each client.
stock s;
s.code = "ABC";
s.name = "A Big Company";
s.open_price = 4.56;
s.high_price = 5.12;
s.low_price = 4.33;
s.last_price = 4.98;
s.buy_price = 4.96;
s.buy_quantity = 1000;
s.sell_price = 4.99;
s.sell_quantity = 2000;
stocks_.push_back(s);
s.code = "DEF";
s.name = "Developer Entertainment Firm";
s.open_price = 20.24;
s.high_price = 22.88;
s.low_price = 19.50;
s.last_price = 19.76;
s.buy_price = 19.72;
s.buy_quantity = 34000;
s.sell_price = 19.85;
s.sell_quantity = 45000;
stocks_.push_back(s);
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_executor()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a accept operation.
void handle_accept(const boost::system::error_code& e, connection_ptr conn)
{
if (!e)
{
// Successfully accepted a new connection. Send the list of stocks to the
// client. The connection::async_write() function will automatically
// serialize the data structure for us.
conn->async_write(stocks_,
boost::bind(&server::handle_write, this,
boost::asio::placeholders::error, conn));
}
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_executor()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a write operation.
void handle_write(const boost::system::error_code& e, connection_ptr conn)
{
// Nothing to do. The socket will be closed automatically when the last
// reference to the connection object goes away.
}
private:
/// The acceptor object used to accept incoming socket connections.
boost::asio::ip::tcp::acceptor acceptor_;
/// The data to be sent to each client.
std::vector<stock> stocks_;
};
} // namespace s11n_example
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 2)
{
std::cerr << "Usage: server <port>" << std::endl;
return 1;
}
unsigned short port = boost::lexical_cast<unsigned short>(argv[1]);
boost::asio::io_context io_context;
s11n_example::server server(io_context, port);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}

Some files were not shown because too many files have changed in this diff Show More