From c3afe351196ec3e8e089571e784c9d6ce8979df2 Mon Sep 17 00:00:00 2001 From: Tomasz Sowa Date: Mon, 13 Mar 2017 08:55:51 +0000 Subject: [PATCH] the code was split from main.cpp to: client.h client.cpp server.h server.cpp git-svn-id: svn://ttmath.org/publicrep/libscorpiohttpserver/trunk@1055 e52654a7-88a9-db11-a3e9-0013d4bc506e --- src/Makefile | 20 +- src/Makefile.dep | 5 + src/client.cpp | 86 +++++ src/client.h | 79 ++++ src/main.cpp | 989 +---------------------------------------------- src/server.cpp | 822 +++++++++++++++++++++++++++++++++++++++ src/server.h | 139 +++++++ 7 files changed, 1149 insertions(+), 991 deletions(-) create mode 100644 src/Makefile.dep create mode 100644 src/client.cpp create mode 100644 src/client.h create mode 100644 src/server.cpp create mode 100644 src/server.h diff --git a/src/Makefile b/src/Makefile index c6d1372..37f2d50 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,8 @@ - CXX = g++5 CXXFLAGS = -g3 -O0 -I/usr/local/include -std=c++11 -I../../pikotools +o=$(patsubst %.cpp,%.o,$(wildcard *.cpp)) +name=webserver #CXX = clang++ #CXXFLAGS = -fsanitize=address -fno-omit-frame-pointer -g3 -O0 -I/usr/local/include -std=c++11 -I../pikotools -I../utf8/ @@ -20,13 +21,24 @@ pikotools: @cd ../../pikotools && make -webserver: main.cpp - g++5 -o webserver $(CXXFLAGS) -Wl,-rpath,/usr/local/lib/gcc5 main.cpp ../../pikotools/space/space.a ../../pikotools/utf8/utf8.a +webserver: $(o) + $(CXX) -o $(name) $(CXXFLAGS) -Wl,-rpath,/usr/local/lib/gcc5 $(o) ../../pikotools/space/space.a ../../pikotools/utf8/utf8.a + + +%.o: %.cpp + $(CXX) -c $(CXXFLAGS) $< clean: - rm -f webserver + rm -f $(name) cleanall: clean @cd ../../pikotools && make clean + +depend: + makedepend -Y. -f- *.cpp > Makefile.dep + + +include Makefile.dep + diff --git a/src/Makefile.dep b/src/Makefile.dep new file mode 100644 index 0000000..a467ae4 --- /dev/null +++ b/src/Makefile.dep @@ -0,0 +1,5 @@ +# DO NOT DELETE + +client.o: client.h +main.o: ./server.h client.h +server.o: ./server.h client.h diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 0000000..1ffdd94 --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,86 @@ +#include "client.h" + + + + +Client::Client() +{ + in_buffer_max_len = WEBSERVER_INPUT_BUFFER_MAX_LEN; + socket = 0; + in_buffer = nullptr; + in_buffer_len = 0; + parsing_headers = true; + parsing_first_header = true; + http_method = http_method_unsupported; + http_version = http_version_unsupported; + answer_generated = false; + output_buffer_sent = 0; + close_connection = false; +} + + +Client::Client(const Client & c) +{ + in_buffer = nullptr; + operator=(c); +} + + +Client::~Client() +{ + delete [] in_buffer; +} + + +Client & Client::operator=(const Client & c) +{ + socket = c.socket; + + if( c.in_buffer ) + AllocInputBuffer(); + else + in_buffer = nullptr; + + in_buffer_len = c.in_buffer_len; + in_buffer_max_len = c.in_buffer_max_len; + parsing_headers = c.parsing_headers; + parsing_first_header = c.parsing_first_header; + http_method = c.http_method; + http_version = c.http_version; + url = c.url; + answer_generated = c.answer_generated; + + output_buffer = c.output_buffer; + output_buffer_sent = c.output_buffer_sent; + close_connection = c.close_connection; + + return *this; +} + + + + +void Client::AllocInputBuffer() +{ + delete [] in_buffer; + in_buffer = new char[in_buffer_max_len]; +} + + +void Client::PrepareToNewRequest() +{ + if( !in_buffer ) + AllocInputBuffer(); + + parsing_headers = true; + parsing_first_header = true; + in_buffer_len = 0; + http_method = http_method_unsupported; + http_version = http_version_unsupported; + answer_generated = false; + output_buffer_sent = 0; + close_connection = false; + + output_buffer.clear(); + url.clear(); +} diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..dea3714 --- /dev/null +++ b/src/client.h @@ -0,0 +1,79 @@ +#ifndef headerfile_libscorpiohttpserver_src_client_h +#define headerfile_libscorpiohttpserver_src_client_h + + +#include + + +#define WEBSERVER_INPUT_BUFFER_MAX_LEN 8196 + + + +enum HTTPMethod +{ + http_method_get = 0, + http_method_post, + http_method_put, + http_method_delete, + http_method_options, + http_method_unsupported +}; + + +enum HTTPVersion +{ + http_version_1_0, + http_version_1_1, + http_version_unsupported +}; + + + + +class Client +{ +public: + + int socket; + PT::Space in; + + std::wstring url; + HTTPMethod http_method; + HTTPVersion http_version; + + bool answer_generated; + + bool close_connection; + + + char * in_buffer; // rename to a better name + size_t in_buffer_max_len; // at least two characters + size_t in_buffer_len; + + std::string input_buffer; + std::string header; + + bool parsing_headers; + bool parsing_first_header; + + + std::string output_buffer; + size_t output_buffer_sent; // how many characters were sent + + + Client(); + Client(const Client & c); + ~Client(); + + Client & operator=(const Client & c); + + void AllocInputBuffer(); + void PrepareToNewRequest(); + + +}; + + + +#endif + diff --git a/src/main.cpp b/src/main.cpp index ffd6d1c..f138738 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,998 +3,13 @@ #include #include #include - - -#include -#include -#include -#include -#include -#include -#include -#include - #include +#include +#include "server.h" -#include -#include -#define WEBSERVER_INPUT_BUFFER_MAX_LEN 8196 -//#define WEBSERVER_INPUT_BUFFER_MAX_LEN 2 - - -enum HTTPMethod -{ - http_method_get = 0, - http_method_post, - http_method_put, - http_method_delete, - http_method_options, - http_method_unsupported -}; - - -enum HTTPVersion -{ - http_version_1_0, - http_version_1_1, - http_version_unsupported -}; - - -class Client -{ -public: - - int socket; - PT::Space in; - - std::wstring url; - HTTPMethod http_method; - HTTPVersion http_version; - - bool answer_generated; - - bool close_connection; - - - char * in_buffer; // rename to a better name - size_t in_buffer_max_len; // at least two characters - size_t in_buffer_len; - - std::string input_buffer; - std::string header; - - bool parsing_headers; - bool parsing_first_header; - - - std::string output_buffer; - size_t output_buffer_sent; // how many characters were sent - - - Client() : in_buffer_max_len(WEBSERVER_INPUT_BUFFER_MAX_LEN) // put as a constant somewhere - { - socket = 0; - in_buffer = nullptr; - in_buffer_len = 0; - parsing_headers = true; - parsing_first_header = true; - http_method = http_method_unsupported; - http_version = http_version_unsupported; - answer_generated = false; - output_buffer_sent = 0; - close_connection = false; - } - - - Client(const Client & c) - { - in_buffer = nullptr; - operator=(c); - } - - - Client & operator=(const Client & c) - { - socket = c.socket; - - if( c.in_buffer ) - AllocInputBuffer(); - else - in_buffer = nullptr; - - in_buffer_len = c.in_buffer_len; - in_buffer_max_len = c.in_buffer_max_len; - parsing_headers = c.parsing_headers; - parsing_first_header = c.parsing_first_header; - http_method = c.http_method; - http_version = c.http_version; - url = c.url; - answer_generated = c.answer_generated; - - output_buffer = c.output_buffer; - output_buffer_sent = c.output_buffer_sent; - close_connection = c.close_connection; - - return *this; - } - - - - ~Client() - { - delete [] in_buffer; - } - - void AllocInputBuffer() - { - delete [] in_buffer; - in_buffer = new char[in_buffer_max_len]; - } - - - void PrepareToNewRequest() - { - if( !in_buffer ) - AllocInputBuffer(); - - parsing_headers = true; - parsing_first_header = true; - in_buffer_len = 0; - http_method = http_method_unsupported; - http_version = http_version_unsupported; - answer_generated = false; - output_buffer_sent = 0; - close_connection = false; - - output_buffer.clear(); - url.clear(); - } - - -}; - - - -class Server -{ -public: - - - - - - - -Server() -{ - main_socket = -1; - close_server = false; - -} - -virtual ~Server() -{ - Close(); -} - - -void CloseMainSocket() -{ - if( main_socket != -1 ) - { - close(main_socket); - main_socket = -1; - } -} - - -void CloseClientSockets() -{ - for(size_t i=0 ; i 0 ) - { - if( FD_ISSET(main_socket, &read_set) ) - { - AddNewClient(); - } - else - { - ReadInputFromClients(); - } - } - } -} - - - - - -private: - -std::vector client_tab; -int main_socket; -fd_set read_set, write_set; -std::wstring tmp_header, tmp_value; -size_t header_index; -std::string url_ascii; -bool close_server; - - -int 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 fd_max ) - fd_max = client_tab[i].socket; - } - } - else - { - std::cout << "there is no main socket" << std::endl; - } - - return fd_max; -} - - - -void 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; - - client_tab.push_back(c); - client_tab.back().PrepareToNewRequest(); - } - else - { - std::cout << "I cannot fcntl (O_NONBLOCK)" << std::endl; - } - } - } -} - - -// change to a better name as it is used to write to a client too -void ReadInputFromClients() -{ - 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 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; - - 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; - //RemoveClientSocket(client.socket); - // do not use client reference anymore - } - 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; - - std::cout << "closing connection, is it correct here?" << std::endl; - client.close_connection = true; // is it correct here? should we send an answer before? - //RemoveClientSocket(client.socket); - // do not use client reference anymore - } - 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 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.output_buffer.clear(); - client.output_buffer_sent = 0; - } - } - } -} - - - - -void 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( IsHeadersEnding(client.input_buffer.c_str() + input_buffer_index) ) - { - ParseHeaders(client); - 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; - - std::cout << "answer generated, closing connection" << std::endl; - client.close_connection = true; - } - - break; - } - } -} - - -// ptr buffer should consists of at least more 3 characters -bool IsHeadersEnding(const char * ptr) -{ - return ptr[0] == '\r' && ptr[0+1] == '\n' && - ptr[0+2] == '\r' && ptr[0+3] == '\n'; -} - - -void ParseHeaders(Client & client) -{ - header_index = 0; - - if( !ParseFirstHeader(client) ) - { - std::cout << "incorrect first header, closing connection" << std::endl; - client.close_connection = true; - return; - } - - // we are testing header_index + 3 < client.input_buffer.size() because we know - // that the \r\n\r\n sequence already exists in this string - // so there is no a problem that we leave some characters at the end of the string - while( header_index + 3 < client.input_buffer.size() && - !IsHeadersEnding(client.input_buffer.c_str() + header_index) ) - { - tmp_header.clear(); - tmp_value.clear(); - - if( ParseHeaderKey(client) ) - { - SkipWhite(client); - ParseHeaderValue(client); - TrimWhiteAtEnd(tmp_value); - - std::wcout << L"wczytalem naglowek" << std::endl; - std::wcout << tmp_header << L"|" << std::endl; - std::wcout << tmp_value << L"|" << std::endl; - - if( tmp_header.size() > 0 ) - client.in.Add(tmp_header, tmp_value); - } - else - { - // add some code to skip this line - } - } -} - - -bool ParseFirstHeader(Client & client) -{ - return ParseFirstHeaderMethodName(client) && - ParseFirstHeaderURL(client) && - ParseFirstHeaderHTTPVersion(client); -} - - -bool ParseFirstHeaderMethodName(Client & client) -{ - wchar_t method_name[32]; - size_t method_name_index = 0; - - while( header_index < client.input_buffer.size() && - client.input_buffer[header_index] != '\r' && - !IsWhite(client.input_buffer[header_index]) ) - { - wchar_t c = (unsigned char)client.input_buffer[header_index++]; - method_name[method_name_index++] = c; - - if( method_name_index >= sizeof(method_name) / sizeof(wchar_t) ) - return false; - } - - method_name[method_name_index++] = 0; - SkipWhite(client); - - return SelectMethodName(client, method_name); -} - - -virtual bool SelectMethodName(Client & client, wchar_t * 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; -} - - -/* - * The generic URI syntax mandates that new URI schemes that provide for the representation - * of character data in a URI must, in effect, represent characters from the unreserved set - * without translation, and should convert all other characters to bytes according to UTF-8, - * and then percent-encode those values. This requirement was introduced in January 2005 - * with the publication of RFC 3986. URI schemes introduced before this date are not affected. - * - * - */ -bool ParseFirstHeaderURL(Client & client) -{ - client.url.clear(); - url_ascii.clear(); - - while( header_index < client.input_buffer.size() && - client.input_buffer[header_index] != '\r' && - !IsWhite(client.input_buffer[header_index]) ) - { - wchar_t c = (unsigned char)client.input_buffer[header_index++]; - - if( c == '+' ) - { - c = ' '; - } - else - if( c == '%' ) - { - if( header_index + 2 < client.input_buffer.size() ) - { - wchar_t c1 = ToLower((unsigned char)client.input_buffer[header_index++]); - wchar_t c2 = ToLower((unsigned char)client.input_buffer[header_index++]); - - if( IsHexDigit(c1) && IsHexDigit(c2) ) - { - int v1 = HexDigitToValue(c1); - int v2 = HexDigitToValue(c2); - - c = static_cast((v1 << 4) + v2); - } - else - { - return false; - } - } - else - { - return false; - } - } - else - if( c < 32 || c > 127 ) - { - return false; - } - - url_ascii += static_cast(c); - } - - bool utf8_correct = PT::UTF8ToWide(url_ascii, client.url); - - SkipWhite(client); - url_ascii.clear(); - - return utf8_correct && !client.url.empty(); -} - - - -bool ParseFirstHeaderHTTPVersion(Client & client) -{ - client.http_version = http_version_unsupported; - - if( header_index + 7 < client.input_buffer.size() && - client.input_buffer[header_index] == 'H' && - client.input_buffer[header_index + 1] == 'T' && - client.input_buffer[header_index + 2] == 'T' && - client.input_buffer[header_index + 3] == 'P' && - client.input_buffer[header_index + 4] == '/' && - IsDecDigit(client.input_buffer[header_index + 5]) && - client.input_buffer[header_index + 6] == '.' && - IsDecDigit(client.input_buffer[header_index + 7]) ) - { - int d1 = client.input_buffer[header_index + 5] - '0'; - int d2 = client.input_buffer[header_index + 7] - '0'; - - int ddd1 = client.input_buffer[header_index + 5] - '0'; - int ddd2 = client.input_buffer[header_index + 7] - '0'; - - if( d1 == 1 ) - { - client.http_version = http_version_1_0; - - } - - if( d2 == 1 ) - { - client.http_version = http_version_1_1; - } - - if( ddd1 == 1 ) - { - client.http_version = http_version_1_0; - - } - - if( ddd2 == 1 ) - { - client.http_version = http_version_1_1; - } - - if( d1 == 1 ) - { - if( d2 == 0 ) - client.http_version = http_version_1_0; - else - if( d2 == 1 ) - client.http_version = http_version_1_1; - } - - header_index += 8; - SkipWhite(client); - } - - return client.http_version != http_version_unsupported; -} - - - -// ParseHeaderKey should increment header_index at least once -bool ParseHeaderKey(Client & client) -{ - while( header_index < client.input_buffer.size() && - client.input_buffer[header_index] != '\r' ) - { - if( client.input_buffer[header_index] == ':' ) - { - header_index += 1; - return true; - } - - wchar_t c = (unsigned char)client.input_buffer[header_index]; - - if( c >= 32 && c < 127 ) - { - // allow only asci characters - - tmp_header += c; - } - - header_index += 1; - } - - // there was not a colon at the end of the name - header_index += 1; - return false; -} - - -void ParseHeaderValue(Client & client) -{ - while( header_index < client.input_buffer.size() ) - { - if( header_index + 1 < client.input_buffer.size() && - client.input_buffer[header_index] == '\r' && - client.input_buffer[header_index+1] == '\n' ) - { - if( header_index + 2 < client.input_buffer.size() && - (client.input_buffer[header_index+2] == ' ' || - client.input_buffer[header_index+2] == '\t') ) - { - // this line will be continued in the next line - header_index += 3; - - } - else - { - header_index += 2; - break; - } - } - else - { - wchar_t c = (unsigned char)client.input_buffer[header_index]; - - if( c >= 32 && c < 127 ) - { - // allow only ascii characters - - tmp_value += c; - } - - header_index += 1; - } - } -} - - -//void RemoveClientSocket(int client_socket) -//{ -// for(size_t i=0 ; i= '0' && c <= '9' ) - return true; - - return false; -} - - -bool IsHexDigit(wchar_t c) -{ - if( c >= '0' && c <= '9' ) - return true; - - if( ToLower(c) >= 'a' && ToLower(c) <= 'f' ) - return true; - - return false; -} - - -int HexDigitToValue(wchar_t c) -{ - if( c >= '0' && c <= '9' ) - return static_cast(c - '0'); - - if( ToLower(c) >= 'a' && ToLower(c) <= 'f' ) - return static_cast(c - 'a') + 10; - - return 0; -} - -wchar_t ToLower(wchar_t c) -{ - if( c >= 'A' && c <= 'Z' ) - c = c - 'A' + 'a'; - - return c; -} - - -bool CompareNoCase(const wchar_t * str1, const wchar_t * str2) -{ - while( *str1 && *str2 && ToLower(*str1) == ToLower(*str2) ) - { - ++str1; - ++str2; - } - - if( *str1 == 0 && *str2 == 0 ) - return true; - -return false; -} - - -bool CompareNoCase(const std::wstring & str1, const wchar_t * str2) -{ - return CompareNoCase(str1.c_str(), str2); -} - - - -// do not use \r or \n here -bool IsWhite(wchar_t c) -{ - return (c == ' ' || c == '\t'); -} - - -void TrimWhiteAtEnd(std::wstring & str) -{ - if( !str.empty() ) - { - size_t i = str.size(); - - while( i > 0 && IsWhite(str[i-1]) ) - { - i -= 1; - } - - if( i < str.size() ) - str.erase(i); - } -} - - -void SkipWhite(Client & client) -{ - while( header_index < client.input_buffer.size() && - IsWhite(client.input_buffer[header_index]) ) - { - header_index += 1; - } -} - - -virtual void CreateAnswer(Client & client) -{ - std::wstring a; - - a = L"HTTP/1.0 200 OK\r\n"; - a += L"Content-Type: text/html; charset=UTF-8\r\n"; - a += L"\r\n"; - a += L"hello world from my webserwer, your requested: " + client.url; - - if( client.url == L"/quit" ) - { - a += L"

