1. 程式人生 > >從零實現一個http伺服器

從零實現一個http伺服器

我始終覺得,天生的出身很重要,但後天的努力更加重要,所以如今的很多“科班”往往不如後天努力的“非科班”。所以,我們需要重新給“專業”和“專家”下一個定義:所謂專業,就是別人搞你不搞,這就是你的“專業”;你和別人同時搞,你比別人搞的好,就是“專家”。

說到http協議和http請求,很多人都知道,但是他們真的“知道”嗎?我面試過很多求職者,一說到http協議,他們能滔滔不絕,然後我問他http協議的具體格式是啥樣子的?很多人不清楚,不清楚就不清楚吧,他甚至能將http協議的頭扯到html文件頭部<head>。當我問http GET和POST請求的時候,GET請求是什麼形式一般人都可以答出來,但是POST請求的資料放在哪裡,伺服器如何識別和解析這些POST資料,很多人又說不清道不明瞭。當說到http伺服器時,很多人離開了apache、Nginx這樣現成的http server之外,自己實現一個http伺服器無從下手,如果實際應用場景有需要使用到一些簡單http請求時,使用apache、Nginx這樣重量級的http伺服器程式實在勞師動眾,你可以嘗試自己實現一個簡單的。

上面提到的問題,如果您不能清晰地回答出來,可以閱讀一下這篇文章,這篇文章在不僅介紹http的格式,同時帶領大家從零實現一個簡單的http伺服器程式。

一、專案背景


二、http協議介紹

1. http協議是應用層協議,一般建立在tcp協議的基礎之上(當然你的實現非要基於udp也是可以的),也就是說http協議的資料收發是通過tcp協議的。

2. http協議也分為head和body兩部分,但是我們一般說的html中的<head>和<body>標記不是http協議的頭和身體,它們都是http協議的body部分。


那麼http協議的頭到底長啥樣子呢?我們來介紹一下http協議吧。

http協議的格式如下:

GET或POST 請求的url路徑(一般是去掉域名的路徑) HTTP協議版本號
欄位1名: 欄位1值\r\n
欄位2名: 欄位2值\r\n
      ...
欄位n名 : 欄位n值\r\n
\r\n
http協議包體內容

也就是說http協議由兩部分組成:包頭和包體,包頭與包體之間使用一個\r\n分割,由於http協議包頭的每一行都是以\r\n結束,所以http協議包頭一般以\r\n\r\n結束。

舉個例子,比如我們在瀏覽器中請求http://www.hootina.org/index_2013.php這個網址,這是一個典型的GET方法,瀏覽器組裝的http資料包格式如下: 

GET /index_2013.php HTTP/1.1\r\n
Host: www.hootina.org\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
\r\n

上面這個請求只有包頭沒有包體,http協議的包體不是必須的,也就是說GET請求一般沒有包體。

如果GET請求帶引數,那麼一般是附加在請求的url後面,引數與引數之間使用&分割,例如請求http://www.hootina.org/index_2013.php?param1=value1&param2=value2&param3=value3,我們看下這個請求組裝的的http協議包格式:

GET /index_2013.php?param1=value1&param2=value2&param3=value3 HTTP/1.1\r\n
Host: www.hootina.org\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
\r\n

對比一下,你現在知道http協議的GET引數放在協議包的什麼位置了吧。

