1.從輸入U(xiǎn)RL到頁面加載發(fā)生了什么
總體來說分為以下幾個(gè)過程:
1.1 DNS解析
1.2 TCP連接
1.3發(fā)送HTTP請(qǐng)求
它主要發(fā)生在客戶端。發(fā)送HTTP請(qǐng)求的過程就是構(gòu)建HTTP請(qǐng)求報(bào)文并通過TCP協(xié)議中發(fā)送到服務(wù)器指定端口(HTTP協(xié)議80/8080,HTTPS協(xié)議443)。HTTP請(qǐng)求報(bào)文是由三部分組成:請(qǐng)求行,請(qǐng)求報(bào)頭和請(qǐng)求正文。
1.4服務(wù)器處理請(qǐng)求并返回HTTP報(bào)文
后端從在固定的端口接收到TCP報(bào)文開始,這一部分對(duì)應(yīng)于編程語言中的socket。它會(huì)對(duì)TCP連接進(jìn)行處理,對(duì)HTTP協(xié)議進(jìn)行解析,并按照?qǐng)?bào)文格式進(jìn)一步封裝成HTTPRequest對(duì)象,供上層使用。這一部分工作一般是由Web服務(wù)器去進(jìn)行,HTTP響應(yīng)報(bào)文也是由三部分組成:狀態(tài)碼,響應(yīng)報(bào)頭和響應(yīng)報(bào)文。
HTTP狀態(tài)碼由三個(gè)十進(jìn)制數(shù)字組成,第一個(gè)十進(jìn)制數(shù)字定義了狀態(tài)碼的類型,后兩個(gè)數(shù)字沒有分類的作用。HTTP狀態(tài)碼共分為5種類型:
1** 信息,服務(wù)器收到請(qǐng)求,需要請(qǐng)求者繼續(xù)執(zhí)行操作
2** 成功,操作被成功接收并處理
3** 重定向,需要進(jìn)一步的操作以完成請(qǐng)求
4** 客戶端錯(cuò)誤,請(qǐng)求包含語法錯(cuò)誤或無法完成請(qǐng)求
5** 服務(wù)器錯(cuò)誤,服務(wù)器在處理請(qǐng)求的過程中發(fā)生了錯(cuò)誤
狀態(tài)碼查詢:http://www.runoob.com/http/http-status-codes.html

1.5瀏覽器解析渲染頁面
瀏覽器在收到HTML,CSS,JS文件后,它是如何把頁面呈現(xiàn)到屏幕上的?下圖對(duì)應(yīng)的就是WebKit(一個(gè)開源的瀏覽器引擎)渲染的過程。

瀏覽器是一個(gè)邊解析邊渲染的過程。首先瀏覽器解析HTML文件構(gòu)建DOM樹,然后解析CSS文件構(gòu)建渲染樹,等到渲染樹構(gòu)建完成后,瀏覽器開始布局渲染樹并將其繪制到屏幕上。
JS的解析是由瀏覽器中的JS解析引擎完成的。JS的執(zhí)行機(jī)制就可以看做是一個(gè)主線程加上一個(gè)任務(wù)隊(duì)列(taskqueue)。同步任務(wù)就是放在主線程上執(zhí)行的任務(wù),異步任務(wù)是放在任務(wù)隊(duì)列中的任務(wù)。所有的同步任務(wù)在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧;異步任務(wù)有了運(yùn)行結(jié)果就會(huì)在任務(wù)隊(duì)列中放置一個(gè)事件;腳本運(yùn)行時(shí)先依次運(yùn)行執(zhí)行棧,然后會(huì)從任務(wù)隊(duì)列里提取事件,運(yùn)行任務(wù)隊(duì)列中的任務(wù),這個(gè)過程是不斷重復(fù)的,所以又叫做事件循環(huán)(Eventloop)。但是當(dāng)文檔加載過程中遇到JS文件,HTML文檔會(huì)掛起渲染過程,不僅要等到文檔中JS文件加載完畢還要等待解析執(zhí)行完畢,才會(huì)繼續(xù)HTML的渲染過程。原因是因?yàn)镴S有可能修改DOM結(jié)構(gòu),這就意味著JS執(zhí)行完成前,后續(xù)所有資源的下載是沒有必要的。

