近期偶然玩了一下CGI,收集點資料寫篇在這裏留檔。html
現在想作HTTP Cache迴歸測試了,爲了模擬不一樣的響應頭及數據大小。就需要一個CGI按需要傳回指定的響應頭和內容。這是從老外的測試頁面學習到的經驗。python
CGI事實上就是使用STDIN和環境變量做爲輸入, STDOUT作爲輸出。按照Http協議生成相應的數據。web
數據輸出遵循HTTP協議,分爲四部分:apache
狀態行 (Status Line):編程
200 OK緩存
響應頭(Response Headers):服務器
Content-Type: text/htmlcookie
Content-Length: 3072網絡
空白行(表明響應頭已經列完, 僅僅能包括回車和換行符):dom
數據(文本或二進制數據):
<html>xxxxxx</html>
參考: HTTP Protocol
狀態行和響應頭中每一行都必須以\r\n(回車及換行)結束。使用這個規則,以簡單的文本組裝就可以完畢響應了。
僅僅要注意二進制數據輸出方法就可以了。 嘗試設定Content-Length時可能沒辦法設定的準確,至少對於Apache Server是這樣,只是不影響最後返回的數據量。
CGI的數據輸入主要是環境變量,如下這個連接有一串定義,
Key |
Value |
DOCUMENT_ROOT |
The root directory of your server |
HTTP_COOKIE |
The visitor's cookie, if one is set |
HTTP_HOST |
The hostname of the page being attempted |
HTTP_REFERER |
The URL of the page that called your program |
HTTP_USER_AGENT |
The browser type of the visitor |
HTTPS |
"on" if the program is being called through a secure server |
PATH |
The system path your server is running under |
QUERY_STRING |
The query string (see GET, below) |
REMOTE_ADDR |
The IP address of the visitor |
REMOTE_HOST |
The hostname of the visitor (if your server has reverse-name-lookups on; otherwise this is the IP address again) |
REMOTE_PORT |
The port the visitor is connected to on the web server |
REMOTE_USER |
The visitor's username (for .htaccess-protected pages) |
REQUEST_METHOD |
GET or POST |
REQUEST_URI |
The interpreted pathname of the requested document or CGI (relative to the document root) |
SCRIPT_FILENAME |
The full pathname of the current CGI |
SCRIPT_NAME |
The interpreted pathname of the current CGI (relative to the document root) |
SERVER_ADMIN |
The email address for your server's webmaster |
SERVER_NAME |
Your server's fully qualified domain name (e.g. www.cgi101.com) |
SERVER_PORT |
The port number your server is listening on |
SERVER_SOFTWARE |
The server software you're using (e.g. Apache 1.3) |
當你需要CGI處理POST請求時,CGI就要使用STDIN作爲輸入來接收數據了。
在Perl裏使用如下的代碼讀取:
use CGI;
my $cgi = CGI->new();
my %params = $cgi->Vars();
而在Python則是:
import cgi
cgi.FieldStorage()
可以寫CGI的開發語言太多,如下附上兩個分別使用Perl和Python編寫的一樣功能的CGI, 正好可以作個對照。
這兩個腳本可以接收Query String, 而後返回不一樣的文件。數據大小,以及緩存相關的頭信息,可以區分二進制數據和文本數據。
CGI腳本支持以Query String改動如下響應頭:
content type,
cache control,
content length (僅僅返回相應大小的數據),
last modified,
expires
如下是返回一個指定大小圖片的樣例:
/cgi/cache_factory.pl?type=image&size=32&last-modified=Fri, 02 Apr 2014 02:34:06 GMT&cache-control=private,max-age=60&expires=Fri, 22 Apr 2014 02:34:06 GMT
代碼很是easy。可以作個參考。
#!/usr/bin/perl use strict; use warnings; use CGI; use constant BUFFER_SIZE => 4_096; use constant DATA_DIRECTORY => "/var/www/Cache"; my $cgi = CGI->new(); my %params = $cgi->Vars(); &parserCommonHeaders; if(exists $params{'type'} && $params{'type'}=="image"){ &generateImageData; } else{ &generateTextData; } sub parserCommonHeaders{ if(exists $params{'cache-control'}){ print 'Cache-Control:',$params{'cache-control'},"\r\n"; } if(exists $params{'last-modified'}){ print 'Last-Modified:',$params{'last-modified'},"\r\n"; } if(exists $params{'expires'}){ print 'Expires:',$params{'expires'},"\r\n"; } if(exists $params{'etag'}){ print 'ETag:ea6186e11526ce1:0',"\r\n"; } } sub generateImageData{ my $buffer = ""; my $targetSize = 100*1024*1024; if(exists $params{'size'} && $params{'size'}>0){ $targetSize = 1024*$params{'size'}; print "Content-length: $targetSize \r\n"; } my $image = DATA_DIRECTORY .'/images/very_big.jpg'; my( $type ) = $image =~ /\.(\w+)$/; $type eq "jpg" and $type == "jpeg"; print $cgi->header( -type => "image/$type", -expires => "-1d" ); binmode STDOUT; local *IMAGE; open IMAGE, $image or die "Cannot open file $image: $!"; my $sentSize = 0; while ( read( IMAGE, $buffer, BUFFER_SIZE ) ) { print $buffer; $sentSize += BUFFER_SIZE; if($sentSize>=$targetSize){ last; } } close IMAGE; } sub generateTextData{ my $startHeader = '<html><head><title>HTTP Cache Testing HTML</title></head><body>'; my $tailPart = '</body></html>'; if(exists $params{'type'}){ print "Content-type:$params{'type'}\r\n"; } else{ print "Content-type:text/html\r\n"; } my $targetTextSize = 100*1024*1024; if(exists $params{'size'} && $params{'size'}>0){ $targetTextSize = 1024*$params{'size'}; print "Content-length: $targetTextSize \r\n"; } print "\r\n"; $targetTextSize -= length($startHeader) + length($tailPart); print "$startHeader"; my $filepath = DATA_DIRECTORY .'/files/big_text.txt'; open(FILE, $filepath) or die $!; my @lines = <FILE>; close(FILE); foreach my $line (@lines) { if( length($line)<=$targetTextSize ){ print $line; } else{ print substr($line,0,$targetTextSize); } $targetTextSize -= length($line); if($targetTextSize<=0){ last; } } print "$tailPart"; }
#!/usr/bin/python import os import cgi import sys BUFFER_SIZE = 4096 DATA_DIRECTORY = "/var/www/Cache" def parserCommonHeaders(formQuery): if('cache-control' in formQuery.keys()): print 'Cache-Control:',formQuery['cache-control'].value,"\r\n", if('last-modified' in formQuery.keys()): print 'Last-Modified:',formQuery['last-modified'].value,"\r\n", if('expires' in formQuery.keys()): print 'Expires:',formQuery['expires'].value,"\r\n", if('etag' in formQuery.keys()): print 'ETag:ea6186e11526ce1:0',"\r\n", def generateImageData(formQuery): targetSize = 100*1024*1024; if('size' in formQuery.keys()): targetSize = 1024*int(formQuery['size'].value) print "Content-length:",targetSize,"\r\n", image = DATA_DIRECTORY+'/images/very_big.jpg' print "Content-Type:image/jpeg\r\n", print sentSize = 0 f = open(image, 'rb') while True: data = f.read(4096) sentSize = sentSize + BUFFER_SIZE sys.stdout.write(data) if sentSize>targetSize: break sys.stdout.flush() close(f) def generateTextData(formQuery): startHeader = '<html><head><title>HTTP Cache Testing HTML</title></head><body>' tailPart = '</body></html>' targetTextSize = 2.3*1024*1024; if('size' in formQuery.keys()): targetTextSize = 1024*int(formQuery['size'].value) print "Content-length:",targetTextSize,"\r\n", if('type' in formQuery.keys()): print "Content-type: %s\r\n"%(formQuery['type'].value), else: print "Content-type: text/html\r\n", print print startHeader targetTextSize = targetTextSize - len(startHeader) - len(tailPart) filepath = DATA_DIRECTORY + '/files/big_text.txt' file = open(filepath) lines = file.readlines() file.close() for line in lines: if( len(line) <= targetTextSize ): print line else: print line[0:targetTextSize] targetTextSize = targetTextSize - len(line) if(targetTextSize<=0): break print tailPart if __name__ =="__main__": formQuery = cgi.FieldStorage() #os.environ['QUERY_STRING'] parserCommonHeaders(formQuery) if('type' in formQuery.keys() and formQuery['type'].value=="image"): generateImageData(formQuery) else: generateTextData(formQuery)
服務器端使用Apache Server+WANem, 配合CGI完畢靈活的需求,開通SFTP端口供相關同窗編輯。方便共享測試用例。
.-----. .-------.
| CGI | | WANem |
'-----'---'-------'
| Apache Server |
'-----------------'
^
|
SFTP & HTTP
|
.------------------.
| Web Page Editor |
| and Browser |
'------------------'
配有WANem最大的優勢就是可以依據需求進網絡狀態調整, 甚至可以用類似如下的方式在測試用例動態調整。
Apache配置 (不要忘記給CGI腳本加上可運行權限)