1. ホーム
  2. C++

C++によるhttpサーバー/webサーバーの作成

2022-02-15 20:06:51
<パス

目次

冒頭のくだり

実はこの記事、ずっと書きたいと思っていたのですが、時間不足に悩まされ、一気に仕上げてしまいたいのです。帰国前夜、上海の小さなホテルの中で、C++で簡単なhttpサーバーを構築する方法を書こうと珍しく張り切っていた。

私はほとんどの人が自分のウェブサイト(でも壊れて壊れてもそれを自分で行う)に憧れたいと信じて、私は最初はとても熱心だった、私はそれが2020年8月だったことを覚えて、私は最終的にソケットを学んだ情報のすべての種類をチェックしたので、私はチャットプログラムを書くために待つことができなかった、マッピングを移植する方法を勉強してきました。当時はまだ、接続が来たらスレッドを開いて、抜けたらスレッドを破棄するというのは非常に愚かで甘かったし、スレッドの生成と破棄のオーバーヘッドが大きいことも知っていましたが、それ以外の良い方法は考えもしませんでした。今振り返ると、この問題は非常に愚かで、なぜスレッドプーリングの技術を使うことを考えなかったのだろう?学習はステップバイステップの進化のプロセスであり、誰をうらやむことはありません、誰と比較することはありません、唯一の自分以上で

この記事は、多くの複雑な概念を伴わないし、読みにくいテンプレート関数を書いていない、コードはシンプルで読みやすい、この記事は、C++でhttpサーバーを書きたいすべての人のためのものです! この章では、私がhttpを学ぶときに遭遇した問題やアイデアを書いていきます。

主な記事

簡単なhttpサーバーはどう書けばいいの?簡単です、最も基本的な3つのことを返せばいいのです。

  1. ステータスコード
  2. 送信ファイルの長さ
  3. 送信するファイルの種類

ステータスコード 200(リクエストファイルが見つかりました)、404(リクエストファイルが見つかりません)など、この2つのステータスコードを比較的簡単に実装することも行いました。

ファイルの長さ 例えば、お客様がindex.htmlのページをリクエストした場合、ブラウザはこのファイルを受信したことをどうやって知るのでしょうか?それは、ファイルの長さに依存します。

文書タイプ htmlはhtml形式、cssはcss形式、画像は画像形式、zipはzip形式です ファイル形式に対応するファイルタイプの一覧表

これらの3つとリクエストされたファイルをクライアントに返すのは問題ありません(リクエストされたファイルが存在する場合)。

もっと複雑にしたい場合は、httpの様々なパラメータについて説明したこちらの記事を参照してください。 記事へのリンク

Http ワークフロー

ここでは、一般的なhttpのワークフローを大まかに説明します。クライアントはURLを通じてあなたのウェブサイトにアクセスします(クライアントは能動的に接続を要求し、サーバーは受動的に接続していることを常に覚えておいてください)。httpのデフォルトのポート番号が80であることを除いて、実際にはip+portを通じてアクセスされます。例えば、まだドメイン名やクラウドサーバーなどを持っていない場合、ローカルでどのようにテストするのでしょうか?それは、ブラウザのボックスにip:portを入力し、例えば127.0.0.1:9996と入力し、エンターキーを押してローカルに自分のhttpサーバーにアクセスすることである。もちろん、httpで顧客(リクエスト元)にホームページを提供し、顧客がページを指定しない場合は、ドメイン名や127.0.0.1:9996を入力するだけで、顧客にデフォルトホームページを提供する、これも実現したいことである。お客様がリクエストファイルを書き込むと、それが存在するかどうかを判断し、存在する場合はステータスコード200とリクエストファイルの内容を返します。これは、最も単純なGETを例にして、どのように判断しているのか、他もほぼ同じなので、興味のある学生は他を勉強してください。我々は、使用する 正規表現 を解析するために、顧客によって送信された要求がGETまたはPOSTまたは他の要求で、ファイルを要求するために分析アウトです。そして、ステートマシンの考え方で、ファイルが存在するかどうかを判断し、存在する場合はファイルの種類を判断します。というのが大まかな流れです。

