diff --git a/.github/workflows/gtest.yml b/.github/workflows/gtest.yml index 343647bd..c8893bb4 100644 --- a/.github/workflows/gtest.yml +++ b/.github/workflows/gtest.yml @@ -12,7 +12,7 @@ jobs: cmake --build build - name: run all tests on Linux - run: ./build/unit_test + run: ./build/unit_test 2>/dev/null - uses: sarisia/actions-status-discord@v1 if: always() @@ -32,7 +32,7 @@ jobs: cmake --build build - name: run all tests on macOS - run: ./build/unit_test + run: ./build/unit_test 2>/dev/null - uses: sarisia/actions-status-discord@v1 if: always() diff --git a/CMakeLists.txt b/CMakeLists.txt index a8206a8c..1f58b706 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,7 @@ add_executable(webserv set (unit_test_srcs test/unit_test/is_valid_file_path/test_get_valid_config_file_path.cpp test/unit_test/TestSocket.cpp - srcs/Server/Server.cpp srcs/Server/Server.hpp) + srcs/Server/Server.cpp srcs/Server/Server.hpp test/unit_test/TestServer.cpp) add_executable(unit_test ${webserv_srcs} diff --git a/Makefile b/Makefile index 75c910f8..63177355 100644 --- a/Makefile +++ b/Makefile @@ -71,8 +71,8 @@ unit : #rm -rf build cmake -S . -B build cmake --build build - #./build/unit_test 2>/dev/null - ./build/unit_test + ./build/unit_test 2>/dev/null + #./build/unit_test #cd build && ctest -include $(DEPS) \ No newline at end of file diff --git a/srcs/Server/Server.cpp b/srcs/Server/Server.cpp index 9242c65b..d2d6332d 100644 --- a/srcs/Server/Server.cpp +++ b/srcs/Server/Server.cpp @@ -1,15 +1,19 @@ +#include #include #include #include +#include #include #include #include "Server.hpp" #include "webserv.hpp" +#include "Color.hpp" Server::Server(const char *server_ip, const char *server_port) : _socket(server_ip, server_port), - _connect_fd(ERROR) { + _connect_fd(ERROR), + _recv_message() { if (this->_socket.get_status() == ERROR) { throw std::runtime_error("[Error] server initialization error"); } @@ -17,76 +21,159 @@ Server::Server(const char *server_ip, Server::Server() {} -Server::~Server() { close_connection(); } +Server::~Server() { + if (this->_connect_fd != ERROR) { + close_connection(this->_connect_fd); + this->_connect_fd = ERROR; + } +} + +void Server::process_client_connection() { + char *buf = NULL; -void Server::run() { - char buf[BUF_SIZE]; // tmp // select // todo // accept - if (accept_connection() == ERROR) { + this->_connect_fd = accept_connection(this->_socket.get_socket_fd(), this->_socket.get_addr_info()); + if (this->_connect_fd == ERROR) { throw std::runtime_error("[Error] accept"); } // recv - if (recv_request(buf) == ERROR) { + if (recv_request(this->_connect_fd, &buf) == ERROR) { throw std::runtime_error("[Error] recv"); } + + this->_recv_message = std::string(buf); + // std::cout << YELLOW "recv_msg(string):[" << this->_recv_message << "]" RESET << std::endl; + // printf(YELLOW "recv_msg(buf):[%s]\n" RESET , buf); + + delete buf; + + return; + + // request, response HttpRequest request = HttpRequest(buf); HttpResponse response = HttpResponse(request); // send - if (send_response(response) == ERROR) { + if (send_response(this->_connect_fd, response) == ERROR) { throw std::runtime_error("[Error] send"); } } -int Server::accept_connection() { +int Server::accept_connection(int socket_fd, struct addrinfo *addr_info) { + int connect_fd; + struct sockaddr *ai_addr = addr_info->ai_addr; + socklen_t ai_addrlen = addr_info->ai_addrlen; + errno = 0; - this->_connect_fd = accept(this->_socket.get_socket_fd(), NULL, NULL); // todo: arg - if (this->_connect_fd == ERROR) { + connect_fd = accept(socket_fd, ai_addr, &ai_addrlen); // todo: arg + // connect_fd = accept(socket_fd, NULL, NULL); // todo: arg + if (connect_fd == ERROR) { std::cerr << strerror(errno) << std::endl; return ERROR; } - return OK; + return connect_fd; +} + +static size_t ft_strlen(const char *str) { + size_t i; + + i = 0; + while (str && str[i]) + i++; + return (i); +} + +static char *ft_strjoin_dst2src(char **dst, const char *src) +{ + char *newdst; + size_t i, j; + size_t len; + + len = ft_strlen(*dst) + ft_strlen(src); + // newdst = (char *)malloc(sizeof(char) * (len + 1)); + try { + newdst = new char[len + 1]; + } + catch (std::bad_alloc const &e) { + std::cerr << e.what() << std::endl; + return NULL; + } + + i = 0; + while (*dst && (*dst)[i]) + { + newdst[i] = (*dst)[i]; + i++; + } + + j = 0; + while (src && src[j]) + { + newdst[i + j] = src[j]; + j++; + } + newdst[i + j] = '\0'; + + delete *dst; + *dst = newdst; + return (*dst); } -// todo: while (size) -// use read? -int Server::recv_request(char *buf) const { +int Server::recv_request(int connect_fd, char **received_request) { ssize_t recv_size; + char *buf; + char *save = NULL; - errno = 0; - recv_size = recv(this->_connect_fd, buf, BUF_SIZE, FLAG_NONE); - if (recv_size == ERROR) { - std::cerr << strerror(errno) << std::endl; + try { + buf = new char[BUFSIZ + 1]; + } + catch (std::bad_alloc const &e) { + std::cerr << e.what() << std::endl; return ERROR; } - buf[recv_size] = '\0'; + + while (true) { + errno = 0; + recv_size = recv(connect_fd, buf, BUFSIZ, FLAG_NONE); + if (recv_size == ERROR) { + std::cerr << strerror(errno) << std::endl; + return ERROR; + } + buf[recv_size] = '\0'; + if (ft_strjoin_dst2src(&save, buf) == NULL) { + return ERROR; + } + if (recv_size == 0) { + break; + } + } + *received_request = save; return OK; } -// todo: while -// use write(fd)? -int Server::send_response(const HttpResponse &response) const { +// todo: while? +int Server::send_response(int connect_fd, const HttpResponse &response) { char *response_message = response.get_response_message(); size_t message_len = response.get_response_size(); errno = 0; - if (send(this->_connect_fd, response_message, message_len, FLAG_NONE) == ERROR) { + if (send(connect_fd, response_message, message_len, FLAG_NONE) == ERROR) { std::cerr << strerror(errno) << std::endl; return ERROR; } return OK; } -void Server::close_connection() { - if (this->_connect_fd != ERROR) { // todo: check here? - errno = 0; - if (close(this->_connect_fd) == ERROR) { - std::cerr << strerror(errno) << std::endl; - this->_connect_fd = ERROR; - } +void Server::close_connection(int connect_fd) { + errno = 0; + if (close(connect_fd) == ERROR) { + std::cerr << strerror(errno) << std::endl; } } + +std::string Server::get_recv_message() const { return this->_recv_message; } +struct addrinfo *Server::get_addr() const { return this->_socket.get_addr_info(); } diff --git a/srcs/Server/Server.hpp b/srcs/Server/Server.hpp index 7c4624bf..3809c5c9 100644 --- a/srcs/Server/Server.hpp +++ b/srcs/Server/Server.hpp @@ -1,9 +1,9 @@ #pragma once +# include # include "Socket.hpp" # define FLAG_NONE 0 -# define BUF_SIZE 1024 // todo:tmp // tmp //////////////////////////////////////////////// @@ -24,18 +24,20 @@ class Server { public: Server(); Server(const char *server_ip, const char *server_port); // tmp - // Server(const Config config); + // Server(const Config config); // todo ~Server(); - void run(); + void process_client_connection(); + std::string get_recv_message() const; // for debug + struct addrinfo *get_addr() const; // for debug private: Socket _socket; int _connect_fd; + std::string _recv_message; - int accept_connection(); - int recv_request(char *buf) const; - int send_response(const HttpResponse &response) const; - - void close_connection(); + static int accept_connection(int socket_fd, struct addrinfo *addr_info); + static int recv_request(int connect_fd, char **buf); + static int send_response(int connect_fd, const HttpResponse &response); + static void close_connection(int connect_fd); }; diff --git a/srcs/Socket/Socket.cpp b/srcs/Socket/Socket.cpp index b0458302..b8426a48 100644 --- a/srcs/Socket/Socket.cpp +++ b/srcs/Socket/Socket.cpp @@ -22,9 +22,10 @@ Socket::Socket() : _status(ERROR), if (listen_socket() == ERROR) { return; } - if (set_fd_to_nonblock() == ERROR) { - return; - } + // todo: accept error if nonblock + // if (set_fd_to_nonblock() == ERROR) { + // return; + // } this->_status = OK; } @@ -33,6 +34,7 @@ Socket::Socket(const char *server_ip, const char *server_port) : _status(ERROR), _addr_info(NULL), _server_ip(server_ip), _server_port(server_port) { + // std::cout << "ip:" << server_ip << ", port:" << server_port << std::endl; if (create_socket() == ERROR) { return; } @@ -42,9 +44,10 @@ Socket::Socket(const char *server_ip, const char *server_port) : _status(ERROR), if (listen_socket() == ERROR) { return; } - if (set_fd_to_nonblock() == ERROR) { - return; - } + // todo: accept error if nonblock + // if (set_fd_to_nonblock() == ERROR) { + // return; + // } this->_status = OK; } @@ -176,3 +179,4 @@ int Socket::get_socket_fd() const { return this->_socket_fd; } int Socket::get_status() const { return this->_status; } // std::string Socket::get_server_port() const { return this->_server_port; } // std::string Socket::get_server_ip() const { return this->_server_ip; } +struct addrinfo *Socket::get_addr_info() const { return this->_addr_info; } diff --git a/srcs/Socket/Socket.hpp b/srcs/Socket/Socket.hpp index eb4c1527..625bb41d 100644 --- a/srcs/Socket/Socket.hpp +++ b/srcs/Socket/Socket.hpp @@ -1,6 +1,7 @@ #pragma once # define SERVER_IP "127.0.0.1" +// # define SERVER_PORT "8080" # define SERVER_PORT "8080" // todo: config -> port, protocol @@ -15,6 +16,7 @@ class Socket { int get_socket_fd() const; int get_status() const; + struct addrinfo *get_addr_info() const; // for debug // std::string get_server_ip() const; // for debug // std::string get_server_port() const; // for debug diff --git a/srcs/main.cpp b/srcs/main.cpp index 192c3048..f00e900a 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -13,18 +13,16 @@ static void validate_argc(int argc) { int main(int argc, char **argv) { std::string config_file_path; // Configuration config; // todo - Server server; // todo + // Server server; try { validate_argc(argc); config_file_path = get_valid_config_file_path(argv[CONFIG_FILE_INDEX]); std::cout << "config_file_path=[" << config_file_path << "]" << std::endl; - // if path is 'default', config set to default // config = Configuration(config_file_path); - - server = Server(SERVER_IP, SERVER_PORT); // load config and setup socket - server.run(); + Server server = Server(SERVER_IP, SERVER_PORT); // load config and setup socket + server.process_client_connection(); } catch (std::exception const &e) { std::cerr << e.what() << std::endl; diff --git a/test/unit_test/TestServer.cpp b/test/unit_test/TestServer.cpp new file mode 100644 index 00000000..1590a392 --- /dev/null +++ b/test/unit_test/TestServer.cpp @@ -0,0 +1,121 @@ +#include +#include +#include +#include "gtest/gtest.h" +#include "webserv.hpp" +#include "Server.hpp" +#include "Color.hpp" + +#define CHILD_PROC 0 + +TEST(ServerUnitTest, Constructor) { + EXPECT_NO_THROW(Server()); + EXPECT_NO_THROW(Server(SERVER_IP, SERVER_PORT)); + + EXPECT_ANY_THROW(Server("hoge", SERVER_PORT)); + EXPECT_ANY_THROW(Server("42", SERVER_PORT)); + EXPECT_ANY_THROW(Server("127.0.0.256", SERVER_PORT)); + EXPECT_ANY_THROW(Server(SERVER_IP, "a")); + EXPECT_ANY_THROW(Server(SERVER_IP, "-1")); + EXPECT_ANY_THROW(Server("huga", "-1")); +} + +void run_client(struct sockaddr server_addr, std::string msg) { + int client_fd; + + sleep(1); + errno = 0; + client_fd = socket(AF_INET, SOCK_STREAM, 0); + if (client_fd == ERROR) { + throw std::runtime_error(strerror(errno)); + } + errno = 0; + if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == ERROR) { + throw std::runtime_error(strerror(errno)); + } + send(client_fd, msg.c_str(), msg.size(), 0); + close(client_fd); +} + +TEST(ServerUnitTest, RecvMessageFromClient1) { + std::string msg = "this is test message"; + + Server server = Server(SERVER_IP, "49152"); + + errno = 0; + pid_t pid = fork(); + if (pid == ERROR) { + throw std::runtime_error(strerror(errno)); + } + if (pid == CHILD_PROC) { + struct addrinfo *addrinfo = server.get_addr(); + struct sockaddr *addr = addrinfo->ai_addr; + run_client(*addr, msg); + exit(0); + } + + server.process_client_connection(); + + std::string recv_msg = server.get_recv_message(); + // std::cout << "recv_msg:[" << recv_msg << "]" << std::endl; + EXPECT_EQ(msg, recv_msg); +} + +TEST(ServerUnitTest, RecvMessageFromClient2) { + std::string msg = ""; + + Server server = Server(SERVER_IP, "49153"); + + errno = 0; + pid_t pid = fork(); + if (pid == ERROR) { + throw std::runtime_error(strerror(errno)); + } + if (pid == CHILD_PROC) { + struct addrinfo *addrinfo = server.get_addr(); + struct sockaddr *addr = addrinfo->ai_addr; + run_client(*addr, msg); + exit(0); + } + + server.process_client_connection(); + + std::string recv_msg = server.get_recv_message(); + // std::cout << "recv_msg:[" << recv_msg << "]" << std::endl; + EXPECT_EQ(msg, recv_msg); +} + +TEST(ServerUnitTest, RecvMessageFromClient3) { + std::string msg = "GET /home.html HTTP/1.1\r\n" + "Host: developer.mozilla.org\r\n" + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + "Accept-Language: en-US,en;q=0.5\r\n" + "Accept-Encoding: gzip, deflate, br\r\n" + "Referer: https://developer.mozilla.org/testpage.html\r\n" + "Connection: keep-alive\r\n" + "Upgrade-Insecure-Requests: 1\r\n" + "If-Modified-Since: Mon, 18 Jul 2016 02:36:04 GMT\r\n" + "If-None-Match: \"c561c68d0ba92bbeb8b0fff2a9199f722e3a621a\"\r\n" + "Cache-Control: max-age=0\r\n"; + + Server server = Server(SERVER_IP, "49154"); + + errno = 0; + pid_t pid = fork(); + if (pid == ERROR) { + throw std::runtime_error(strerror(errno)); + } + if (pid == CHILD_PROC) { + struct addrinfo *addrinfo = server.get_addr(); + struct sockaddr *addr = addrinfo->ai_addr; + run_client(*addr, msg); + exit(0); + } + + server.process_client_connection(); + + std::string recv_msg = server.get_recv_message(); + // std::cout << "recv_msg:[" << recv_msg << "]" << std::endl; + EXPECT_EQ(msg, recv_msg); +}