Better Concurrent HTTP Server example
This commit is contained in:
parent
60316be04c
commit
094c211f7f
@ -10,9 +10,27 @@
|
||||
#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
|
||||
//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::Success)
|
||||
{
|
||||
@ -20,77 +38,57 @@ int main()
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
for(size_t a = 0; a < std::thread::hardware_concurrency(); ++a)
|
||||
{
|
||||
if(fork() <= 0)
|
||||
break;
|
||||
}
|
||||
//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);
|
||||
|
||||
//Setup our socket selector and add the listener to it
|
||||
fr::SocketSelector selector;
|
||||
selector.add(listener, nullptr);
|
||||
|
||||
//Enter main server loop
|
||||
volatile bool running = true;
|
||||
bool running = true;
|
||||
while(running)
|
||||
{
|
||||
//Wait for a socket to be ready with no timeout
|
||||
auto ready_sockets = selector.wait();
|
||||
|
||||
//Process each socket
|
||||
//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 this is the listener, then accept a new connection
|
||||
//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>();
|
||||
auto client = std::make_shared<fr::TcpSocket>(); //Or fr::SSLSocket
|
||||
if(listener->accept(*client) == fr::Socket::Success)
|
||||
{
|
||||
//We've accepted the connection, so add it to the selector, associating a new fr::HttpRequest
|
||||
//Object which will slowly be filled with the client's request. You could also pass a struct
|
||||
//associated with the connection for easier state management.
|
||||
selector.add(client, new fr::HttpRequest());
|
||||
client->set_blocking(false);
|
||||
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
|
||||
}
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Else, a socket is sending data/has disconnected, accept it
|
||||
auto client = std::static_pointer_cast<fr::Socket>(ready_socket.first); //fr::TcpSocket -> fr::Socket -> fr::SocketDescriptor
|
||||
auto partial_request = static_cast<fr::HttpRequest*>(ready_socket.second);
|
||||
//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::Success)
|
||||
{
|
||||
//We receied data, so parse it using the partial HTTP request associated with this connection
|
||||
auto parse_status = partial_request->parse(data, received);
|
||||
//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::Success)
|
||||
{
|
||||
//The client has sent a full request, send them something back
|
||||
//This could alternatively pass this connection/request off to a handler in another
|
||||
//thread for further processing.
|
||||
fr::HttpResponse response;
|
||||
response.set_body("<h1>Hello World!</h1>");
|
||||
client->send(response);
|
||||
//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::NotEnoughData)
|
||||
else if(parse_status != fr::Socket::NotEnoughData)
|
||||
{
|
||||
//More data needed, wait for it
|
||||
}
|
||||
else
|
||||
{
|
||||
//HTTP error, disconnect the client
|
||||
delete partial_request;
|
||||
selector.remove(client);
|
||||
client->disconnect();
|
||||
//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::Disconnected)
|
||||
else if(recv_status != fr::Socket::WouldBlock)
|
||||
{
|
||||
//Free the allocated fr::HttpRequest. The socket is auto-removed from the selector as it notified us.
|
||||
delete partial_request;
|
||||
//Error, disconnect it. Remove from socket selector, and delete opaque data.
|
||||
delete (SessionState*)listen_loop_selector.remove(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user