系列文章:
自己動(dòng)手寫http服務(wù)器(一) -- UNIX C 網(wǎng)絡(luò)編程
自己動(dòng)手寫http服務(wù)器(二) -- http協(xié)議分析
自己動(dòng)手寫http服務(wù)器(三) -- 代碼實(shí)現(xiàn)
要編寫一個(gè) http 服務(wù)器,第一步就是分析 http 協(xié)議格式,之后才能對發(fā)送過來的http數(shù)據(jù)包進(jìn)行正常解析,并返回正確的數(shù)據(jù)包;
Http協(xié)議包的格式
首先,讓我們用 netcat 捕獲瀏覽器發(fā)送給服務(wù)器的數(shù)據(jù)包,來見一見其廬山真面目。
(1)捕捉 http 協(xié)議的數(shù)據(jù)包
通過命令:
nc -l 127.0.0.1 8888 > http.data
開啟本地的 8888 號端口,在瀏覽器中輸入 url 地址 http://127.0.0.1:8888 ,瀏覽器將會(huì)發(fā)送給一個(gè)Get請求給nc,nc將接收到的數(shù)據(jù)寫入文件 http.data , 接收到的內(nèi)容如下:
00000000: 4745 5420 2f20 4854 5450 2f31 2e31 0d0a GET / HTTP/1.1..
00000010: 486f 7374 3a20 3132 372e 302e 302e 313a Host: 127.0.0.1:
00000020: 3838 3838 0d0a 436f 6e6e 6563 7469 6f6e 8888..Connection
00000030: 3a20 6b65 6570 2d61 6c69 7665 0d0a 5570 : keep-alive..Up
00000040: 6772 6164 652d 496e 7365 6375 7265 2d52 grade-Insecure-R
00000050: 6571 7565 7374 733a 2031 0d0a 5573 6572 equests: 1..User
00000060: 2d41 6765 6e74 3a20 4d6f 7a69 6c6c 612f -Agent: Mozilla/
00000070: 352e 3020 2858 3131 3b20 4c69 6e75 7820 5.0 (X11; Linux
00000080: 7838 365f 3634 2920 4170 706c 6557 6562 x86_64) AppleWeb
00000090: 4b69 742f 3533 372e 3336 2028 4b48 544d Kit/537.36 (KHTM
000000a0: 4c2c 206c 696b 6520 4765 636b 6f29 2043 L, like Gecko) C
000000b0: 6872 6f6d 652f 3539 2e30 2e33 3037 312e hrome/59.0.3071.
000000c0: 3131 3520 5361 6661 7269 2f35 3337 2e33 115 Safari/537.3
000000d0: 360d 0a41 6363 6570 743a 2074 6578 742f 6..Accept: text/
000000e0: 6874 6d6c 2c61 7070 6c69 6361 7469 6f6e html,application
000000f0: 2f78 6874 6d6c 2b78 6d6c 2c61 7070 6c69 /xhtml+xml,appli
00000100: 6361 7469 6f6e 2f78 6d6c 3b71 3d30 2e39 cation/xml;q=0.9
00000110: 2c69 6d61 6765 2f77 6562 702c 696d 6167 ,image/webp,imag
00000120: 652f 6170 6e67 2c2a 2f2a 3b71 3d30 2e38 e/apng,*/*;q=0.8
00000130: 0d0a 4163 6365 7074 2d45 6e63 6f64 696e ..Accept-Encodin
00000140: 673a 2067 7a69 702c 2064 6566 6c61 7465 g: gzip, deflate
00000150: 2c20 6272 0d0a 4163 6365 7074 2d4c 616e , br..Accept-Lan
00000160: 6775 6167 653a 207a 682d 434e 2c7a 683b guage: zh-CN,zh;
00000170: 713d 302e 382c 6c61 3b71 3d30 2e36 2c64 q=0.8,la;q=0.6,d
00000180: 613b 713d 302e 340d 0a0d 0a a;q=0.4....
左側(cè)是接收的數(shù)據(jù)原始二進(jìn)制流,右側(cè)是對應(yīng)的ASCII碼;
可見瀏覽器默認(rèn)使用的http協(xié)議是 HTTP/1.1,其頭信息肯定是文本(ASCII編碼);
(2)捕捉 https 協(xié)議的數(shù)據(jù)包
通過命令:
nc -l 127.0.0.1 8888 > https.data
在瀏覽器中輸入 url 地址 https://127.0.0.1:8888 ,即可獲得瀏覽器發(fā)送給服務(wù)器的數(shù)據(jù),內(nèi)容如下:
00000000: 1603 0100 c2ae 0100 00c2 aa03 0328 6fc2 .............(o.
00000010: a227 0f31 c392 c388 c392 42c2 8dc2 9dc2 .'.1......B.....
00000020: 8275 c297 2324 c38f 484d 75c2 8b23 5bc2 .u..#$..HMu..#[.
00000030: aac3 98c3 a17c 0d70 6cc2 be00 001c 2a2a .....|.pl.....**
00000040: c380 2bc3 802f c380 2cc3 8030 c38c c2a9 ..+../..,..0....
00000050: c38c c2a8 c380 13c3 8014 00c2 9c00 c29d ................
00000060: 002f 0035 000a 0100 0065 c2aa c2aa 0000 ./.5.....e......
00000070: c3bf 0100 0100 0017 0000 0023 0000 000d ...........#....
00000080: 0014 0012 0403 0804 0401 0503 0805 0501 ................
00000090: 0806 0601 0201 0005 0005 0100 0000 0000 ................
000000a0: 1200 0000 1000 0e00 0c02 6832 0868 7474 ..........h2.htt
000000b0: 702f 312e 3175 5000 0000 0b00 0201 0000 p/1.1uP.........
000000c0: 0a00 0a00 086a 6a00 1d00 1700 182a 2a00 .....jj......**.
000000d0: 0100 0a ...
可見,https協(xié)議的頭信息是二進(jìn)制數(shù)據(jù)流而非文本;
本文只對 http1.1 協(xié)議進(jìn)行分析;
(3)分析 http Get請求數(shù)據(jù)包
瀏覽器發(fā)送到服務(wù)器端的請求數(shù)據(jù)為:
GET / HTTP/1.1
Host: 127.0.0.1:8888
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,la;q=0.6,da;q=0.4
對于HTTP報(bào)文來說,第一行為報(bào)文的起始行,格式為
<method> <request-URL> <version>
每個(gè)字段用空格分隔;
在該例子中, method 為 GET ,request-URL 為 / ,version 為 HTTP/1.1 ;
在這里,因?yàn)槲覀冎皇窃跒g覽器中輸入一個(gè)ip地址及端口號,默認(rèn)的請求資源為 / ;
如果在瀏覽器中輸入 http://127.0.0.1:8888/xxx/yy?name=abc&age=23 則 request-URL 的值將是 * /xxx/yy?name=abc&age=23 * ;
(4)捕獲 http Post 請求數(shù)據(jù)包
下面我們來捕獲以下Post的請求包,看看其與Get請求包的不同;
首先,我們創(chuàng)建一個(gè)html文件,文件地址為 :
/home/hbfeng/Code/Year2017/Mon07/Day19/x.html
文件內(nèi)容為:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<form action="http://127.0.0.1:8888" method="POST">
color:<input type="text" name="color">
<input type="submit" value="提交" />
</form>
</body>
</html>
之后,開啟服務(wù)器 :
nc -l 127.0.0.1 8888 > post.dat
在瀏覽器中輸入 :
file:///home/hbfeng/Code/Year2017/Mon07/Day19/x.html
可以出現(xiàn)如下頁面,在文本框中填入內(nèi)容,點(diǎn)擊 提交 即可獲得一個(gè)Post數(shù)據(jù)包:

Post數(shù)據(jù)包的內(nèi)容如下:
POST / HTTP/1.1
Host: 127.0.0.1:8888
Connection: keep-alive
Content-Length: 12
Cache-Control: max-age=0
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,la;q=0.6,da;q=0.4
color=yellow
與Get請求的數(shù)據(jù)相比,Post數(shù)據(jù)包多出了以下我們后續(xù)編寫代碼時(shí)需要使用的內(nèi)容:
Content-Length: 12: 表示HTTP正文的大小。POST請求將數(shù)據(jù)以URL編碼的形式放在HTTP正文中,字段形式為 fieldname=value,用&分隔每個(gè)字段;HTTP信息頭與HTTP正文之間有一行空行;
HTTP中有表單內(nèi)容
color=yellow,正好等于Content-Length的長度;
服務(wù)器的工作流程
知道了瀏覽器給我們發(fā)送的數(shù)據(jù)格式以后,我們的http服務(wù)器就可以將數(shù)據(jù)包進(jìn)行解析,并動(dòng)態(tài)生成頁面發(fā)送給瀏覽器;
服務(wù)器的大致工作流程如下圖所示:

反饋給客戶端的數(shù)據(jù)格式
知道了服務(wù)器的運(yùn)行流程,我們需要知道瀏覽器希望從服務(wù)器端得到什么格式的數(shù)據(jù);
服務(wù)器按照HTTP協(xié)議返回?cái)?shù)據(jù)給客戶端,如響應(yīng)碼為400,返回的內(nèi)容為:
HTTP/1.0 400 BAD REQUEST
Content-type: text/html
<!DOCTYPE>
<html>
<!-- html內(nèi)容 -->
... ...
</html>
每一行最后都跟 \r\n ,表示一行的結(jié)束;
第一行的3個(gè)參數(shù)用空格隔開,第一個(gè)參數(shù)說明服務(wù)器所使用的http協(xié)議為 HTTP/1.0 ,第二個(gè)參數(shù)是一個(gè)返回碼,第三個(gè)參數(shù)是對返回碼的解釋;
第二行聲明的是http正文內(nèi)容的類型,text/html 表示正文是一個(gè)html文件的內(nèi)容,瀏覽器將其解釋并顯示,如果是 text/plain 表示正文是純文本,瀏覽器直接將內(nèi)容顯示,不需要解釋、渲染等操作;
之后的空行表示http信息頭結(jié)束;
空行之后的內(nèi)容即為http正文;
動(dòng)態(tài)生成Web頁面技術(shù)
CGI (Common Gateway Interface,通用網(wǎng)關(guān)接口) :一種重要的互聯(lián)網(wǎng)技術(shù),是指根據(jù)瀏覽器發(fā)送過來的請求,服務(wù)器執(zhí)行一定的動(dòng)作,如數(shù)據(jù)庫查詢、系統(tǒng)信息查詢等,甚至可以讓服務(wù)器刪除某些文件,之后生成對應(yīng)的內(nèi)容返回給瀏覽器以顯示執(zhí)行結(jié)果;
可以將CGI理解為通過瀏覽器就可以讓服務(wù)器執(zhí)行某些在服務(wù)器端已經(jīng)定義好的功能,實(shí)現(xiàn)遠(yuǎn)程調(diào)用 ;
CGI是這種技術(shù)的定義,而其實(shí)現(xiàn)方式多種多樣,如 Perl 是一個(gè)廣泛被用來編寫CGI程序的語言,另外,像 Python、Ruby、C/C++、PHP 等也可以實(shí)現(xiàn)CGI,甚至是 Shell腳本 文件也能勝任該任務(wù);
CGI可以用任何一種語言編寫,只要這種語言具有標(biāo)準(zhǔn)輸入、輸出和環(huán)境變量。
在下一篇中,我們就來根據(jù)上面的流程圖來實(shí)現(xiàn)一個(gè)小型的http服務(wù)器;
參考
HTTP/1.1 Header Field Definitions
完!