如果你對WEB服務器編程和LINUX環境下PERL CGI編程感興趣,建議你閱讀下面內容,即使沒有CGI開發經驗,只要了解perl語言,就不會感覺困難。由于我第1次寫作Blog文章,如有錯誤請指出,謝謝。
HTTP消息分為2種,客戶機發送給服務器的HTTP請求以及服務器回送給客戶機的HTTP響應.
這2者都由 一個必不可少的頭(由一些要求的和許多可選的頭行組成) + 一個可選的主體(要傳輸的文檔) 構成.以下是獲取頭和主體的各種方法。
GET:從服務器獲取頭和主體
GET /index.html.HTTP/1.1
Host:www.myserver.com
HEAD:只獲得資源頭
HEAD /index.html.HTTP/1.1
Host:www.myserver.com
PUT:在HTTP主體中發送信息給服務器
PUT /doc/newdoc.html HTTP/1.1
Host:www.myserver.com
Content-length:2043
<html>
...........................
POST:在HTTP主體中發送信息給服務器
POST /index.html.HTTP/1.1
Host:www.myserver.com
Content-Length:23
Roses=red&violets=blue
TRACE:跟蹤一個HTTP請求---響應交換,不是一個用于正常請求的方法
TRACE * HTTP/1.1
Host:www.myserver.com
DELETE:出于安全考慮,一般服務器不支持此方法
DELETE:/doc/olddoc.html HTTP/1.1
host:www.myserver.com
POST用于發送CGI腳本這樣的服務器應用程序將處理,以創建資源(例如在服務器上的進程,為每個用戶創建進程也成為人們指責CGI的重要原因)的內容,URL指明了數據發送的應用程序,比如如果表單數據超過了256個字符,將不能用GET方法,而只能使用POST。
PUT指示正在創建一個由URL描述的新資源。URL描述了新資源,并且可以同隨后的GET請求一起來檢索它。
HTTP響應: HTTP狀態行(HTTP協議,響應碼) + 描述響應類型的3位數 + 響應的文本描述
Eg: HTTP/1.1 200 OK
Eg: HTTP/1.1 404 NOT FOUND
HTTP頭:任意,客戶與服務器使用頭相互通信 (#頭第1字母大寫,:后需有空格)
GET /~unixdb/test.html HTTP1.1 #狀態行
Connection: Keep-Alive
User-Agent: Mozilla/4.75 [en] (X11;u;Linux 2.2.17 i686)
Host: www.myserver.com
Aclearcase/" target="_blank" >ccept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, text/html, */*
Accept-Encoding: gzip
Accept-Language: en
Accept-Charset: iso-8859-1,*,utf-8
這樣,一個典型的GET請求的服務器響應:
HTTP1/1 200 OK #HTTP status
Date: Tue,7 Sep 2004 23:35 GMT #HTTP Header
Server: Apache /1.3.12(Unix) mod_ssl/2.6.4 OpenSSL/0.9.5a
Connection: close
Content-Type: text/html
<Title>testing</Title>
testing
HTTP主體:PUT和POST方法需要主體,非可選。在Perl中,主體與頭使用\n\n(正式分割符序列是\012\015\012\015) 分開
Eg:
my $request=<>;
my (@headers,$body);
while(<>)
{
push @headers,$_ if 1../^$/;
$body.=$_ if /^$/..eof; #means $body=$body . $_;
}
對照:CGI響應是沒有狀態行的HTTP響應,被要求發送的頭有一個是Content-Type頭,后面跟著一個介質類型,以定義主體內容。
Eg1: print “Content-Type: text/plain\n\n“;
Eg2: print “Content-Type: image/jpeg\n\n”;
CGI環境變量我們可以參考
my $docroot=$ENV {'DOCUMENT_ROOT'};
例如我們可以這樣查看我們的環境變量:
#!/usr/bin/perl -w
#author:nick
#goal:check the ENV
#env.cgi
use strict;
print "content-type: text/html \n\n";
print "<html><title>Enviroment Variables</title><head>\n";
print "Here is your Enviroment Variables</head>\n" ;
print "<body><blockquote><table border=1>\n";
foreach (sort keys %ENV)
{
print "\t<tr><td> $_ </td><td>$ENV{$_} </td></tr>\n";
}
print "</table></blockquote></body></html>\n";
#run the program
#you can see:
#CONTENT-LENGTH: the length of HTTP requirement,here it should display 0,for we
#did not put or post mainbody here
#GATEWAY-INTERFACE: cgi protocol and version
#HTTP-REFERER: URI of resource
#HTTP_USER_AGENT: client's software,maybe IE or Netscape
#PATH: client's additional path
#QUERY_STRING: requesting URI's query-string, maybe null or important for CGI
#programs
#REMOTE_ADDR: client's IP
#REMOTE_HOST: client's host name
#REQUEST_METHOD : the HTTP method of request,maybe "get" or "post"
#SCRIPT_FILENAME: script's File Path
#SCRIPT_NAME: script's URL
#SERVER_NAME :server's host name
#SERVER_SOFTWARE: web server listening request
Perl 創建HTTP頭和HTML頭的過程:
面向對象: print $cgi->header (print $cgi->header('image/jpeg')) print $cgi->start_html(“my cgipage“);
面向過程: print header;(print header('image/jpeg')) print start_html(“my cgipage“);
高級頭:
print $cgi->header(-status=>'200 Nistal',
-type=>'text/html',
-expires=>'+30s',
-nph=>1,
);
產生的頭如下:
HTTP/1.0 200 Nistal
Status:200 Nistal
Expires:Mon 25 Dec 2005 4:35 GMT
Date:Mon 25 Dec 2005 4:15 GMT
Content-Type: text/html; charset=ISO-8859-1
參數列表:
-status 響應代碼和消息
-expires截止時間或日期
-nph 切換到非解析頭模式 腳本輸出產生時被直接發送給客戶機,服務器不干預
為了使輸出非緩沖,我們可以設置$|=1來激活autoflush模式;也可以把nph作為導入標記來傳遞激活nph模式
use CGI qw(:standard'nph');
高級Document頭:
#! /usr/bin/perl
#headertag.cgi
use warnings;
use strict;
#import invented 'link' and 'myheadertag' elements
use CGI qw(:standard link myheadertag);
print header;
print start_html(
-title=>'Big Document Header',
-author=>'cloudfordnick@yahoo.com.cn',
-xbase=>'http://www.myserver.com', #文檔的基本URL
-target=>'my_panel', #文檔目標楨
-meta=>{ #指向元標記的名字/值對的一個哈希引用
description=>'How to define a CGI header with Metatags',
keywords=>'meta,metadata,cgi,tags,html,perl',
},
-style=>{
src=>'/css/mystylesheet.css'
},
};
CGI模塊自動為我們處理URL大部分轉義和非轉義字符
print “<a href=“,$cgi->escape($unescaped_url).'?'.$cgi->escape($key).'='.$cgi->escape($value),“>Link</a>“;
URL方法僅僅返回腳本的URL
$cgi->url(-full=>1) # http://myserver/path/script/nistal
$cgi->url(-absolute=>1) #absolute path /home/sites/cgi/script/nistal
$cgi->url(-relative=>1) #relative URL /cgi/script/nistal
保存和加載CGI狀態
保存: if(open(STATE , “> $state“))
{
$cgi->save(STATE);
close STATE;
}
加載: if(open(STATE,$state))
{
$cgi->new CGI(STATE);
close STATE;
}
以下是簡單的CGI腳本:
1.一個簡單的Web服務器,為由$docroot聲明的跟目錄外的頁面服務。它僅僅處理GET請求,并且如果他們同聲明的URL相匹配的話將文檔回送給客戶機
#! /usr/local/perl -w
#httpd.pl
use warnings;
use HTTP::Daemon;
use HTTP::Status; #for RC_FORBIDDEN
my $docroot= “/home/httpd/html“;
my $httpd= new HTTP::Daemon;
print “Server running at :“,$httpd->url(),“\n“;
while(my $connection=$httpd->accept)
{
while(my $request=$connection->get_request)
{
if($request->method eq 'GET')
{
my $file=$request->url->path;
$connection->send_file_response(“/$docroot/$file“);
}
else{
$connection->send_error(RC_FORBIDDEN);
}
$connection->close;
}
undef($connection);
}
HTTP::Daemon對象從IO::Socket::INET模塊繼承,我們可以在其上進行SOCKET操作。daemon對象掃描本地主機以獲取一個可能的名字,并選擇一個端口號服務 Eg:在Unix服務器上
Server running at: http://localhost.localdomain:1640/
通過瀏覽器指向服務器發送簡單請求后,創建Daemon對象,并由其接收呼叫,等待連接,當客戶機連接后,返回一個連接對象。檢索將GET請求翻譯成一個路徑的URI,最后我們在連接對象上調用send_file_response將請求文檔回送客戶機,if not found,發送404 Not Found響應,if 是目錄,發送501 Not Implemented錯誤。
列舉HTTPD::Daemon的方法:
1.new 創建新服務器
Eg: $httpd=new HTTPD:: Daemon(
LocalAddr=>'www.myserver.com',
Localport=>80,
);
2.$httpd->accept 接受來自客戶機連接請求,返回HTTP::Daemon::ClientCoon對象
3.$httpd->url 由后臺程序處理的主機和端口名 http://server:port/
4.$conn->get_request 讀取來自客戶機的HTTP請求并返回一個HTTP::request對象。能接受塊傳輸和使用multipart/form-data編碼的文檔上載。一旦頭 被讀取就讓get_request返回,然后就可以使用read_buffer來成塊讀數據。
5.$conn->read_buffer
6.$conn->reason 將get_request失敗的原因返回
7.$conn->send_status_line 發送一個HTTP響應狀態行給客戶機
Eg1: $conn->send_status_line(RC_NOT_FOUND);
#generate standard '404' response
Eg2: $conn->send_status_line(404,“It wasn't there!“);
#Eg2 has the same effect as Eg1 has
8.$conn->send_file_response 試圖打開,讀和發送文檔內容給客戶機
9.$conn->send_file 試圖發送傳遞的文檔句柄內容給客戶機
2.一個簡單的服務器推計數器
服務器推:不斷的用新的信息來更新客戶機
#! /usr/bin/perl -w
#nph-push.cgi
#use strict
use CGI::Push qw(:standard);
my $cgi=new CGI::Push ;
$cgi->do_push(-next_page=> \$refresh);
sub refresh{
my ($cgi,$count) = @_; #passed in by CGI::Push
my $page=start_html(“CGI PUSH DEMO“)
.p(“The count is $count“)
.end_html;
returen $page;
}
CGI::PUSH應用程序使用do_push方法注冊一個子程序,調用該子程序以在每一次循環產生頁面內容。CGI::PUSH跟蹤記數并傳遞給子程序
如果do-push 注冊的子程序回送一個未定義值作為其結果來選擇結束更新和終止HTTP響應。
#! /usr/bin/perl -w
#nph-pushlast.cgi
use warnings;
use strict;
use CGI::Push;
my $cgi=new CGI::Push;
$cgi->do_push(-next_page=> \$refresh,-last_page=>\$done );
sub refresh{
my ($cgi,$count)=@_; #passed in by CGI::Push
return undef if $count==10;
return $cgi->start_html,$cgi->p(“The count is $count“),$cgi->end_html;
}
sub done{
my ($cgi,$count)=@_;
return $cgi->start_html,“Count stopped on $count“,$cgi->end_html;
}
模塊在內部把這個列表發送給print,所以print將接受的任何東西是-next_page或-last_page子程序的一個正確返回值