那麼POST的資料放在什麼位置呢?我們再12306網站(https://kyfw.12306.cn/otn/login/init)中登陸輸入使用者名稱和密碼:


然後發現瀏覽器以POST方式組裝了http協議包傳送了我們的使用者名稱、密碼和其他一些資訊,組裝的包格式如下:

POST /passport/web/login HTTP/1.1\r\n
Host: kyfw.12306.cn\r\n
Connection: keep-alive\r\n
Content-Length: 55\r\n
Accept: application/json, text/javascript, */*; q=0.01\r\n
Origin: https://kyfw.12306.cn\r\n
X-Requested-With: XMLHttpRequest\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n
Referer: https://kyfw.12306.cn/otn/login/init\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
Cookie: _passport_session=0b2cc5b86eb74bcc976bfa9dfef3e8a20712; _passport_ct=18d19b0930954d76b8057c732ce4cdcat8137; route=6f50b51faa11b987e576cdb301e545c4; RAIL_EXPIRATION=1526718782244; RAIL_DEVICEID=QuRAhOyIWv9lwWEhkq03x5Yl_livKZxx7gW6_-52oTZQda1c4zmVWxdw5Zk79xSDFHe9LJ57F8luYOFp_yahxDXQAOmEV8U1VgXavacuM2UPCFy3knfn42yTsJM3EYOy-hwpsP-jTb2OXevJj5acf40XsvsPDcM7; BIGipServerpool_passport=300745226.50215.0000; BIGipServerotn=1257243146.38945.0000; BIGipServerpassport=1005060362.50215.0000\r\n
\r\n
username=balloonwj%40qq.com&password=iloveyou&appid=otn

其中username=balloonwj%40qq.com&password=iloveyou&appid=otn就是我們的POST資料,但是大家需要注意的以下幾種,不要搞錯:

1. 我的使用者名稱是[email protected],到POST裡面變成balloonwj%40qq.com,其中%40是@符號的16進位制轉碼形式。這個碼錶可以參考這裡:http://www.w3school.com.cn/tags/html_ref_urlencode.html

2.這裡有三個變數,分別是username、password和appid,他們之間使用&符號分割,但是請注意的是,這不意味著傳遞多個POST變數時必須使用&符號分割,只不過這裡是瀏覽器html表單(輸入使用者名稱和密碼的文字框是html表單的一種)分割多個變數採用的預設方式而已。你可以根據你的需求,來自由定製,只要讓伺服器知道你的解析方式即可。比如可以這麼分割:

方法一:
username=balloonwj%40qq.com|password=iloveyou|appid=otn

方法二:
username:balloonwj%40qq.com\r\n
password:iloveyou\r\n
appid:otn\r\n

方法三
username,password,appid=balloonwj%40qq.com,iloveyou,otn

不管怎麼分割,只要你能自己按一定的規則解析出來就可以了。

不知道你注意到沒有,上面的POST資料放在http包體中,伺服器如何解析呢?可能你沒明白我的意思,看下圖:


如上圖所示,由於http協議是基於tcp協議的,tcp協議是流式協議,包頭部分可以通過多出的\r\n來分界,包體部分如何分界呢?這是協議本身要解決的問題。目前一般有兩種方式,第一種方式就是在包頭中有個content-Length欄位,這個欄位的值的大小標識了POST資料的長度,上圖中55就是資料username=balloonwj%40qq.com&password=iloveyou&appid=otn的長度,伺服器收到一個數據包後,先從包頭解析出這個欄位的值,再根據這個值去讀取相應長度的作為http協議的包體資料。還有一個格式叫做http chunked技術(分塊),大致意思是將大包分成小包,具體的詳情有興趣的讀者可以自行搜尋學習。

三、http客戶端實現

如果您能掌握以上說的http協議,你就可以自己通過程式碼組裝http協議傳送http請求了(也是各種開源http庫的做法)。我們先簡單地介紹一下如何模擬傳送http。舉個例子,我們要請求http://www.hootina.org/index_2013.php,那麼我們可以先通過域名得到ip地址,即通過socket API gethostbyname()得到www.hootina.org的ip地址,由於http伺服器預設的埠號是80,有了域名和ip地址之後,我們使用socket API connect()去連線伺服器,然後根據上面介紹的格式組裝成http協議包,利用socket API send()函式發出去,如果伺服器有應答,我們可以使用socket API recv()去接受資料,接下來就是解析資料(先解析包頭和包體)。

四、http伺服器實現

我們這裡簡化一些問題,假設客戶端傳送的請求都是GET請求,當客戶端發來http請求之後,我們拿到http包後就做相應的處理。我們以為我們的flamingo伺服器實現一個支援http格式的註冊請求為例。假設使用者在瀏覽器裡面輸入以下網址,就可以實現一個註冊功能:

http://120.55.94.78:12345/register.do?p={"username": "13917043329", "nickname": "balloon", "password": "123"}

這裡我們的http協議使用的是12345埠號而不是預設的80埠。如何偵聽12345埠,這個是非常基礎的知識了,這裡就不介紹了。當我們收到資料以後:

void HttpSession::OnRead(const std::shared_ptr<TcpConnection>& conn, Buffer* pBuffer, Timestamp receivTime)
{
    //LOG_INFO << "Recv a http request from " << conn->peerAddress().toIpPort();
    
    string inbuf;
    //先把所有資料都取出來
    inbuf.append(pBuffer->peek(), pBuffer->readableBytes());
    //因為一個http包頭的資料至少\r\n\r\n,所以大於4個字元
    //小於等於4個字元,說明資料未收完,退出,等待網路底層接著收取
    if (inbuf.length() <= 4)
        return;

    //我們收到的GET請求資料包一般格式如下:
    /*
    GET /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22} HTTP/1.1\r\n
    Host: 120.55.94.78:12345\r\n
    Connection: keep-alive\r\n
    Upgrade-Insecure-Requests: 1\r\n
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
    Accept-Encoding: gzip, deflate\r\n
    Accept-Language: zh-CN, zh; q=0.9, en; q=0.8\r\n
    \r\n
     */
    //檢查是否以\r\n\r\n結束,如果不是說明包頭不完整,退出
    string end = inbuf.substr(inbuf.length() - 4);
    if (end != "\r\n\r\n")
        return;

    //以\r\n分割每一行
    std::vector<string> lines;
    StringUtil::Split(inbuf, lines, "\r\n");
    if (lines.size() < 1 || lines[0].empty())
    {
        conn->forceClose();
        return;
    }

    std::vector<string> chunk;
    StringUtil::Split(lines[0], chunk, " ");
    //chunk中至少有三個字串:GET+url+HTTP版本號
    if (chunk.size() < 3)
    {
        conn->forceClose();
        return;
    }

    LOG_INFO << "url: " << chunk[1] << " from " << conn->peerAddress().toIpPort();
    //inbuf = /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}
    std::vector<string> part;
    //通過?分割成前後兩端,前面是url,後面是引數
    StringUtil::Split(chunk[1], part, "?");
    //chunk中至少有三個字串:GET+url+HTTP版本號
    if (part.size() < 2)
    {
        conn->forceClose();
        return;
    }

    string url = part[0];
    string param = part[1].substr(2);
        
    if (!Process(conn, url, param))
    {
        LOG_ERROR << "handle http request error, from:" << conn->peerAddress().toIpPort() << ", request: " << pBuffer->retrieveAllAsString();
    }

    //短連線,處理完關閉連線
    conn->forceClose();
}

