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. 11
      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. 71
      srcs/sock/Master.cpp
  13. 9
      srcs/webserv.cpp

2
Makefile

@ -14,7 +14,7 @@ $(NAME): $(OBJS)
$(CXX) $(OBJS) -o $(NAME)
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:
rm -rf $(OBJS)

7
includes/Client.hpp

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

3
includes/Env.hpp

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

11
includes/Master.hpp

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

4
includes/webserv.hpp

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

1
public/html/basique.html

@ -16,6 +16,7 @@
<li><a href="/docs/">Documentss</a></li>
<li><a href="/img/">Images</a></li>
<li><a href="/index.php">php-cgi test</a></li>
<li><a href="/big_cgi.py">big_cgi</a></li>
</ul>
<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()
# Get data from fields
first_name = form.getvalue('first_name')
last_name = form.getvalue('last_name')
print("Content-Type: text/html")
print()
print("Content-type:text/html\r\n\r\n")
print("<html>")
print("<head>")
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());
}
}
Master::_first_cli_id = Master::_poll_id_amount - 1;
if ((node = conf->obj()["allowed_methods"])) {
JSONList lst = node->lst();
for (JSONList::iterator it = lst.begin(); it < lst.end(); it++) {
@ -54,29 +55,19 @@ Env::~Env() {
* - refresh and handle requests
*/
void Env::cycle(void) {
FD_ZERO(&Master::_readfds);
Master::_max_fd = Master::_min_fd;
pre_select();
cout << "|===||===| Waiting some HTTP request... |===||===|\n";
int activity = select(Master::_max_fd + 1, &(Master::_readfds), NULL, NULL, NULL);
if ((activity < 0) && (errno != EINTR)) std::cerr << "Select: " << strerror(errno) << "\n";
post_select();
}
/// @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();
int pollResult = poll(Master::_pollfds, Master::_poll_id_amount + 1, 5000);
if ((pollResult < 0) && (errno != EINTR)) std::cerr << "Select: " << strerror(errno) << "\n";
if (pollResult > 0) post_poll();
}
/**
* @brief Refresh all master_sockets and their clients datas (disconnect, new
* connection, etc..) and parse requests recieved.
*/
void Env::post_select(void) {
void Env::post_poll() {
cout << "==> Handle requests and answers:\n";
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(); }
}

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++) {
if (listen->ip.at(0) != '[') try {
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";
}
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) {
close(_fd);
_headers.clear();
cout << "Host disconnected, ip " << _ip_port.ip << ", port " << _ip_port.port << "\n";
}
void Client::init(void) {
_finish = false;
_server = NULL;
_route = NULL;
_method = _uri = _host = _header = _body = "";
_len = 0;
_last_chunk = false;
_request.clear();
_headers.clear();
}
bool Client::getRequest(Env *env, string paquet) {
@ -74,11 +76,11 @@ bool Client::parseHeader(Env *env) {
if (DEBUG) cout << "Parsing header...\n";
lines = split(_header, "\r\n");
method = split(lines.at(0), " ");
_request["Method:"] = method;
_headers["Method:"] = method;
if (lines.size() > 0) {
for (vec_string::iterator it = lines.begin() + 1; it < lines.end(); 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);
@ -105,7 +107,7 @@ bool Client::parseHeader(Env *env) {
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) {
vec_string allowed;
@ -147,47 +149,46 @@ void Client::create_file(string path) {
else {
file << _body;
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.
*
* @param cgi_path The cgi binary location specified in configuration file according to the file requested.
* @param path The path to the file requested.
*/
* @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 path The path to the file requested.
*/
void Client::cgi(string cgi_path, string path) {
int status;
int fd[2];
std::stringstream ss;
string ret;
int pipe_in[2];
send(_fd, "HTTP/1.1 200 OK\r\n", 17, MSG_NOSIGNAL);
if (!std::ifstream(cgi_path.c_str()).good()) return send_error(404);
pipe(fd);
if (DEBUG) std::cout << "Send cgi\n";
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[1] = path.c_str();
args[2] = NULL;
args[1] = NULL;
string path_info = "PATH_INFO=" + _route->getRoot();
string query = "QUERY_STRING=" + _query;
const char **env = new const char *[path_info.length() + query.length() + 2];
env[0] = path_info.c_str();
env[1] = query.c_str();
env[2] = NULL;
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
close(fd[0]);
pipe(pipe_in);
std::stringstream tmp;
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);
exit(1);
}
close(fd[1]);
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());
_finish = true;
}
/**
@ -195,22 +196,22 @@ void Client::cgi(string cgi_path, string path) {
*
* @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) {
case 301:
return send_answer("HTTP/1.1 301 Moved Permanently\r\nLocation: " + opt + "\r\n\r\n");
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:
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:
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:
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:
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");
}
}

71
srcs/sock/Master.cpp

@ -41,28 +41,12 @@ Master::Master(ip_port_t list) : _listen(list) {
fcntl(socket, F_SETFL, O_NONBLOCK);
#endif
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.
* - 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.
*/
void Master::post_select(Env *env) {
int valread;
int addrlen = sizeof(_address);
char buffer[128];
void Master::post_poll(Env *env) {
int addrlen = sizeof(_address);
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);
if (new_socket < 0) throw std::runtime_error("accept() error:" + string(strerror(errno)));
#ifdef __APPLE__
fcntl(new_socket, F_SETFL, O_NONBLOCK);
#endif
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;
for (std::vector<Client *>::iterator it = _childs.begin(); it < _childs.end(); it++) {
child_fd = (*it)->_fd;
if (FD_ISSET(child_fd, &_readfds)) {
valread = read(child_fd, buffer, 127);
int i = (*it)->_poll_id;
if (_pollfds[i].fd > 0 && _pollfds[i].revents & POLLIN) {
char buffer[128];
int valread = read(child_fd, buffer, 127);
buffer[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);
_childs.erase(it);
_pollfds[i].fd = 0;
_pollfds[i].events = 0;
_pollfds[i].revents = 0;
_poll_id_amount--;
} else if ((*it)->getRequest(env, buffer)) {
(*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;
ip_listen = split((*it).ip, ".");
vec_string::iterator r = ip_required.begin();
for (vec_string::iterator l = ip_listen.end(); l >= ip_listen.begin(); --l) {
if (*l != *r && *l != "0") is_inrange = false;
vec_string::iterator r = ip_required.end();
vec_string::iterator l = ip_listen.end();
while (r > ip_required.begin()) {
if (*(--l) != *(--r) && *l != "0") is_inrange = false;
}
if (is_inrange) inrange.push_back(*server);
}

9
srcs/webserv.cpp

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

Loading…
Cancel
Save