Perl & Python編寫CGI

近期偶然玩了一下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的數據輸入主要是環境變量,如下這個連接有一串定義,

     CGI Environment Variables

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()



參考: Perl CGI Input Test


三. 開發語言

可以寫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。可以作個參考。


Perl版本號

#!/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";
}


Python版本號

#!/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腳本加上可運行權限)

   CGI Programming 101 (Perl)

   Debugging CGI Programs (Perl)

   Python CGI編程

   Python CGI Debugging

相關文章
相關標籤/搜索