淺談 HTTP

URL格式

URL即'網址'

  • 服務器地址實際上是指ip地址,這裏用域名錶示,便於記憶
  • http服務器端口號默認80,可以不使用80
  • 帶層次的文件路徑不同,訪問服務器上的文件就不同
  • 查詢字符串 : 都是以鍵值對的方式存儲的, 鍵和值之間使用等號分隔,每一組鍵值對之間用 & 分隔
  • 片段標識符 : 表示要跳轉到本頁面的某一部分

urlencode和urldencode

即 '編碼' 和 '解碼'
我們可以看到,在上面的url格式中,有一些我們固定使用的字符,比如 / ? : 等,這些字符在url中有着特殊的含義,所以我們不可以隨便的使用他
如果某個參數中帶有這樣的特殊字符,我們就必須對他進行轉義

轉義規則:
將需要轉碼的字符轉爲16進制,然後從右到左,取4位(不足四位直接丟掉),沒兩位做一位,前面加上%,編碼成 %XY 的格式

例如百度搜索C++時:

我們可以看到, + 號被轉化爲了 %2B


HTTP報文

用於HTTP協議交互的信息我們稱之爲報文,客戶端的HTTP報文我們叫做請求報文,服務器端的HTTP報文我們叫做響應報文
HTTP報文大致可分爲 報文首部,Header,空行,Body

通過下面我們使用抓包工具抓的HTTP包可以看出來,http協議是純文本協議,行文本

HTTP請求報文

GET請求 : 

首行:    以下三部分信息都用空格分隔
  • GET : http協議的方法,表示瀏覽器要從服務器上獲取某一個數據
  • http://baidu.com/ : 一個url,表示具體訪問服務器上哪一個資源
  • HTTP/1.1 : 當前HTTP版本號
Header:    以鍵值對的方式存儲,每一對鍵值對換一行,鍵和值以 [冒號][空格] 分隔(請求的Header字段有很多,這裏主要講解一下上面出現過的字段)
  • Host : 表示訪問主機名,也就是ip地址的別名,即域名
  • Connection: 逐跳首部,連接的管理
  • User-Agent : 包含操作系統信息和瀏覽器信息
  • Accept: 用戶代理可處理的類型
  • Accept-Encoding: 優先的內容編碼
  • Accept-Language: 優先的語言(自然語言)
  • Cookie : 用來保存一個從服務器返回回來的字符串,並且保存到本地,下一次訪問同一個網站,會將這個信息帶回去
最常見的保存的是用戶身份信息 : 例如用戶登錄信息,一旦在首頁登陸過一次,訪問其他頁面就不需要登錄
空行 : Header的結束標誌


POST請求:
  • 首行 : [方法]+[url]+[版本]
  • Header :
Content-Type : 如果最後面是 -from-urlencoded,那麼body的格式就和querystr一致
Content-Length : 表示Body一共佔多少字節
Referer : 表示當前頁面是從哪一個頁面跳轉過來的
  • 空行 : 表示Header結束
  • Body :
格式很有可能和querystr格式一樣,主要取決於Header中的Content-Type ( querystr :  鍵值對形式存儲,鍵值對之間用 & 隔開,鍵和值之間用 = 相連)

GET和POST請求的區別 : POST有Body,並且在Header中用Content-Type和Content-Length來表示Body的類型和大小

HTTP響應報文


首行 :     以空格分隔
  • [版本號] + [狀態碼] + [狀態碼解釋]
Header : 以鍵值對的方式存儲,每一對鍵值對換一行,鍵和值以 [冒號][空格] 分隔
  • Sever: HTTP服務器安裝信息
  • Content-Type : text/html 表示body的格式是一個xml的文本格式; charset說明字符集爲utf-8
  • Content-Length : Body部分的字節數
  • Vary: 代理服務器緩存的管理信息
  • Location: 令客戶端重定向到指定的URL
  • Set-Cookie : 和請求中的Cookie對應.服務器利用Set-Cookie向客戶端返回用來表示身份信息的的字符串,下次訪問時就會重新帶入,避免再次登錄.