bye bye"; - close_server = true; - } - - PT::WideToUTF8(a, client.output_buffer); -} - - - - -}; - - Server server; diff --git a/src/server.cpp b/src/server.cpp new file mode 100644 index 0000000..7ee8809 --- /dev/null +++ b/src/server.cpp @@ -0,0 +1,822 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "server.h" + + + + +Server::Server() +{ + main_socket = -1; + close_server = false; + header_index = 0; +} + +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 0 ) + { + if( FD_ISSET(main_socket, &read_set) ) + { + AddNewClient(); + } + else + { + ReadInputFromClients(); + } + } + } +} + + + +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 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; + + client_tab.push_back(c); + client_tab.back().PrepareToNewRequest(); + } + else + { + std::cout << "I cannot fcntl (O_NONBLOCK)" << std::endl; + } + } + } +} + + +// change to a better name as it is used to write to a client too +void Server::ReadInputFromClients() +{ + 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; + + 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; + //RemoveClientSocket(client.socket); + // do not use client reference anymore + } + 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; + + std::cout << "closing connection, is it correct here?" << std::endl; + client.close_connection = true; // is it correct here? should we send an answer before? + //RemoveClientSocket(client.socket); + // do not use client reference anymore + } + 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.output_buffer.clear(); + client.output_buffer_sent = 0; + } + } + } +} + + + + +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( IsHeadersEnding(client.input_buffer.c_str() + input_buffer_index) ) + { + ParseHeaders(client); + 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; + + std::cout << "answer generated, closing connection" << std::endl; + client.close_connection = true; + } + + break; + } + } +} + + +// ptr buffer should consists of at least more 3 characters +bool Server::IsHeadersEnding(const char * ptr) +{ + return ptr[0] == '\r' && ptr[0+1] == '\n' && + ptr[0+2] == '\r' && ptr[0+3] == '\n'; +} + + +void Server::ParseHeaders(Client & client) +{ + header_index = 0; + + if( !ParseFirstHeader(client) ) + { + std::cout << "incorrect first header, closing connection" << std::endl; + client.close_connection = true; + return; + } + + // we are testing header_index + 3 < client.input_buffer.size() because we know + // that the \r\n\r\n sequence already exists in this string + // so there is no a problem that we leave some characters at the end of the string + while( header_index + 3 < client.input_buffer.size() && + !IsHeadersEnding(client.input_buffer.c_str() + header_index) ) + { + tmp_header.clear(); + tmp_value.clear(); + + if( ParseHeaderKey(client) ) + { + SkipWhite(client); + ParseHeaderValue(client); + TrimWhiteAtEnd(tmp_value); + + std::wcout << L"wczytalem naglowek" << std::endl; + std::wcout << tmp_header << L"|" << std::endl; + std::wcout << tmp_value << L"|" << std::endl; + + if( tmp_header.size() > 0 ) + client.in.Add(tmp_header, tmp_value); + } + else + { + // add some code to skip this line + } + } +} + + +bool Server::ParseFirstHeader(Client & client) +{ + return ParseFirstHeaderMethodName(client) && + ParseFirstHeaderURL(client) && + ParseFirstHeaderHTTPVersion(client); +} + + +bool Server::ParseFirstHeaderMethodName(Client & client) +{ + wchar_t method_name[32]; + size_t method_name_index = 0; + + while( header_index < client.input_buffer.size() && + client.input_buffer[header_index] != '\r' && + !IsWhite(client.input_buffer[header_index]) ) + { + wchar_t c = (unsigned char)client.input_buffer[header_index++]; + method_name[method_name_index++] = c; + + if( method_name_index >= sizeof(method_name) / sizeof(wchar_t) ) + return false; + } + + method_name[method_name_index++] = 0; + SkipWhite(client); + + return SelectMethodName(client, method_name); +} + + +bool Server::SelectMethodName(Client & client, wchar_t * 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; +} + + +/* + * The generic URI syntax mandates that new URI schemes that provide for the representation + * of character data in a URI must, in effect, represent characters from the unreserved set + * without translation, and should convert all other characters to bytes according to UTF-8, + * and then percent-encode those values. This requirement was introduced in January 2005 + * with the publication of RFC 3986. URI schemes introduced before this date are not affected. + * + * + */ +bool Server::ParseFirstHeaderURL(Client & client) +{ + client.url.clear(); + url_ascii.clear(); + + while( header_index < client.input_buffer.size() && + client.input_buffer[header_index] != '\r' && + !IsWhite(client.input_buffer[header_index]) ) + { + wchar_t c = (unsigned char)client.input_buffer[header_index++]; + + if( c == '+' ) + { + c = ' '; + } + else + if( c == '%' ) + { + if( header_index + 2 < client.input_buffer.size() ) + { + wchar_t c1 = ToLower((unsigned char)client.input_buffer[header_index++]); + wchar_t c2 = ToLower((unsigned char)client.input_buffer[header_index++]); + + if( IsHexDigit(c1) && IsHexDigit(c2) ) + { + int v1 = HexDigitToValue(c1); + int v2 = HexDigitToValue(c2); + + c = static_cast((v1 << 4) + v2); + } + else + { + return false; + } + } + else + { + return false; + } + } + else + if( c < 32 || c > 127 ) + { + return false; + } + + url_ascii += static_cast(c); + } + + bool utf8_correct = PT::UTF8ToWide(url_ascii, client.url); + + SkipWhite(client); + url_ascii.clear(); + + return utf8_correct && !client.url.empty(); +} + + + +bool Server::ParseFirstHeaderHTTPVersion(Client & client) +{ + client.http_version = http_version_unsupported; + + if( header_index + 7 < client.input_buffer.size() && + client.input_buffer[header_index] == 'H' && + client.input_buffer[header_index + 1] == 'T' && + client.input_buffer[header_index + 2] == 'T' && + client.input_buffer[header_index + 3] == 'P' && + client.input_buffer[header_index + 4] == '/' && + IsDecDigit(client.input_buffer[header_index + 5]) && + client.input_buffer[header_index + 6] == '.' && + IsDecDigit(client.input_buffer[header_index + 7]) ) + { + int d1 = client.input_buffer[header_index + 5] - '0'; + int d2 = client.input_buffer[header_index + 7] - '0'; + + int ddd1 = client.input_buffer[header_index + 5] - '0'; + int ddd2 = client.input_buffer[header_index + 7] - '0'; + + if( d1 == 1 ) + { + client.http_version = http_version_1_0; + + } + + if( d2 == 1 ) + { + client.http_version = http_version_1_1; + } + + if( ddd1 == 1 ) + { + client.http_version = http_version_1_0; + + } + + if( ddd2 == 1 ) + { + client.http_version = http_version_1_1; + } + + if( d1 == 1 ) + { + if( d2 == 0 ) + client.http_version = http_version_1_0; + else + if( d2 == 1 ) + client.http_version = http_version_1_1; + } + + header_index += 8; + SkipWhite(client); + } + + return client.http_version != http_version_unsupported; +} + + + +// ParseHeaderKey should increment header_index at least once +bool Server::ParseHeaderKey(Client & client) +{ + while( header_index < client.input_buffer.size() && + client.input_buffer[header_index] != '\r' ) + { + if( client.input_buffer[header_index] == ':' ) + { + header_index += 1; + return true; + } + + wchar_t c = (unsigned char)client.input_buffer[header_index]; + + if( c >= 32 && c < 127 ) + { + // allow only asci characters + + tmp_header += c; + } + + header_index += 1; + } + + // there was not a colon at the end of the name + header_index += 1; + return false; +} + + +void Server::ParseHeaderValue(Client & client) +{ + while( header_index < client.input_buffer.size() ) + { + if( header_index + 1 < client.input_buffer.size() && + client.input_buffer[header_index] == '\r' && + client.input_buffer[header_index+1] == '\n' ) + { + if( header_index + 2 < client.input_buffer.size() && + (client.input_buffer[header_index+2] == ' ' || + client.input_buffer[header_index+2] == '\t') ) + { + // this line will be continued in the next line + header_index += 3; + + } + else + { + header_index += 2; + break; + } + } + else + { + wchar_t c = (unsigned char)client.input_buffer[header_index]; + + if( c >= 32 && c < 127 ) + { + // allow only ascii characters + + tmp_value += c; + } + + header_index += 1; + } + } +} + + +//void Server::RemoveClientSocket(int client_socket) +//{ +// for(size_t i=0 ; i= '0' && c <= '9' ) + return true; + + return false; +} + + +bool Server::IsHexDigit(wchar_t c) +{ + if( c >= '0' && c <= '9' ) + return true; + + if( ToLower(c) >= 'a' && ToLower(c) <= 'f' ) + return true; + + return false; +} + + +int Server::HexDigitToValue(wchar_t c) +{ + if( c >= '0' && c <= '9' ) + return static_cast(c - '0'); + + if( ToLower(c) >= 'a' && ToLower(c) <= 'f' ) + return static_cast(c - 'a') + 10; + + return 0; +} + +wchar_t Server::ToLower(wchar_t c) +{ + if( c >= 'A' && c <= 'Z' ) + c = c - 'A' + 'a'; + + return c; +} + + +bool Server::CompareNoCase(const wchar_t * str1, const wchar_t * str2) +{ + while( *str1 && *str2 && ToLower(*str1) == ToLower(*str2) ) + { + ++str1; + ++str2; + } + + if( *str1 == 0 && *str2 == 0 ) + return true; + +return false; +} + + +bool Server::CompareNoCase(const std::wstring & str1, const wchar_t * str2) +{ + return CompareNoCase(str1.c_str(), str2); +} + + + +// do not use \r or \n here +bool Server::IsWhite(wchar_t c) +{ + return (c == ' ' || c == '\t'); +} + + +void Server::TrimWhiteAtEnd(std::wstring & str) +{ + if( !str.empty() ) + { + size_t i = str.size(); + + while( i > 0 && IsWhite(str[i-1]) ) + { + i -= 1; + } + + if( i < str.size() ) + str.erase(i); + } +} + + +void Server::SkipWhite(Client & client) +{ + while( header_index < client.input_buffer.size() && + IsWhite(client.input_buffer[header_index]) ) + { + header_index += 1; + } +} + + +void Server::CreateAnswer(Client & client) +{ + std::wstring a; + + a = L"HTTP/1.0 200 OK\r\n"; + a += L"Content-Type: text/html; charset=UTF-8\r\n"; + a += L"\r\n"; + a += L"hello world from my webserwer, your requested: " + client.url; + + if( client.url == L"/quit" ) + { + a += L"