1.6連接結(jié)束
2. dns劫持
當(dāng)用戶輸入一個(gè)URL時(shí),想要能夠訪問我們的路由器管理界面首先就需要將改URL的DNS解析到路由器的web服務(wù)器地址上,這時(shí)候,我們需要dns劫持。Dns劫持中主要用到一個(gè)開源的軟件-dnsmasq。
首先我們利用dnsmasq將自己的工作站配置為一個(gè)能夠解析開發(fā)域名的server,解析的ip地址設(shè)置為工作站的ip地址。利用dnsmasq建立了一個(gè)dnsmapping table,將www.baidu.com的域名解析為路由器管理界面地址192.168.2.1。這時(shí)候,通過www.baidu.com訪問時(shí)會(huì)轉(zhuǎn)跳至192.168.2.1。

3. url重定向
這時(shí)候我們已經(jīng)可以成功的通過域名www.baidu.com訪問到路由器的web服務(wù)器。
url重定向的實(shí)現(xiàn)可以在前端實(shí)現(xiàn)或是后端實(shí)現(xiàn)。這時(shí)候web服務(wù)器需要將指定的html返回給客戶端,比如我們的快速向?qū)ы撁婊蚴鞘醉擁撁妗_@就需要重新定向用戶輸入的url。
3.1.前端實(shí)現(xiàn)
3.1.1 html頁面跳轉(zhuǎn)方式
可以使用html的meta標(biāo)簽實(shí)現(xiàn)頁面的跳轉(zhuǎn)。

meta是html語言head區(qū)的一個(gè)輔助性標(biāo)簽。meta標(biāo)簽共有兩個(gè)屬性,它們分別是http-equiv屬性和name屬性,不同的屬性又有不同的參數(shù)值,這些不同的參數(shù)值就實(shí)現(xiàn)了不同的網(wǎng)頁功能。
http-equiv屬性:相當(dāng)于http的文件頭作用,它可以向?yàn)g覽器傳回一些有用的信息,以幫助正確和精確地顯示網(wǎng)頁內(nèi)容,與之對(duì)應(yīng)的屬性值為content,content中的內(nèi)容其實(shí)就是各個(gè)參數(shù)的變量值。
<metahttp-equiv="參數(shù)"content="參數(shù)變量值">;
3.1.2 JS頁面跳轉(zhuǎn)方式
1.使用window.location.href= "newurl"

也可以用window.location= "newurl"
2. 使用window.navigate
<script>
window.navigate("http://www.csdn.net");
</script>
3.2.后端實(shí)現(xiàn)
后端實(shí)現(xiàn)主要是通過響應(yīng)頭中的http響應(yīng)location字段,令客戶端重定向至指定URL。
數(shù)據(jù)交互過程
:http://172.17.200.153:8800/bbs/index.php?/topic/167-httpd%E7%AE%80%E4%BB%8B/
3.2.1 http消息
HTTP是基于客戶端/服務(wù)端(C/S)的架構(gòu)模型,通過一個(gè)可靠的鏈接來交換信息,是一個(gè)無狀態(tài)的請(qǐng)求/響應(yīng)協(xié)議。
一個(gè)HTTP"客戶端"是一個(gè)應(yīng)用程序(Web瀏覽器或其他任何客戶端),通過連接到服務(wù)器達(dá)到向服務(wù)器發(fā)送一個(gè)或多個(gè)HTTP的請(qǐng)求的目的。一個(gè)HTTP"服務(wù)器"同樣也是一個(gè)應(yīng)用程序(通常是一個(gè)Web服務(wù),如ApacheWeb服務(wù)器或IIS服務(wù)器等),通過接收客戶端的請(qǐng)求并向客戶端發(fā)送HTTP響應(yīng)數(shù)據(jù)。
HTTP協(xié)議的請(qǐng)求和響應(yīng)都是一段按一定規(guī)則組織起來的文本,其請(qǐng)求的頭部包括請(qǐng)求行(請(qǐng)求方式method、請(qǐng)求的路徑path、協(xié)議版本protocol),請(qǐng)求頭標(biāo)(一系列key:value形式組織的文本行),空行(分隔請(qǐng)求頭部與數(shù)據(jù))和請(qǐng)求數(shù)據(jù)。
1. 客戶端請(qǐng)求
客戶端發(fā)送一個(gè)HTTP請(qǐng)求到服務(wù)器的請(qǐng)求消息包括以下格式:請(qǐng)求行(requestline)、請(qǐng)求頭部(header)、空行和請(qǐng)求數(shù)據(jù)四個(gè)部分組成。
2. 服務(wù)器響應(yīng)消息
HTTP響應(yīng)也由四個(gè)部分組成,分別是:狀態(tài)行、消息報(bào)頭、空行和響應(yīng)正文。

