In the latter half of the first edition of normal Linux programming, there is a chapter to create a web server (littiehttpd) in C language. I rewrote this WEB server with C ++ 14. Although it is free software, since the book is copyrighted, the part that has not been rewritten in C ++ is not described. The source comment only describes the rewritten part. For more details and unclear points that this article alone is not a working source, please refer to the first edition of normal Linux programming.
Makefile In the book, it is explained that Makefile is automatically generated by autoconf, but I did not adopt it because it is difficult to create it unless I am accustomed to it. Instead, I decided to automatically generate a Makefile with CMake, which was pushed hard by 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 Added smart pointer typedef. I changed the string to std :: string and the linked list to std :: list. Since it is a smart pointer, free_request (HTTPRequest *) is not necessary.
main.cpp
// Declaration
struct HTTPHeaderField {
typedef std::shared_ptr<HTTPHeaderField> sptr;
std::string name;
std::string value;
//next is std::list<sptr>It is unnecessary because it is a list of
};
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() There are two changes: I changed the docroot and port variables to std :: string, and ended with std :: quick_exit ().
main.cpp
int main(int argc, char* argv[])
……abridgement……
std::string port = DEFAULT_PORT;
std::string docroot;
……abridgement……
……
server_main(server, docroot);
std::quick_exit(EXIT_SUCCESS);
}
In C ++, it does not expand variadic at run time, but at compile time. Format specification (% s etc.) reboots due to a specification error, so let the compiler determine the 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...);
}
Uses std :: thread instead of fork (), so child process related signals are unnecessary
listen_socket Only changed the argument port to std :: string.
main.cpp
static int listen_socket(std::string &port){
……abridgement……
}
server_main Accept () and fork () are the same as books. Parallel processing with std :: thread instead of fork ().
main.cpp
static void server_main(int server, std::string &docroot) {
……abridgement……
//sock std::Pass to thread(be bound), Thread processing
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;
}
}
//Close with fdBuf destructor
//close(sock);
return;
}, sock, docroot);
th.detach();
} // end-for
}
The fdBuf class is a streamBuffer class for processing sockets with iostream. The simplest implementation is used to check the operation, and the processing is slow. If you rewrite here, the processing speed will increase.
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()Is not thread safe and is not needed
//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*/ {
//Simple implementation for checking operation and slow
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(); }
//Simple implementation and slow to check operation
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() Since HTTPRequest is a smart pointer, there is no need to call 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() Headers and body are not used. Implemented based on the policy of rewriting book processing to 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() You can use std :: getline () to get input without size restrictions. Istream :: getline () is used from the description of the book that it is dangerous to read unlimited input character strings from the outside.
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>/Search for blanks immediately after the method of
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++;
//uppercase letter/HTTP1 without case sensitivity.X or compare
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;
}
Defines an error class to terminate the thread when disconnecting from the 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()Does not include line breaks
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 is a smart pointer, so free () is not done.
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_iterator skips whitespace and line breaks
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 the book, \ r \ n was written at the end of the response string every time and output by fprintf. I created an output function for the response.
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() No change except that FileInfo is a smart pointer.
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);
……abridgement……
return info;
} catch(std::bad_alloc e) {
log_exit(e.what());
}
return nullptr;
}
build_fspath() Although it is not in the book, a check for specifying the path outside the DOC Root and a process to change the file name to "index.html" when the path ends with'/' are added.
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 = "../";
//Check outside document root
pos = urlpath.find_first_of(fs);
// '/'Start from
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;
//path is"/"Index when ending with.Let it be html
if (path.back() == '/') {
path += "index.html";
}
return path;
}
out_put_common_header_fields() Since C ++ 20 corresponds to the day of the week with std :: chrono, the character string creation of the date and time is the same implementation as the book.
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) {
……abridgement……
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 the book, text / plain was returned fixedly, but I implemented the minimum so that it can be displayed correctly in the browser.
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