scorpioengine/src/server.cpp

868 lines
17 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 <errno.h>
#include <sys/stat.h>
#include "server.h"
#include "string_functions.h"
Server::Server()
{
main_socket = -1;
close_server = false;
in_buffer_max_len = 2048;
in_buffer = nullptr;
}
Server::~Server()
{
Close();
}
void Server::CloseMainSocket()
{
if( main_socket != -1 )
{
close(main_socket);
main_socket = -1;
}
}
void Server::CloseClientSockets()
{
for(auto i=client_tab.begin() ; i != client_tab.end() ; ++i)
{
close(i->socket);
if( i->external_resource.external_resource_fd >= 0 )
close(i->external_resource.external_resource_fd);
}
client_tab.clear();
}
void Server::Close()
{
CloseMainSocket();
CloseClientSockets();
}
void Server::AllocInputBuffer()
{
delete [] in_buffer;
in_buffer = new char[in_buffer_max_len];
}
void Server::Prepare()
{
PrepareMainSocket();
AllocInputBuffer();
}
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 )
{
timeval t;
int fd_max;
int how_many_external_resources;
PrepareSocketsForSelect(fd_max, t, how_many_external_resources);
timeval * pt = 0;
if( how_many_external_resources > 0 )
pt = &t;
int ready_fd = select(fd_max + 1, &read_set, &write_set, 0, pt);
if( how_many_external_resources > 0 )
ReadExternalResource(how_many_external_resources);
if( ready_fd > 0 )
{
if( FD_ISSET(main_socket, &read_set) )
{
AddNewClient();
}
else
{
ReadWriteToClients();
}
}
}
}
void Server::PrepareSocketsForSelect(int & fd_max, timeval & t, int & how_many_external_resources)
{
FD_ZERO(&read_set);
FD_ZERO(&write_set);
fd_max = 0;
t.tv_sec = 0;
t.tv_usec = 0;
how_many_external_resources = 0;
if( main_socket >= 0 )
{
FD_SET(main_socket, &read_set);
fd_max = main_socket;
for(auto i=client_tab.begin() ; i != client_tab.end() ; ++i)
{
if( !i->answering_to_client )
{
/*
* do not read the next request until the answer is sent
* (client can use pipelining)
*
*/
FD_SET(i->socket, &read_set);
}
else
{
FD_SET(i->socket, &write_set);
}
if( i->socket > fd_max )
fd_max = i->socket;
if( i->external_resource.reading_external_resource )
{
how_many_external_resources += 1;
}
}
}
else
{
std::cout << "there is no main socket" << std::endl;
}
if( how_many_external_resources > 0 )
t.tv_usec = 1;
}
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;
c.PrepareToNewRequest();
client_tab.push_back(c);
}
else
{
std::cout << "I cannot fcntl (O_NONBLOCK)" << std::endl;
}
}
}
}
void Server::ReadWriteToClients()
{
for(auto i=client_tab.begin() ; i != client_tab.end() ; )
{
if( FD_ISSET(i->socket, &read_set))
{
ReadInputFromClient(*i);
}
if( FD_ISSET(i->socket, &write_set) )
{
WriteOutputToClient(*i);
}
if( !i->answering_to_client && i->close_connection && (!i->external_resource.reading_external_resource || !i->external_resource.waiting_for_last_read) )
{
std::cout << "closing connection for client nr " << i->socket << std::endl;
close(i->socket);
if( i->external_resource.external_resource_fd >= 0 )
close(i->external_resource.external_resource_fd);
auto next_i = i;
++next_i;
client_tab.erase(i);
i = next_i;
}
else
{
++i;
}
}
}
void Server::ReadExternalResource(int how_many_external_resources)
{
int count = 0;
for(auto i=client_tab.begin() ; i != client_tab.end() && count < how_many_external_resources ; ++i)
{
if( i->external_resource.reading_external_resource )
{
if( i->reading_to_buffer == 1 )
{
ReadExternalResource(*i, i->output_buffer);
}
else
if( i->reading_to_buffer == 2 )
{
ReadExternalResource(*i, i->output_buffer_two);
}
count += 1;
}
}
}
void Server::ReadExternalResource(Client & client, std::string & output_buffer)
{
if( !client.external_resource.waiting_for_last_read )
{
client.iocb.aio_fildes = client.external_resource.external_resource_fd;
client.iocb.aio_offset = client.external_resource.file_how_many_read;
client.iocb.aio_buf = (volatile void*)client.read_static_buffer;
client.iocb.aio_nbytes = client.read_static_buffer_size;
/*
* at the beginning we have some headers in the first output buffer
*
* zmniejszamy ilosc bajtow do odczytania tak aby cala dlugosc bufora byla podzielna przez ilosc pakietow do wyslania
*
*/
if( output_buffer.size() < client.iocb.aio_nbytes )
client.iocb.aio_nbytes -= output_buffer.size();
int res = aio_read(&client.iocb);
if( res < 0 )
{
int err = errno;
std::cout << "aio_read has problems: errno: " << err << std::endl;
}
else
{
client.external_resource.waiting_for_last_read = true;
}
}
else
{
int res = aio_error(&client.iocb);
std::cout << "aio_error returned: " << res << std::endl;
if( res == EINPROGRESS )
{
std::cout << "res is equal EINPROGRESS" << std::endl;
}
else
if( res == 0 )
{
std::cout << "reading completed" << std::endl;
res = aio_return(&client.iocb);
if( res < 0 )
{
client.reading_to_buffer = 0;
client.external_resource.reading_external_resource = false;
close(client.external_resource.external_resource_fd);
client.external_resource.external_resource_fd = -1;
int err = errno;
std::cout << "aio_return returned error, errno: " << err << std::endl;
}
else
if( res == 0 )
{
std::cout << "koniec czytania pliku" << std::endl;
if( client.sending_from_buffer == 0 )
client.sending_from_buffer = client.reading_to_buffer;
client.reading_to_buffer = 0;
client.external_resource.reading_external_resource = false;
close(client.external_resource.external_resource_fd);
client.external_resource.external_resource_fd = -1;
}
else
{
std::cout << "przeczytano bajtow: " << res << std::endl;
output_buffer.append(client.read_static_buffer, res);
client.external_resource.file_how_many_read += (size_t)res;
}
client.external_resource.waiting_for_last_read = false;
if( output_buffer.size() > 1500 ) // send at least one full packed, add to config
{
if( client.reading_to_buffer == 1 )
{
client.reading_to_buffer = 0;
if( client.sending_from_buffer != 2 )
{
client.reading_to_buffer = 2;
}
if( client.sending_from_buffer == 0 )
{
client.sending_from_buffer = 1;
}
}
else
if( client.reading_to_buffer == 2 )
{
client.reading_to_buffer = 0;
if( client.sending_from_buffer != 1 )
{
client.reading_to_buffer = 1;
}
if( client.sending_from_buffer == 0 )
{
client.sending_from_buffer = 2;
}
}
}
}
else
{
std::cout << "some error" << std::endl;
// what about this buffer?
}
}
}
void Server::ReadInputFromClient(Client & client)
{
int read_len = read(client.socket, in_buffer, in_buffer_max_len);
if( read_len < 0 )
{
// read failed, we do not parse this request
std::cout << "read failed for client " << client.socket << std::endl;
client.close_connection = true;
}
else
if( read_len == 0 )
{
// the client has terminated connection
std::cout << "read returned 0, client " << client.socket << " closed the connection" << std::endl;
client.close_connection = true;
}
else
{
size_t input_buffer_index = client.input_buffer.size();
client.input_buffer.append(in_buffer, read_len);
std::cout << "read from client " << client.socket << ":";
for(size_t i=0 ; i<(size_t)read_len ; ++i)
std::cout << in_buffer[i];
std::cout << std::endl;
if( client.parsing_headers )
CheckHeaders(client, input_buffer_index);
}
}
void Server::WriteOutputToClient(Client & client, std::string & output_buffer, size_t & output_buffer_sent)
{
const char * data = output_buffer.c_str() + output_buffer_sent;
size_t how_many_to_send = output_buffer.size() - output_buffer_sent;
int len = 0;
if( how_many_to_send > 0 )
{
len = send(client.socket, data, how_many_to_send, 0);
if( len < 0 )
{
std::cout << "writing failed" << std::endl;
}
}
if( len >= 0 )
{
output_buffer_sent += len;
if( output_buffer_sent >= output_buffer.size() )
{
output_buffer.clear();
output_buffer_sent = 0;
if( client.sending_from_buffer == 1 )
{
client.sending_from_buffer = 0;
if( client.reading_to_buffer != 2 && !client.output_buffer_two.empty() )
{
client.sending_from_buffer = 2;
}
if( client.external_resource.reading_external_resource && client.reading_to_buffer == 0 )
{
client.reading_to_buffer = 1;
}
}
else
if( client.sending_from_buffer == 2 )
{
client.sending_from_buffer = 0;
if( client.reading_to_buffer != 1 && !client.output_buffer.empty() )
{
client.sending_from_buffer = 1;
}
if( client.external_resource.reading_external_resource && client.reading_to_buffer == 0 )
{
client.reading_to_buffer = 2;
}
}
}
}
}
void Server::WriteOutputToClient(Client & client)
{
if( client.sending_from_buffer == 1 )
{
WriteOutputToClient(client, client.output_buffer, client.output_buffer_sent);
}
else
if( client.sending_from_buffer == 2 )
{
WriteOutputToClient(client, client.output_buffer_two, client.output_buffer_two_sent);
}
else
if( !client.external_resource.reading_external_resource && client.output_buffer.empty() && client.output_buffer_two.empty() )
{
client.PrepareToNewRequest();
}
}
void Server::CheckHeaders(Client & client, size_t input_buffer_index)
{
// we check at least 3 bytes before because we are looking for \r\n\r\n
// and the input_buffer_index can point at the last \n now
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) )
{
ParseHeaders(client);
client.input_buffer.erase(0, input_buffer_index + 4);
break;
}
}
}
void Server::ParseHeaders(Client & client)
{
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;
// temporarily we are using only get method with http 1.0 (closing connection after answering)
if( client.http_method == http_method_get )
{
CreateAnswer(client);
/*
*
* we cannot close the connection here (a file can be send)
*
if( client.http_version == http_version_1_0 )
{
if( !CompareNoCase(client.in.Text(L"connection"), L"keep-alive") )
{
//std::cout << "answer generated, closing connection for http 1.0" << std::endl;
client.close_connection = true;
}
else
{
//std::cout << "keep-alive is set, not closing connection for http 1.0" << std::endl;
}
}
*/
}
if( client.http_method != http_method_get )
{
// at the moment only get method
client.close_connection = true;
}
}
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;
// }
// }
//}
int Server::FileLen(std::string & file_name, size_t & file_len)
{
struct stat s;
file_len = 0;
int res = stat(file_name.c_str(), &s);
if( res == 0 )
{
file_len = s.st_size;
return true;
}
return false;
}
void Server::CreateOutputHeaders(Client & client)
{
std::wstring a;
wchar_t buf[32];
swprintf(buf, sizeof(buf)/sizeof(wchar_t), L"%d", client.status);
a = L"HTTP/";
if( client.http_version == http_version_1_0 )
a += L"1.0";
else
a += L"1.1";
a += L" ";
a += buf;
a += L" ";
if( client.status == 200 )
a += L"OK";
else
if( client.status == 404 )
a += L"Not Found";
a += L"\r\n";
PT::WideToUTF8(a, client.output_buffer); // we can use directly client.output_buffer without 'a'
auto i = client.out_headers.table_single.begin();
bool has_connection = false;
for( ; i != client.out_headers.table_single.end() ; ++i)
{
a = i->first;
a += L": ";
a += i->second;
a += L"\r\n";
PT::WideToUTF8(a, client.output_buffer, false);
if( CompareNoCase(i->first, L"connection") )
has_connection = true;
}
if( !has_connection && CompareNoCase(client.in.Text(L"connection"), L"keep-alive") )
{
client.output_buffer += "Connection: keep-alive\r\n";
}
a = L"\r\n";
PT::WideToUTF8(a, client.output_buffer, false);
}
void Server::CreateAnswer(Client & client)
{
// tmp, a user method will be called
std::wstring a, c;
std::string c_ascii;
if( client.url == L"/static/styles.css" )
{
a.clear();
}
if( client.url == L"/quit" )
{
close_server = true;
}
//if( client.url == L"/swinka.jpg" )
{
std::string file_name_ascii;
client.send_file = L"/home/tomek/roboczy/prog/libscorpiohttpserver/www-data";
client.send_file += client.url;
PT::WideToUTF8(client.send_file, file_name_ascii);
if( FileLen(file_name_ascii, client.external_resource.file_len) )
{
client.status = 200;
//client.out_headers.Add(L"Content-Type", L"image/jpeg;");
client.out_headers.Add(L"Content-Length", client.external_resource.file_len);
CreateOutputHeaders(client);
if( client.external_resource.external_resource_fd != -1 )
close(client.external_resource.external_resource_fd);
// improve me (utf8)
client.external_resource.external_resource_fd = open(file_name_ascii.c_str(), O_RDONLY | O_NONBLOCK);
if( client.external_resource.external_resource_fd >= 0 )
{
std::cout << "przygotowuje do czytania pliku " << file_name_ascii << std::endl;
client.reading_to_buffer = 1;
client.sending_from_buffer = 0;
client.answering_to_client = true;
client.external_resource.reading_external_resource = true;
client.external_resource.type = ExternalResource::type_file;
}
return;
}
else
{
client.status = 404;
client.out_headers.Add(L"Content-Type", L"text/html; charset=UTF-8");
c = L"404 nie ma czegos takiego";
PT::WideToUTF8(c, c_ascii);
client.out_headers.Add(L"Content-Length", c_ascii.size());
CreateOutputHeaders(client);
client.output_buffer += c_ascii;
client.reading_to_buffer = 0;
client.sending_from_buffer = 1;
client.answering_to_client = true;
}
return;
}
/*
else
{
client.status = 200;
client.out_headers.Add(L"Content-Type", L"text/html; charset=UTF-8");
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;
}
PT::WideToUTF8(c,c_ascii);
client.out_headers.Add(L"Content-Length", c_ascii.size());
CreateOutputHeaders(client);
client.output_buffer += c_ascii;
client.reading_to_buffer = 0;
client.sending_from_buffer = 1;
client.answering_to_client = true;
}
*/
}