一、認(rèn)識(shí)HTTP協(xié)議
HTTP協(xié)議是應(yīng)用層協(xié)議,它叫超文本傳輸協(xié)議。應(yīng)用層協(xié)議一般是我們自己自定義的,HTTP協(xié)議是計(jì)算機(jī)大佬們定義的一套現(xiàn)成的協(xié)議,因?yàn)樗约癏TTPS使用的場景非常多,現(xiàn)在已經(jīng)成為一套協(xié)議標(biāo)準(zhǔn),我們可以學(xué)習(xí)這套標(biāo)準(zhǔn)直接拿來使用。
當(dāng)我們?cè)谑褂脼g覽器上網(wǎng)時(shí),比如查閱文檔、看視頻聽音樂等,這些都是以網(wǎng)頁的形式呈現(xiàn)出來的,網(wǎng)頁本質(zhì)也是文件,它是以.html為后綴的文件,當(dāng)我們用瀏覽器瀏覽網(wǎng)頁時(shí),瀏覽器作為客戶端會(huì)向服務(wù)器發(fā)起HTTP請(qǐng)求,服務(wù)器會(huì)將網(wǎng)頁文件響應(yīng)回給客戶端。所以HTTP協(xié)議就是向特定的服務(wù)器申請(qǐng)?zhí)囟ㄙY源,把這些資源獲取到本地進(jìn)行展示或操作的。這里所說的資源指的是比如網(wǎng)頁資源、視頻資源、音頻資源,其實(shí)它們的本質(zhì)也是文件。
HTTP協(xié)議叫超文本傳輸協(xié)議其實(shí)是非常形象的,它可以傳輸文本比如網(wǎng)頁上我們看到的一大段一大段的文字,也可以傳輸類似于圖片、音頻、視頻等超文本的內(nèi)容。
1.認(rèn)識(shí)URL
URL(Uniform Resoure Locator)叫作統(tǒng)一資源定位符,它就是我們平時(shí)所俗稱的網(wǎng)址,一般由下面這幾部分組成:協(xié)議方案名、登錄信息、服務(wù)器地址、服務(wù)器端口號(hào)、帶層次的文件路徑、查詢字符串、片段標(biāo)識(shí)符。
- 協(xié)議方案名:表示采用的是什么協(xié)議。
- 登錄信息:表示用戶的登錄信息,這個(gè)字段一般可以不需要我們自己輸入,現(xiàn)在的網(wǎng)頁一般只需要我們輸入賬號(hào)密碼,或者快捷登錄,瀏覽器客戶端拿到我們的登錄信息之后自動(dòng)幫我們做轉(zhuǎn)換并填充URL中的這一個(gè)字段,所以這一字段一般是省略的,不需要我們顯式地去寫在URL上。
- 服務(wù)器地址:這個(gè)字段是必須有的,它表示的是服務(wù)器的IP地址,雖然我們平時(shí)瀏覽的網(wǎng)頁看到的這一字段并不是IP地址的形式,但這是因?yàn)榫W(wǎng)頁用域名代替了它的IP地址,域名方便記憶,瀏覽器客戶端自動(dòng)會(huì)幫我們將域名轉(zhuǎn)換成對(duì)應(yīng)的IP地址。
- 服務(wù)器端口號(hào):服務(wù)器地址后冒號(hào)緊跟的是服務(wù)器端口號(hào)。使用網(wǎng)絡(luò)服務(wù)必須有IP地址和端口號(hào),因?yàn)榫W(wǎng)絡(luò)通信的本質(zhì)就是套接字通信。一般網(wǎng)頁的URL我們是看不到端口號(hào)的,原因是使用確定協(xié)議的時(shí)候,端口號(hào)是缺省的,雖然URL上沒有顯示出來,但瀏覽器客戶端在發(fā)起網(wǎng)絡(luò)請(qǐng)求的時(shí)候會(huì)自動(dòng)幫我們添加上缺省的端口號(hào)。特定的眾所周知的服務(wù),它的端口號(hào)必須是確定的比如HTTP服務(wù)的端口號(hào)是80,HTTPS服務(wù)的端口是443。
- 帶層次的文件路徑:表示的是服務(wù)器上要返回給客戶端的資源的路徑,比如服務(wù)器要返回網(wǎng)頁文件給瀏覽器,這里就要填上該網(wǎng)頁文件在服務(wù)器上對(duì)應(yīng)的路徑。
- 查詢字符串和片段標(biāo)識(shí)符:這兩個(gè)都是在問號(hào)后面的,在URL中問號(hào)后面的代表的是一些參數(shù),參數(shù)的類型非常多樣。
urlencode和urldecode:
在URL中像/
、?
、:
等這樣的字符是有特殊含義的,因此這些字符不能隨意地出現(xiàn),如果我們的參數(shù)中出現(xiàn)了這些字符,就必須對(duì)其進(jìn)行轉(zhuǎn)義。urlencode就可以對(duì)URL進(jìn)行編碼,將特殊含義的字符進(jìn)行轉(zhuǎn)義。urldecode可以對(duì)URL進(jìn)行解碼。轉(zhuǎn)義的規(guī)則是:將需要轉(zhuǎn)碼的字符轉(zhuǎn)為16進(jìn)制,然后從右到左,取4位(不足4位直接處理),每2位做一位,前面加上%,編碼成%XY格式。
比如我們?cè)诎俣葹g覽器搜索關(guān)鍵詞C++和關(guān)鍵詞CPP,復(fù)制出來它們的URL進(jìn)行對(duì)比可以發(fā)現(xiàn):
在它們的URL中都有一個(gè)wd字段,wd是word的簡寫,代表的就是我們搜索的關(guān)鍵詞,當(dāng)關(guān)鍵詞是C++的時(shí)候,由于加號(hào)字符在URL中是有特殊含義的,所以它被轉(zhuǎn)義了,當(dāng)關(guān)鍵詞是CPP的時(shí)候就沒有被轉(zhuǎn)義。
二、HTTP的協(xié)議格式
1.HTTP協(xié)議的請(qǐng)求格式
HTTP是基于行格式的一套協(xié)議,第一個(gè)部分也是第一行,叫作請(qǐng)求行,包括三個(gè)字段,每個(gè)字段以空格分開,分別是請(qǐng)求方法、URL和HTTP版本,請(qǐng)求方法常見的是GET方法和POST方法,URL這里一般是去掉域名和端口號(hào)的URL,最后請(qǐng)求行以\r\n
結(jié)尾。
第二部分由很多行構(gòu)成,叫作HTTP的請(qǐng)求報(bào)頭,這里面放的都是請(qǐng)求報(bào)頭的屬性。這些屬性都是key: value
格式的,前面是key值,key值后面緊跟一個(gè)冒號(hào),冒號(hào)后跟一個(gè)空格,空格后跟value值。
第三部分的內(nèi)容只有\r\n
,它其實(shí)是一個(gè)空行,這行其實(shí)起到的是類似于分隔符的作用,在別人解析HTTP請(qǐng)求的時(shí)候,只要讀到了這個(gè)空行,就代表讀取完了HTTP請(qǐng)求前半部分的請(qǐng)求行和報(bào)頭數(shù)據(jù)。
第四部分是在空行以后,代表的是有效載荷。也叫作請(qǐng)求正文,這部分的內(nèi)容一般比如包括用戶的登錄賬號(hào)和密碼,用戶的個(gè)人信息,音頻資源、視頻資源等等。
我們可以用代碼演示一下HTTP協(xié)議的請(qǐng)求報(bào)文,我們寫一個(gè)TCP服務(wù)器,讓瀏覽器作為客戶端以HTTP協(xié)議請(qǐng)求服務(wù)端,服務(wù)端讀取客戶端發(fā)送過來的信息并把它打印出來即可。
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
using namespace std;
class TcpServer;
struct ThreadData
{
int _sock;
TcpServer *_server;
ThreadData(int sock, TcpServer *server)
: _sock(sock), _server(server)
{
}
};
class TcpServer
{
public:
TcpServer(uint16_t port, const string &ip = "")
: _listen_socket(0), _port(port), _ip(ip)
{
}
~TcpServer()
{
}
public:
void init()
{
// 1.創(chuàng)建套接字
_listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (_listen_socket < 0)
{
cerr << "socket error" << endl;
exit(1);
}
cout << "socket success" << endl;
// 2.bind
// 2.1填充網(wǎng)絡(luò)信息
sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
// 2.2bind網(wǎng)絡(luò)信息
if (bind(_listen_socket, (const sockaddr *)&local, sizeof(local)) < 0)
{
cerr << "bind error" << endl;
exit(2);
}
cout << "bind success" << endl;
// 3.listen
if (listen(_listen_socket, 5) < 0)
{
cerr << "listen error" << endl;
exit(3);
}
cout << "listen success" << endl;
}
void start()
{
while (true)
{
// 1.accept
sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
socklen_t peer_len = sizeof(peer);
int accept_socket = accept(_listen_socket, (sockaddr *)&peer, &peer_len);
if (accept_socket < 0)
{
continue;
}
// 獲取連接成功,開始提供服務(wù)
// 創(chuàng)建多線程,主線程負(fù)責(zé)accept獲取連接,新線程負(fù)責(zé)提供服務(wù)
pthread_t tid;
ThreadData *td = new ThreadData(accept_socket, this);
pthread_create(&tid, nullptr, httpServer, (void *)td);
}
}
public:
static void *httpServer(void *args)
{
ThreadData *td = (ThreadData *)args;
while (true)
{
char buffer[10240];
ssize_t readRes = read(td->_sock, buffer, sizeof(buffer) - 1);
if (readRes > 0)
{
buffer[readRes] = '\0';
// 將請(qǐng)求打印出來
cout << buffer << endl;
}
}
}
private:
int _listen_socket;
uint16_t _port;
string _ip;
};
// ./server port ip
int main(int argc, char *argv[])
{
// 命令行參數(shù)的格式輸入錯(cuò)誤
if (argc != 2 && argc != 3)
{
cerr << "argc error,usage:./server port ip";
exit(4);
}
uint16_t server_port = (uint16_t)atoi(argv[1]);
string server_ip = "";
if (argc == 3)
{
server_ip = argv[2];
}
TcpServer svr(server_port, server_ip);
svr.init();
svr.start();
return 0;
}
運(yùn)行代碼啟動(dòng)服務(wù)器以后,我們讓瀏覽器以HTTP協(xié)議請(qǐng)求我們的服務(wù)器,就可以看到它發(fā)送過來的請(qǐng)求報(bào)文:
我們可以看到它是以Get方式發(fā)起請(qǐng)求的,HTTP版本是HTTP1.1
,Host指的是服務(wù)器主機(jī)的地址和端口號(hào),Connection指的是請(qǐng)求是所采用的連接方式,
2.HTTP協(xié)議的響應(yīng)格式
HTTP的響應(yīng)格式也是以行為單位的,并且相應(yīng)格式也是包含四個(gè)部分:
第一部分也是第一行,叫作狀態(tài)碼字段,它用來描述這一次HTTP響應(yīng)的狀態(tài)是什么,這一行的具體內(nèi)容首先是HTTP協(xié)議的版本,然后緊跟一個(gè)空格,空格后面是狀態(tài)碼,狀態(tài)碼后面緊跟一個(gè)空格,空格后面是狀態(tài)碼描述,描述該狀態(tài)碼代表什么含義。這里的HTTP協(xié)議的版本和請(qǐng)求格式中的HTTP協(xié)議版本不一定一樣,舉個(gè)例子,如果客戶端是老的客戶端,但我們服務(wù)器更新了內(nèi)容,我們需要保證的是如果客戶端沒有更新,依然能正常使用但是不能看到新的內(nèi)容,只有更新了客戶端才能看到新的內(nèi)容。這種操作其實(shí)就是在版本號(hào)這里做判斷,如果客戶端發(fā)來的請(qǐng)求中,版本號(hào)是老的版本號(hào),我們就提供對(duì)應(yīng)老版本號(hào)的服務(wù)給它,如果版本號(hào)是新的,我們就提供新版本好的服務(wù)給它。
第二部分也是由很多行構(gòu)成,叫作HTTP的響應(yīng)報(bào)頭,這里和請(qǐng)求格式中的請(qǐng)求報(bào)頭類似,放的都是響應(yīng)報(bào)頭的屬性,這些屬性都是key: value
格式的,前面是key值,key值后面緊跟一個(gè)冒號(hào),冒號(hào)后跟一個(gè)空格,空格后跟value值。
第三部分與HTTP協(xié)議的請(qǐng)求格式一樣,也是空行,起到分隔符的作用。
第四部分也是有效載荷,也叫作響應(yīng)正文,這里的響應(yīng)正文對(duì)應(yīng)的是URL中請(qǐng)求的資源路徑下的資源,比如html網(wǎng)頁文件,圖片文件,音視頻文件等。
我們可以再在上面的代碼基礎(chǔ)上增加響應(yīng)代碼,首先我們不弄特別負(fù)責(zé)的響應(yīng),做一個(gè)簡單的響應(yīng)報(bào)文演示一下即可:
static void *httpServer(void *args)
{
ThreadData *td = (ThreadData *)args;
while (true)
{
char buffer[10240];
ssize_t readRes = read(td->_sock, buffer, sizeof(buffer) - 1);
if (readRes > 0)
{
buffer[readRes] = '\0';
// 將請(qǐng)求打印出來
cout << buffer << endl;
}
// 開始響應(yīng)回給客戶端
// 添加狀態(tài)碼行
string response_package = "HTTP/1.0 200 OK\r\n";
// 添加空行
response_package += "\r\n";
// 添加響應(yīng)正文
response_package += "hello world, test for http";
// 將響應(yīng)發(fā)回給客戶端
send(td->_sock, response_package.c_str(), response_package.size(), 0);
}
}
運(yùn)行代碼啟動(dòng)服務(wù)器以后,我們用兩種方法來查看服務(wù)端發(fā)送回來的響應(yīng):
第一種是采用telnet工具發(fā)起http的請(qǐng)求,然后獲得響應(yīng)正文:
- 輸入指令
telnet IP 端口號(hào)
- 輸入
ctrl+]
- 再按一次回車
- 輸入
get / http/1.0
發(fā)起http的get請(qǐng)求
第二種方法是采用瀏覽器來訪問我們的服務(wù)器,讓瀏覽器作為客戶端發(fā)起HTTP請(qǐng)求,瀏覽器來接收HTTP響應(yīng)正文:
HTTP是如何保證自己的報(bào)頭和有效載荷被全部讀取的呢?
- 要想讀取到完整的報(bào)頭,只需要按行讀取,直到讀取到空行為止即可。
- 要想讀取到完整的有效載荷,就需要用到報(bào)頭屬性中的正文長度,根據(jù)正文長度來讀取。
三、利用HTTP協(xié)議返回網(wǎng)頁
我們寫一個(gè)簡單的代碼來演示一下如何將一個(gè)網(wǎng)頁信息給客戶端返回回去,上面我們演示的代碼都是在響應(yīng)報(bào)文中添加字符串作為響應(yīng),所以客戶端訪問服務(wù)端也能看到對(duì)應(yīng)的字符串。那么我們可以利用這個(gè)思路,將網(wǎng)頁的html文件用字符串提取出來,然后將整個(gè)html的文件內(nèi)容以字符串的形式響應(yīng)回去,這樣客戶端就能獲取到我們的網(wǎng)頁資源了。
1.getPath函數(shù)
客戶端給服務(wù)器發(fā)送過來的請(qǐng)求中,在請(qǐng)求的第一行就包含了資源路徑,我們可以從客戶端的請(qǐng)求中提取出資源路徑。
由于HTTP協(xié)議請(qǐng)求格式是以\r\n
作為分隔符的,那么我們查找第一個(gè)\r\n
分隔符就一定能提取到第一行內(nèi)容。第一行內(nèi)容是形如 GET /a/b.html HTTP/1.0\r\n
這樣的格式,資源路徑在第二部分,所以我們先正向查找第一個(gè)空格的位置,再逆向查找最后一個(gè)空格的位置,兩個(gè)空格之間的子串就是資源路徑。
但是我們還需要對(duì)提取出來的資源路徑做判斷,如果提取上來的web根目錄,我們就在資源路徑上添加首頁文件再返回。
// GET /a/b.html HTTP/1.0\r\n
string getPath(const string &request)
{
// 查找第一個(gè)\r\n,就能查找到第一行
size_t pos = request.find(CRLF);
if (pos == string::npos)
{
return "";
}
// 截取第一行
string line = request.substr(0, pos);
// 在第一行中查找第一個(gè)空格
size_t first = line.find(SPACE);
if (first == string::npos)
{
return "";
}
// 在第一行中查找最后一個(gè)空格
size_t second = line.rfind(SPACE);
if (second == string::npos)
{
return "";
}
// 截取路徑
string path = line.substr(first + SPACE_LEN, second - (first + SPACE_LEN));
cout << "path: " << path << endl;
// 如果是web根目錄,則加上首頁路徑
if (path.size() == 1 && path[0] == '/')
{
cout << "web根目錄" << endl;
path += "index.html";
}
return path;
}
2.readFile函數(shù)
提取到資源路徑之后,我們需要到對(duì)應(yīng)的路徑下去讀取文件內(nèi)容,再將文件內(nèi)容以字符串的形式返回。
string readFile(const string &path)
{
// 打開文件
ifstream in(path);
if (!in.is_open())
{
return "open file error";
}
// 按行讀取文件
string content;
string line;
while (getline(in, line))
{
content += line;
}
// 關(guān)閉文件
in.close();
return content;
}
3.回調(diào)函數(shù)
static void *httpServer(void *args)
{
ThreadData *td = (ThreadData *)args;
while (true)
{
char buffer[10240];
string request_package;
ssize_t readRes = read(td->_sock, buffer, sizeof(buffer) - 1);
if (readRes > 0)
{
buffer[readRes] = '\0';
request_package = buffer;
// 將請(qǐng)求打印出來
cout << buffer << endl;
}
// 提取請(qǐng)求中的資源路徑
if (request_package.empty())
{
continue;
}
string recourse_path = td->_server->getPath(request_package);
string file_path = "./wwwroot";
file_path += recourse_path;
cout << file_path << endl;
// 讀取資源路徑下的文件
string html = td->_server->readFile(file_path);
// 開始響應(yīng)回給客戶端
// 添加狀態(tài)碼行
string response_package = "HTTP/1.1 200 OK\r\n";
response_package += "Content-Type: text/html\r\n";
// 添加空行
response_package += "\r\n";
// 添加響應(yīng)正文
response_package += html;
// 將響應(yīng)發(fā)回給客戶端
send(td->_sock, response_package.c_str(), response_package.size(), 0);
}
}
四、HTTP協(xié)議的請(qǐng)求方法
我們利用網(wǎng)絡(luò)上網(wǎng)的行為可以分為兩種:
- 把遠(yuǎn)端的資源拿到本地,比如我們上面演示的獲取網(wǎng)頁資源、獲取圖片資源等。
- 把我們的屬性字段提交到遠(yuǎn)端,比如把我們的搜索關(guān)鍵字提交到遠(yuǎn)端,把我們的登錄信息提交到遠(yuǎn)端。
網(wǎng)站一般被分為靜態(tài)網(wǎng)站和動(dòng)態(tài)網(wǎng)站,靜態(tài)網(wǎng)站就是只能把遠(yuǎn)端的東西拿到本地,不能提交資源到遠(yuǎn)端,沒有交互式的網(wǎng)站;相反的,動(dòng)態(tài)網(wǎng)站就是既能把遠(yuǎn)端的東西拿到本地,也可以提交資源到遠(yuǎn)端,就是具有交互式的網(wǎng)站,比如我們寫博客,CSDN官網(wǎng)就是一個(gè)交互式的動(dòng)態(tài)網(wǎng)站。
將遠(yuǎn)端資源拿到本地可以采用GET方法,將本地資源提交到遠(yuǎn)端可以采用GET方法或者POST方法。
GET方法和POST方法有什么區(qū)別?
- GET方法是通過URL進(jìn)行傳參的,客戶端的參數(shù)會(huì)以明文的形式顯示在URL中。
- POST方法是通過正文進(jìn)行傳參的,客戶端的參數(shù)不會(huì)顯示在URL中,但是會(huì)出現(xiàn)在請(qǐng)求正文中。
- 所以GET方法是不私密的,它也是不安全的,比如我們?cè)诰W(wǎng)頁輸入賬號(hào)密碼的時(shí)候,這些會(huì)作為參數(shù)顯示在URL中。
- POST方法因?yàn)槭峭ㄟ^正文傳參,所以相對(duì)比較私密,但它也是不安全的,比如我們?cè)诰W(wǎng)頁輸入的賬號(hào)密碼,雖然URL中不會(huì)顯示出來,但如果別人用一些抓包工具抓取到了你的請(qǐng)求正文,也是可以輕而易舉地獲取你的賬戶信息的。
GET方法和POST方法是HTTP協(xié)議最常用的兩種方法,除了GET方法和POST方法之外,還有一些不太常用的方法,下面表格是對(duì)所有方法的匯總介紹:
其中有一些方法通常是會(huì)被服務(wù)器禁止掉的,比如DELETE方法,必須被禁止,否則別人通過客戶端發(fā)起一個(gè)DELETE請(qǐng)求就可以隨意刪除我們服務(wù)端的文件,是非常危險(xiǎn)的。
方法 | 說明 | 支持的HTTP協(xié)議版本 |
---|---|---|
GET | 獲取資源 | 1.0、1.1 |
POST | 傳輸實(shí)體主體 | 1.0、1.1 |
PUT | 傳輸文件 | 1.0、1.1 |
HEAD | 獲取報(bào)文首部 | 1.0、1.1 |
DELETE | 刪除文件 | 1.0、1.1 |
OPTIONS | 詢問支持的方法 | 1.1 |
TRACE | 追蹤路徑 | 1.1 |
CONNECT | 要求用隧道協(xié)議連接代理 | 1.1 |
LINK | 建立和資源之間的聯(lián)系 | 1.0 |
UNLINK | 斷開連接關(guān)系 | 1.0 |
五、HTTP的狀態(tài)碼
HTTP協(xié)議的狀態(tài)碼有五類,分別以數(shù)字1、2、3、4、5開頭,每一類都有其特殊的含義:
- 1XX:以數(shù)字1開頭的這一類狀態(tài)碼是信息性狀態(tài)碼,表示當(dāng)前服務(wù)器正在處理接收的請(qǐng)求,所以如果等待時(shí)間比較長的話,服務(wù)器可以響應(yīng)這一類狀態(tài)碼表示請(qǐng)求正在處理,耐心等待。
- 2XX:以數(shù)字2開頭的這一類狀態(tài)碼是成功狀態(tài)碼,表示當(dāng)前服務(wù)器已經(jīng)正常處理完畢客戶端的請(qǐng)求。
- 3XX:以數(shù)字3開頭的這一類狀態(tài)碼是重定向狀態(tài)碼,表示需要附加操作以完成請(qǐng)求。目前瀏覽器主流接受的重定向狀態(tài)碼就只有兩個(gè),一個(gè)是301另一個(gè)是302。301是永久重定向,它的應(yīng)用場景一般是某些網(wǎng)站因?yàn)橛蛎牧?,為了讓老用戶從老域名也能訪問到新網(wǎng)站,就使用永久重定向,當(dāng)別人訪問老域名時(shí)會(huì)自動(dòng)跳轉(zhuǎn)到新域名。302是臨時(shí)重定向,它的應(yīng)用場景一般是某些網(wǎng)站要更新資源時(shí),為了不讓網(wǎng)站停機(jī),會(huì)創(chuàng)建一個(gè)臨時(shí)重定向讓用戶訪問原來的資源,當(dāng)新資源更新好了以后再撤銷這個(gè)臨時(shí)重定向。
- 4XX:以數(shù)字4開頭的這一類狀態(tài)碼是客戶端錯(cuò)誤狀態(tài)碼,表示服務(wù)器無法處理請(qǐng)求。比如我們經(jīng)常見到的404狀態(tài)碼。
- 5XX:以數(shù)字5開頭的這一類狀態(tài)碼是服務(wù)器錯(cuò)誤狀態(tài)碼,表示服務(wù)器處理請(qǐng)求時(shí)服務(wù)器出錯(cuò)了。
六、HTTP常見的報(bào)頭屬性
- Content-Type:數(shù)據(jù)類型(text/html等)
- Content-Length:Body的長度
- Host:客戶端告知服務(wù)器, 所請(qǐng)求的資源是在哪個(gè)主機(jī)的哪個(gè)端口上
- User-Agent:聲明用戶的操作系統(tǒng)和瀏覽器版本信息
- referer:當(dāng)前頁面是從哪個(gè)頁面跳轉(zhuǎn)過來的
- location:搭配3xx狀態(tài)碼使用, 告訴客戶端接下來要去哪里訪問
- Cookie:用于在客戶端存儲(chǔ)少量信息. 通常用于實(shí)現(xiàn)會(huì)話(session)的功能
七、cookie和session
HTTP協(xié)議有一個(gè)特點(diǎn)就是,用戶的訪問行為,HTTP協(xié)議不會(huì)記錄。但是我們會(huì)發(fā)現(xiàn),我們?cè)谑褂脼g覽器上網(wǎng)的時(shí)候,瀏覽器是會(huì)有記錄我們的訪問行為的,再比如說我們用一些網(wǎng)站在登錄的時(shí)候,第一次登錄需要我們輸入賬號(hào)密碼,但是第二次登錄就不需要了。
這種功能是通過cookie來實(shí)現(xiàn)的,它的原理是:當(dāng)我們用瀏覽器作為客戶端訪問服務(wù)器的時(shí)候,如果需要登錄,第一次登錄我們需要在客戶端輸入用戶名和密碼,客戶端再將用戶名和密碼發(fā)送回給服務(wù)端,服務(wù)端拿到用戶名和密碼后去數(shù)據(jù)庫中比對(duì),如果查找到了該用戶且密碼正確就可以成功登錄,成功登錄以后服務(wù)端會(huì)將成功登錄的信息響應(yīng)回給客戶端,客戶端拿到這些信息就會(huì)生成對(duì)應(yīng)的cookie文件,該文件中就保存著用戶名和密碼。在下一次客戶端發(fā)起HTTP請(qǐng)求的時(shí)候,它會(huì)自動(dòng)攜帶cookie文件的內(nèi)容,也就不再需要我們輸入用戶名和密碼了。
但是cookie策略是有安全隱患的,如果cookie文件保存了過于私密的信息,一旦cookie文件泄露出去,可能會(huì)對(duì)我們的數(shù)據(jù)安全或者財(cái)產(chǎn)安全產(chǎn)生很大影響。所以現(xiàn)在主流的會(huì)話保持策略是cookie+session方案。
cookie+session的方案是當(dāng)用戶第一次登錄輸入用戶名和密碼時(shí),服務(wù)端會(huì)幫我們生成一份session文件用來保存用戶的私密信息,并且會(huì)為這個(gè)session文件生成唯一的文件名,也就相當(dāng)于生成了一份唯一的文件編號(hào)。然后服務(wù)端再將成功登錄的信息響應(yīng)回給客戶端,并且會(huì)將session文件的對(duì)應(yīng)編號(hào)值發(fā)送給客戶端,客戶端收到之后就會(huì)生成cookie,此時(shí)客戶端的cookie文件就不再保存用戶的私密信息了,而是將session文件的編號(hào)值保存在cookie文件中。下一次訪問服務(wù)器的時(shí)候,客戶端發(fā)送cookie文件中的編號(hào)值即可。也就是說,這種方案是將原來保存在客戶端本地的用戶私密信息保存到了遠(yuǎn)端的服務(wù)器上。
八、Connection字段
在HTTP協(xié)議的請(qǐng)求和響應(yīng)報(bào)文中,有一個(gè)Connection字段,它表示HTTP協(xié)議是長鏈接還是短鏈接,在HTTP/1.0版本中是基于短鏈接的,所謂的短鏈接(closed)就是客戶端發(fā)起一次請(qǐng)求,服務(wù)端響應(yīng)之后就關(guān)閉鏈接了,短鏈接一次只處理一個(gè)HTTP請(qǐng)求。我們?nèi)粘?吹降木W(wǎng)頁,它可能這一張網(wǎng)頁就發(fā)起了幾十次上百次的HTTP請(qǐng)求,HTTP協(xié)議底層采用的就是TCP協(xié)議,每一次鏈接都需要在底層經(jīng)歷三次握手,斷開鏈接都需要經(jīng)歷四次揮手,如果現(xiàn)在主流的網(wǎng)站還是采用短鏈接的話,一個(gè)HTTP請(qǐng)求處理完之后就斷開,下一個(gè)HTTP請(qǐng)求來的時(shí)候再建立鏈接,那一張網(wǎng)頁加載出來可能要幾十次上百次的發(fā)起HTTP請(qǐng)求,在底層就要經(jīng)歷很多次的三次握手和四次揮手,這樣的成本是很高的效率也是很低的。文章來源:http://www.zghlxwxcb.cn/news/detail-423971.html
所以短鏈接就不再適合現(xiàn)在主流的HTTP請(qǐng)求了,我們采用主流的長鏈接。采用長鏈接的方案,客戶端和服務(wù)端在建立網(wǎng)絡(luò)連接之前先要進(jìn)行HTTP版本和連接方式協(xié)商,如果版本號(hào)一致并且Connection字段是keep-alive
,代表雙方采用長鏈接的方式。雙方建立鏈接之后,服務(wù)端不會(huì)在處理完一個(gè)請(qǐng)求之后就斷開鏈接,而是接收客戶端的多次HTTP請(qǐng)求,按順序處理之后再響應(yīng)回給客戶端。當(dāng)所有的請(qǐng)求完畢時(shí)才會(huì)斷開鏈接,比如說我們?cè)诩虞d一張網(wǎng)頁時(shí),它可能會(huì)發(fā)起多次HTTP請(qǐng)求,當(dāng)一張完整的網(wǎng)頁被加載出來時(shí),雙方的鏈接才會(huì)被斷開。文章來源地址http://www.zghlxwxcb.cn/news/detail-423971.html
到了這里,關(guān)于HTTP協(xié)議介紹的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!