CGI編程學習

原文轉自:http://blog.csdn.net/kaloha3/article/details/8548027


一.基本原理

CGI:通用網關接口(Common Gateway Interface)是一個Web服務器主機提供信息服務的標準接口。通過CGI接口,Web服務器就能夠獲取客戶端提交的信息,轉交給服務器端的CGI程序進行處理,最後返回結果給客戶端。

組成CGI通信系統的是兩部分:一部分是html頁面,就是在用戶端瀏覽器上顯示的頁面。另一部分則是運行在服務器上的Cgi程序。

它們之間的通訊方式如下圖:

服務器

客戶端

CGI程序

HTTP通信

標準輸入輸出

(環境變量)

       服務器和客戶端之間的通信,是客戶端的瀏覽器和服務器端的http服務器之間的HTTP通信,我們只需要知道瀏覽器請求執行服務器上哪個CGI程序就可以了,其他不必深究細節,因爲這些過程不需要程序員去操作。

       服務器和CGI程序之間的通訊纔是我們關注的。一般情況下,服務器和CGI程序之間是通過標準輸入輸出來進行數據傳遞的,而這個過程需要環境變量的協作方可實現。

 

1.    服務器將URL指向一個應用程序

2.    服務器爲應用程序執行做準備

3.    應用程序執行,讀取標準輸入和有關環境變量

4.    應用程序進行標準輸出

 

對於Windows系統而言,還可以通過profile文件進行數據傳輸(如ini文件),但在

這裏不做研究。

環境變量在CGI中有着重要的地位!每個CGI程序只能處理一個用戶請求,所以在激

活一個CGI程序進程時也創建了屬於該進程的環境變量。

 

二.環境變量

       對於CGI程序來說,它繼承了系統的環境變量。CGI環境變量在CGI程序啓動時初始化,在結束時銷燬。

       當一個CGI程序不是被HTTP服務器調用時,它的環境變量幾乎是系統環境變量的複製。

當這個CGI程序被HTTP服務器調用時,它的環境變量就會多了以下關於HTTP服務器、客戶端、CGI傳輸過程等項目。

 

與請求相關的環境變量

REQUEST_METHOD

服務器與CGI程序之間的信息傳輸方式

QUERY_STRING

採用GET時所傳輸的信息

CONTENT_LENGTH

STDIO中的有效信息長度

CONTENT_TYPE

指示所傳來的信息的MIME類型

CONTENT_FILE

使用Windows HTTPd/WinCGI標準時,用來傳送數據的文件名

PATH_INFO

路徑信息

PATH_TRANSLATED

CGI程序的完整路徑名

SCRIPT_NAME

所調用的CGI程序的名字

與服務器相關的環境變量

GATEWAY_INTERFACE

服務器所實現的CGI版本

SERVER_NAME

服務器的IP或名字

SERVER_PORT

主機的端口號

SERVER_SOFTWARE

調用CGI程序的HTTP服務器的名稱和版本號

與客戶端相關的環境變量

REMOTE_ADDR

客戶機的主機名

REMOTE_HOST

客戶機的IP地址

ACCEPT

例出能被次請求接受的應答方式

ACCEPT_ENCODING

列出客戶機支持的編碼方式

ACCEPT_LANGUAGE

表明客戶機可接受語言的ISO代碼

AUTORIZATION

表明被證實了的用戶

FORM

列出客戶機的EMAIL地址

IF_MODIFIED_SINGCE

當用get方式請求並且只有當文檔比指定日期更早時才返回數據

PRAGMA

設定將來要用到的服務器代理

REFFERER

指出連接到當前文檔的文檔的URL

USER_AGENT

客戶端瀏覽器的信息

       CONTENT_TYPE:application/x-www-form-urlencoded,表示數據來自HTML表單,並且經過了URL編碼。

ACCEPT:客戶機所支持的MIME類型清單,內容如:”image/gif,image/jpeg”

 

REQUEST_METHOD:它的值一般包括兩種:POSTGET,但我們寫CGI程序時,最後還要考慮其他的情況。

1POST方法

如果採用POST方法,那麼客戶端來的用戶數據將存放在CGI進程的標準輸入中,同時將用戶數據的長度賦予環境變量中的CONTENT_LENGTH。客戶端用POST方式發送數據有一個相應的MIME類型(通用Internet郵件擴充服務:Multi-purpose Internet Mail Extensions)。目前,MIME類型一般是:application/x-wwww-form-urlencoded,該類型表示數據來自HTML表單。該類型記錄在環境變量CONTENT_TYPE中,CGI程序應該檢查該變量的值。

