TinyWS —— 一個C++寫的簡易WEB服務器(一)

寫在前面

每一個碼農可能都會偶爾有本身作一個經常使用軟件的想法,好比操做系統,編譯器,郵件服務器/客戶端,文字編輯器等等。這裏面有些很難,好比操做系統,作一個最簡單的也要付出很大的努力,但是大部分經常使用工具都是能夠比較容易的作一個簡易版本(固然也是隻能玩玩而已)。因而我作了一個很是簡陋的WEB服務器 —— TinyWS。這裏主要是記錄下本身整個過程當中的一些想法。html

TinyWS是用C++」從頭開始「作的,也就是說,除了C/C++的標準庫和操做系統的系統調用,並無使用第三方庫。我並不喜歡C++(甚至有些厭惡其紛繁複雜的語法規則),正因如今後,雖然其是個人工做語言,但我也學的很粗糙。此次使用它主要也是爲了本身能學習一下吧,畢竟拿了公司的錢,hee。linux

若是使用Python等其餘」高級「的語言,會更快的實現,事實上幾乎全部的WEB框架都會自帶一個(固然都比TinyWS強大的多)。但若是使用這些語言,恐怕也很難真正的」從頭開始「。git

目前,代碼已經託管在 https://git.oschina.net/augustus/TinyWS.gitubuntu

能夠用git clone下來。因爲我可能會偶爾作一些修改,不能保證git 庫上的代碼與blog裏的徹底一致(實際上也不可能把全部的代碼都貼在這裏)。另外,TinyWS是基於linux寫的(ubuntu 14.10 + eclipse luna,eclipse工程我也push到了git庫),故在Windows上可能沒法正常編譯主要是系統調用 部分可能會不一樣)。瀏覽器

原理

WEB的原理很簡單,你們都懂,我就簡單寫幾句,不然直接貼代碼可能比較突兀。服務器

WEB實際上也是一個客戶端/服務器的程序,而它們之間基本使用HTTP/HTTPS/FTP等協議通訊。協議不過是數據傳輸的一種方式,而對於傳輸的內容來講,WEB基本是html文檔,固然也能夠傳其餘的任何文件,不過做爲一個玩具,TinyWS只支持HTTP協議。網絡

WEB的客戶端就是瀏覽器,實質是一個html的解釋器,而咱們要作的,就是提供一個服務器,讓瀏覽器能夠訪問到HTML文檔。瀏覽器是經過uri來訪問服務器端的資源,好比一個保存在服務器上的index.html文檔,在瀏覽器端,可使用http://serverip:port/index.html 這樣的方式就能夠取回這個文檔並解析。咱們要解決的問題其實就是瀏覽器發出這個請求以後,給予正確的迴應。框架

咱們知道主機之間的網絡通訊實際上最終都是經過socket傳數據。而socket的本質是操做系統內核實現一個映射,使得用戶程序使用網絡就像使用本地文件同樣。即便用socket打開一個端口後,會返回一個文件描述符,以後全部的操做都和讀寫一個本地文件徹底相同了。瞭解了這個,實際上咱們就已經解決了一半的問題。eclipse

另外一半的問題就是咱們如何實現HTTP協議。好在HTTP是一個比較簡單的協議,其核心是一個」請求與應答「的過程,」請求「是一些稱爲」方法「的操做過程,實際上就是告訴服務器,要請求服務器返回某資源(uri)或者對資源進行某些操做。經常使用的方法就是GET和POST,目前TinyWS只實現了GET方法,其餘的方法可能後面也會作一下吧。socket

對於socket和HTTP,有許多專題能夠查,這裏就不羅嗦了。

RequestManager

TinyWS核心的業務實際就是接收HTTP請求,並給予正確的應答,因此這裏先從上層業務講起吧。TinyWS運行以後,首先會打開socket並監聽某端口,以後就會運行RequestManager的run方法,不斷的等待HTTP請求到來。請求到來以後,會解析內容,分析出客戶端的請求方法和uri,從而交給相關的」方法「去處理。

// RequestManager.h
class RequestManager
{
public:
    RequestManager(int connfd);
    void run();

private:
    Request* getRequestHandle();

private:
    int fileDescriptor;

    Request* request;
};

其中Request 就是具體方法的基類,其子類能夠是GET,POST等等。

// RequestManager.cpp
namespace
{
class Parser
{
public:
    Parser(int connfd)
    {
        parseRequestHeaders(connfd);
    }

    const std::string getMethodName()
    {
        return method;
    }

    const std::string getUri()
    {
        return uri;
    }

private:
    void parseRequestHeaders(int fd)
    {
        IoReader reader(fd);
        std::vector<std::string> header;
        reader.getLineSplitedByBlank(header);

        method = header[0];
        uri = header[1];
        version = header[2];
    }

private:
    std::string method;
    std::string uri;
    std::string version;
};
}

RequestManager::RequestManager(int connfd) : fileDescriptor(connfd), request(0)
{
}

void RequestManager::run()
{
    if(getRequestHandle())
        request->execute();
}

Request* RequestManager::getRequestHandle()
{
    Parser parser(fileDescriptor);
    return request = RequestCreater::getRequestHandler(parser.getMethodName(), fileDescriptor, parser.getUri());
}

在CPP文件中,首先要解析客戶端的請求數據,分析出method,uri,version(協議版本,這裏實際上並無用到)。這個工做有Parser類完成,因爲只有這一處使用,封在了匿名namespace中。解析中使用了IoReader類,它負責從socket讀入數據,封裝了底層的IO操做,這個後面再說。

回到正題。RequestManager的實現中,其實使用了一個工廠類( RequestCreater),根據解析出的method,創造不一樣的方法實例,這裏雖然只支持GET,但仍然使用了工廠,是考慮到後面還會實現POST等其餘方法,應該也不算過分設計吧,hee。

// Request.h
class
Request { public: void init(int fd, std::string uri); void execute(); virtual ~Request() { } protected: int getFileDescriptor() const; const std::string& getUri() const; private: virtual void doExecute() = 0; private: int fileDescriptor; std::string uri; };

 

Request是一個抽象類,每個子類都須要實現doExecute方法才能實例化。這裏也使用了一個簡單的」模板方法「,讓整個繼承體系對外接口統一。

// Request.cpp
void Request::init(int fd, std::string uri)
{
     this->fileDescriptor = fd;
     this->uri = uri;
}

void Request::execute()
{
    doExecute();
}

int Request::getFileDescriptor() const
{
    return fileDescriptor;
}

const std::string& Request::getUri() const
{
    return uri;
}

真正幹活的是Request的子類GetRequest。不過不早了,今天先到這裏,下次再說吧。

相關文章
相關標籤/搜索