一個支援 cgi 的簡易 http 伺服器
阿新 • • 發佈:2019-02-03
1. boa 移植以及使用測試
1. 下載 boa-0.94.14rc21.tar.bz2
2. 編譯安裝
- 解壓:tar -xjf boa-0.94.14rc21.tar.bz2
- 配置:./configure –host=arm-linux –prefix=$PWD/tmp
- 編譯:make
- 去掉冗餘資訊:
ls src/boa -l -rwxr-xr-x 1 book book 233741 2016-08-14 15:34 src/boa arm-linux-strip src/boa -rwxr-xr-x 1 book book 72896 2016-08-14 15:37 src/boa
3. 配置
cd examples/ cp boa.conf boa.conf_ok
- 修改 boa.conf_ok
# User: The name or UID the server should run as.
# Group: The group name or GID the server should run as.
# 修改為 root 使用者,表示 boa 使用 root 使用者
User root
Group root
# ServerName: the name of this server that should be sent back to
# clients if different than that returned by gethostname + gethostbyname
# 去掉註釋即可
118 行:ServerName www.your.org.here
# DocumentRoot: The root directory of the HTML documents.
# Comment out to disable server non user files.
# 改變為網頁存放位置
159 行:DocumentRoot /www
# ScriptAlias: Maps a virtual path to a directory for serving scripts
# Example: ScriptAlias /htbin/ /www/htbin/
# 修改 CGI 檔案存放的位置
ScriptAlias /cgi-bin/ /www/cgi-bin/
- 開發板:
mkdir -p /var/log/boa /* 日誌檔案都存放在這裡 */ mkdir /www /* 網頁檔案存放在這裡 */ mkdir /www/cgi-bin /* cgi 檔案存放的位置 */ mkdir /etc/boa /* boa.conf 檔案放在這裡 */ cp boa.conf_ok /etc/boa/boa.conf /* 拷貝配置檔案 */ cp mime.types /etc/ cp boa /sbin/ /* 拷貝二進位制檔案 */
- 在 www 目錄下面編輯 index.html 檔案,內容如下
<html>
<head>
<title>
BOA test
</title>
</head>
<body>
Hellow world!
</body>
</html>
- 在 /www/cgi-bin 目錄下面編輯 cgi-test.c 檔案,內容如下
#include <stdio.h>
int main()
{
printf("Content-type: text/html\n\n");
printf("<html>\n");
printf("<head>\n");
printf("<title>CGI Output</title>\n");
printf("</head>\n");
printf("<body>");
printf("<h1> Hellow, world. </h1>");
printf("</body>");
printf("</html>\n");
return 0;
}
arm-linux-gcc -o cgi-test.cgi cgi-test.c
- 修改 /etc/init.d/rcS
新增 /sbin/boa 以開機啟動 boa
4. 測試
- 在本機瀏覽器裡面輸入:
http://192.168.2.100/cgi-bin/cgi-test.cgi
可以看到Hellow, world.
在本機瀏覽器裡面輸入:http://192.168.2.100/
可以看到Hellow, world.
2. 自己寫 http 伺服器
1、tiny-httpd 註釋
int main(void) { 初始化一個 socket ,獲得套接字描述符,繫結到埠,監聽 while(1){ // 迴圈體,接受連線 阻塞等待客戶端的連線,一旦有連線就獲得客戶端的地址以及套接字描述符等資訊 建立執行緒迴應客戶端的請求 } 關閉伺服器埠 返回 } /**********************************************************************/ /* 相應請求執行緒 */ /* 處理來自客戶端的請求資訊 */ /* 引數: 連線的客戶端的 socket */ /**********************************************************************/ void accept_request(int client) { 獲取第一行發來的資料 獲取方式 "GET" or "POST" 獲取請求的資源連結 如果是 "GET" 方式的話,獲取資源的引數,截斷資源連結緩衝區 獲取資源的完整路徑 獲取資源的檔案描述,fstat ,如果失敗,說明不存在這個檔案 如果成功,判斷資原始檔的屬性,是資料夾的話加上 index.html 如果是檔案有可執行許可權的話就作為 cgi 檔案處理,否則的話作為 html 檔案處理 處理完畢,關閉客戶端 } /**********************************************************************/ /* 傳送檔案到客戶端. 如果有錯誤發生的話就向客戶端報告錯誤 * 引數: 客戶端 socket 描述符 * 檔案路徑名 */ /**********************************************************************/ void serve_file(int client, const char *filename) { 讀取剩下的客戶端發來的資料流 開啟檔案 返回響應頭部資訊 傳送請求的檔案 關閉檔案 } /**********************************************************************/ /* Execute a CGI script. Will need to set environment variables as * appropriate. * Parameters: client socket descriptor * path to the CGI script */ /**********************************************************************/ void execute_cgi(int client, const char *path, const char *method, const char *query_string) { 如果是 "GET" 方式,就讀取剩下的資料 如果是 "POST" 方式,搜尋 "Content-Length:" 字串獲取報文實體的長度 傳送響應請求的頭部資訊 建立 cgi 輸入輸出管道 fork 本程序,主程序中返回是非0,子程序返回0 子程序設定環境變數,執行 cgi 程式 主程序接收剩下的 while (read(cgi_output[0], &c, 1) > 0) 傳送標準輸出結果到客戶端 }
2. 一個錯誤
502 Bad Gateway
The CGI was not CGI/1.1 compliant
錯誤可能有如下情況:
- 檔案沒有可執行許可權
- 頭部與報文體沒有用一個空行隔開
- 程式內部某個地方有執行錯誤
一個 html 檔案的請求與回覆過程
GET /index.html HTTP/1.1
Host: 192.168.2.100:39681
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2824.2 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
HTTP/1.0 200 OK
Server: jdbhttpd/0.1.0
Content-Type: text/html
<HTML>
<TITLE>Index</TITLE>
<BODY>
<P>Welcome to J. David's webserver.
<H1>CGI demo
<FORM ACTION="color.cgi" METHOD="POST">
Enter a color: <INPUT TYPE="text" NAME="color">
<INPUT TYPE="submit">
</FORM>
</BODY>
</HTML>
- 一個 cgi 檔案的請求與回覆過程
POST /color.cgi HTTP/1.1
Host: 192.168.2.100:39681
Connection: keep-alive
Content-Length: 6
Cache-Control: max-age=0
Origin: http://192.168.2.100:39681
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2824.2 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://192.168.2.100:39681/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
HTTP/1.0 200 OK
Content-type: text/html
<html>
<head>
<title>CGI Output</title>
</head>
<body><h1> Hello, world. </h1></body></html>
HTTP/1.1 200 OK
Date: Thu, 01 Jan 1970 04:55:14 GMT
Server: Boa/0.94.14rc21
Accept-Ranges: bytes
Connection: Keep-Alive
Keep-Alive: timeout=10, max=999
Content-Length: 6119
Last-Modified: Thu, 01 Jan 1970 05:01:06 GMT
Content-Type: text/plain
3. 程式流程:
- 主程式
int main(int argc, char *argv[]) { 格式初始化 準備環境:設定使用者,當前目錄 建立伺服器 socket fork 主程序,主程序退出,留下子程序往下執行 while(1){ 等待客戶端連線 若成功,執行響應函式 HandleRequest 失敗,進入下一輪等待 } }
- 響應
void HandleRequest(int iClientSocketFd) { 獲取客戶端的請求頭 如成功,返回響應報文 關閉 iClientSocketFd }
4. 開發過程中遇到的重要的知識點
post 與 get 的區別
- http 協議中
post:長度沒有規定上限。用於修改伺服器資料
get: 長度沒有規定上限。用於獲取伺服器資料
method 與 data 沒有關係,兩者是相互獨立的概念 - html 協議中
post:約定請求引數放在報文體中,長度隨瀏覽器以及伺服器的不同而不同
get: 約定請求引數放在 url 或者 cookie 中,長度隨瀏覽器以及伺服器的不同而不同。有的伺服器接受 get 方式含有 body,但是瀏覽器不會這樣請求
兩種方式的安全性沒有本質的區別,都是可以通過一定方式加密,但是也都可以人為進行截獲解密,至於 get 的請求引數放在 url 中可能就比 post 更容易獲取吧,後者還要開除錯介面
- http 協議中
send recv 函式
- send
send 與 write 唯一的區別就是 send 的第四個引數:flag
檢查 socket 傳送緩衝區,如果 buf 大於整個傳送緩衝區,返回錯誤
如果 buf 小於傳送緩衝區剩餘空間長度,就把 buf 內容拷貝到傳送緩衝區中
如果傳送緩衝區剩餘空間一直小於 buf 長度,函式會阻塞等待
在 send 過程中網路斷開會導致程序終止 - recv
如果 socket 傳送緩衝區裡面有資料,等待發送緩衝區空
如果接收緩衝區空或者正在接收,阻塞等待資料到來
在接收過程中網路斷開會導致程序終止
- send
伺服器 cgi 支援
伺服器設定的環境變數:
SERVER_SOFTWARE SERVER_NAME GATEWAY_INTERFACE SERVER_PROTOCOL SERVER_PORT REQUEST_METHOD PATH_INFO PATH_TRANSLATED SCRIPT_NAME QUERY_STRING /* 重要,對 get 方式來說不可缺少 */ REMOTE_HOST REMOTE_ADDR AUTH_TYPE REMOTE_USER REMOTE_IDENT HTTP_COOKIE /* 瀏覽器 cookie */ CONTENT_TYPE /* 重要,對 cgi post 方式不可缺少 */ CONTENT_LENGTH /* 重要,對 cgi post 方式不可缺少 */
- putenv 與 setenv 的區別
putenv 可以使用已有的變數,不給此變數所指向的內容重新分配空間,例:
char strTmp[256];
putenv(strTmp); /* 這裡使用的是 strTmp,並沒有重新分配空間,如果多次使用 strTmp 會以第一個為準 */
也可以為常量字串分配空間,例:
putenv(“QUERY_STRING=action=add?name=1”); /* 這裡為常量重新分配了空間 */
s**etenv 則是每一個環境變數都重新分配一次空間,即使引數不是一個字串常量也會重新為其分配空間**
- 在設計編寫程式的時候遇到的問題:
- url 請求過長,伺服器是否做了限制以及處理
- 報文體過長,伺服器是否做了限制以及處理
- 連線成功建立了,但是瀏覽器資料發生了延遲或者丟失,伺服器是否會永遠等待,造成程式死掉
- send 資料但是瀏覽器沒有接收到,或者是協議並沒有成功傳送資料導致緩衝區滿,那麼下一次 send 就會阻塞等待,而這種等待永遠持續下去怎麼處理
- 在資料傳送接收過程中出現斷網現象,程式會不會出現卡死或者崩潰