程式碼註釋都寫的很清楚,我們先利用\r\n分割得到每一行,其中第一行的資料是:

GET /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22} HTTP/1.1

其中%22是雙引號的url轉碼形式,%20是空格的url轉碼形式,然後我們根據空格分成三段,其中第二段就是我們的網址和引數:

/register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}

然後我們根據網址與引數之間的問號將這個分成兩段:第一段是網址,第二段是引數:

bool HttpSession::Process(const std::shared_ptr<TcpConnection>& conn, const std::string& url, const std::string& param)
{
    if (url.empty())
        return false;

    if (url == "/register.do")
    {
        OnRegisterResponse(param, conn);
    }
    else if (url == "/login.do")
    {
        OnLoginResponse(param, conn);
    }
    else if (url == "/getfriendlist.do")
    {

    }
    else if (url == "/getgroupmembers.do")
    {

    }
    else
        return false;

    
    return true;
}

然後我們根據url匹配網址,如果是註冊請求,會走註冊處理邏輯:

void HttpSession::OnRegisterResponse(const std::string& data, const std::shared_ptr<TcpConnection>& conn)
{
    string retData;
    string decodeData;
    URLEncodeUtil::Decode(data, decodeData);
    BussinessLogic::RegisterUser(decodeData, conn, false, retData);
    if (!retData.empty())
    {
        std::string response;
        URLEncodeUtil::Encode(retData, response);
        MakeupResponse(retData, response);
        conn->send(response);

        LOG_INFO << "Response to client: cmd=msg_type_register" << ", data=" << retData << conn->peerAddress().toIpPort();;
    }
}