Http.h

#pragma once
#include 

#include 

#include 

#include 
Http.cpp
#include "Http.h"
#include "TcpClient.h"
#include "Logger.h"


#include 

#include 

#include 

#include 


using namespace std;
void Http::addHeader(const string& head)
{
    if (!head.empty())
    {
        header_ += head;
        header_ += "\r\n";
    // Console.WriteLine("I'm here head!= null" + header_);
    }
    // automatically add a closing
    else
    {
        header_ += "\r\n";
    // Console.WriteLine("I'm here head == null" + header_);
    }
}

void Http::Header(bool flag)
{
    // determine the header to send true means 200 false means 404
    if(flag == true)
    { 
        header_ = "HTTP/1.1 200 OK\r\n";
    }
    else
    {
        header_ = "HTTP/1.1 404 NOTFOUND\r\nContent-Length:0\r\n\r\n";
    }
}

void Http::processHead()
{
    string ContentType = "Content-Type:";
    if (fileType_ == "html")
    {
        ContentType += "text/html";
    }
    else if (fileType_ == "js")
    {
        ContentType += "application/x-javascript";
    }
    else if(fileType_ == "css")
    {
        ContentType += "text/css";
    }
    else if(fileType_ == "jpg" || fileType_ == "png")
    {
        ContentType += "image/" + fileType_;
    }
    else if (fileType_== "zip" || fileType_ == "tar")
    {
        ContentType += "application/" + fileType_;
    }
    addHeader(ContentType);

    // substitute perfect, to open the file filePath_ is the path to the requested file
    fileSize_ = fileStat_.st_size;
    string ContentLength = "Content-Length:" + to_string(fileSize_);
    addHeader(ContentLength);
    // add a closing at the end
    addHeader("");
    // Console.WriteLine("process fileContent_:" + );
}

void Http::addFilePath(const string& requestFile)
{
    filePath_ += requestFile;
}

void Http::analyseFileType(const string& requestFile)
{
    for (int i = 0; i < requestFile.size(); ++i)
    {
        if (requestFile[i] == '.')
        {
            // get what the request file ends with
            fileType_ = requestFile.substr(i + 1);
        }
    }
}

bool Http::fileIsExist(){
    fileFd_ = ::open(filePath_.c_str(),O_CLOEXEC | O_RDWR);
    if (fileFd_ < 0)
    { // means the requested file was found
        return false;
    }
    return true;
}

bool Http::analyseFile(const string& request)
{
    // call the header of
    // The ^ in [] starts with something or other and is placed inside [] to mean right or wrong
    string pattern = "^([A-Z]+) ([A-Za-z./1-9-]*)";
    regex reg(pattern);
    smatch mas;
    regex_search(request,mas,reg);
    // because the subscript 0 is for the whole match
    if(mas.size() < 3){
        LOG_INFO("Not a normal request");
        // return false directly for nothing
        return false;
    }
    string requestMode = mas[1];
    if(requestMode == "POST"){
        isPostMode_ = true;
        cout << "POST request !!!!! " << endl;
    }
    // The specific file to request
    string requestFile = mas[2];
    // Get the requested file first

    bool flag;
    if (requestFile == "/")
    { // give default value if it is /
        filePath_.clear(); // Clear a zero first
        filePath_ = path_;
        filePath_ += "/run.html";
        // Add the type of the file to the person as well
        fileType_ = "html"; 
    }
    else
    {
        filePath_.clear(); // Clear a zero first
        filePath_ = path_;
        addFilePath(requestFile);
        // Use the open function
        
    }
    flag = fileIsExist();
    // if the file is not found
    if(!flag){
        LOG_INFO("The file the client wants was not found");
        cout << filePath_ << endl;
        return false;
    }
    ::fstat(fileFd_,&fileStat_);
    // If the file doesn't exist then there is no need to analyze the type
    analyseFileType(requestFile);
    return true;
}