2GET方法

在該方法下,CGI程序無法直接從服務器的標準輸入中獲取數據,因爲服務器把它從標

準輸入接收到得數據編碼到環境變量QUERY_STRING(或PATH_INFO)。

GETPOST的區別:採用GET方法提交HTML表單數據的時候,客戶機將把這些數

據附加到由ACTION標記命名的URL的末尾,用一個包括把經過URL編碼後的信息與CGI程序的名字分開:http://www.mycorp.com/hello.htmlname=hgq$id=1QUERY_STRING的值爲name=hgq&id=1

有些程序員不願意採用GET方法,因爲在他們看來,把動態信息附加在URL的末尾有

URL的出發點:URL作爲一種標準用語,一般是用作網絡資源的唯一定位標示。

 

環境變量是一個保存用戶信息的內存區。當客戶端的用戶通過瀏覽器發出CGI請求時,服務器就尋找本地的相應CGI程序並執行它。在執行CGI程序的同時,服務器把該用戶的信息保存到環境變量裏。接下來,CGI程序的執行流程是這樣的:查詢與該CGI程序進程相應的環境變量:第一步是request_method,如果是POST,就從環境變量的len,然後到該進程相應的標準輸入取出len長的數據。如果是GET,則用戶數據就在環境變量的QUERY_STRING裏。

3POSTGET的區別

        GET方式接收的數據是有長度限制,而用 POST方式接收的數據是沒有長度限制的。並且,以 GET方式發送數據,可以通過 URL的形式來發送,但 POST方式發送的數據必須要通過 Form纔到發送。

三.CGI程序實現步驟

1.從服務器獲取數據

C語言實現代碼:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

int get_inputs()

{

int length;

char *method;

char *inputstring;

 

method = getenv(“REQUEST_METHOD”); //將返回結果賦予指針

if(method == NULL)

    return 1;       //找不到環境變量REQUEST_METHOD

if(!strcmp(method, ”POST”)) // POST方法

{

    length = atoi(getenv(“CONTENT_LENGTH”)); //結果是字符,需要轉換

    if(length != 0)

    {

        inputstring = malloc(sizeof(char)*length + 1) //必須申請緩存,因爲stdin是不帶緩存的。

        fread(inputstring, sizeof(char), length, stdin); //從標準輸入讀取一定數據

}

}

else if(!strcmp(method, “GET”))

{

    Inputstring = getenv(“QUERY_STRING”);   

    length = strlen(inputstring);

}

if(length == 0)

return 0;

}

Perl實現代碼:

$method = $ENV{‘REQUEST_METHOD’};

if($method eq ‘POST’)

{

    Read(STDIN, $input, $ENV{‘CONTENT_LENGTH’});

}

if($method eq ‘GET’ || $method eq ‘HEAD’)

{

    $input = $ENV{‘QUERY_STRING’};

}

if($input eq “”)

{

&print_form;

exit;

}

       PYTHON代碼實現

#!/usr/local/bin/python

import cgi

def main():

form = cgi.FieldStorage()

 

Python代碼實現更簡單,cgi.FieldStorage()返回一個字典,字典的每一個key就是變量名,key對應的值就是變量名的值,更本無需用戶再去進行數據解碼!

 

       獲取環境變量的時候,如果先判斷“REQUEST_METHOD”是否存在,程序會更健壯,否則在某些情況下可能會造成程序崩潰。因爲假若CGI程序不是由服務器調用的,那麼環境變量集裏就沒有與CGI相關的環境變量(如REQUEST_METHODREMOTE_ADDR等)添加進來,也就是說“getenv(“REQUEST_METHOD”)”將返回NULL

2URL編碼

不管是POST還是GET方式,客戶端瀏覽器發送給服務器的數據都不是原始的用戶數據,而是經過URL編碼的。此時,CGI的環境變量Content_type將被設置,如Content_type = application/x-www-form-urlencode就表示服務器收到的是經過URL編碼的包含有HTML表單變量數據。

編碼的基本規則是:

變量之間用“&”分開;

變量與其對應值用“=”連接;

空格用“+”代替;

保留的控制字符則用“%”連接對應的16禁止ASCII碼代替;

某些具有特殊意義的字符也用“%”接對應的16進制ASCII碼代替;

空格是非法字符;

任意不可打印的ASCII控制字符均爲非法字符。