註冊結果放在retData中,為了發給客戶端,我們將結果中的特殊字元如雙引號轉碼,如返回結果是:

{"code":0, "msg":"ok"}

會被轉碼成:

{%22code%22:0,%20%22msg%22:%22ok%22}

然後,將資料組裝成http協議發給客戶端,給客戶端的應答協議與http請求協議有一點點差別,就是將請求的url路徑換成所謂的http響應碼,如200表示應答正常返回、404頁面不存在。應答協議格式如下:

GET或POST 響應碼 HTTP協議版本號
欄位1名: 欄位1值\r\n
欄位2名: 欄位2值\r\n
      ...
欄位n名 : 欄位n值\r\n
\r\n
http協議包體內容

舉個例子如:

HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Content-Length:42\r\n
\r\n
{%22code%22:%200,%20%22msg%22:%20%22ok%22}

注意,包頭中的Content-Length長度必須正好是包體{%22code%22:%200,%20%22msg%22:%20%22ok%22}的長度,這裡是42。這也符合我們瀏覽器的返回結果:


當然,需要注意的是,我們一般說http連線一般是短連線,這裡我們也實現了這個功能(看上面的程式碼:conn->forceClose();),不管一個http請求是否成功,伺服器處理後立馬就關閉連線。

當然,這裡還有一些沒處理好的地方,如果你仔細觀察上面的程式碼就會發現這個問題,就是不滿足一個http包頭時的處理,如果某個客戶端(不是使用瀏覽器)通過程式模擬了一個連線請求,但是遲遲不發含有\r\n\r\n的資料,這路連線將會一直佔用。我們可以判斷收到的資料長度,防止別有用心的客戶端給我們的伺服器亂髮資料。我們假定,我們能處理的最大url長度是2048,如果使用者傳送的資料累積不含\r\n\r\n,且超過2048個,我們認為連線非法,將連線斷開。程式碼修改成如下形式:

void HttpSession::OnRead(const std::shared_ptr<TcpConnection>& conn, Buffer* pBuffer, Timestamp receivTime)
{
    //LOG_INFO << "Recv a http request from " << conn->peerAddress().toIpPort();
    
    string inbuf;
    //先把所有資料都取出來
    inbuf.append(pBuffer->peek(), pBuffer->readableBytes());
    //因為一個http包頭的資料至少\r\n\r\n,所以大於4個字元
    //小於等於4個字元,說明資料未收完,退出,等待網路底層接著收取
    if (inbuf.length() <= 4)
        return;

    //我們收到的GET請求資料包一般格式如下:
    /*
    GET /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22} HTTP/1.1\r\n
    Host: 120.55.94.78:12345\r\n
    Connection: keep-alive\r\n
    Upgrade-Insecure-Requests: 1\r\n
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
    Accept-Encoding: gzip, deflate\r\n
    Accept-Language: zh-CN, zh; q=0.9, en; q=0.8\r\n
    \r\n
     */
    //檢查是否以\r\n\r\n結束,如果不是說明包頭不完整,退出
    string end = inbuf.substr(inbuf.length() - 4);
    if (end != "\r\n\r\n")
        return;
    //超過2048個字元,且不含\r\n\r\n,我們認為是非法請求
    else if (inbuf.length() >= MAX_URL_LENGTH)
    {
        conn->forceClose();
        return;
    }

    //以\r\n分割每一行
    std::vector<string> lines;
    StringUtil::Split(inbuf, lines, "\r\n");
    if (lines.size() < 1 || lines[0].empty())
    {
        conn->forceClose();
        return;
    }

    std::vector<string> chunk;
    StringUtil::Split(lines[0], chunk, " ");
    //chunk中至少有三個字串:GET+url+HTTP版本號
    if (chunk.size() < 3)
    {
        conn->forceClose();
        return;
    }

    LOG_INFO << "url: " << chunk[1] << " from " << conn->peerAddress().toIpPort();
    //inbuf = /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}
    std::vector<string> part;
    //通過?分割成前後兩端,前面是url,後面是引數
    StringUtil::Split(chunk[1], part, "?");
    //chunk中至少有三個字串:GET+url+HTTP版本號
    if (part.size() < 2)
    {
        conn->forceClose();
        return;
    }

    string url = part[0];
    string param = part[1].substr(2);
        
    if (!Process(conn, url, param))
    {
        LOG_ERROR << "handle http request error, from:" << conn->peerAddress().toIpPort() << ", request: " << pBuffer->retrieveAllAsString();
    }

    //短連線,處理完關閉連線
    conn->forceClose();
}

但這隻能解決傳送非法資料的情況,如果一個客戶端連上來不給我們發任何資料,這段邏輯就無能為力了。如果不斷有客戶端這麼做,會浪費我們大量的連線資源,所以我們還需要一個定時器去定時檢測哪些http連線超過一定時間內沒給我們發資料,找到後將連線斷開。這又涉及到伺服器定時器如何設計了,關於這部分請參考我寫的其他文章。

限於作者經驗水平有限,文中難免有錯亂之處,歡迎拍磚。另外,關於上面的程式碼,可以去github上下載,地址是:

全文完。

歡迎關注公眾號『easyserverdev』。如果有任何技術或者職業方面的問題需要我提供幫助,可通過這個公眾號與我取得聯絡,此公眾號不僅分享高效能伺服器開發經驗和故事,同時也免費為廣大技術朋友提供技術答疑和職業解惑,您有任何問題都可以在微信公眾號直接留言,我會盡快回復您。



相關推薦

實現一個http伺服器

我始終覺得,天生的出身很重要,但後天的努力更加重要,所以如今的很多“科班”往往不如後天努力的“非科班”。所以,我們需要重新給“專業”和“專家”下一個定義:所謂專業,就是別人搞你不搞,這就是你的“專業”;你和別人同時搞,你比別人搞的好,就是“專家”。說到http協議和http請

實現一個http服務器

retrieve vba ilove ext TP 應用場景 註釋 end HA 我始終覺得,天生的出身很重要,但後天的努力更加重要,所以如今的很多“科班”往往不如後天努力的“非科班”。所以,我們需要重新給“專業”和“專家”下一個定義:所謂專業,就是別人搞你不搞,這就是你的

開始一個http伺服器(六)-多路複用和壓力測試

從零開始一個http伺服器(六)-多路複用和壓力測試 程式碼地址 : https://github.com/flamedancer/cserver git checkout step6 執行: make clean && make && ./myserver.out 測試 瀏

開始一個http服務器-請求request解析(二)

tor pen ica nice 測試 nec 代碼 acc print 從零開始一個http服務器 (二) 代碼地址 : https://github.com/flamedancer/cserver git checkout step2 解析http request 觀

開始一個http服務器-模擬cgi(五)

pclose github 網頁瀏覽 erro malloc 地址 pen bash doc 從零開始一個http服務器-模擬cgi(五) 代碼地址 : https://github.com/flamedancer/cserver git checkout step5 運行

實現一個HTTP伺服器的Demo

在學完linux高階程式設計後,開啟一個HTTP伺服器的demo,因為一個完整的http伺服器涉及的範圍很廣,遠不是一個人可以全部完成的。所以這個demo只是實現了http伺服器的一個簡單的功能->GET請求。包括請求html檔案和圖片(jpg &png)。下面是實現的一些

實現一個自定義html5播放器

