commit 4368dbd19471f5ada58ecc521eb624943615050e Author: Tomasz Sowa Date: Wed Mar 1 13:36:29 2017 +0000 - importing: lib scorpio http server start working on a http server git-svn-id: svn://ttmath.org/publicrep/libscorpiohttpserver/trunk@1048 e52654a7-88a9-db11-a3e9-0013d4bc506e diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..c6d1372 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,32 @@ + + +CXX = g++5 +CXXFLAGS = -g3 -O0 -I/usr/local/include -std=c++11 -I../../pikotools + +#CXX = clang++ +#CXXFLAGS = -fsanitize=address -fno-omit-frame-pointer -g3 -O0 -I/usr/local/include -std=c++11 -I../pikotools -I../utf8/ + + +export CXX +export CXXFLAGS + + +all: pikotools webserver + +.PHONY: pikotools + + +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 + + +clean: + rm -f webserver + +cleanall: clean + @cd ../../pikotools && make clean + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ffd6d1c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,1036 @@ +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; + + +void signal_handler_sigpipe(int) +{ + std::cout << "SIGPIPE we are continuing" << std::endl; + +} + + + + +void signal_handler(int s) +{ + server.Close(); + + + std::cout << "bye bye (signal caught)" << std::endl; + std::exit(0); +} + + + +int main() +{ + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); + + signal(SIGPIPE, signal_handler_sigpipe); + + + server.PrepareMainSocket(); + server.Wait(); + + +} + + +