void Http::SendFile(int clientFd,bool isRequestOk
Do not consider the case of high concurrency, the design of a synchronous blocking epoll can be, look at the three elements necessary for http has been able to write a server, my underlying socket using their own encapsulated network library, Reactor model, one loop per thread code file is more, so did not put up, but as long as the status code, the type of file (that large if, the length of the file) these three implementation will be able to build a simple http server. But as long as the status code, the file type (the big if), and the length of the file are realized, you can build a simple http server. You can use sendfile zero copy to send files
To add to this, the http protocol ends in \r\n. There is a \r\n at the end, for example, 404, HTTP/1.1 404 NOTFOUND\r\nContent-Length:0\r\n\r\n, followed by a \r\n at the end, and a \r\n at the end of the paragraph
Personal website links
Personal website
Source code address
2021.10.25 more, recently finally had time to refactor their own code, I feel that the writing is okay, attached links
github link
If you are interested, you can click a star hahahahaha, you can check it out on github
#include "Http.h" #include "TcpClient.h" #include "Logger.h" #include #include #include #include using namespace std; void Http::addHeader(const string& head) { if (!head.empty()) { header_ += head; header_ += "\r\n"; // Console.WriteLine("I'm here head!= null" + header_); } // automatically add a closing else { header_ += "\r\n"; // Console.WriteLine("I'm here head == null" + header_); } } void Http::Header(bool flag) { // determine the header to send true means 200 false means 404 if(flag == true) { header_ = "HTTP/1.1 200 OK\r\n"; } else { header_ = "HTTP/1.1 404 NOTFOUND\r\nContent-Length:0\r\n\r\n"; } } void Http::processHead() { string ContentType = "Content-Type:"; if (fileType_ == "html") { ContentType += "text/html"; } else if (fileType_ == "js") { ContentType += "application/x-javascript"; } else if(fileType_ == "css") { ContentType += "text/css"; } else if(fileType_ == "jpg" || fileType_ == "png") { ContentType += "image/" + fileType_; } else if (fileType_== "zip" || fileType_ == "tar") { ContentType += "application/" + fileType_; } addHeader(ContentType); // substitute perfect, to open the file filePath_ is the path to the requested file fileSize_ = fileStat_.st_size; string ContentLength = "Content-Length:" + to_string(fileSize_); addHeader(ContentLength); // add a closing at the end addHeader(""); // Console.WriteLine("process fileContent_:" + ); } void Http::addFilePath(const string& requestFile) { filePath_ += requestFile; } void Http::analyseFileType(const string& requestFile) { for (int i = 0; i < requestFile.size(); ++i) { if (requestFile[i] == '.') { // get what the request file ends with fileType_ = requestFile.substr(i + 1); } } } bool Http::fileIsExist(){ fileFd_ = ::open(filePath_.c_str(),O_CLOEXEC | O_RDWR); if (fileFd_ < 0) { // means the requested file was found return false; } return true; } bool Http::analyseFile(const string& request) { // call the header of // The ^ in [] starts with something or other and is placed inside [] to mean right or wrong string pattern = "^([A-Z]+) ([A-Za-z./1-9-]*)"; regex reg(pattern); smatch mas; regex_search(request,mas,reg); // because the subscript 0 is for the whole match if(mas.size() < 3){ LOG_INFO("Not a normal request"); // return false directly for nothing return false; } string requestMode = mas[1]; if(requestMode == "POST"){ isPostMode_ = true; cout << "POST request !!!!! " << endl; } // The specific file to request string requestFile = mas[2]; // Get the requested file first bool flag; if (requestFile == "/") { // give default value if it is / filePath_.clear(); // Clear a zero first filePath_ = path_; filePath_ += "/run.html"; // Add the type of the file to the person as well fileType_ = "html"; } else { filePath_.clear(); // Clear a zero first filePath_ = path_; addFilePath(requestFile); // Use the open function } flag = fileIsExist(); // if the file is not found if(!flag){ LOG_INFO("The file the client wants was not found"); cout << filePath_ << endl; return false; } ::fstat(fileFd_,&fileStat_); // If the file doesn't exist then there is no need to analyze the type analyseFileType(requestFile); return true; } void Http::SendFile(int clientFd,bool isRequestOk