例如,假設3HTML表單變量filenamee-mailcomments,它們的值對應分別爲hello[email protected]I’ll be there for you,則經過URL編碼後應爲:

 

filename=hello&[email protected]&comments=I%27ll+be+there+for+you

 

 

所以,CGI程序從標準輸入或環境變量中獲取客戶端數據後,還需要進行解碼。解碼的過程就是URL編碼的逆變:根據“&”和“=”分離HTML表單變量,以及特殊字符的替換。

在解碼方面,PYTHON代碼實現是最理想的,cgi.FieldStorage()函數在獲取數據的同時就已自動進行代碼轉換了,無需程序員再進行額外的代碼編寫。Perl其次,因爲在一個現成的Perl庫:cgi-lib.pl中提供了ReadParse函數,用它來進行URL解碼很簡單:

require ‘cgi-lib.pl’;

&ReadParse(*input);

 

3CGI數據輸出

CGI程序如何將信息處理結果返回給客戶端?這實際上是CGI格式化輸出。

CGI程序中的標準輸出stdout是經過重定義了的,它並沒有在服務器上產生任何的輸出內容,而是被重定向到客戶瀏覽器,這與它是由C,還是PerlPython實現無關。

所以,我們可以用打印來實現客戶端新的HTML頁面的生成。比如,Cprintf是向該進程的標準輸出發送數據,PerlPythonprint向該進程的標準輸出發送數據。

(1)    CGI標題

CGI的格式輸出內容必須組織成標題/內容的形式。CGI標準規定了CGI程序可以使用

的三個HTTP標題。標題必須佔據第一行輸出!而且必須隨後帶有一個空行。

標題

描述

Content_type  (內容類型)

設定隨後輸出數據所用的MIME類型

Location   (地址)

