J'ai essayé de réécrire le serveur WEB de la 1ère édition de programmation Linux normale avec C ++ 14

introduction

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.

environnement

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);
}

Enregistrement

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...);
}

signal

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;
}

Les références

Recommended Posts

J'ai essayé de réécrire le serveur WEB de la 1ère édition de programmation Linux normale avec C ++ 14
J'ai essayé de trouver l'entropie de l'image avec python
J'ai essayé de trouver la moyenne de plusieurs colonnes avec TensorFlow
J'ai essayé de résoudre l'édition du débutant du livre des fourmis avec python
[Linux] J'ai essayé de résumer les commandes de confirmation des ressources
J'ai essayé d'automatiser l'arrosage du pot avec Raspberry Pi
J'ai essayé d'agrandir la taille du volume logique avec LVM
J'ai essayé d'améliorer l'efficacité du travail quotidien avec Python
J'ai essayé le serveur asynchrone de Django 3.0
J'ai essayé d'obtenir le code d'authentification de l'API Qiita avec Python.
J'ai essayé d'extraire automatiquement les mouvements des joueurs Wiire avec un logiciel
J'ai essayé d'analyser la négativité de Nono Morikubo. [Comparer avec Posipa]
J'ai essayé de rationaliser le rôle standard des nouveaux employés avec Python
J'ai essayé de visualiser le texte du roman "Weather Child" avec Word Cloud
[Linux] J'ai essayé de vérifier la méthode de confirmation sécurisée du FQDN (CentOS7)
J'ai essayé d'obtenir les informations sur le film de l'API TMDb avec Python
J'ai essayé de prédire le comportement du nouveau virus corona avec le modèle SEIR.
J'ai essayé de créer Othello AI avec tensorflow sans comprendre la théorie de l'apprentissage automatique ~ Battle Edition ~
J'ai essayé Web Scraping pour analyser les paroles.
J'ai essayé de sauvegarder les données avec discorde
J'ai essayé de corriger la forme trapézoïdale de l'image
J'ai essayé d'utiliser Linux avec Discord Bot
J'ai essayé de vectoriser les paroles de Hinatazaka 46!
J'ai essayé de visualiser facilement les tweets de JAWS DAYS 2017 avec Python + ELK
L'histoire de la fabrication de soracom_exporter (j'ai essayé de surveiller SORACOM Air avec Prometheus)
J'ai essayé de créer un modèle avec l'exemple d'Amazon SageMaker Autopilot
J'ai essayé d'envoyer automatiquement la littérature du nouveau virus corona à LINE avec Python
J'ai essayé d'entraîner la fonction péché avec chainer
J'ai essayé d'extraire des fonctionnalités avec SIFT d'OpenCV
J'ai essayé de toucher un fichier CSV avec Python
J'ai essayé de résoudre Soma Cube avec python
J'ai essayé de visualiser les informations spacha de VTuber
J'ai essayé d'effacer la partie négative de Meros
J'ai essayé de résoudre le problème avec Python Vol.1
J'ai essayé de classer les voix des acteurs de la voix
J'ai essayé de résumer les opérations de chaîne de Python
J'ai essayé de faire quelque chose comme un chatbot avec le modèle Seq2Seq de TensorFlow
J'ai essayé d'automatiser la mise à jour de l'article du blog Livedoor avec Python et sélénium.
J'ai essayé de visualiser les caractéristiques des nouvelles informations sur les personnes infectées par le virus corona avec wordcloud
J'ai essayé de visualiser les données de course du jeu de course (Assetto Corsa) avec Plotly
J'ai essayé de comparer la vitesse de traitement avec dplyr de R et pandas de Python
Le 15e temps réel hors ligne, j'ai essayé de résoudre le problème de l'écriture avec python
[Courses de chevaux] J'ai essayé de quantifier la force du cheval de course
J'ai essayé la "correction gamma" de l'image avec Python + OpenCV
J'ai essayé de simuler la propagation de l'infection avec Python
J'ai essayé d'analyser les émotions de tout le roman "Weather Child" ☔️
J'ai essayé d'afficher les données du groupe de points DB de la préfecture de Shizuoka avec Vue + Leaflet
J'ai essayé de publier automatiquement sur ChatWork au moment du déploiement avec Fabric et ChatWork Api
Je voulais résoudre le concours de programmation Panasonic 2020 avec Python
J'ai essayé de démarrer le serveur de Django avec VScode au lieu de Pycharm
J'ai essayé de résoudre le problème de F02 comment écrire en temps réel hors ligne avec Python
J'ai essayé de notifier les informations de retard de train avec LINE Notify
J'ai essayé de visualiser la consommation électrique de ma maison avec Nature Remo E lite
Vérifiez l'état de la mémoire du serveur avec la commande gratuite Linux
Vérifiez l'état de fonctionnement du serveur avec la commande Linux top