Last active
August 29, 2015 14:12
-
-
Save mpapierski/dc6a760cda7265bf6ba7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cassert> | |
#include <boost/asio.hpp> | |
#include <boost/bind.hpp> | |
#include <boost/lexical_cast.hpp> | |
extern "C" { | |
#include "http-server/http-server.h" | |
} | |
struct http_error_category | |
: boost::system::error_category | |
{ | |
const char * name() const | |
{ | |
return "http_server"; | |
} | |
std::string message(int rc) const | |
{ | |
char * str = http_server_errstr(static_cast<http_server_errno>(rc)); | |
assert(str); | |
return str; | |
} | |
}; | |
inline boost::system::error_category & get_http_error_category() | |
{ | |
static http_error_category category; | |
return category; | |
} | |
struct http_server_request | |
{ | |
std::string url; | |
}; | |
class http_server_service | |
: public boost::asio::io_service::service | |
{ | |
public: | |
static boost::asio::io_service::id id; | |
http_server_service(boost::asio::io_service & io_service) | |
: boost::asio::io_service::service(io_service) | |
, srv_() | |
, handler_() | |
{ | |
http_server_init(&srv_); | |
// srv_.sock_listen_data = NULL; // should be null by default | |
int result; | |
// Set callbacks | |
result = http_server_setopt(&srv_, HTTP_SERVER_OPT_OPEN_SOCKET_DATA, this); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); | |
} | |
result = http_server_setopt(&srv_, HTTP_SERVER_OPT_OPEN_SOCKET_FUNCTION, &opensocket_function); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); | |
} | |
result = http_server_setopt(&srv_, HTTP_SERVER_OPT_CLOSE_SOCKET_DATA, this); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); | |
} | |
result = http_server_setopt(&srv_, HTTP_SERVER_OPT_CLOSE_SOCKET_FUNCTION, &closesocket_function); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); | |
} | |
result = http_server_setopt(&srv_, HTTP_SERVER_OPT_SOCKET_DATA, this); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); | |
} | |
result = http_server_setopt(&srv_, HTTP_SERVER_OPT_SOCKET_FUNCTION, &socket_function); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); | |
} | |
// Prepare handler object | |
handler_.on_url = &on_url; | |
handler_.on_url_data = this; | |
handler_.on_message_complete = &on_message_complete; | |
handler_.on_message_complete_data = this; | |
result = http_server_setopt(&srv_, HTTP_SERVER_OPT_HANDLER, &handler_); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); | |
} | |
result = http_server_setopt(&srv_, HTTP_SERVER_OPT_HANDLER_DATA, this); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); | |
} | |
} | |
void shutdown_service() | |
{ | |
} | |
void start() | |
{ | |
int result = http_server_start(&srv_); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_start"); | |
} | |
} | |
private: | |
static http_server_socket_t opensocket_function(void * clientp) | |
{ | |
http_server_service * svc = static_cast<http_server_service *>(clientp); | |
boost::asio::ip::tcp::acceptor * acceptor = new boost::asio::ip::tcp::acceptor(svc->get_io_service()); | |
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 5000); | |
acceptor->open(endpoint.protocol()); | |
acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); | |
acceptor->bind(endpoint); | |
acceptor->listen(); | |
fprintf(stderr, "new acceptor: %d\n", acceptor->native_handle()); | |
svc->acceptors_.insert(std::make_pair(static_cast<http_server_socket_t>(acceptor->native_handle()), acceptor)); | |
return acceptor->native_handle(); | |
} | |
static int closesocket_function(http_server_socket_t socket, void * clientp) | |
{ | |
http_server_service * svc = static_cast<http_server_service *>(clientp); | |
http_server_service::acceptors_t::iterator it = svc->acceptors_.find(socket); | |
if (it != svc->acceptors_.end()) | |
{ | |
fprintf(stderr, "erase asio acceptor socket %d\n", it->first); | |
it->second->cancel(); | |
delete it->second; | |
svc->acceptors_.erase(it); | |
return HTTP_SERVER_OK; | |
} | |
http_server_service::sockets_t::iterator it2 = svc->sockets_.find(socket); | |
if (it2 != svc->sockets_.end()) | |
{ | |
fprintf(stderr, "erase asio client socket %d\n", it2->first); | |
it2->second->cancel(); | |
delete it2->second; | |
svc->sockets_.erase(it2); | |
return HTTP_SERVER_OK; | |
} | |
fprintf(stderr, "close socket invalid socket: %d\n", socket); | |
return HTTP_SERVER_INVALID_SOCKET; | |
} | |
static int socket_function(void * clientp, http_server_socket_t sock, int flags, void * socketp) | |
{ | |
http_server_service * svc = static_cast<http_server_service *>(clientp); | |
http_server_service::acceptors_t::iterator it = svc->acceptors_.find(sock); | |
if (it != svc->acceptors_.end()) | |
{ | |
// Call acceptor | |
fprintf(stderr, "async_accept on acceptor %d\n", it->second->native_handle()); | |
// Prepare new socket | |
boost::asio::ip::tcp::socket * new_socket = new boost::asio::ip::tcp::socket(svc->get_io_service()); | |
it->second->async_accept(*new_socket, | |
boost::bind(&http_server_service::handle_accept, svc, | |
boost::asio::placeholders::error, | |
new_socket)); | |
return 0; | |
} | |
fprintf(stderr, "clientp=%p sock=%d flags=%d socketp=%p\n", clientp, sock, flags, socketp); | |
http_server_service::sockets_t::iterator it2 = svc->sockets_.find(sock); | |
if (it2 == svc->sockets_.end()) | |
{ | |
fprintf(stderr, "socket %d not found!\n", sock); | |
//abort(); | |
return 0; | |
} | |
if (flags & HTTP_SERVER_POLL_REMOVE) | |
{ | |
fprintf(stderr, "cancel asio socket %d\n", sock); | |
it2->second->cancel(); | |
} | |
if (flags & HTTP_SERVER_POLL_IN) | |
{ | |
it2->second->async_read_some(boost::asio::null_buffers(), | |
boost::bind(&http_server_service::handle_read, svc, | |
boost::asio::placeholders::error, | |
boost::asio::placeholders::bytes_transferred, | |
it2->second)); | |
} | |
if (flags & HTTP_SERVER_POLL_OUT) | |
{ | |
it2->second->async_write_some(boost::asio::null_buffers(), | |
boost::bind(&http_server_service::handle_write, svc, | |
boost::asio::placeholders::error, | |
boost::asio::placeholders::bytes_transferred, | |
it2->second)); | |
} | |
return 0; | |
} | |
void handle_accept(const boost::system::error_code & error, | |
boost::asio::ip::tcp::socket * socket) | |
{ | |
if (error) | |
{ | |
fprintf(stderr, "unable to accept new connection: %s\n", error.message().c_str()); | |
// TODO: Remove socket? | |
return; | |
} | |
fprintf(stderr, "handle new conn %d\n", socket->native_handle()); | |
sockets_.insert(std::make_pair( | |
static_cast<http_server_socket_t>(socket->native_handle()), | |
socket)); | |
int result = http_server_socket_action(&srv_, socket->native_handle(), 0); | |
if (result != HTTP_SERVER_OK) | |
{ | |
sockets_.erase(socket->native_handle()); | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_socket_action"); | |
} | |
} | |
void handle_read(const boost::system::error_code & error, | |
std::size_t bytes_transferred, | |
boost::asio::ip::tcp::socket * socket) | |
{ | |
if (error) | |
{ | |
fprintf(stderr, "Unable to read data from socket: %s", error.message().c_str()); | |
return; | |
} | |
fprintf(stderr, "handle read %d\n", socket->native_handle()); | |
int result = http_server_socket_action(&srv_, socket->native_handle(), HTTP_SERVER_POLL_IN); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_socket_action"); | |
} | |
} | |
void handle_write(const boost::system::error_code & error, | |
std::size_t bytes_transferred, | |
boost::asio::ip::tcp::socket * socket) | |
{ | |
if (error) | |
{ | |
fprintf(stderr, "Unable to write data to socket: %s", error.message().c_str()); | |
return; | |
} | |
fprintf(stderr, "handle write %d\n", socket->native_handle()); | |
int result = http_server_socket_action(&srv_, socket->native_handle(), HTTP_SERVER_POLL_OUT); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_socket_action"); | |
} | |
} | |
static int on_url(http_server_client * client, void * data, const char * buf, size_t size) | |
{ | |
assert(data); | |
if (!client->data) | |
{ | |
client->data = new http_server_request; | |
} | |
http_server_service * svc = static_cast<http_server_service *>(data); | |
static_cast<http_server_request *>(client->data)->url.append(buf, size); | |
//fprintf(stderr, "URL chunk: %.*s\n", (int)size, buf); | |
return 0; | |
} | |
static int on_message_complete(http_server_client * client, void * data) | |
{ | |
http_server_request * req = static_cast<http_server_request *>(client->data); | |
// Write response | |
http_server_response * res = http_server_response_new(); | |
int result = http_server_response_begin(client, res); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_response_begin"); | |
} | |
// Start response by writing header | |
result = http_server_response_write_head(res, 200); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_response_write_head"); | |
} | |
// Send message | |
char chunk[1024]; | |
int length = sprintf(chunk, "Hello world!\n"); | |
result = http_server_response_write(res, chunk, length); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_response_write"); | |
} | |
length = sprintf(chunk, "URL: %s!\n", req->url.c_str()); | |
result = http_server_response_write(res, chunk, length); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_response_write"); | |
} | |
// Finish response | |
result = http_server_response_end(res); | |
if (result != HTTP_SERVER_OK) | |
{ | |
throw boost::system::system_error(result, get_http_error_category(), "http_server_response_end"); | |
} | |
delete req; | |
client->data = NULL; | |
return 0; | |
} | |
http_server srv_; | |
http_server_handler handler_; | |
// Map of open sockets | |
typedef std::map<http_server_socket_t, boost::asio::ip::tcp::acceptor *> acceptors_t; | |
acceptors_t acceptors_; | |
typedef std::map<http_server_socket_t, boost::asio::ip::tcp::socket *> sockets_t; | |
sockets_t sockets_; | |
}; | |
boost::asio::io_service::id http_server_service::id; | |
int | |
main(int argc, char * argv[]) | |
{ | |
int port; | |
boost::asio::io_service io_service; | |
boost::asio::use_service<http_server_service>(io_service) | |
.start(); | |
io_service.run(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment