從零實(shí)現(xiàn)HTTP服務(wù)器——Minihttpd(二)

上一篇中我們實(shí)現(xiàn)了接受瀏覽器的請求,并返回本地的網(wǎng)頁給瀏覽器展示,接下來對該簡單的功能進(jìn)行下一步完善

Content-Type

http響應(yīng)頭中非常重要的一個字段是Content-Type,它決定了瀏覽器如何解析返回的響應(yīng)內(nèi)容,如果該字段缺失則默認(rèn)為text/html格式,因此我們上文返回的簡單html網(wǎng)頁并沒有添加該字段,瀏覽器也能正常解析。
但是稍微復(fù)雜的前端頁面都包含了css樣式文件,JavaScript腳本文件等,如果不指定Content-Type字段,則瀏覽器無法正確解析這些文件(有些瀏覽器超強(qiáng)的兼容性可以一定程度上自動判斷內(nèi)容格式)
因此我們在返回本地的文件作為響應(yīng)時,需要手動設(shè)置Content-Type字段,判斷依據(jù)為文件的擴(kuò)展名,這里使用了一個map維護(hù)擴(kuò)展名與Content-Type映射關(guān)系。

void HttpResponse::init_content_type_map(){
    content_type_map.insert(pair<string,string>("html","text/html"));
    content_type_map.insert(pair<string,string>("htm","text/html"));
    content_type_map.insert(pair<string,string>("shtml","text/html"));
    content_type_map.insert(pair<string,string>("css","text/css"));
    content_type_map.insert(pair<string,string>("js","text/javascript"));
    content_type_map.insert(pair<string,string>("txt","text/plain"));
    content_type_map.insert(pair<string,string>("js","text/javascript"));
    content_type_map.insert(pair<string,string>("xml","text/xml"));

    content_type_map.insert(pair<string,string>("ico","image/x-icon"));
    content_type_map.insert(pair<string,string>("jpg","image/jpeg"));
    content_type_map.insert(pair<string,string>("jpeg","image/jpeg"));
    content_type_map.insert(pair<string,string>("jpe","image/jpeg"));
    content_type_map.insert(pair<string,string>("gif","image/gif"));
    content_type_map.insert(pair<string,string>("png","image/png"));
    content_type_map.insert(pair<string,string>("tiff","image/tiff"));
    content_type_map.insert(pair<string,string>("tif","image/tiff"));
    content_type_map.insert(pair<string,string>("rgb","image/x-rgb"));

    content_type_map.insert(pair<string,string>("mpeg","video/mpeg"));
    content_type_map.insert(pair<string,string>("mpg","video/mpeg"));
    content_type_map.insert(pair<string,string>("mpe","video/mpeg"));
    content_type_map.insert(pair<string,string>("qt","video/quicktime"));
    content_type_map.insert(pair<string,string>("mov","video/quicktime"));
    content_type_map.insert(pair<string,string>("avi","video/x-msvideo"));
    content_type_map.insert(pair<string,string>("movie","video/x-sgi-movie"));

    content_type_map.insert(pair<string,string>("woff","application/font-woff"));
    content_type_map.insert(pair<string,string>("ttf","application/octet-stream"));
}

這里添加了一些常用格式的Content-Type類型,后續(xù)涉及到更復(fù)雜的文件類型時對其進(jìn)一步擴(kuò)展

gzip壓縮

在http響應(yīng)的結(jié)構(gòu)中,我們常??梢钥吹揭粋€名為Content-Encoding的字段,其值大多為gzip,deflate等。該字段決定的是http響應(yīng)體的編碼格式。目前主流瀏覽器均支持gzip等格式的壓縮格式。使用壓縮格式的最大好處就是減少網(wǎng)絡(luò)傳輸?shù)男畔⒘?,提高網(wǎng)頁加載速度,但由于服務(wù)端多了壓縮的步驟,也一定程度增加了服務(wù)器的負(fù)擔(dān)(客戶端單次處理時,解壓的影響可以忽略不計(jì))。
而gzip格式又是應(yīng)用最廣泛的一種壓縮格式,其對文本內(nèi)容的壓縮率常??梢赃_(dá)到40%以上,對于html,css,javascript文件均有著非常好的壓縮效果。

本文實(shí)現(xiàn)的Minihttpd為了增加gzip格式的壓縮功能使用了zlib庫,其代碼均為c編寫,使用方法相對簡單,這里列出部分供參考

//raw_data為原始數(shù)據(jù),buffer為壓縮后數(shù)據(jù)存儲緩沖區(qū),buffer_size為緩沖區(qū)大小,返回值為壓縮后數(shù)據(jù)字節(jié)數(shù)
uLong gzip_compress(string raw_data,Bytef*& buffer,int buffer_size){
    size_t raw_data_size = raw_data.size();
    z_stream strm;
    z_stream d_stream;
    d_stream.zalloc = NULL;
    d_stream.zfree = NULL;
    d_stream.opaque = NULL;
    d_stream.next_in = (Bytef*)raw_data.c_str();
    d_stream.avail_in = raw_data_size;
    d_stream.next_out = buffer;
    d_stream.avail_out = buffer_size;

    int ret = deflateInit2(&d_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
                        MAX_WBITS + 16, 8, Z_DEFAULT_STRATEGY);
    if (Z_OK != ret)
    {
        Log::log("init deflate error",ERROR);
        // cout<< ret <<endl;
    }

    int err = 0;
    int flag = 0;
    for(;;) {
        if((err = deflate(&d_stream, Z_FINISH)) == Z_STREAM_END) break;
        if(flag > 3){
            stringstream ss;
            ss<< "deflate failed,errNo = "<<err;
            Log::log(ss.str(),ERROR);
            return 0;
        }

        //輸出緩沖區(qū)不足,嘗試擴(kuò)容,最多三次擴(kuò)容失敗則放棄壓縮
        if(err == Z_BUF_ERROR){
            flag++;
            delete buffer;
            buffer_size = buffer_size*1.5;
            buffer = new Bytef[buffer_size];
            d_stream.next_out = buffer;
            d_stream.avail_out = buffer_size;
            stringstream ss;
            ss<< "deflate buffer error,try larger buffer :"<<flag;
            Log::log(ss.str(),WARN);
        }
    }
    if(deflateEnd(&d_stream) != Z_OK){
        Log::log("deflate failed when end",ERROR);
        return 0;
    }
    return d_stream.total_out;
}

主要工作流程為:

  1. deflateInit2() 設(shè)置壓縮格式等信息
  2. deflate() 進(jìn)行壓縮
  3. deflateEnd() 壓縮完畢釋放臨時空間等收尾工作

編碼格式判別

接收到http請求后,首先判斷請求頭中是否包含Accept-Encoding字段,如果存在,檢查其后面接受的壓縮格式等,決定是否使用gzip壓縮(注意響應(yīng)頭也需要添加Content-Encoding:gzip字段)

Github

https://github.com/njuwuyuxin/MiniHttpd
歡迎共同學(xué)習(xí)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容