寫在最前 本次的分享是一個基於HTML5<vedio>標籤實現的一個自定義視訊播放器。其中實現了播放暫停、進度拖拽、音量控制及全屏等功能。 歡迎關注我的部落格,不定期更新中—— 效果預覽 點我檢視原始碼倉庫。 核心思路 我相信一定會有些沒有接觸過製作自定義播放器的童鞋對於<vedio&

實現一個React:Luster(一):JSX解析器

前言 這是之前在掘金髮的兩條沸點,懶得寫了,直接複製過來作為前言了。然後這個專案可能之後還會繼續寫,增加一些路由或者模板引擎的指令什麼的,但是再過沒多久寒假就有大塊時間了就可能不摸這個魚去開其它坑了,隨緣吧。所以先寫JSX的解析器吧,這個部分也比較獨立 掘金沸點裡有一些程式碼截圖,就不發在markdown裡

實現jQuery的extend

log 如何 asc 基本類型 是否 query 解決 復制 上進 前言 jQuery 的 extend 是 jQuery 中應用非常多的一個函數,今天我們一邊看 jQuery 的 extend 的特性,一邊實現一個 extend! extend 基本用法 先來看看 ext

開始學HTTP (二) HTTP結構與基礎

現象 encode 伸縮 協議 for 服務端 例如 lis 格式 HTTP結構與基礎 這篇文章中,我們主要針對HTTP\1.1版本進行介紹 請求報文和響應報文 請求報文 請求報文由客戶端發出,其格式為: 請求方法 請求URI 協議版本 可選的請求首部字段和內容實體,

實現Lumen-JWT擴展包(序):前因

height targe ctu 就是 internal 結果 lazy 黑名單 ace 轉自:https://zhuanlan.zhihu.com/p/22531819?refer=lsxiao 最近這段時間我尋思著把幾個月前爬下來的6萬多首詩詞曲文做成一個API,免費

Rails: 到部署至伺服器

which gem 在這篇文章裡,我們將學到 1. 怎麼用Rails來生成我們的第一個app。 2. 學會怎麼使用Git進行版本控制。 3.怎麼將程式部署至Heroku,Rails服務提供商。 Ruby on Rails是一個最流行、最強大的構建動態網站的一個框架。有

React開始——一個詳細的範例

範例說明 接下來我們要通過一個簡單的案例,詳細的學習React的內容 如上圖所示,兩個按鈕,點選加號按鈕,數字加一,點選減號按鈕,數字減一 程式碼結構 使用create-react-app建立一個工程,將其中的程式碼結構刪減到最簡單的模式 修改

實現 Spring Boot 2.0 整合 weixin-java-mp(weixin-java-tools) 獲取 openId,用於微信授權

步驟: 一、內網穿透申請二級域名(有伺服器和域名者可略過) 二、申請微信公眾平臺測試號(有已認證的微信服務號者可略過) 三、搭建 Spring Boot 2.0 專案實現獲取openId 一、內網穿透: 因為要直接用內網本機開發除錯,微信網頁授權在回撥時要訪問本機,所以直接

打造一個CMDB(一)資料庫設計

俠義的CMDB都是偏向純資產管理,但運維繫統往往圍繞著這些資產中心,從資產進行不斷外充擴容 在其基礎之外擴展出各功能,通過cmdb 擴展出各個子系統  涉及工具:workbench 一個例子:設計一個數據庫實現主機資訊、交換機資訊,如何將之間的資訊關聯起來 初步的傳統設計:

啟動一個JAVA SSM專案詳細步驟目錄

詳解大型網際網路專案服務端架構演進歷程   JAVA環境配置 1、Linux軟體源配置操作 2、Linux系統 jdk安裝實操 3、Linux系統tomcat安裝 4、Linux下Maven專案構建與管理工具 5、Linux下vsftpd檔案伺服器安裝與配置

一個Java WEB框架(七)Controller層轉換器

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

一個Java WEB框架(六)Controller層優化

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

一個Java WEB框架(五)IOC建立

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

一個Java WEB框架(四)框架的演進

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github