Cookie 是以域名爲維度進行存儲;
Cookie 的存儲能力有限,瀏覽器默認4k,所以一般只保存身份標識,避免網絡交互過程中佔用過多的帶寬;
空行 : Header結束標誌
Body :
  • 瀏覽器見到這一部分內容,就會按照Content-Type的格式展示出來.

HTTP方法


方法說明支持的HTTP協議版本
GET獲取資源1.0 / 1.1
POST傳輸實體主體1.0 / 1.1
PUT傳輸文件1.0 / 1.1
HEAD獲得報文首部1.0 / 1.1
DELETE刪除文件1.0 / 1.1
OPTIONS詢問支持的方法1.1
TRACE追蹤路徑1.1
CONNECT要求用隧道協議連接代理1.1
LINK建立和資源自檢的聯繫1.0 
UNLINK斷開連接關係1.0 
1.0 是一個基於請求-響應的短連接


HTTP狀態碼


 類別原因短語經常見到的狀態碼描述
1XXInformational(信息性狀態碼)接收的請求正在處理  
2XXSuccess(成功狀態碼)請求正常處理完畢200 OK 請求成功(其後是對GET和POST請求的應答文檔。)
3XXRedirection(重定向狀態碼)需要進行附加操作以完成請求
302 Found
所請求的頁面已經臨時轉移至新的url。
4XXClient Error(客戶端錯誤狀態碼)服務器無法處理請求
404 Not Found
服務器無法找到被請求的頁面。
5XXServer Error(服務器錯誤狀態碼)服務器處理請求出錯
504 Gateway Timeout
網關超時。

實現簡單地HTTP服務器

這裏是一個最簡易版本的http服務器,不管客戶端發出什麼請求,服務器都相應一個hello world 頁面
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>

int ServerInit(short port)
{
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(fd < 0)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(port);
    int ret = bind(fd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return -2;
    }
    ret = listen(fd,3);
    if(ret < 0)
    {
        perror("listen");
        return -3;
    }
    return fd;
}

void* ThreadEntry(void* arg)
{
    int* pnew_sock = (int*)arg;
    int new_sock = *pnew_sock;

    // 構造一個足夠大的響應空間,獲取HTTP請求
    char buf[1024 * 10] = {0};

    read(new_sock,buf,sizeof(buf)-1);
    printf("[Request] %s\n",buf);

    // 構造HTTP響應
    const char* first_line = "HTTP/1.1 200 OK\n"; //此處構造http響應首部
    const char* blank_line = "\n"; //header和body之間的空行
    const char* body_line = "<html><h1>Hello World<h1></html>"; //此處構造Body,最簡單的頁面
    char header_buf[128] = {0};
    //構造header,這裏僅包含body的類型和長度
    sprintf(header_buf,"Content-Type: text/html;\nContent-Length: %lu\n",strlen(body_line));

    write(new_sock,first_line,strlen(first_line));
    write(new_sock,header_buf,strlen(header_buf));
    write(new_sock,blank_line,strlen(blank_line));
    write(new_sock,body_line,strlen(body_line));

    close(new_sock);
    return NULL;
}

int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        printf("Usage : ./http_server [port]\n");
        return 1;
    }
    // 創建 listen_sock
    int listen_sock = ServerInit(atoi(argv[1]));
    if(listen_sock < 0)
    {
        perror("ServerInit Faild\n");
        return 2;
    }
    printf("ServerInit OK\n");

    // 進入事件循環
    while(1)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int new_sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
        if(new_sock < 0)
        {
            perror("accept");
            return 3;
        }

        // 使用線程完成多用戶可訪問模式
        pthread_t tid;
        pthread_create(&tid,NULL,ThreadEntry,(void*)&new_sock);
        pthread_detach(tid);
    }
    return 0;
}
執行指令:  ./http_server 9090
演示結果:


我們接收到的http請求

發佈了77 篇原創文章 · 獲贊 50 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章