bye bye"; + close_server = true; + } + + PT::WideToUTF8(a, client.output_buffer); +} + + diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..7cbe6bb --- /dev/null +++ b/src/server.h @@ -0,0 +1,139 @@ +#ifndef headerfile_libscorpiohttpserver_src_server_h +#define headerfile_libscorpiohttpserver_src_server_h + + +#include +#include "client.h" + + + +class Server +{ +public: + + + Server(); + virtual ~Server(); + + + void CloseMainSocket(); + void CloseClientSockets(); + void Close(); + + void PrepareMainSocket(); + void Wait(); + + + +private: + + std::vector client_tab; + int main_socket; + fd_set read_set, write_set; + std::wstring tmp_header, tmp_value; + size_t header_index; + std::string url_ascii; + bool close_server; + + + int AddSocketsToSet(); + void AddNewClient(); + + // change to a better name as it is used to write to a client too + void ReadInputFromClients(); + + void ReadInputFromClient(Client & client); + void WriteOutputToClient(Client & client); + + void CheckHeaders(Client & client, size_t input_buffer_index); + + + // ptr buffer should consists of at least more 3 characters + bool IsHeadersEnding(const char * ptr); + + void ParseHeaders(Client & client); + bool ParseFirstHeader(Client & client); + + + bool ParseFirstHeaderMethodName(Client & client); + + + virtual bool SelectMethodName(Client & client, wchar_t * method_name); + + + /* + * The generic URI syntax mandates that new URI schemes that provide for the representation + * of character data in a URI must, in effect, represent characters from the unreserved set + * without translation, and should convert all other characters to bytes according to UTF-8, + * and then percent-encode those values. This requirement was introduced in January 2005 + * with the publication of RFC 3986. URI schemes introduced before this date are not affected. + * + * + */ + bool ParseFirstHeaderURL(Client & client); + + bool ParseFirstHeaderHTTPVersion(Client & client); + + + + // ParseHeaderKey should increment header_index at least once + bool ParseHeaderKey(Client & client); + + + void ParseHeaderValue(Client & client); + + + //void RemoveClientSocket(int client_socket); + + + bool IsDecDigit(wchar_t c); + bool IsHexDigit(wchar_t c); + int HexDigitToValue(wchar_t c); + wchar_t ToLower(wchar_t c); + bool CompareNoCase(const wchar_t * str1, const wchar_t * str2); + bool CompareNoCase(const std::wstring & str1, const wchar_t * str2); + + // do not use \r or \n here + bool IsWhite(wchar_t c); + + void TrimWhiteAtEnd(std::wstring & str); + void SkipWhite(Client & client); + virtual void CreateAnswer(Client & client); + + +}; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#endif + +