Il y a un chapitre sur la création d'un serveur WEB (littie httpd) en langage C dans la seconde moitié de la première édition de la programmation Linux ordinaire. J'ai réécrit ce serveur WEB avec C ++ 14. Bien qu'il s'agisse d'un logiciel libre, le livre étant sous copyright, la partie qui n'a pas été réécrite en C ++ n'est pas décrite. Le commentaire source décrit uniquement la partie réécrite. Pour plus de détails et des points peu clairs sur le fait que cet article à lui seul n'est pas une source de travail, veuillez vous référer à la 1ère édition de la programmation Linux normale.
Makefile Le livre explique que makefile est généré automatiquement par autoconf, mais je ne l'ai pas utilisé car il est difficile de le créer à moins d'y être habitué. Au lieu de cela, j'ai décidé de générer automatiquement un Makefile avec CMake, qui a été poussé dur par Qiita.
CMakeLists.txt
add_executable(littlehttpd++
main.cpp)
set(CMAKE_VERBOSE_MAKEFILE FALSE)
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I ./")
set(CMAKE_CXX_STANDARD 14)
struct HTTPRequest Ajout de typedef du pointeur intelligent. La chaîne de caractères a été changée en std :: string et la liste de liens a été changée en std :: list. Puisqu'il s'agit d'un pointeur intelligent, free_request (HTTPRequest *) n'est pas nécessaire.
main.cpp
// Declaration
struct HTTPHeaderField {
typedef std::shared_ptr<HTTPHeaderField> sptr;
std::string name;
std::string value;
//le prochain est std::list<sptr>Cela n'est pas nécessaire car il s'agit d'une liste de
};
struct HTTPRequest {
typedef std::shared_ptr<HTTPRequest> sptr;
int32_t protocol_minor_version;
std::string method;
std::string path;
std::list<HTTPHeaderField::sptr> headers;
std::string body;
int32_t length;
std::string lookup_header_field_value(std::string name) const;
};
std::string HTTPRequest::lookup_header_field_value(std::string name) const {
for (auto h : this->headers) {
if (name == h->name) {
return h->value;
}
}
return "";
}
main() Il y a deux changements: changer les variables docroot et port en std :: string et se terminer par std :: quick_exit ().
main.cpp
int main(int argc, char* argv[])
……réduction……
std::string port = DEFAULT_PORT;
std::string docroot;
……réduction……
……
server_main(server, docroot);
std::quick_exit(EXIT_SUCCESS);
}
En C ++, les arguments de variable ne sont pas développés au moment de l'exécution, mais sont développés au moment de la compilation. La spécification de format (% s etc.) redémarre en raison d'une erreur de spécification, laissez donc le compilateur déterminer le type
main.cpp
static void log_exit() {
std::quick_exit(EXIT_FAILURE);
}
static void log_exit(std::string str) {
if (debug_mode) {
std::cerr << str << std::endl;
} else {
syslog(LOG_ERR, str.c_str());
}
log_exit();
}
template <class Tail>
static void log_exit(std::stringstream &ss, Tail tail) {
ss << tail;
if (debug_mode) {
std::cerr << ss.str() << std::endl;
} else {
syslog(LOG_ERR, ss.str().c_str());
}
log_exit();
}
template <class Head, class... Tail>
static void log_exit(std::stringstream &ss, Head head, Tail... tail) {
ss << head;
log_exit(ss, tail...);
}
template <class Head, class... Tail>
static void log_exit(Head head, Tail... tail) {
std::stringstream ss;
log_exit(ss, head, tail...);
}
Utilise std :: thread au lieu de fork (), donc les signaux liés au processus enfant ne sont pas nécessaires
listen_socket Seulement changé le port d'argument en std :: string.
main.cpp
static int listen_socket(std::string &port){
……réduction……
}
server_main Accept () et fork () sont les mêmes que les livres. Traitement parallèle avec std :: thread au lieu de fork ().
main.cpp
static void server_main(int server, std::string &docroot) {
……réduction……
//chaussette std::Passer au fil(être lié), Traitement du fil
std::thread th([=](intsock,std::stringdocroot) {
fdBuf sbuf(sock);
try {
std::istream ins(&sbuf);
std::ostream outs(&sbuf);
service(ins, outs, docroot);
} catch (interrupt_error e) {
if (debug_mode) {
std::cout << e.what() << std::endl;
}
}
//Fermer avec fdBuf destructor
//close(sock);
return;
}, sock, docroot);
th.detach();
} // end-for
}
La classe fdBuf est une classe streamBuffer pour le traitement des sockets avec iostream. La mise en œuvre la plus simple est utilisée pour vérifier l'opération et le traitement est lent. Si vous réécrivez ici, la vitesse de traitement augmentera.
main.cpp
class fdBuf : public std::streambuf {
public:
fdBuf(int sock);
virtual ~fdBuf(void);
int state(void) const;
static const int ERROR = -1;
static const int CLOSE_EOF = -2;
protected:
int underflow(void) override;
int uflow(void) override;
int overflow(int c = std::char_traits<char>::eof()) override;
int sync(void) override;
int m_socket;
bool m_gpend;
int m_gpend_char;
bool m_isEof;
auto ceof(void) { return std::char_traits<char>::eof(); }
};
fdBuf::fdBuf(int sock) {
this->m_gpend = false;
m_gpend_char = ceof();
//dup()N'est pas thread-safe et n'est pas nécessaire
//m_socket = ::dup(sock);
m_socket = sock;
m_isEof = false;
}
fdBuf::~fdBuf() {
if (m_socket != -1) {
::close(m_socket);
}
}
int fdBuf::state(void) const {
if (m_socket == -1) {
return fdBuf::ERROR;
}
if (m_isEof) {
return fdBuf::CLOSE_EOF;
}
return 0;
}
int fdBuf::underflow(void) /*override*/ {
//C'est une mise en œuvre simple pour vérifier le fonctionnement et est lente
unsigned char buf[2] = {0};
int ret = 0;
if (m_gpend) { return m_gpend_char; }
if (m_socket == -1) {
return ceof();
}
ret = ::recv(m_socket, buf, 1, 0);
if (ret == -1) {
return ceof();
} else if (ret == 0) {
m_isEof = true;
return ceof();
} else if (ret != 1) {
return ceof();
}
int c = (int)buf[0];
m_gpend = true;
m_gpend_char = (c & 255);
return m_gpend_char;
}
int fdBuf::uflow(void) /*override*/ {
int c = this->underflow();
m_gpend = false;
return c;
}
int fdBuf::overflow(int c) /*override*/ {
if (c == ceof()) { return 0; }
if (m_socket == -1) { return ceof(); }
//Mise en œuvre simple et fonctionnement lent à vérifier
unsigned char buf[2] = { 0 };
buf[0] = (unsigned char)(c & 255);
if (::send(m_socket, buf, 1, 0) != 1) {
return ceof();
}
return c;
}
int fdBuf::sync(void) /*override*/ {
return 0;
}
service() Puisque HTTPRequest est un pointeur intelligent, il n'est pas nécessaire d'appeler free ().
main.cpp
static void service(std::istream &in, std::ostream &out, std::string docroot) {
HTTPRequest::sptr req = read_request(in);
response_to(req, out, docroot);
}
read_request() les en-têtes et le corps ne sont pas utilisés. Implémenté sur la base de la politique de réécriture du traitement des livres en C ++.
main.cpp
static HTTPRequest::sptr read_request(std::istream &in) {
try {
HTTPRequest::sptr req = std::make_shared<HTTPRequest>();
read_request_line(req, in);
while (HTTPHeaderField::sptr h = read_header_field(in)) {
req->headers.push_front(h);
}
req->length = content_length(req);
if (req->length != 0) {
if (req->length > MAX_REQUEST_BODY_LENGTH) {
log_exit("request body too long");
return nullptr;
}
req->body.reserve(req->length);
in.read(const_cast<char *>(req->body.data()), req->length);
if (!(in.rdstate() & std::ios::eofbit) ||
(req->body.length() != req->length)) {
log_exit("failed to read request body");
return nullptr;
}
} else {
req->body.clear();
}
return req;
} catch(std::bad_alloc e) {
log_exit(e.what());
} catch (interrupt_error e) {
throw std::move(e);
} catch (...) {
log_exit("Unkonw Exception occured");
}
return nullptr;
}
read_request_line() Vous pouvez utiliser std :: getline () pour obtenir des entrées sans restrictions de taille. Istream :: getline () est utilisé à partir de la description du livre qu'il est dangereux de lire un nombre illimité de chaînes de caractères d'entrée de l'extérieur.
main.cpp
static void read_request_line(HTTPRequest::sptr &req, std::istream &in) {
std::string strbuf;
std::string::size_type path = 0, p = 0;
try {
std::string::value_type buf[LINE_BUF_SIZE] = {0};
in.getline(buf, LINE_BUF_SIZE);
if (in.fail()) {
if (dynamic_cast<fdBuf*>(in.rdbuf())->state() == fdBuf::CLOSE_EOF) {
if (debug_mode) {
std::cout << "fdBuf::CLOSE_EOF" << std::endl << std::flush;
}
throw interrupt_error("Client disconnected");
}
log_exit("istream.getline(buf,", LINE_BUF_SIZE, ") failed(2)");
return ;
}
// GET /<path>/Recherchez les blancs immédiatement après la méthode de
strbuf = buf;
p = strbuf.find_first_of(' ');
if (p == std::string::npos) {
log_exit("parse error on request line(1): ", strbuf);
return;
}
req->method = strbuf.substr(0, p - 0);
std::transform(req->method.begin(), req->method.end(),
req->method.begin(), toupper);
p++;
path = p;
p = strbuf.find_first_of(' ', path);
if (p == std::string::npos) {
log_exit("parse error on request line(2): ", strbuf);
return;
}
req->path = strbuf.substr(path, p - path);
p++;
//lettre majuscule/HTTP1 sans respect de la casse.X ou comparer
std::string strHttp;
strHttp = strbuf.substr(p);
std::transform(strHttp.begin(), strHttp.end(),
strHttp.begin(), toupper);
p = strHttp.find_first_of("HTTP/1.");
if (p == std::string::npos) {
log_exit("parse error on request line(3): ", strbuf);
return;
}
std::string strVersion = strHttp.substr(strlen("HTTP/1."));
req->protocol_minor_version = std::stoi(strVersion);
} catch(interrupt_error e) {
throw std::move(e);
} catch(...) {
log_exit(__func__, ": Exception occured");
}
return;
}
Définit une classe d'erreur pour terminer un thread lorsqu'il est déconnecté d'un client
main.cpp
class interrupt_error : public std::runtime_error {
public:
interrupt_error(const std::string& message) : std::runtime_error(message) { }
interrupt_error(const char *message) : std::runtime_error(message) { }
};
read_header_field()
main.cpp
static HTTPHeaderField::sptr read_header_field(std::istream &in) {
std::string strbuf;
std::string::size_type p = 0, tpos = 0;
std::string::value_type buf[LINE_BUF_SIZE] = {0};
in.getline(buf, LINE_BUF_SIZE);
if (in.eof()) {
return nullptr;
}
if (in.fail()) {
log_exit("istream.getline(buf,", LINE_BUF_SIZE, ") failed(1)");
return nullptr;
}
//istream::getline()N'inclut pas les sauts de ligne
strbuf = buf;
if (strbuf[0] == 0 || strbuf[0] == '\r') {
return nullptr;
}
p = strbuf.find_first_of(':');
if (p == std::string::npos) {
log_exit("parse error on request header field: ", strbuf);
return nullptr;
}
HTTPHeaderField::sptr h = std::make_shared<HTTPHeaderField>();
h->name = strbuf.substr(0, p - 0);
++p;
while (1) {
tpos = strbuf.find(" \t", p);
if (tpos == std::string::npos) {
break;
}
p = tpos;
p += 2;
}
h->value = strbuf.substr(p);
return h;
}
content_length()
main.cpp
static long content_length(HTTPRequest::sptr &req) {
std::string val = req->lookup_header_field_value(FIELD_NAME_CONTENT_LENGTH);
if (val.length() == 0) {
return 0;
}
long len = std::stol(val);
if (len < 0) {
log_exit("nagative Content-Length value");
return 0;
}
return len;
}
response_to()
main.cpp
static const std::string METHOD_HEAD = "HEAD";
static const std::string METHOD_GET = "GET";
static const std::string METHOD_POST = "POST";
static void response_to(HTTPRequest::sptr &req, std::ostream &out,
std::string &docroot) {
if (req->method == METHOD_GET) {
do_file_response(req, out, docroot);
} else if (req->method == METHOD_HEAD) {
do_file_response(req, out, docroot);
} else if (req->method == METHOD_POST) {
method_not_allowed(req, out);
} else {
not_implemented(req, out);
}
}
do_file_response() FileInfo est un pointeur intelligent, donc free () n'est pas fait.
main.cpp
static const std::string FIELD_NAME_CONTENT_LENGTH = "Content-Length";
static const std::string FIELD_NAME_CONTENT_TYPE = "Content-Type";
static const std::string HTTP_OK = "200 OK";
……
static void do_file_response(HTTPRequest::sptr &req, std::ostream &out,
std::string &docroot) {
FileInfo::sptr info = get_fileinfo(docroot, req->path);
if (!(info->ok)) {
not_found(req, out);
return;
}
out_put_common_header_fields(req, out, HTTP_OK);
out_one_line(out, FIELD_NAME_CONTENT_LENGTH, ": ", info->size);
out_one_line(out, FIELD_NAME_CONTENT_TYPE, ": ", guess_content_type(info));
out_one_line(out);
if (req->method != METHOD_HEAD) {
std::ifstream ifs(info->path);
if (!ifs) {
log_exit("failed to open ", info->path, ": ", strerror(errno));
return;
}
// istream_l'itérateur ignore les espaces et les sauts de ligne
std::copy(std::istreambuf_iterator<std::ifstream::char_type>(ifs),
std::istreambuf_iterator<std::ifstream::char_type>(),
std::ostreambuf_iterator<std::ostream::char_type>(out));
out << std::flush;
}
return;
}
Dans le livre, \ r \ n était écrit à la fin de la chaîne de réponse à chaque fois et sorti par fprintf. J'ai créé une fonction de sortie pour la réponse.
main.cpp
template <typename Stream>
static void out_one_line(Stream &stream) {
stream << "\r\n";
}
template <typename Stream, class Tail>
static void out_one_line(Stream &stream, Tail tail) {
stream << tail;
out_one_line(stream);
}
template <typename Stream, class Head, class... Tail>
static void out_one_line(Stream &stream, Head head, Tail... tail) {
stream << head;
out_one_line(stream, tail...);
}
get_fileinfo() Aucun changement sauf que FileInfo est un pointeur intelligent.
main.cpp
static FileInfo::sptr get_fileinfo(std::string &docroot, std::string &urlpath) {
try {
struct stat st;
FileInfo::sptr info = std::make_shared<FileInfo>();
info->path = build_fspath(docroot, urlpath);
……réduction……
return info;
} catch(std::bad_alloc e) {
log_exit(e.what());
}
return nullptr;
}
build_fspath() Bien qu'il ne soit pas dans le livre, une vérification pour spécifier le chemin en dehors de la racine DOC et un processus pour changer le nom de fichier en "index.html" lorsque le chemin se termine par '/' sont ajoutés.
main.cpp
static std::string build_fspath(std::string &docroot, std::string &urlpath) {
std::string::size_type pos = 0;
std::string::size_type pos1 = 0;
int32_t cnt = 0;
char fs = '/';
std::string upDir = "../";
//Vérifier la racine du document extérieur
pos = urlpath.find_first_of(fs);
// '/'Commencer à partir de
if (pos == 0) {
pos1= 1;
}
while (1) {
pos = urlpath.find_first_of(fs, pos1);
if (pos == std::string::npos) { break; }
std::string dir = urlpath.substr(pos1, pos - pos1 + 1);
if (dir == upDir) {
--cnt;
} else {
++cnt;
}
if (cnt < 0) {
log_exit("Invalid url path: ", urlpath);
}
++pos;
pos1 = pos;
}
std::string path = docroot;
if (path.back() == '/') {
path.pop_back();
}
if (urlpath.front() != '/') {
path += "/";
}
path += urlpath;
//le chemin est"/"Index en terminant par.Que ce soit HTML
if (path.back() == '/') {
path += "index.html";
}
return path;
}
out_put_common_header_fields() Puisque C ++ 20 correspond au jour dans std :: chrono, la création de la chaîne de caractères de date et d'heure est la même implémentation que le livre.
main.cpp
static const std::string SERVER_NAME = ……
static const std::string SERVER_VERSION = ……
static void
out_put_common_header_fields(HTTPRequest::sptr &req, std::ostream &out,
const std::string &status) {
……réduction……
out_one_line(out, "HTTP/1.", HTTP_MINOR_VERSION, " ", status);
out_one_line(out, "Date: ", buf);
out_one_line(out, "Server: ", SERVER_NAME, "/", SERVER_VERSION);
out_one_line(out, "Connection: close");
}
guess_content_type() Dans le livre, text / plain a été renvoyé de manière fixe, mais j'ai implémenté le minimum pour qu'il puisse être affiché correctement dans le navigateur.
main.cpp
static std::string guess_content_type(FileInfo::sptr &info) {
static std::map<std::string, std::string> mimeMap
= { { "html", "text/html" }, { "htm", "text/html" }, {"js", "text/javascript"},
{"jpg", "image/jpeg"}, {"jpeg", "image/jpeg"}, {"png", "image/png"},
{"bmp", "image/bmp"}, {"gif", "image/gif"}, {"svg", "image/svg+xml"},
{"css", "text/css"}, {"au", "audio/basic"}, {"wav", "audio/wav"},
{"mpeg", "video/mpeg"}, {"mp3", "audio/mp3"}, {"ogg", "application/ogg"},
{"pac","application/x-ns-proxy-autoconfig"} };
int extPos = info->path.find_last_of(".");
std::string extention = info->path.substr(extPos + 1, info->path.size() - extPos);
if (mimeMap.count(extention) != 0) {
return mimeMap.at(extention);
}
return "text/plain";
}
not_found()
main.cpp
static const std::string HTTP_NOT_FOUND = "404 Not Found";
static void not_found(HTTPRequest::sptr &req, std::ostream &out) {
out_put_common_header_fields(req, out, HTTP_NOT_FOUND);
out_one_line(out, FIELD_NAME_CONTENT_TYPE, ": text/html");
out_one_line(out);
if (req->method != METHOD_HEAD) {
out_one_line(out, "<html>");
out_one_line(out, "<header><title>Not Found</title></header>");
out_one_line(out, "<body><p>File not found</p></body>");
out_one_line(out, "</html>");
}
out << std::flush;
}
Recommended Posts