1. 程式人生 > >一個支援 cgi 的簡易 http 伺服器

一個支援 cgi 的簡易 http 伺服器

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

  • 錯誤可能有如下情況:

    1. 檔案沒有可執行許可權
    2. 頭部與報文體沒有用一個空行隔開
    3. 程式內部某個地方有執行錯誤
  • 一個 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 的區別

    1. http 協議中
      post:長度沒有規定上限。用於修改伺服器資料
      get: 長度沒有規定上限。用於獲取伺服器資料
      method 與 data 沒有關係,兩者是相互獨立的概念
    2. html 協議中
      post:約定請求引數放在報文體中,長度隨瀏覽器以及伺服器的不同而不同
      get: 約定請求引數放在 url 或者 cookie 中,長度隨瀏覽器以及伺服器的不同而不同。有的伺服器接受 get 方式含有 body,但是瀏覽器不會這樣請求
      兩種方式的安全性沒有本質的區別,都是可以通過一定方式加密,但是也都可以人為進行截獲解密,至於 get 的請求引數放在 url 中可能就比 post 更容易獲取吧,後者還要開除錯介面
  • send recv 函式

    1. send
      send 與 write 唯一的區別就是 send 的第四個引數:flag
      檢查 socket 傳送緩衝區,如果 buf 大於整個傳送緩衝區,返回錯誤
      如果 buf 小於傳送緩衝區剩餘空間長度,就把 buf 內容拷貝到傳送緩衝區中
      如果傳送緩衝區剩餘空間一直小於 buf 長度,函式會阻塞等待
      在 send 過程中網路斷開會導致程序終止
    2. recv
      如果 socket 傳送緩衝區裡面有資料,等待發送緩衝區空
      如果接收緩衝區空或者正在接收,阻塞等待資料到來
      在接收過程中網路斷開會導致程序終止
  • 伺服器 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 則是每一個環境變數都重新分配一次空間,即使引數不是一個字串常量也會重新為其分配空間**


  • 在設計編寫程式的時候遇到的問題:
  1. url 請求過長,伺服器是否做了限制以及處理
  2. 報文體過長,伺服器是否做了限制以及處理
  3. 連線成功建立了,但是瀏覽器資料發生了延遲或者丟失,伺服器是否會永遠等待,造成程式死掉
  4. send 資料但是瀏覽器沒有接收到,或者是協議並沒有成功傳送資料導致緩衝區滿,那麼下一次 send 就會阻塞等待,而這種等待永遠持續下去怎麼處理
  5. 在資料傳送接收過程中出現斷網現象,程式會不會出現卡死或者崩潰