Browse Source

change to poll instead of select

master
nicolas-arnaud 2 years ago
parent
commit
db2f5567e5
  1. 2
      Makefile
  2. 7
      includes/Client.hpp
  3. 3
      includes/Env.hpp
  4. 9
      includes/Master.hpp
  5. 4
      includes/webserv.hpp
  6. 1
      public/html/basique.html
  7. 15
      public/html/big_cgi.py
  8. 9
      public/html/index.py
  9. 21
      srcs/load/Env.cpp
  10. 4
      srcs/load/Server.cpp
  11. 69
      srcs/sock/Client.cpp
  12. 69
      srcs/sock/Master.cpp
  13. 9
      srcs/webserv.cpp

2
Makefile

@ -14,7 +14,7 @@ $(NAME): $(OBJS)
$(CXX) $(OBJS) -o $(NAME) $(CXX) $(OBJS) -o $(NAME)
debug: debug:
$(CXX) -I includes -Werror -Wextra -Wall -std=c++98 -g -fsanitize=address $(SRCS) -o $(NAME) -D DEBUG=1 $(CXX) -I includes -Werror -Wextra -Wall -std=c++98 -g $(SRCS) -o $(NAME) -D DEBUG=1
clean: clean:
rm -rf $(OBJS) rm -rf $(OBJS)

7
includes/Client.hpp

@ -2,16 +2,19 @@
#include "webserv.hpp" #include "webserv.hpp"
class Client { class Client {
int _poll_id;
int _fd; int _fd;
ip_port_t _ip_port; ip_port_t _ip_port;
Master *_parent; Master *_parent;
Server *_server; Server *_server;
Env *_env; Env *_env;
Route *_route; Route *_route;
string _method, _uri, _query, _host, _header, _body; string _header, _body;
string _method, _uri, _query, _host;
int _len; int _len;
bool _last_chunk; bool _last_chunk;
std::map<string, vec_string> _request; std::map<string, vec_string> _headers;
bool _finish;
void init(void); void init(void);
bool getBody(string paquet); bool getBody(string paquet);

3
includes/Env.hpp

@ -10,6 +10,5 @@ public:
Env(JSONNode *conf); Env(JSONNode *conf);
~Env(void); ~Env(void);
void cycle(void); void cycle(void);
void pre_select(void); void post_poll(void);
void post_select(void);
}; };

9
includes/Master.hpp

@ -2,6 +2,7 @@
#include "webserv.hpp" #include "webserv.hpp"
class Master { class Master {
int _poll_id;
int _fd; int _fd;
std::vector<Client *> _childs; std::vector<Client *> _childs;
struct sockaddr_in _address; struct sockaddr_in _address;
@ -10,11 +11,11 @@ public:
Master(ip_port_t listen); Master(ip_port_t listen);
~Master(void); ~Master(void);
void pre_select(void); void post_poll(Env *env);
void post_select(Env *env);
Server *choose_server(Env *env, string host); Server *choose_server(Env *env, string host);
ip_port_t _listen; ip_port_t _listen;
static fd_set _readfds; static int _poll_id_amount;
static int _max_fd, _min_fd; static int _first_cli_id;
static struct pollfd *_pollfds;
}; };

4
includes/webserv.hpp

@ -2,11 +2,15 @@
#ifndef DEBUG #ifndef DEBUG
#define DEBUG 0 #define DEBUG 0
#endif #endif
#ifndef MAX_CLIENTS
#define MAX_CLIENTS 5000
#endif
#include <arpa/inet.h> #include <arpa/inet.h>
#include <dirent.h> #include <dirent.h>
#include <fcntl.h> #include <fcntl.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/time.h> #include <sys/time.h>

1
public/html/basique.html

@ -16,6 +16,7 @@
<li><a href="/docs/">Documentss</a></li> <li><a href="/docs/">Documentss</a></li>
<li><a href="/img/">Images</a></li> <li><a href="/img/">Images</a></li>
<li><a href="/index.php">php-cgi test</a></li> <li><a href="/index.php">php-cgi test</a></li>
<li><a href="/big_cgi.py">big_cgi</a></li>
</ul> </ul>
<h1> Query and python cgi test </h1> <h1> Query and python cgi test </h1>

15
public/html/big_cgi.py

@ -0,0 +1,15 @@
import string
import random
print("Content-type: text/html")
print()
# initializing size of string
N = 10000000
# using random.choices()
# generating random strings
res = ''.join(random.choices(string.ascii_uppercase +
string.digits, k=N))
# print result
print("The generated random string : " + str(res))

9
public/html/index.py

