frnetlib/examples/concurrent_http_server/ConcurrentHTTPServer.cpp
2019-05-01 17:06:09 +01:00

95 lines
3.9 KiB
C++

//
// Created by fred.nicolson on 01/10/18.
//
#include <frnetlib/TcpListener.h>
#include <iostream>
#include <frnetlib/SocketSelector.h>
#include <frnetlib/Http.h>
#include <frnetlib/HttpRequest.h>
#include <frnetlib/HttpResponse.h>
#include <thread>
class SessionState
{
public:
fr::HttpRequest partial_request;
};
void process_complete_request(const std::shared_ptr<fr::Socket> &client, fr::HttpRequest request)
{
//Note: *NEVER* disconnect the client in the handler. Or it will never be removed from
//the socket selector, and its opaque data will never be free'd. You're better off having a
//disconnection queue which is processed by the listening thread, and added to here.
fr::HttpResponse response;
response.set_body("<h1>Hello World!</h1>");
client->send(response);
}
int main()
{
//Bind to port. Note that it is possible to fork/create new threads and then bind to the same
//port multiple times. Each thread should have its own fr::SocketSelector, this will
//spread connections over multiple workers.
auto listener = std::make_shared<fr::TcpListener>();
if(listener->listen("8080") != fr::Socket::Status::Success)
{
std::cerr << "Failed to bind to port" << std::endl;
return EXIT_FAILURE;
}
//Create a socket selector, and add the listener so we get notified on connection requests
fr::SocketSelector listen_loop_selector;
listen_loop_selector.add(listener, nullptr);
bool running = true;
while(running)
{
//Pass a timeout here, so that we can periodically check 'running' to see if we should exit
auto ready_sockets = listen_loop_selector.wait(std::chrono::milliseconds(100));
for(auto &ready_socket : ready_sockets)
{
//If it's the listener, accept a new connection
if(ready_socket.first->get_socket_descriptor() == listener->get_socket_descriptor())
{
auto client = std::make_shared<fr::TcpSocket>(); //Or fr::SSLSocket
if(listener->accept(*client) == fr::Socket::Status::Success)
{
client->set_blocking(false); //This is important
listen_loop_selector.add(client, new SessionState()); //We assign a 'SessionState' object for each connection as opaque data
}
continue;
}
//Else, we have new activity on a client socket
auto client = std::static_pointer_cast<fr::Socket>(ready_socket.first);
auto session = static_cast<SessionState*>(ready_socket.second);
//Try and receive data
char data[0x1000];
size_t received = 0;
auto recv_status = client->receive_raw(data, sizeof(data), received);
if(recv_status == fr::Socket::Status::Success)
{
//We received data, so parse it using the partial HTTP request associated with this connection
auto parse_status = session->partial_request.parse(data, received);
if(parse_status == fr::Socket::Status::Success)
{
//The client has sent a full request, queue it for processing
process_complete_request(client, std::move(session->partial_request));
session->partial_request = fr::HttpRequest();
}
else if(parse_status != fr::Socket::Status::NotEnoughData)
{
//HTTP error, disconnect the client. Remove from socket selector, and delete opaque data.
delete (SessionState*)listen_loop_selector.remove(client);
}
}
else if(recv_status != fr::Socket::Status::WouldBlock)
{
//Error, disconnect it. Remove from socket selector, and delete opaque data.
delete (SessionState*)listen_loop_selector.remove(client);
}
}
}
}