Linux企業級專案實踐之網路爬蟲(12)——處理HTTP應答頭

尹成發表於2014-08-31
Web伺服器的HTTP應答一般由以下幾項構成:一個狀態行,一個或多個應答頭,一個空行,內容文件。設定HTTP應答頭往往和設定狀態行中的狀態程式碼結合起來。例如,有好幾個表示“文件位置已經改變”的狀態程式碼都伴隨著一個Location頭,而401(Unauthorized)狀態程式碼則必須伴隨一個WWW-Authenticate頭。
然而,即使在沒有設定特殊含義的狀態程式碼時,指定應答頭也是很有用的。應答頭可以用來完成:設定Cookie,指定修改日期,指示瀏覽器按照指定的間隔重新整理頁面,宣告文件的長度以便利用持久HTTP連線,……等等許多其他任務。
設定應答頭最常用的方法是HttpServletResponse的setHeader,該方法有兩個引數,分別表示應答頭的名字和值。和設定狀態程式碼相似,設定應答頭應該在傳送任何文件內容之前進行。

HTTP應答頭 說明:
Allow 伺服器支援哪些請求方法(如GET、POST等)。
Content-Encoding 文件的編碼(Encode)方法。只有在解碼之後才可以得到Content-Type頭指定的內容型別。利用gzip壓縮文件能夠顯著地減少HTML文件的下載時間。Java的GZIPOutputStream可以很方便地進行gzip壓縮,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支援它。因此,Servlet應該通過檢視Accept-Encoding頭(即request.getHeader("Accept-Encoding"))檢查瀏覽器是否支援gzip,為支援gzip的瀏覽器返回經gzip壓縮的HTML頁面,為其他瀏覽器返回普通頁面。
Content-Length 表示內容長度。只有當瀏覽器使用持久HTTP連線時才需要這個資料。如果你想要利用持久連線的優勢,可以把輸出文件寫入ByteArrayOutputStram,完成後檢視其大小,然後把該值放入Content-Length頭,最後通過byteArrayStream.writeTo(response.getOutputStream()傳送內容。
Content-Type 表示後面的文件屬於什麼MIME型別。Servlet預設為text/plain,但通常需要顯式地指定為text/html。由於經常要設定Content-Type,因此HttpServletResponse提供了一個專用的方法setContentType。
Date 當前的GMT時間。你可以用setDateHeader來設定這個頭以避免轉換時間格式的麻煩。
Expires 應該在什麼時候認為文件已經過期,從而不再快取它?



void * recv_response(void * arg)
{
    begin_thread();


    int i, n, trunc_head = 0, len = 0;
    char * body_ptr = NULL;
    evso_arg * narg = (evso_arg *)arg;
    Response *resp = (Response *)malloc(sizeof(Response));
    resp->header = NULL;
    resp->body = (char *)malloc(HTML_MAXLEN);
    resp->body_len = 0;
    resp->url = narg->url;


    regex_t re;
    if (regcomp(&re, HREF_PATTERN, 0) != 0) {/* compile error */
        SPIDER_LOG(SPIDER_LEVEL_ERROR, "compile regex error");
    }


    SPIDER_LOG(SPIDER_LEVEL_INFO, "Crawling url: %s/%s", narg->url->domain, narg->url->path);


    while(1) {
        /* what if content-length exceeds HTML_MAXLEN? */
        n = read(narg->fd, resp->body + len, 1024);
        if (n < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { 
                /**
                 * TODO: Why always recv EAGAIN?
                 * should we deal EINTR
                 */
                //SPIDER_LOG(SPIDER_LEVEL_WARN, "thread %lu meet EAGAIN or EWOULDBLOCK, sleep", pthread_self());
                usleep(100000);
                continue;
            } 
            SPIDER_LOG(SPIDER_LEVEL_WARN, "Read socket fail: %s", strerror(errno));
            break;


        } else if (n == 0) {
            /* finish reading */
            resp->body_len = len;
            if (resp->body_len > 0) {
                extract_url(&re, resp->body, narg->url);
            }
            /* deal resp->body */
            for (i = 0; i < (int)modules_post_html.size(); i++) {
                modules_post_html[i]->handle(resp);
            }


            break;


        } else {
            //SPIDER_LOG(SPIDER_LEVEL_WARN, "read socket ok! len=%d", n);
            len += n;
            resp->body[len] = '\0';


            if (!trunc_head) {
                if ((body_ptr = strstr(resp->body, "\r\n\r\n")) != NULL) {
                    *(body_ptr+2) = '\0';
                    resp->header = parse_header(resp->body);
                    if (!header_postcheck(resp->header)) {
                        goto leave; /* modulues filter fail */
                    }
                    trunc_head = 1;


                    /* cover header */
                    body_ptr += 4;
                    for (i = 0; *body_ptr; i++) {
                        resp->body[i] = *body_ptr;
                        body_ptr++;
                    }
                    resp->body[i] = '\0';
                    len = i;
                } 
                continue;
            }
        }
    }


leave:
    close(narg->fd); /* close socket */
    free_url(narg->url); /* free Url object */
    regfree(&re); /* free regex object */
    /* free resp */
    free(resp->header->content_type);
    free(resp->header);
    free(resp->body);
    free(resp);


    end_thread();
    return NULL;
}


相關文章