@ -1,13 +1,16 @@
#!/usr/bin/python
import cgi # Import modules for CGI handling
import cgi, cgitb
# Create instance of FieldStorage
form = cgi.FieldStorage() form = cgi.FieldStorage()
# Get data from fields
first_name = form.getvalue('first_name') first_name = form.getvalue('first_name')
last_name = form.getvalue('last_name') last_name = form.getvalue('last_name')
print("Content-Type: text/html") print("Content-type:text/html\r\n\r\n")
print()
print("<html>") print("<html>")
print("<head>") print("<head>")
print("<title>Hello - Second CGI Program</title>") print("<title>Hello - Second CGI Program</title>")

21
srcs/load/Env.cpp

@ -27,6 +27,7 @@ Env::Env(JSONNode *conf) {
this->_masters.insert(this->_masters.end(), tmp_s.begin(), tmp_s.end()); this->_masters.insert(this->_masters.end(), tmp_s.begin(), tmp_s.end());
} }
} }
Master::_first_cli_id = Master::_poll_id_amount - 1;
if ((node = conf->obj()["allowed_methods"])) { if ((node = conf->obj()["allowed_methods"])) {
JSONList lst = node->lst(); JSONList lst = node->lst();
for (JSONList::iterator it = lst.begin(); it < lst.end(); it++) { for (JSONList::iterator it = lst.begin(); it < lst.end(); it++) {
@ -54,29 +55,19 @@ Env::~Env() {
* - refresh and handle requests * - refresh and handle requests
*/ */
void Env::cycle(void) { void Env::cycle(void) {
FD_ZERO(&Master::_readfds);
Master::_max_fd = Master::_min_fd;
pre_select();
cout << "|===||===| Waiting some HTTP request... |===||===|\n"; cout << "|===||===| Waiting some HTTP request... |===||===|\n";
int activity = select(Master::_max_fd + 1, &(Master::_readfds), NULL, NULL, NULL); int pollResult = poll(Master::_pollfds, Master::_poll_id_amount + 1, 5000);
if ((activity < 0) && (errno != EINTR)) std::cerr << "Select: " << strerror(errno) << "\n"; if ((pollResult < 0) && (errno != EINTR)) std::cerr << "Select: " << strerror(errno) << "\n";
post_select(); if (pollResult > 0) post_poll();
}
/// @brief Append each master_sockets and their clients to list of fds SELECT must look at.
void Env::pre_select(void) {
cout << "==> Check sockets still alive to listen\n";
for (std::vector<Master *>::iterator it = this->_masters.begin(); it < this->_masters.end(); it++)
(*it)->pre_select();
} }
/** /**
* @brief Refresh all master_sockets and their clients datas (disconnect, new * @brief Refresh all master_sockets and their clients datas (disconnect, new
* connection, etc..) and parse requests recieved. * connection, etc..) and parse requests recieved.
*/ */
void Env::post_select(void) { void Env::post_poll() {
cout << "==> Handle requests and answers:\n"; cout << "==> Handle requests and answers:\n";
for (std::vector<Master *>::iterator it = this->_masters.begin(); it < this->_masters.end(); it++) try { for (std::vector<Master *>::iterator it = this->_masters.begin(); it < this->_masters.end(); it++) try {
(*it)->post_select(this); (*it)->post_poll(this);
} catch (std::exception &e) { std::cerr << e.what(); } } catch (std::exception &e) { std::cerr << e.what(); }
} }

4
srcs/load/Server.cpp

@ -64,7 +64,9 @@ std::vector<Master *> Server::create_masters(JSONNode *server) {
for (std::vector<ip_port_t>::iterator listen = _listens.begin(); listen < _listens.end(); listen++) { for (std::vector<ip_port_t>::iterator listen = _listens.begin(); listen < _listens.end(); listen++) {
if (listen->ip.at(0) != '[') try { if (listen->ip.at(0) != '[') try {
ret.push_back(new Master(*listen)); ret.push_back(new Master(*listen));
} catch (std::exception &e) { std::cerr << e.what() << '\n'; } } catch (std::exception &e) {
std::cerr << "Ip: " << listen->ip << ", port: " << listen->port << " -> " << e.what() << '\n';
}
else cout << "Listen: IPv6 isn't supported\n"; else cout << "Listen: IPv6 isn't supported\n";
} }
return ret; return ret;

69
srcs/sock/Client.cpp

@ -20,16 +20,18 @@ Client::Client(int fd, ip_port_t ip_port, Master *parent) : _fd(fd), _ip_port(ip
Client::~Client(void) { Client::~Client(void) {
close(_fd); close(_fd);
_headers.clear();
cout << "Host disconnected, ip " << _ip_port.ip << ", port " << _ip_port.port << "\n"; cout << "Host disconnected, ip " << _ip_port.ip << ", port " << _ip_port.port << "\n";
} }
void Client::init(void) { void Client::init(void) {
_finish = false;
_server = NULL; _server = NULL;
_route = NULL; _route = NULL;
_method = _uri = _host = _header = _body = ""; _method = _uri = _host = _header = _body = "";
_len = 0; _len = 0;
_last_chunk = false; _last_chunk = false;
_request.clear(); _headers.clear();
} }
bool Client::getRequest(Env *env, string paquet) { bool Client::getRequest(Env *env, string paquet) {
@ -74,11 +76,11 @@ bool Client::parseHeader(Env *env) {
if (DEBUG) cout << "Parsing header...\n"; if (DEBUG) cout << "Parsing header...\n";
lines = split(_header, "\r\n"); lines = split(_header, "\r\n");
method = split(lines.at(0), " "); method = split(lines.at(0), " ");
_request["Method:"] = method; _headers["Method:"] = method;
if (lines.size() > 0) { if (lines.size() > 0) {
for (vec_string::iterator it = lines.begin() + 1; it < lines.end(); it++) { for (vec_string::iterator it = lines.begin() + 1; it < lines.end(); it++) {
line = split(*it, " "); line = split(*it, " ");
_request[line.at(0)] = vec_string(line.begin() + 1, line.end()); _headers[line.at(0)] = vec_string(line.begin() + 1, line.end());
} }
} }
_method = header_pick("Method:", 0); _method = header_pick("Method:", 0);
@ -105,7 +107,7 @@ bool Client::parseHeader(Env *env) {
return true; return true;
} }
string Client::header_pick(string key, size_t id) { return _request[key].size() <= id ? "" : _request[key].at(id); } string Client::header_pick(string key, size_t id) { return _headers[key].size() <= id ? "" : _headers[key].at(id); }
bool Client::check_method(void) { bool Client::check_method(void) {
vec_string allowed; vec_string allowed;
@ -147,47 +149,46 @@ void Client::create_file(string path) {
else { else {
file << _body; file << _body;
file.close(); file.close();
send_answer("HTTP/1.1 201 Accepted\r\nContent-Length: 0\r\n\r\n"); send_answer("HTTP/1.1 201 Accepted\r\n\r\n");
} }
} }
/** /**
* @brief Launch cgi binary to parse the file requested by the client. * @brief Launch cgi binary to parse the file requested by the client.
* *
* @param cgi_path The cgi binary location specified in configuration file according to the file requested. * @param cgi_path The cgi binary location specified in configuration file according to the file requested.
* @param path The path to the file requested. * @param path The path to the file requested.
*/ */
void Client::cgi(string cgi_path, string path) { void Client::cgi(string cgi_path, string path) {
int status; int pipe_in[2];
int fd[2];
std::stringstream ss;
string ret;
send(_fd, "HTTP/1.1 200 OK\r\n", 17, MSG_NOSIGNAL);
if (!std::ifstream(cgi_path.c_str()).good()) return send_error(404); if (!std::ifstream(cgi_path.c_str()).good()) return send_error(404);
pipe(fd); if (DEBUG) std::cout << "Send cgi\n";
if (fork() == 0) { if (fork() == 0) {
const char **args = new const char *[cgi_path.length() + path.length() + 2]; const char **args = new const char *[cgi_path.length() + 1];
args[0] = cgi_path.c_str(); args[0] = cgi_path.c_str();
args[1] = path.c_str(); args[1] = NULL;
args[2] = NULL;
string path_info = "PATH_INFO=" + _route->getRoot(); string path_info = "PATH_INFO=" + _route->getRoot();
string query = "QUERY_STRING=" + _query; string query = "QUERY_STRING=" + _query;
const char **env = new const char *[path_info.length() + query.length() + 2]; const char **env = new const char *[path_info.length() + query.length() + 2];
env[0] = path_info.c_str(); env[0] = path_info.c_str();
env[1] = query.c_str(); env[1] = query.c_str();
env[2] = NULL; env[2] = NULL;
dup2(fd[1], STDOUT_FILENO); pipe(pipe_in);
close(fd[1]); std::stringstream tmp;
close(fd[0]); tmp << std::ifstream(path.c_str()).rdbuf();
string file = tmp.str();
write(pipe_in[1], file.c_str(), file.size());
close(pipe_in[1]);
dup2(pipe_in[0], STDIN_FILENO);
close(pipe_in[0]);
dup2(_fd, STDOUT_FILENO);
close(_fd);
execve(cgi_path.c_str(), (char **)args, (char **)env); execve(cgi_path.c_str(), (char **)args, (char **)env);
exit(1);
} }
close(fd[1]); _finish = true;
waitpid(-1, &status, 0);
char buffer[10000];
buffer[read(fd[0], buffer, 10000)] = 0;
ret = string(buffer);
ss << "HTTP/1.1 200 OK\r\nContent-Length: " << ret.length() - ret.find("\r\n\r\n") - 4 << "\r\n\r\n" << ret;
send_answer(ss.str());
} }
/** /**
@ -195,22 +196,22 @@ void Client::cgi(string cgi_path, string path) {
* *
* @param error_code The HTTP response code to send. * @param error_code The HTTP response code to send.
*/ */
void Client::send_error(int error_code, string opt = "") { void Client::send_error(int error_code, string opt) {
switch (error_code) { switch (error_code) {
case 301: case 301:
return send_answer("HTTP/1.1 301 Moved Permanently\r\nLocation: " + opt + "\r\n\r\n"); return send_answer("HTTP/1.1 301 Moved Permanently\r\nLocation: " + opt + "\r\n\r\n");
case 400: case 400:
return send_answer("HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"); return send_answer("HTTP/1.1 400 Bad Request\r\n\r\n");
case 403: case 403:
return send_answer("HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n"); return send_answer("HTTP/1.1 403 Forbidden\r\n\r\n");
case 404: case 404:
return send_answer("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"); return send_answer("HTTP/1.1 404 Not Found\r\n\r\n");
case 405: case 405:
return send_answer("HTTP/1.1 405 Method Not Allowed\r\nConnection: " return send_answer("HTTP/1.1 405 Method Not Allowed\r\nConnection: "
"close\r\nContent-Length: 0\r\n\r\n"); "close\r\n\r\n");
case 413: case 413:
return send_answer("HTTP/1.1 413 Payload Too " return send_answer("HTTP/1.1 413 Payload Too "
"Large\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"); "Large\r\nConnection: close\r\n\r\n");
} }
} }

69
srcs/sock/Master.cpp

@ -41,28 +41,12 @@ Master::Master(ip_port_t list) : _listen(list) {
fcntl(socket, F_SETFL, O_NONBLOCK); fcntl(socket, F_SETFL, O_NONBLOCK);
#endif #endif
cout << "New master socket with fd " << _fd << " which listen " << ip << ":" << port << "\n"; cout << "New master socket with fd " << _fd << " which listen " << ip << ":" << port << "\n";
if (_fd < _min_fd) _min_fd = _fd; _pollfds[_poll_id_amount].fd = _fd;
_pollfds[_poll_id_amount].events = POLLIN | POLLPRI;
_poll_id = _poll_id_amount;
_poll_id_amount++;
} }
/**
* @brief The pre select operations:
* Add master's socket descriptor and each one of his childs to the select list of descriptor.
*/
void Master::pre_select(void) {
FD_SET(_fd, &_readfds);
if (_fd > _max_fd) _max_fd = _fd;
for (std::vector<Client *>::iterator child = _childs.begin(); child < _childs.end(); child++) {
FD_SET((*child)->_fd, &_readfds);
if ((*child)->_fd > _max_fd) _max_fd = (*child)->_fd;
}
}
/* |==========|
* Refresh master socket datas after select()
* - look first for new clients
* - look then if known clients sent requests or disconnected
* - if client sent request, handle it to generate answer adapted
*/
/** /**
* @brief Checkk master and his clients sockets after select performed. * @brief Checkk master and his clients sockets after select performed.
* - First look for new clients * - First look for new clients
@ -72,32 +56,54 @@ void Master::pre_select(void) {
* *
* @param env The environment object which contain the liste of servers to know which one the client is trying to reach. * @param env The environment object which contain the liste of servers to know which one the client is trying to reach.
*/ */
void Master::post_select(Env *env) { void Master::post_poll(Env *env) {
int valread;
int addrlen = sizeof(_address); int addrlen = sizeof(_address);
char buffer[128];
if (FD_ISSET(_fd, &_readfds)) { /// < incomming master request if (_pollfds[_poll_id].revents & POLLIN) { /// < incomming master request
int new_socket = accept(_fd, (struct sockaddr *)&_address, (socklen_t *)&addrlen); int new_socket = accept(_fd, (struct sockaddr *)&_address, (socklen_t *)&addrlen);
if (new_socket < 0) throw std::runtime_error("accept() error:" + string(strerror(errno))); if (new_socket < 0) throw std::runtime_error("accept() error:" + string(strerror(errno)));
#ifdef __APPLE__ #ifdef __APPLE__
fcntl(new_socket, F_SETFL, O_NONBLOCK); fcntl(new_socket, F_SETFL, O_NONBLOCK);
#endif #endif
ip_port_t cli_listen = get_ip_port_t(inet_ntoa(_address.sin_addr), ntohs(_address.sin_port)); ip_port_t cli_listen = get_ip_port_t(inet_ntoa(_address.sin_addr), ntohs(_address.sin_port));
_childs.push_back(new Client(new_socket, cli_listen, this)); Client *new_cli = new Client(new_socket, cli_listen, this);
_childs.push_back(new_cli);
for (int i = _first_cli_id; i < MAX_CLIENTS; i++) {
if (_pollfds[i].fd != 0) continue;
_pollfds[i].fd = new_socket;
_pollfds[i].events = POLLIN | POLLPRI;
new_cli->_poll_id = i;
_poll_id_amount++;
break;
}
} }
int child_fd; int child_fd;
for (std::vector<Client *>::iterator it = _childs.begin(); it < _childs.end(); it++) { for (std::vector<Client *>::iterator it = _childs.begin(); it < _childs.end(); it++) {
child_fd = (*it)->_fd; child_fd = (*it)->_fd;
if (FD_ISSET(child_fd, &_readfds)) { int i = (*it)->_poll_id;
valread = read(child_fd, buffer, 127); if (_pollfds[i].fd > 0 && _pollfds[i].revents & POLLIN) {
char buffer[128];
int valread = read(child_fd, buffer, 127);
buffer[valread] = '\0'; buffer[valread] = '\0';
if (valread == 0) { if (valread == 0) {
getpeername(child_fd, (struct sockaddr *)&_address, (socklen_t *)&addrlen); // getpeername(child_fd, (struct sockaddr *)&_address, (socklen_t *)&addrlen);
delete (*it); delete (*it);
_childs.erase(it); _childs.erase(it);
_pollfds[i].fd = 0;
_pollfds[i].events = 0;
_pollfds[i].revents = 0;
_poll_id_amount--;
} else if ((*it)->getRequest(env, buffer)) { } else if ((*it)->getRequest(env, buffer)) {
(*it)->handleRequest(); (*it)->handleRequest();
if ((*it)->_finish) {
delete (*it);
_childs.erase(it);
_pollfds[i].fd = 0;
_pollfds[i].events = 0;
_pollfds[i].revents = 0;
_poll_id_amount--;
}
} }
} }
} }
@ -133,9 +139,10 @@ Server *Master::choose_server(Env *env, string host) {
} }
bool is_inrange = true; bool is_inrange = true;
ip_listen = split((*it).ip, "."); ip_listen = split((*it).ip, ".");
vec_string::iterator r = ip_required.begin(); vec_string::iterator r = ip_required.end();
for (vec_string::iterator l = ip_listen.end(); l >= ip_listen.begin(); --l) { vec_string::iterator l = ip_listen.end();
if (*l != *r && *l != "0") is_inrange = false; while (r > ip_required.begin()) {
if (*(--l) != *(--r) && *l != "0") is_inrange = false;
} }
if (is_inrange) inrange.push_back(*server); if (is_inrange) inrange.push_back(*server);
} }

9
srcs/webserv.cpp

@ -8,11 +8,10 @@
#include "webserv.hpp" #include "webserv.hpp"
fd_set Master::_readfds; /// < The sockets fd which will be select int Master::_poll_id_amount = 0;
int Master::_min_fd = INT_MAX; /// < The lower socket fd int Master::_first_cli_id = 0;
int Master::_max_fd = 0; /// < The higher one struct pollfd *Master::_pollfds = new struct pollfd[MAX_CLIENTS + 1];
/* *******************************/
/** /**
* @brief The server launcher * @brief The server launcher
* *
@ -37,8 +36,10 @@ int main(int ac, char **av) {
if (!conf) return EXIT_FAILURE; if (!conf) return EXIT_FAILURE;
// Here we start the server and his environment using conf // Here we start the server and his environment using conf
cout << "Initialization of server...\n"; cout << "Initialization of server...\n";
std::memset(Master::_pollfds, 0, sizeof(*Master::_pollfds) * (MAX_CLIENTS));
Env env(conf); Env env(conf);
while (1) env.cycle(); while (1) env.cycle();
delete[] Master::_pollfds;
} catch (const std::exception &e) { } catch (const std::exception &e) {
std::cerr << e.what() << "\n"; std::cerr << e.what() << "\n";
return EXIT_FAILURE; return EXIT_FAILURE;

Loading…
Cancel
Save