3.2.2 http消息源碼分析
1 客戶端請(qǐng)求解析
客服端的請(qǐng)求處理其實(shí)就是將請(qǐng)求拆解,分解各個(gè)字段,提取出header中的信息。
首先,uhttpd會(huì)將收到的請(qǐng)求存放在一個(gè)buffer中。在uhttpd中有一個(gè)狀態(tài)機(jī)來處理http請(qǐng)求

這三個(gè)狀態(tài)分別用來處理客服端請(qǐng)求中的請(qǐng)求行(requestline)、請(qǐng)求頭部(header)、請(qǐng)求數(shù)據(jù)。

Uhttpd 中默認(rèn)的需要獲取的 http 請(qǐng)求包括以下字段:

1.CLIENT_STATE_INIT
狀態(tài)-處理請(qǐng)求行,在Init狀態(tài)中,調(diào)用staticbool client_init_cb(struct client *cl, char *buf, int len)函數(shù)來method,url, version
獲取成功后將狀態(tài)變?yōu)镃LIENT_STATE_HEADER
2. CLIENT_STATE_HEADER
狀態(tài)--處理請(qǐng)求頭部,調(diào)用staticbool client_header_cb(struct client *cl, char *buf, intlen)函數(shù)來解析requestheader;
解析的方式就是staticbool client_header_cb(struct client *cl, char *buf, intlen);函數(shù)中通過/r/n作為標(biāo)志將buffer中的數(shù)據(jù)一行一行讀入。然后將每一行數(shù)據(jù)通過“:”為標(biāo)志存到結(jié)構(gòu)體中傳入staticvoid client_parse_header(struct client *cl, char *data);函數(shù)中來獲取文件頭。
這兩個(gè)函數(shù)中將buffer中的httpheader按照字符串解析的方式取出有用信息,存放到client結(jié)構(gòu)體中。當(dāng)buffer中全部解析完成之后狀態(tài)切換到CLIENT_STATE_DATA;
3. CLIENT_STATE_DATA
處理請(qǐng)求數(shù)據(jù),調(diào)用函數(shù)voidclient_poll_post_data(struct client *cl)
沒看明白……大概是按照content-length取出數(shù)據(jù)。╮(╯_╰)╭
2 服務(wù)器響應(yīng)消息處理
Uhttpd在完成CLIENT_STATE_HEADER處理的時(shí)候會(huì)調(diào)用uh_handle_request(cl)函數(shù)來處理客戶端的請(qǐng)求。

響應(yīng)主要是處理url并返回狀態(tài)碼。響應(yīng)的處理主要在file.c文件中進(jìn)行處理。簡(jiǎn)單的就是講url當(dāng)做是相對(duì)于www文件夾的文件路徑來查找文件。比如p.to/cgi-bin,其中“/cgi-bin”就會(huì)進(jìn)入file.c文件處理。在www文件夾下尋找cgi-bin。
file.c文件的函數(shù)入口在voiduh_handle_request(struct client *cl);
在這個(gè)函數(shù)中調(diào)用staticbool __handle_file_request(struct client *cl, char *url)來處理請(qǐng)求;
其中又調(diào)用了static struct path_info *uh_path_lookup(struct client*cl, const char *url)函數(shù)來尋找路徑。
其中,在uh_path_lookup()函數(shù)中,當(dāng)url訪問的是一個(gè)目錄,但是url中沒有“/”的時(shí)候會(huì)轉(zhuǎn)跳到302,將url加上“/”

這里面的path_phys[docroot_len]為根目錄,K2中就是www文件夾,默認(rèn)將重定向到根文件夾中。
p.query ? "?" : "",
p.query ? p.query : "");用來提取query信息,也就是url中的查詢信息。
我們可以在這里通過location字段對(duì)url進(jìn)行重定向。

附錄
1幾個(gè)重要的結(jié)構(gòu)體
存放客戶端數(shù)據(jù)的結(jié)構(gòu)體client.

其中,uh_addr結(jié)構(gòu)體

可以用來表示一個(gè)32位的IPv4地址
得到local_addr,就是我們的lanip。

存放http請(qǐng)求和響應(yīng)的字段
2配置參數(shù)讀入
Uhttpd的參數(shù)位于uhttpd.config文件中。在main.c的main函數(shù)中通過while循環(huán)讀入配置參數(shù);


函數(shù)中設(shè)定默認(rèn)初始值。


