scorpioengine/src/server.cpp

495 lines
9.2 KiB
C++

#include <iostream>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <utf8/utf8.h>
#include "server.h"
#include "string_functions.h"
Server::Server()
{
main_socket = -1;
close_server = false;
}
Server::~Server()
{
Close();
}
void Server::CloseMainSocket()
{
if( main_socket != -1 )
{
close(main_socket);
main_socket = -1;
}
}
void Server::CloseClientSockets()
{
for(size_t i=0 ; i<client_tab.size() ; ++i)
close(client_tab[i].socket);
client_tab.clear();
}
void Server::Close()
{
CloseMainSocket();
CloseClientSockets();
}
void Server::PrepareMainSocket()
{
CloseMainSocket();
main_socket = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if( main_socket < 0 )
{
std::cout << "I cannot create a socket" << std::endl;
return;
}
int optval = 1;
if( setsockopt(main_socket, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) < 0 )
{
std::cout << "I cannot setsockopt " << std::endl;
return;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = PF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(3012);
socklen_t len = sizeof(addr);
if( bind(main_socket, (struct sockaddr*)&addr, len) < 0 )
{
std::cout << "bind failed: " << std::endl;
return;
}
if( listen(main_socket, 100) < 0 )
{
std::cout << "listen failed" << std::endl;
return;
}
}
void Server::Wait()
{
while( !close_server )
{
int fd_max = AddSocketsToSet();
int ready_fd = select(fd_max + 1, &read_set, &write_set, 0, 0);
if( ready_fd < 0 )
{
std::cout << "select failed" << std::endl;
return;
}
if( ready_fd > 0 )
{
if( FD_ISSET(main_socket, &read_set) )
{
AddNewClient();
}
else
{
ReadWriteToClients();
}
}
}
}
int Server::AddSocketsToSet()
{
FD_ZERO(&read_set);
FD_ZERO(&write_set);
int fd_max = 0;
if( main_socket >= 0 )
{
FD_SET(main_socket, &read_set);
fd_max = main_socket;
for(size_t i=0 ; i<client_tab.size() ; ++i)
{
FD_SET(client_tab[i].socket, &read_set);
if( client_tab[i].answer_generated && !client_tab[i].output_buffer.empty() )
FD_SET(client_tab[i].socket, &write_set);
if( client_tab[i].socket > fd_max )
fd_max = client_tab[i].socket;
}
}
else
{
std::cout << "there is no main socket" << std::endl;
}
return fd_max;
}
void Server::AddNewClient()
{
struct sockaddr_in client_addr;
socklen_t len;
int client_socket;
//int optval;
memset(&client_addr, 0, sizeof(client_addr));
len = sizeof(client_addr);
client_socket = accept(main_socket, (struct sockaddr*)&client_addr, &len);
if( client_socket < 0 )
{
std::cout << "accept failed" << std::endl;
}
else
{
int opt = fcntl(client_socket, F_GETFL);
if( opt >= 0 )
{
opt = opt | O_NONBLOCK;
if( fcntl(client_socket, F_SETFL, opt) >= 0 )
{
Client c;
c.socket = client_socket;
c.close_connection = false;
client_tab.push_back(c);
client_tab.back().PrepareToNewRequest();
}
else
{
std::cout << "I cannot fcntl (O_NONBLOCK)" << std::endl;
}
}
}
}
void Server::ReadWriteToClients()
{
size_t i = 0;
while( i < client_tab.size() )
{
if( FD_ISSET(client_tab[i].socket, &read_set))
{
ReadInputFromClient(client_tab[i]);
}
if( client_tab[i].answer_generated && FD_ISSET(client_tab[i].socket, &write_set) )
{
WriteOutputToClient(client_tab[i]);
}
if( client_tab[i].output_buffer.empty() && client_tab[i].close_connection )
{
std::cout << "closing connection for client nr " << client_tab[i].socket << std::endl;
close(client_tab[i].socket);
client_tab.erase(client_tab.begin() + i);
}
else
{
i += 1;
}
}
}
void Server::ReadInputFromClient(Client & client)
{
std::cout << "trying read something from client number: " << client.socket << std::endl;
// we need at least one char more (for terminating zero for logging/debugging)
if( client.in_buffer_max_len - client.in_buffer_len < 2 )
{
client.input_buffer.append(client.in_buffer, client.in_buffer_len);
client.in_buffer_len = 0;
}
char * old_pointer = client.in_buffer + client.in_buffer_len;
// after adding to client.input_buffer we set in_buffer_len to 0
// so here is sufficient client.in_buffer (without adding client.in_buffer_len)
int read_len = read(client.socket, client.in_buffer + client.in_buffer_len, client.in_buffer_max_len - client.in_buffer_len - 1);
if( read_len < 0 )
{
// read failed, we do not parse this request
std::cout << "read failed" << std::endl;
client.close_connection = true;
}
else
if( read_len == 0 )
{
// end
if( client.in_buffer_len > 0 )
{
client.input_buffer.append(client.in_buffer, client.in_buffer_len);
client.in_buffer_len = 0;
}
std::cout << "------------------------------ client: " << client.socket << " calosc komunikatu " << std::endl;
std::cout << client.input_buffer;
std::cout << "----------------------------------------" << std::endl;
// the client has terminated connection
client.close_connection = true;
}
else
{
client.in_buffer_len += read_len;
size_t input_buffer_index = client.input_buffer.size();
client.input_buffer.append(client.in_buffer, client.in_buffer_len);
client.in_buffer_len = 0;
if( client.parsing_headers )
CheckHeaders(client, input_buffer_index);
client.in_buffer[client.in_buffer_len] = 0; // only for logging (we have at least one character more for terminating zero)
std::cout << "------------------------------ client: " << client.socket << std::endl;
std::cout << old_pointer;
std::cout << "----------------------------------------" << std::endl;
}
}
void Server::WriteOutputToClient(Client & client)
{
std::cout << "writing to client nr " << client.socket << std::endl;
const char * data = client.output_buffer.c_str() + client.output_buffer_sent;
size_t how_many_to_send = client.output_buffer.size() - client.output_buffer_sent;
if( how_many_to_send > 0 )
{
int len = send(client.socket, data, how_many_to_send, 0);
if( len < 0 )
{
std::cout << "writing failed" << std::endl;
}
else
{
client.output_buffer_sent += len;
if( client.output_buffer_sent == client.output_buffer.size() )
{
client.PrepareToNewRequest();
}
}
}
}
void Server::CheckHeaders(Client & client, size_t input_buffer_index)
{
// at least 3 bytes before
if( input_buffer_index > 3 )
input_buffer_index -= 3;
else
input_buffer_index = 0;
for( ; input_buffer_index + 3 < client.input_buffer.size() ; ++input_buffer_index)
{
if( headers_parser.IsHeadersEnding(client.input_buffer.c_str() + input_buffer_index) )
{
headers_parser.ParseHeaders(client);
if( !SelectMethodName(client, client.http_method_str) )
{
std::cout << "unsupported http method" << std::endl;
client.close_connection = true;
}
client.parsing_headers = false;
client.input_buffer.erase(0, input_buffer_index + 4);
// temporarily we are using only get method with http 1.0 (closing connection after answering)
if( client.http_method == http_method_get )
{
CreateAnswer(client);
client.answer_generated = true;
if( client.http_version == http_version_1_0 )
{
std::cout << "answer generated, closing connection for http 1.0" << std::endl;
client.close_connection = true;
}
}
if( client.http_method != http_method_get )
{
// at the moment only get method
client.close_connection = true;
}
break;
}
}
}
bool Server::SelectMethodName(Client & client, const std::wstring & method_name)
{
client.http_method = http_method_unsupported;
if( CompareNoCase(method_name, L"get") )
{
client.http_method = http_method_get;
}
else
if( CompareNoCase(method_name, L"put") )
{
client.http_method = http_method_put;
}
else
if( CompareNoCase(method_name, L"post") )
{
client.http_method = http_method_post;
}
else
if( CompareNoCase(method_name, L"delete") )
{
client.http_method = http_method_delete;
}
else
if( CompareNoCase(method_name, L"options") )
{
client.http_method = http_method_options;
}
return client.http_method != http_method_unsupported;
}
//void Server::RemoveClientSocket(int client_socket)
//{
// for(size_t i=0 ; i<client_tab.size() ; ++i)
// {
// if( client_tab[i].socket == client_socket )
// {
// close(client_socket);
// client_tab.erase(client_tab.begin() + i);
//
// // warning: in ReadWriteToClients we have a loop through client_tab
// // (next item will be skipped by ++i)
// break;
// }
// }
//}
void Server::CreateAnswer(Client & client)
{
std::wstring a, c;
c = L"hello world from my webserver, your requested: " + client.url;
c += L"\r\n";
if( client.url == L"/quit" )
{
c += L"<br><br>bye bye";
close_server = true;
}
std::string c_ascii;
PT::WideToUTF8(c,c_ascii);
a = L"HTTP/";
if( client.http_version == http_version_1_0 )
a += L"1.0";
else
a += L"1.1";
a += L" 200 OK\r\n";
a += L"Content-Type: text/html; charset=UTF-8\r\n";
wchar_t buf[32];
swprintf(buf, sizeof(buf)/sizeof(wchar_t), L"%d", c_ascii.size());
a += L"Content-Length: ";
a += buf;
a += L"\r\n";
a += L"\r\n";
PT::WideToUTF8(a, client.output_buffer);
client.output_buffer += c_ascii;
}