In der zweiten Hälfte der ersten Ausgabe der normalen Linux-Programmierung gibt es ein Kapitel zum Erstellen eines WEB-Servers (littiehttpd) in C-Sprache. Ich habe diesen WEB-Server mit C ++ 14 umgeschrieben. Obwohl es sich um freie Software handelt, wird der Teil, der in C ++ nicht neu geschrieben wurde, nicht beschrieben, da das Buch urheberrechtlich geschützt ist. Der Quellkommentar beschreibt nur den umgeschriebenen Teil. Weitere Einzelheiten und unklare Punkte, dass dieser Artikel allein keine funktionierende Quelle ist, finden Sie in der 1. Ausgabe von Normal Linux Programming.
Makefile Das Buch erklärt, dass Makefile automatisch von Autoconf generiert wird, aber ich habe es nicht verwendet, da es schwierig ist, es zu erstellen, wenn ich nicht daran gewöhnt bin. Stattdessen habe ich beschlossen, automatisch ein Makefile mit CMake zu generieren, das von Qiita hart vorangetrieben wurde.
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 Typedef des intelligenten Zeigers hinzugefügt. Die Zeichenfolge wurde in std :: string geändert, und die Linkliste wurde in std :: list geändert. Da es sich um einen intelligenten Zeiger handelt, ist free_request (HTTPRequest *) nicht erforderlich.
main.cpp
// Declaration
struct HTTPHeaderField {
typedef std::shared_ptr<HTTPHeaderField> sptr;
std::string name;
std::string value;
//als nächstes ist std::list<sptr>Es ist unnötig, weil es eine Liste von ist
};
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() Es gibt zwei Änderungen: Die eine besteht darin, die Variablen docroot und port in std :: string zu ändern, und die andere darin, mit std :: quick_exit () zu enden.
main.cpp
int main(int argc, char* argv[])
……Kürzung……
std::string port = DEFAULT_PORT;
std::string docroot;
……Kürzung……
……
server_main(server, docroot);
std::quick_exit(EXIT_SUCCESS);
}
In C ++ werden die Variablenargumente nicht zur Laufzeit erweitert, sondern zur Kompilierungszeit. Die Formatspezifikation (% s usw.) wird aufgrund eines Spezifikationsfehlers neu gestartet. Lassen Sie den Compiler den Typ bestimmen
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...);
}
Verwendet std :: thread anstelle von fork (), sodass untergeordnete prozessbezogene Signale nicht erforderlich sind
listen_socket Nur der Argumentport wurde in std :: string geändert.
main.cpp
static int listen_socket(std::string &port){
……Kürzung……
}
server_main Accept () und fork () sind dieselben wie Bücher. Parallele Verarbeitung mit std :: thread anstelle von fork ().
main.cpp
static void server_main(int server, std::string &docroot) {
……Kürzung……
//Socke std::An Thread übergeben(gebunden sein), Thread-Verarbeitung
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;
}
}
//Schließen Sie mit dem fdBuf-Destruktor
//close(sock);
return;
}, sock, docroot);
th.detach();
} // end-for
}
Die fdBuf-Klasse ist eine streamBuffer-Klasse zum Verarbeiten von Sockets mit iostream. Die einfachste Implementierung wird verwendet, um den Vorgang zu überprüfen, und die Verarbeitung ist langsam. Wenn Sie hier neu schreiben, erhöht sich die Verarbeitungsgeschwindigkeit.
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()Ist nicht threadsicher und wird nicht benötigt
//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*/ {
//Es ist eine einfache Implementierung zur Überprüfung des Vorgangs und langsam
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(); }
//Einfache Implementierung und langsame Überprüfung des Betriebs
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() Da HTTPRequest ein intelligenter Zeiger ist, muss free () nicht aufgerufen werden.
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() Header und Body werden nicht verwendet. Implementiert basierend auf der Richtlinie zum Umschreiben der Buchverarbeitung in 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() Sie können std :: getline () verwenden, um Eingaben ohne Größenbeschränkungen abzurufen. Istream :: getline () wird aus der Beschreibung des Buches verwendet, dass es gefährlich ist, eine unbegrenzte Anzahl von Eingabezeichenfolgen von außen zu lesen.
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>/Suchen Sie unmittelbar nach der Methode von nach Leerzeichen
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++;
//Großbuchstabe/HTTP1 ohne Groß- und Kleinschreibung.X oder vergleichen
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;
}
Definiert eine Fehlerklasse zum Beenden eines Threads, wenn die Verbindung zu einem Client getrennt wird
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()Enthält keine Zeilenumbrüche
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 ist ein intelligenter Zeiger, daher wird free () nicht ausgeführt.
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_Der Iterator überspringt Leerzeichen und Zeilenumbrüche
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;
}
In dem Buch wurde \ r \ n jedes Mal am Ende der Antwortzeichenfolge geschrieben und von fprintf ausgegeben. Ich habe eine Ausgabefunktion für die Antwort erstellt.
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() Keine Änderung, außer dass FileInfo ein intelligenter Zeiger ist.
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);
……Kürzung……
return info;
} catch(std::bad_alloc e) {
log_exit(e.what());
}
return nullptr;
}
build_fspath() Obwohl dies nicht im Buch enthalten ist, werden eine Überprüfung zum Angeben des Pfads außerhalb des DOC-Stamms und ein Prozess zum Ändern des Dateinamens in "index.html" hinzugefügt, wenn der Pfad mit "/" endet.
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 = "../";
//Überprüfen Sie den externen Dokumentstamm
pos = urlpath.find_first_of(fs);
// '/'Beginne am
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;
//Weg ist"/"Index am Ende mit.Lass es HTML sein
if (path.back() == '/') {
path += "index.html";
}
return path;
}
out_put_common_header_fields() Da C ++ 20 dem Tag in std :: chrono entspricht, entspricht die Erstellung von Zeichenfolgen für Datum und Uhrzeit der Implementierung des Buches.
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) {
……Kürzung……
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() In dem Buch wurde Text / Plain fest zurückgegeben, aber ich habe das Minimum implementiert, damit es im Browser korrekt angezeigt werden kann.
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