Ich habe versucht, den WEB-Server der normalen Linux-Programmierung 1st Edition mit C ++ 14 neu zu schreiben

Einführung

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.

Umgebung

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

Protokollierung

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

Signal

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

Verweise

Recommended Posts

Ich habe versucht, den WEB-Server der normalen Linux-Programmierung 1st Edition mit C ++ 14 neu zu schreiben
Ich habe versucht, die Entropie des Bildes mit Python zu finden
Ich habe versucht, mit TensorFlow den Durchschnitt mehrerer Spalten zu ermitteln
Ich habe versucht, die Anfängerausgabe des Ameisenbuchs mit Python zu lösen
[Linux] Ich habe versucht, die Ressourcenbestätigungsbefehle zusammenzufassen
Ich habe versucht, die Bewässerung des Pflanzgefäßes mit Raspberry Pi zu automatisieren
Ich habe versucht, die Größe des logischen Volumes mit LVM zu erweitern
Ich habe versucht, die Effizienz der täglichen Arbeit mit Python zu verbessern
Ich habe den asynchronen Server von Django 3.0 ausprobiert
Ich habe versucht, den Authentifizierungscode der Qiita-API mit Python abzurufen.
Ich habe versucht, die Bewegungen von Wiire-Playern automatisch mit Software zu extrahieren
Ich habe versucht, die Negativität von Nono Morikubo zu analysieren. [Vergleiche mit Posipa]
Ich habe versucht, die Standardrolle neuer Mitarbeiter mit Python zu optimieren
Ich habe versucht, den Text des Romans "Wetterkind" mit Word Cloud zu visualisieren
[Linux] Ich habe versucht, die sichere Bestätigungsmethode von FQDN (CentOS7) zu überprüfen.
Ich habe versucht, die Filminformationen der TMDb-API mit Python abzurufen
Ich habe versucht, das Verhalten des neuen Koronavirus mit dem SEIR-Modell vorherzusagen.
Ich habe versucht, Othello AI mit Tensorflow zu erstellen, ohne die Theorie des maschinellen Lernens zu verstehen ~ Battle Edition ~
Ich habe Web Scraping versucht, um die Texte zu analysieren.
Ich habe versucht, die Daten mit Zwietracht zu speichern
Ich habe versucht, die Trapezform des Bildes zu korrigieren
Ich habe versucht, Linux mit Discord Bot zu betreiben
Ich habe versucht, die Texte von Hinatazaka 46 zu vektorisieren!
Ich habe versucht, die Tweets von JAWS DAYS 2017 mit Python + ELK einfach zu visualisieren
Die Geschichte von soracom_exporter (Ich habe versucht, SORACOM Air mit Prometheus zu überwachen)
Ich habe versucht, ein Modell mit dem Beispiel von Amazon SageMaker Autopilot zu erstellen
Ich habe versucht, die Literatur des neuen Corona-Virus mit Python automatisch an LINE zu senden
Ich habe versucht, die Sündenfunktion mit Chainer zu trainieren
Ich habe versucht, Funktionen mit SIFT von OpenCV zu extrahieren
Ich habe versucht, eine CSV-Datei mit Python zu berühren
Ich habe versucht, Soma Cube mit Python zu lösen
Ich habe versucht, die Spacha-Informationen von VTuber zu visualisieren
Ich habe versucht, den negativen Teil von Meros zu löschen
Ich habe versucht, das Problem mit Python Vol.1 zu lösen
Ich habe versucht, die Stimmen der Sprecher zu klassifizieren
Ich habe versucht, die String-Operationen von Python zusammenzufassen
Ich habe versucht, mit dem Seq2Seq-Modell von TensorFlow so etwas wie einen Chatbot zu erstellen
Ich habe versucht, das Artikel-Update des Livedoor-Blogs mit Python und Selen zu automatisieren.
Ich habe versucht, die Eigenschaften der neuen Informationen über mit dem Corona-Virus infizierte Personen mit Wordcloud zu visualisieren
Ich habe versucht, die Laufdaten des Rennspiels (Assetto Corsa) mit Plotly zu visualisieren
Ich habe versucht, die Verarbeitungsgeschwindigkeit mit dplyr von R und pandas von Python zu vergleichen
Beim 15. Offline-Echtzeitversuch habe ich versucht, das Problem des Schreibens mit Python zu lösen
[Pferderennen] Ich habe versucht, die Stärke des Rennpferdes zu quantifizieren
Ich habe versucht, das Bild mit Python + OpenCV "gammakorrektur" zu machen
Ich habe versucht zu simulieren, wie sich die Infektion mit Python ausbreitet
Ich habe versucht, die Emotionen des gesamten Romans "Wetterkind" zu analysieren
Ich habe versucht, die Punktgruppendaten-DB der Präfektur Shizuoka mit Vue + Leaflet anzuzeigen
Ich habe versucht, zum Zeitpunkt der Bereitstellung mit Fabric und ChatWork Api automatisch in ChatWork zu posten
Ich wollte den Panasonic Programming Contest 2020 mit Python lösen
Ich habe versucht, Djangos Server mit VScode anstelle von Pycharm zu starten
Ich habe versucht, das Problem von F02 zu lösen, wie man mit Python offline in Echtzeit schreibt
Ich habe versucht, die Zugverspätungsinformationen mit LINE Notify zu benachrichtigen
Ich habe versucht, den Stromverbrauch meines Hauses mit Nature Remo E lite zu visualisieren
Überprüfen Sie den Speicherstatus des Servers mit dem Befehl Linux free
Überprüfen Sie den Betriebsstatus des Servers mit dem Linux-Befehl top