設定輸出爲另外一個文檔(URL

Status     (狀態)

指定HTTP狀態碼

 

MIME

向標準輸出發送網頁內容時要遵守MIME格式規則:

任意輸出前面必須有一個用於定義MIME類型的輸出內容(Content-type)行,而且隨後還必須跟一個空行。如果遺漏了這一條,服務將會返回一個錯誤信息。(同樣使用於其他標題)

例如PerlPython

print “Content-type:text/html\n\n”;  //輸出HTML格式的數據

print “<body>welcome<br>”

print “</body>”

C語言:

printf( “Content-type:text/html\n\n”);

printf(“Welcome\n”);

 

MIME類型以類型/子類型(type/subtype)的形式表示。

其中type表示一下幾種典型文件格式的一種:

TextAudioVideoImageApplicationMutipartMessage

Subtype則用來描述具體所用的數據格式。

Application/msword

微軟的Word文件

Application/octet-stream

一種通用的二進制文件格式

Application/zip

Zip壓縮文件

Application/pdf

Pdf文件

。。。。。。。。。。。。。。。。。。。。。。。。。。

。。。。。。。。。。。。。。。。。。。。。。。。。

 

Location

使用Location標題,一個CGI可以使當前用戶轉而訪問同一服務器上的另外一個程序,甚至可以訪問另外一個URL,但服務器對他們的處理方式不一樣。

使用Location的格式爲:LocationFilename/URL,例如:

print “Location:/test.html\n\n”;

這與直接鏈接到test.html的效果是一樣的。

 

print “Location:http://www.chinaunix.com/\n\n”

由於該URL並不指向當前服務器,用戶瀏覽器並不會直接鏈接到指定的URL,而是給用戶輸出提示信息。

 

 

HTTP狀態碼:

       表示了請求的結果狀態,是CGI程序通過服務器用來通知用戶其請求是否成功執行的信息碼,本文不做研究。

四.CGI中的信號量和文件鎖

       因爲CGI程序時公用的,而WEB服務器都支持多進程運行,因此可能會發生同時有多個用戶訪問同一個CGI程序的情況。比如,有2個用戶幾乎同時訪問同一個CGI程序,服務器爲他們創建了2CGI程序進程,設爲進程A和進程B。假如進程A首先打開了某個文件,然後由於某種原因被掛起(一般是由於操作系統的進程調度);而就在進程A被掛起的這段時間內,進程B完成了對文件的整個操作流程:打開,寫入,關閉;進程A再繼續往下執行,但進程A所操作的文件依舊是原來文件的就版本,此時進程A的操作結果將覆蓋進程B的操作結果。

爲了防止這種情況發生,需要用到文件鎖或者信號量。

鑰匙文件?

 

假如有多個不同的HTML可以調用同一個CGI程序,那麼CGI程序如何區分它們呢?一個是通過隱含的INPUT標籤。不過覺得這個比較麻煩,因爲CGI必須經過一系列解碼後才能找到這個隱含INPUT的變量和其值。

五.設置HTTP服務器以兼容CGI

       Perl編寫的CGI程序後綴爲:.plPython編寫的CGI程序後綴爲:.py;而C編寫的CGI程序後綴爲:.cgi,如果在win下編譯出來的是.exe,最好將它重命名爲.cgi。這些都是爲了HTTP服務能夠識別並調用它們。

       當使用appche httpd服務器時,請編輯它的配置文件httpd.conf如下:

       修改AddHandler cgi-script一句爲AddHandler cgi-script .cgi .py .pl

六.關於CGI的C語言庫——cgihtml

       Cgihtml是一個應用非常廣泛的C語言編寫的CGI庫。它提供的功能函數如下:

       Read_cgi_input():獲取並解析HTML表單輸入,返回一個指向某結構體的指針

       Cgi_val():獲取每個表單變量的值

       Html_header():輸出HTML標題欄

       Html_begin():輸出HTML文檔的開始部分

       H1():輸出一行字符,字體爲H1

Html_end():輸出HTML文檔的結尾部分。

#include “cgi-lib.h”

#include “html-lib.h”

#include “string-lib.h”

六.後話

有的人認爲可以用JavaScript來代替CGI程序,這其實是一個概念上的錯誤。JavaScript只能夠在客戶瀏覽器中運行,而CGI卻是工作在服務器上的。他們所做的工作有一些交集,比如表單數據驗證一類的,但是JavaScript是絕對無法取代CGI的。但可以這樣說,如果一項工作即能夠用JavaScript來做,又可以用CGI來做,那麼絕對要使用JavaScript,在執行的速度上,JavaScriptCGI有着先天的優勢。只有那些在客戶端解決不了的問題,比如和某個遠程數據庫交互,這時就應該使用CGI了。

 

 

SSI:一種用來動態輸出HTML文本的特殊程序。

網頁裏包含有某個變量,提交給服務器後,只有該變量改變。此時我們希望服務器不要把整個頁面內容都發送過來,而只需要告訴客戶端的瀏覽器,哪個變量的值便成什麼樣了,瀏覽器會自動更新。

SSI在服務器端運行。

SSI不需要外部接口,它不像CGI從標準輸入接收信息。

你瀏覽你的HTML文檔時看不到SSI標記,因爲它已經被相應的程序輸出所替代。

所有的SSI命令都是嵌入在普通的HTML註釋行中的。當服務器無法解釋SSI時,它將不解釋並直接把文檔傳給瀏覽器,由於命令在註釋中,故瀏覽器將忽略它們。而當服務器識別SSI時,它並不將該命令傳給瀏覽器,相反,服務器將從上到下掃描HTML文檔,執行每一個嵌入註釋的命令,並將命令的執行結果代替原註釋。

<! –註釋文本-- >。服務器將根本不查看註釋,除非已啓動SSI

與純註釋不同的是,所有的SSI命令都是以#打頭。

<! --#command tagname = “parameter”-- >,command指出服務器做什麼,tagname指出參數類型,parameter是該命令的用戶定義值。

The current date is<! --#echo var = “DATE.LOCAL”-- >,服務器將向瀏覽器輸出時間。

 

 

七、CGI編程入門--GET與POST示例

 

 


關於CGI的編程,我也還是新手!但只要懂C,則基於C的CGI編程就不會很難!
下面就GET和POST方法的應用,做一個小小的demo,給剛學習CGI編程的新手提供一點感性認識!

 

 


GET方法:做一個加法運算,需要接收兩個參數 

 

文件get.c如下:
 
-------------------------------
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
        char *data;
        char a[10],b[10];
        printf("Content-Type:text/html\n\n");
        printf("<HTML>\n");
        printf("<HEAD>\n<TITLE >Get Method</TITLE>\n</HEAD>\n");
        printf("<BODY>\n");
        printf("<div style=\"font-size:12px\">\n");
        data = getenv("QUERY_STRING");
        if(sscanf(data,"a=%[^&]&b=%s",a,b)!=2){
                printf("<DIV STYLE=\"COLOR:RED\">Error parameters should be entered!</DIV>\n");
        }
        else{
               printf("<DIV STYLE=\"COLOR:GREEN; font-size:15px;font-weight:bold\">a + b = %d</DIV>\n",atoi(a)+atoi(b));
        }
        printf("<HR COLOR=\"blue\" align=\"left/" width=\"100\">");
        printf("<input type=\"button\" value=\"Back CGI/" onclick=\"javascript:window.location='../cgi.html'/">");
        printf("
\n");
        printf("</BODY>\n");
        printf("</HTML>\n");
        return 0;
}
 


POST方法:做一個乘法運算,需要接收兩個參數 

 

文件post.c如下:
--------------------------------

#include <stdio.h>
#include <stdlib.h>
int main(void){
        int len;
        char *lenstr,poststr[20];
        char m[10],n[10];
        printf("Content-Type:text/html\n\n");
        printf("<HTML>\n");
        printf("<HEAD>\n<TITLE >post Method</TITLE>\n</HEAD>\n");
        printf("<BODY>/n");
        printf("<div style= \"font-size:12px\">\n");
        lenstr=getenv("CONTENT_LENGTH");
        if(lenstr == NULL)
                printf("<DIV STYLE=\"COLOR:RED\">Error parameters should be entered!</DIV>\n");
        else{
                len=atoi(lenstr);
                fgets(poststr,len+1,stdin);
                if(sscanf(poststr,"m=%[^&]&n=%s",m,n)!=2){
                        printf("<DIV STYLE=\"COLOR:RED\">Error: Parameters are not right!</DIV>\n");
                }
                else{
                       printf("<DIV STYLE=\"COLOR:GREEN; font-size:15px;font-weight:bold\">m * n = %d</DIV>\n",atoi(m)*atoi(n));
                }
        }
        printf("<HR COLOR=\"blue\" align=\"left\" width=\"100\">");
        printf("<input type=\"button\" value=\"Back CGI\" onclick=\"javascript:window.location='../cgi.html'\">");
        printf("\n");
        printf("</BODY>\n");
        printf("</HTML>\n");
        fflush(stdout);
        return 0;
}


 

再附上html測試文件cgi.html:
--------------------------------
<html>
<head>
<title>CGI Testing</title>
</head>
<body>
<table width="200" height="180" border="0" style="font-size:12px">
<tr><td>
<div style="font-weight:bold; font-size:15px">Method: GET
<div>please input two number:<div>
<form method="get" action="./cgi-bin/get">
<input type="txt" size="3" name="a">+
<input type="txt" size="3" name="b">=
<input type="submit" value="sum">
</form>
</td></tr>
<tr><td>
<div style="font-weight:bold; font-size:15px">Method: POST
<div>please input two number:<div>
<form method="post" action="./cgi-bin/post">
<input type="txt" size="3" name="m">*
<input type="txt" size="3" name="n">=
<input type="submit" value="resu">
</form>
</td></tr>
<tr><td><inputtype="button" value="Back Home"onclick='javascript:window.location="./index.html"'></td></tr>
</table>
</body>
</html>

 


簡要說明:

 


(1) printf("Content-Type:text/html/n/n");
此行通過標準輸出將字符串″Contenttype:text/plain/n/n″傳送給Web服務器。它是一個MIME頭信息,它告訴Web服務器隨 後的輸出是以純ASCII文本的形式。請注意在這個頭信息中有兩個換行符,這是因爲Web服務器需要在實際的文本信息開始之前先看見一個空行。

(2) data = getenv("QUERY_STRING");
CGI定義:當GET方法提交的表單被髮送到服務器斷後,表單中的數據被保存在服務器上一個叫做QUERY_STRING的環境變量中。這種表單的處理相對簡單,只要讀取環境變量就可以了。

(3) sscanf(data,"a=%[^&]&b=%s",a,b)!=2
這個是關於sscanf函數的使用問題,自己可以上網搜索一下,這裏不再詳述!

(4)atoi(a)+atoi(b)
atoi函數的功能是將字符型成整型,只有轉換之後纔可以進行加法運算!

(5) lenstr=getenv("CONTENT_LENGTH");
Web服務器在調用使用POST方法的CGI程序時設置此環境變量,它的文本值表示Web服務器傳送給CGI程序的輸入中的字符數目,因此需要使用函數atoi() 將此環境變量的值轉換成整數,並賦給變量len(下面有定義)。

(6) fgets(poststr,len+1,stdin);
這個是關於fgets函數的使用問題,自己可以上網搜索一下,這裏不再詳述!

 

其他cgi相關的博客:

c語言寫CGI程序:http://blog.csdn.net/wxhlinux/article/details/8541902

 cgi的ppt教程: http://download.csdn.net/detail/kaloha3/5034831


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章