71.JAVA编程思想——JAVA与CGI

Java 程序可向一个服务器发出一个CGI 请求,这与HTML 表单页没什么两样。而且和HTML 页一样,这个请求既可以设为GET(下载),亦可设为POST(上传)。除此以外,Java 程序还可拦截CGI 程序的输出,所以不必依赖程序来格式化一个新页,也不必在出错的时候强迫用户从一个页回转到另一个页。事实上,程序的外观可以做得跟以前的版本别无二致。

代码也要简单一些,毕竟用CGI 也不是很难就能写出来(前提是真正地理解它)。所以我们准备办个CGI 编程速成班。为解决常规问题,将用C++创建一些CGI 工具,以便我们编写一个能解决所有问题的CGI 程序。这样做的好处是移植能力特别强——即将看到的例子能在支持CGI 的任何系统上运行,而且不存在防火墙的问题。

这个例子也阐示了如何在程序片(Applet)和CGI 程序之间建立连接,以便将其方便地改编到自己的项目中。

1     C G I 数据的编码

在这个版本中,我们将收集名字和电子函件地址,并用下述形式将其保存到文件中:

First Last<email@domain.com>;

这对任何E-mail 程序来说都是一种非常方便的格式。由于只需收集两个字段,而且CGI 为字段中的编码采用了一种特殊的格式,所以这里没有简便的方法。如果自己动手编制一个原始的HTML 页,并加入下述代码行,即可正确地理解这一点:

<Form method="GET"ACTION="/cgi-bin/Listmgr2.exe">

<P>Name: <INPUT TYPE ="text" NAME = "name"

VALUE = "" size ="40"></p>

<P>Email Address:<INPUT TYPE = "text"

NAME = "email" VALUE ="" size = "40"></p>

<p><input type ="submit" name = "submit" > </p>

</Form>

上述代码创建了两个数据输入字段(区),名为name 和email。另外还有一个submit(提交)按钮,用于收集数据,并将其发给CGI 程序。Listmgr2.exe 是驻留在特殊程序目录中的一个可执行文件。在我们的Web 服务器上,该目录一般都叫作“cgi-bin”。如果在那个目录里找不到该程序,结果就无法出现。填好这个表单,然后按下提交按钮,即可在浏览器的URL 地址窗口里看到象下面这样的内容.

从中可稍微看出如何对数据编码并传给CGI。至少有一件事情能

够肯定——空格是不允许的(因为它通常用于分隔命令行参数)。所有必需的空格都用“+”号替代,每个字段都包含了字段名(具体由HTML 页决定),后面跟随一个“=”号以及正式的字段数据,最后用一个“&”结束。

大家也许会对“+”,“=”以及“&”的使用产生疑惑。假如必须在字段里使用这些字符,那么该如何声明呢?例如,我们可能使用“John & MarshaSmith”这个名字,其中的“&”代表“And”。事实上,它会编码成下面这个样子:

John+%26+Marsha+Smith

也就是说,特殊字符会转换成一个“%”,并在后面跟上它的十六进制ASCII 编码。

幸运的是,Java 有一个工具来帮助我们进行这种编码。这是URLEncoder 类的一个静态方法,名为encode()。可用下述程序来试验这个方法:

1.1     代码

import java.net.*;

public class EncodeDemo {

public staticvoidmain(String[] args){

String s = "";

for (int i = 0; i < args.length; i++)

s += args[i] + " ";

s = URLEncoder.encode(s.trim());

System.out.println(s);

}

} /// :~

该程序将获取一些命令行参数,把它们合并成一个由多个词构成的字串,各词之间用空格分隔(最后一个空格用String.trim()剔除了)。随后对它们进行编码,并打印出来。

为调用一个CGI 程序,程序片要做的全部事情就是从自己的字段或其他地方收集数据,将所有数据都编码成正确的URL 样式,然后汇编到单独一个字串里。每个字段名后面都加上一个“=”符号,紧跟正式数据,再紧跟一个“&”。为构建完整的CGI 命令,我们将这个字串置于CGI 程序的URL 以及一个“?”后。这是调用所有CGI 程序的标准方法。大家马上就会看到,用一个程序片能够很轻松地完成所有这些编码与合并。

2     程序片

程序片实际要比NameSender.java 简单一些。这部分是由于很容易即可发出一个GET 请求。此外,也不必等候回复信息。现在有两个字段,而非一个,但大家会发现许多程序片都是熟悉的,比较NameSender.java。

2.1.1       代码

import java.awt.*;

import java.applet.*;

import java.net.*;

import java.io.*;

public class NameSender2 extends Applet {

final String CGIProgram= "Listmgr2.exe";

Button send = new Button("Add email address to mailing list");

TextField name = new TextField("type your name here", 40),

email = new TextField("type your email address here",40);

String str = new String();

Label l = new Label(), l2 = new Label();

int vcount= 0;

public voidinit() {

setLayout(new BorderLayout());

Panel p = new Panel();

p.setLayout(new GridLayout(3, 1));

p.add(name);

p.add(email);

p.add(send);

add("North", p);

Panel labels = new Panel();

labels.setLayout(new GridLayout(2, 1));

labels.add(l);

labels.add(l2);

add("Center", labels);

l.setText("Ready to send email address");

}

public booleanaction(Event evt,Object arg){

if (evt.target.equals(send)) {

l2.setText("");

// Check for errors in data:

if (name.getText().trim().indexOf(' ') == -1) {

l.setText("Please give first and last name");

l2.setText("");

return true;

}

str = email.getText().trim();

if (str.indexOf(' ')!= -1) {

l.setText("Spaces not allowed in email name");

l2.setText("");

return true;

}

if (str.indexOf(',')!= -1) {

l.setText("Commas not allowed in email name");

return true;

}

if (str.indexOf('@')== -1) {

l.setText("Email name must include '@'");

l2.setText("");

return true;

}

if (str.indexOf('@')== 0) {

l.setText("Name must preceed '@' in email name");

l2.setText("");

return true;

}

String end = str.substring(str.indexOf('@'));

if (end.indexOf('.')== -1) {

l.setText("Portion after '@' must "+ "have an extension, such as'.com'");

l2.setText("");

return true;

}

// Build and encode the email data:

String emailData = "name=" + URLEncoder.encode(name.getText().trim()) + "&email="

+ URLEncoder.encode(email.getText().trim().toLowerCase()) + "&submit=Submit";

// Send the name using CGI's GET process:

try {

l.setText("Sending...");

URL u = new URL(getDocumentBase(), "cgi-bin/"+ CGIProgram+ "?"+ emailData);

l.setText("Sent: " + email.getText());

send.setLabel("Re-send");

l2.setText("Waiting for reply " + ++vcount);

DataInputStream server = new DataInputStream(u.openStream());

String line;

while ((line = server.readLine()) != null)

l2.setText(line);

} catch (MalformedURLException e) {

l.setText("Bad URl");

} catch (IOException e) {

l.setText("IO Exception");

}

} else

return super.action(evt, arg);

return true;

}

} /// :~

CGI 程序的名字是Listmgr2.exe。许多Web 服务器都在Unix 机器上运行(Linux 也越来越

受到青睐)。根据传统,它们一般不为自己的可执行程序采用.exe 扩展名。但在Unix 操作系统中,可以把自己的程序称呼为自己希望的任何东西。若使用的是.exe 扩展名,程序毋需任何修改即可通过Unix 和Win32的运行测试。

和往常一样,程序片设置了自己的用户界面(这次是两个输入字段,不是一个)。唯一显著的区别是在action()方法内产生的。该方法的作用是对按钮按下事件进行控制。名字检查过以后,大家会发现下述代码行:

StringemailData= "name="+ URLEncoder.encode(name.getText().trim()) + "&email="

+ URLEncoder.encode(email.getText().trim().toLowerCase()) + "&submit=Submit";

// Send the name using CGI's GET process:

try {

l.setText("Sending...");

URL u = new URL(getDocumentBase(), "cgi-bin/"+ CGIProgram+ "?"+ emailData);

l.setText("Sent: " + email.getText());

send.setLabel("Re-send");

l2.setText("Waiting for reply " + ++vcount);

DataInputStream server = new DataInputStream(u.openStream());

String line;

while ((line = server.readLine()) != null)

l2.setText(line);

…….

name 和email 数据都是它们对应的文字框里提取出来,而且两端多余的空格都用trim()剔去了。为了进入列表,email 名字被强制换成小写形式,以便能够准确地对比(防止基于大小写形式的错误判断)。来自每个字段的数据都编码为URL 形式,随后采用与HTML 页中一样的方式汇编GET 字串(这样一来,我们可将Java程序片与现有的任何CGI 程序结合使用,以满足常规的HTML GET 请求)。到这时,一些Java 的魔力已经开始发挥作用了:如果想同任何URL 连接,只需创建一个URL 对象,并将地址传递给构建器即可。构建器会负责建立同服务器的连接(对Web 服务器来说,所有连接行动都是根据作为URL 使用的字串来判断的)。就目前这种情况来说,URL 指向的是当前Web 站点的cgi-bin 目录(当前Web站点的基础地址是用getDocumentBase()设定的)。一旦Web 服务器在URL 中看到了一个“cgi-bin”,会接着希望在它后面跟随了cgi-bin 目录内的某个程序的名字,那是我们要运行的目标程序。程序名后面是一个问号以及CGI 程序会在QUERY_STRING 环境变量中查找的一个参数字串。

我们发出任何形式的请求后,一般都会得到一个回应的HTML 页。但若使用Java 的URL 对象,我们可以拦截自CGI程序传回的任何东西,只需从URL 对象里取得一个InputStream(输入数据流)即可。这是用URL 对象的openStream()方法实现,它要封装到一个DataInputStream 里。随后就可以读取数据行,若readLine()返回一个null(空值),就表明CGI 程序已结束了它的输出。我们即将看到的CGI 程序返回的仅仅是一行,它是用于标志成功与否(以及失败的具体原因)的一个字串。这一行会被捕获并置放第二个Label 字段里,使用户看到具体发生了什么事情。

2.1.2       从程序片里显示一个Web 页

程序亦可将CGI 程序的结果作为一个Web 页显示出来,就象它们在普通HTML 模式中运行那样。可用下述代码做到这一点:

getAppletContext().showDocument(u);

其中,u 代表URL 对象。这是将我们重新定向于另一个Web 页的一个简单例子。那个页凑巧是一个CGI 程序的输出,但可以非常方便地进入一个原始的HTML 页,所以可以构建这个程序片,令其产生一个由密码保护的网关,通过它进入自己Web站点的特殊部分:

2.1.3       代码

import java.awt.*;

import java.applet.*;

import java.net.*;

import java.io.*;

public class ShowHTML extends Applet {

static finalString CGIProgram= "MyCGIProgram";

Button send = new Button("Go");

Label l = new Label();

public voidinit() {

add(send);

add(l);

}

public booleanaction(Event evt,Object arg){

if (evt.target.equals(send)) {

try {

// This could be an HTML page instead of

// a CGI program. Notice that this CGI

// program doesn't use arguments, but

// you can add them in the usual way.

URL u = new URL(getDocumentBase(), "cgi-bin/"+ CGIProgram);

// Display the output of the URL using

// the Web browser, as an ordinary page:

getAppletContext().showDocument(u);

} catch (Exception e) {

l.setText(e.toString());

}

} else

return super.action(evt, arg);

return true;

}

} /// :~

URL 类的最大的特点就是有效地保护了我们的安全。可以同一个Web 服务器建立连接,毋需知道幕后的任何东西。

2.2     用C + + 写的C G I 程序

大家应该能够根据例子用ANSI C 为自己的服务器写出CGI 程序。之所以选用ANSI C,是因为它几乎随处可见,是最流行的C 语言标准。当然,现在的C++也非常流行了,特别是采用GNU C++编译器(g++)形式的那一些。可从网上许多地方免费下载g++,而且可选用几乎所有平台的版本(通常与Linux 那样的操作系统配套提供,且已预先安装好)。正如大家即将看到的那样,从CGI 程序可获得面向对象程序设计的许多好处。GNU 的全称是“Gnu's Not Unix”。这最早是由“自由软件基金会”(FSF)负责开发的一个项目,致力于用一个免费的版本取代原有的Unix 操作系统。现在的Linux 似乎正在做前人没有做到的事情。但GNU 工具在Linux 的开发中扮演了至关重要的角色。事实上,Linux 的整套软件包附带了数量非常多的GNU 组件。

为避免第一次就提出过多的新概念,这个程序并未打算成为一个“纯”C++程序;有些代码是用普通C 写成的——尽管还可选用C++的一些替用形式。但这并不是个突出的问题,因为该程序用C++制作最大的好处就是能够创建类。在解析CGI 信息的时候,由于我们最关心的是字段的“名称/值”对,所以要用一个类(Pair)来代表单个名称/值对;另一个类(CGI_vector)则将CGI 字串自动解析到它会容纳的Pair 对象里(作为一个vector),这样即可在有空的时候把每个Pair(对)都取出来。

这个程序同时也非常有趣,因为它演示了C++与Java 相比的许多优缺点。大家会看到一些相似的东西;比如class 关键字。访问控制使用的是完全相同的关键字public 和private,但用法却有所不同。它们控制的是一个块,而非单个方法或字段(也就是说,如果指定private: ,后续的每个定义都具有private 属性,直到我们再指定public:为止)。另外在创建一个类的时候,所有定义都自动默认为private。在这儿使用C++的一个原因是要利用C++“标准模板库”(STL)提供的便利。至少,STL 包含了一个vector类。这是一个C++模板,可在编译期间进行配置,令其只容纳一种特定类型的对象(这里是Pair 对象)。和Java 的Vector不同,如果我们试图将除Pair 对象之外的任何东西置入vector,C++的vector 模板都会造成一个编译期错误;而Java 的Vector 能够照单全收。而且从vector 里取出什么东西的时候,它会自动成为一个Pair 对象,毋需进行造型处理。所以检查在编译期进行,这使程序显得更为“健壮”。此外,程序的运行速度也可以加快,因为没有必要进行运行期间的造型。vector 也会过载operator[],所以可以利用非常方便的语法来提取Pair 对象。vector 模板将在CGI_vector创建时使用;在那时,大家就可以体会到如此简短的一个定义居然蕴藏有那么巨大的能量。

若提到缺点,就一定不要忘记Pair 在下列代码中定义时的复杂程度。与我们在Java 代码中看到的相比,Pair 的方法定义要多得多。这是由于C++的程序员必须提前知道如何用副本构建器控制复制过程,而且要用过载的operator=完成赋值。正如第12 章解释的那样,我们有时也要在Java 中考虑同样的事情。但在C++中,几乎一刻都不能放松对这些问题的关注。

这个项目首先创建一个可以重复使用的部分,由C++头文件中的Pair 和CGI_vector 构成。从技术角度看,确实不应把这些东西都塞到一个头文件里。但就目前的例子来说,这样做不会造成任何方面的损害,而且更具有Java 风格,所以大家阅读理解代码时要显得轻松一些:

2.2.1       头文件代码

#include <string.h>

#include <vector> // STL vector

using namespace std;

// A class to hold a single name-value pairfrom

// a CGI query. CGI_vector holds Pairobjects and

// returns them from its operator[].

class Pair {

char*nm;

char*val;

public:

Pair(){nm = val = 0;}

Pair(char*name, char* value) {

// Creates new memory:

nm= decodeURLString(name);

val= decodeURLString(value);

}

constchar* name() const {return nm;}

constchar* value() const {return val;}

// Test for "emptiness"

boolempty() const {

return(nm == 0) || (val == 0);

}

// Automatic type conversion for booleantest:

operatorbool() const {

return(nm != 0) && (val != 0);

}

// The following constructors &destructor are

// necessary for bookkeeping in C++.

// Copy-constructor:

Pair(constPair& p) {

if(p.nm== 0 || p.val == 0) {

nm= val = 0;

}else {

// Create storage & copy rhs values:

nm= new char[strlen(p.nm) + 1];

strcpy(nm,p.nm);

val= new char[strlen(p.val) + 1];

strcpy(val,p.val);

}

}

// Assignment operator:

Pair&operator=(const Pair& p) {

// Clean up old lvalues:

deletenm;

deleteval;

if(p.nm== 0 || p.val == 0) {

nm= val = 0;

}else {

// Create storage & copy rhs values:

nm= new char[strlen(p.nm) + 1];

strcpy(nm,p.nm);

val= new char[strlen(p.val) + 1];

strcpy(val,p.val);

}

return*this;

}

~Pair(){ // Destructor

deletenm;// 0 value OK

deleteval;

}

// If you use this method outide thisclass,

// you're responsible for calling 'delete'on

// the pointer that's returned:

staticchar*

decodeURLString(constchar* URLstr) {

intlen = strlen(URLstr);

char*result = new char[len + 1];

memset(result,len + 1, 0);

for(inti = 0, j = 0; i <= len; i++, j++) {

if(URLstr[i]== '+')

result[j]= ' ';

elseif(URLstr[i] == '%') {

result[j] =

translateHex(URLstr[i+ 1]) * 16 +

translateHex(URLstr[i+ 2]);

i+= 2; // Move past hex code

}else // An ordinary character

result[j]= URLstr[i];

}

returnresult;

}

// Translate a single hex character; usedby

// decodeURLString():

staticchar translateHex(char hex) {

if(hex>= 'A')

return(hex & 0xdf) - 'A' + 10;

else

returnhex - '0';

}

};

// Parses any CGI query and turns it

// into an STL vector of Pair objects:

class CGI_vector : publicvector<Pair> {

char*qry;

constchar* start; // Save starting position

// Prevent assignment andcopy-construction:

voidoperator=(CGI_vector&);

569

CGI_vector(CGI_vector&);

public:

// const fields must be initialized in theC++

// "Constructor initializerlist":

CGI_vector(char*query) :

start(newchar[strlen(query) + 1]) {

qry= (char*)start; // Cast to non-const

strcpy(qry,query);

Pairp;

while((p= nextPair()) != 0)

push_back(p);

}

// Destructor:

~CGI_vector(){delete start;}

private:

// Produces name-value pairs from the query

// string. Returns an empty Pair whenthere's

// no more query string left:

PairnextPair() {

char*name = qry;

if(name== 0 || *name == '\0')

returnPair(); // End, return null Pair

char*value = strchr(name, '=');

if(value== 0)

returnPair();// Error, return null Pair

// Null-terminate name, move value to start

// of its set of characters:

*value= '\0';

value++;

// Look for end of value, marked by'&':

qry= strchr(value, '&');

if(qry== 0) qry = "";// Last pair found

else{

*qry= '\0'; // Terminate value string

qry++;//Move to next pair

}

returnPair(name, value);

}

}; ///:~

在#include 语句后,可看到有一行是:

using namespace std;

C++中的“命名空间”(Namespace)解决了由Java 的package 负责的一个问题:将库名隐藏起来。std 命名空间引用的是标准C++库,而vector就在这个库中,所以这一行是必需的。

Pair 类表面看异常简单,只是容纳了两个(private)字符指针而已——一个用于名字,另一个用于值。默认构建器将这两个指针简单地设为零。这是由于在C++中,对象的内存不会自动置零。第二个构建器调用方法decodeURLString(),在新分配的堆内存中生成一个解码过后的字串。这个内存区域必须由对象负责管理及清除,这与“破坏器”中见到的相同。name()和value()方法为相关的字段产生只读指针。利用empty()方法,我们查询Pair 对象它的某个字段是否为空;返回的结果是一个bool——C++内建的基本布尔数据类型。

operator bool()使用的是C++“运算符过载”的一种特殊形式。它允许我们控制自动类型转换。如果有一个名为p 的Pair 对象,而且在一个本来希望是布尔结果的表达式中使用,比如if(p){//...,那么编译器能辨别出它有一个Pair,而且需要的是个布尔值,所以自动调用operator bool(),进行必要的转换。接下来的三个方法属于常规编码,在C++中创建类时必须用到它们。根据C++类采用的所谓“经典形式”,我们必须定义必要的“原始”构建器,以及一个副本构建器和赋值运算符——operator=(以及破坏器,用于清除内存)。之所以要作这样的定义,是由于编译器会“默默”地调用它们。在对象传入、传出一个函数的时候,需要调用副本构建器;而在分配对象时,需要调用赋值运算符。只有真正掌握了副本构建器和赋值运算符的工作原理,才能在C++里写出真正“健壮”的类,但这需要需要一个比较艰苦的过程。

只要将一个对象按值传入或传出函数,就会自动调用副本构建器Pair(constPair&)。也就是说,对于准备为其制作一个完整副本的那个对象,我们不准备在函数框架中传递它的地址。这并不是Java 提供的一个选项,由于我们只能传递句柄,所以在Java 里没有所谓的副本构建器(如果想制作一个本地副本,可以“克隆”那个对象——使用clone())。类似地,如果在Java 里分配一个句柄,它会简单地复制。但C++中的赋值意味着整个对象都会复制。在副本构建器中,我们创建新的存储空间,并复制原始数据。但对于赋值运算符,我们必须在分配新存储空间之前释放老存储空间。我们要见到的也许是C++类最复杂的一种情况,但那正是Java 的支持者们论证Java 比C++简单得多的有力证据。在Java中,我们可以自由传递句柄,善后工作则由垃圾收集器负责,所以可以轻松许多。

但事情并没有完。Pair 类为nm 和val 使用的是char*,最复杂的情况主要是围绕指针展开的。如果用较时髦的C++ string 类来代替char*,事情就要变得简单得多(当然,并不是所有编译器都提供了对string 的支持)。那么,Pair 的第一部分看起来就象下面这样:

class Pair {

string nm;

string val;

public:

Pair() { }

Pair(char* name, char* value) {

nm = decodeURLString(name);

val = decodeURLString(value);

}

const char* name() const {return nm.c_str(); }

const char* value() const {

return val.c_str();

}

// Test for"emptiness"

bool empty() const {

return (nm.length() == 0)

|| (val.length() == 0);

}

// Automatic type conversion forboolean test:

operator bool() const {

return (nm.length() != 0)

&& (val.length() != 0);

}

(此外,对这个类decodeURLString()会返回一个string,而不是一个char*)。我们不必定义副本构建器、operator=或者破坏器,因为编译器已帮我们做了,而且做得非常好。但即使有些事情是自动进行的,C++程序员也必须了解副本构建以及赋值的细节。Pair 类剩下的部分由两个方法构成:decodeURLString()以及一个“帮助器”方法translateHex()——将由decodeURLString()使用。注意translateHex()并不能防范用户的恶意输入,比如“%1H”。分配好足够的存储空间后(必须由破坏器释放),decodeURLString()就会其中遍历,将所有“+”都换成一个空格;将所有十六进制代码(以一个“%”打头)换成对应的字符。

CGI_vector 用于解析和容纳整个CGIGET 命令。它是从STL vector 里继承的,后者例示为容纳Pair。C++中的继承是用一个冒号表示,在Java 中则要用extends。此外,继承默认为private 属性,所以几乎肯定需要用到public 关键字,就象这样做的那样。大家也会发现CGI_vector 有一个副本构建器以及一个operator=,但它们都声明成private。这样做是为了防止编译器同步两个函数(如果不自己声明它们,两者就会同步)。但这同时也禁止了客户程序员按值或者通过赋值传递一个CGI_vector。

CGI_vector 的工作是获取QUERY_STRING,并把它解析成“名称/值”对,这需要在Pair 的帮助下完成。它首先将字串复制到本地分配的内存,并用常数指针start 跟踪起始地址(稍后会在破坏器中用于释放内存)。随后,它用自己的nextPair()方法将字串解析成原始的“名称/值”对,各个对之间用一个“=”和“&”符号分隔。这些对由nextPair()传递给Pair 构建器,所以nextPair()返回的是一个Pair 对象。随后用push_back()将该对象加入vector。nextPair()遍历完整个QUERY_STRING 后,会返回一个零值。现在基本工具已定义好,它们可以简单地在一个CGI程序中使用,就象下面这样:

2.2.2       Listmgr2.cpp

#include <stdio.h>

#include "CGITools.h"

const char* dataFile = "list2.txt";

const char* notify = "toaddb@163.com";

#undef DEBUG

// Similar code as before, except that it looks

// for the email name inside of '<>':

int inList(FILE* list, const char* emailName) {

const intBSIZE = 255;

char lbuf[BSIZE];

char emname[BSIZE];

// Put the email name in '<>' so there's no

// possibility of a match within another name:

sprintf(emname, "<%s>", emailName);

// Go to the beginning of the list:

fseek(list, 0, SEEK_SET);

// Read each line in the list:

while (fgets(lbuf, BSIZE, list)) {

// Strip off the newline:

char * newline = strchr(lbuf, '\n');

if (newline != 0)

*newline = '\0';

if (strstr(lbuf, emname) != 0)

return 1;

}

return 0;

}

void main() {

// You MUST print this out, otherwise the

// server will not send the response:

printf("Content-type: text/plain\n\n");

FILE* list = fopen(dataFile, "a+t");

if (list == 0) {

printf("error: could not open database. ");

printf("Notify %s", notify);

return;

}

// For a CGI "GET," the server puts the data

// in the environment variable QUERY_STRING:

CGI_vector query(getenv("QUERY_STRING"));

#if defined(DEBUG)

// Test: dump all names and values

for(int i = 0; i < query.size(); i++) {

printf("query[%d].name()= [%s], ",

i,query[i].name());

printf("query[%d].value()= [%s]\n",

i, query[i].value());

}

#endif(DEBUG)

Pair name = query[0];

Pair email = query[1];

if (name.empty() || email.empty()) {

printf("error: null name or email");

return;

}

if (inList(list, email.value())) {

printf("Already in list: %s", email.value());

return;

}

// It's not in the list, add it:

fseek(list, 0, SEEK_END);

fprintf(list, "%s <%s>;\n", name.value(),email.value());

fflush(list);

fclose(list);

printf("%s <%s> added to list\n",name.value(), email.value());

}///:~

alreadyInList()函数与前一个版本几乎是完全相同的,只是它假定所有电子函件地址都在一个“<>”内。在使用GET 方法时(通过在FORM 引导命令的METHOD 标记内部设置,但这在这里由数据发送的方式控制),Web 服务器会收集位于“?”后面的所有信息,并把它们置入环境变量QUERY_STRING(查询字串)里。所以为了读取那些信息,必须获得QUERY_STRING 的值,这是用标准的C 库函数getnv()完成的。在main()中,注意对QUERY_STRING 的解析有多么容易:只需把它传递给用于CGI_vector 对象的构建器(名为query),剩下的所有工作都会自动进行。从这时开始,我们就可以从query 中取出名称和值,把它们当作数组看待(这是由于operator[]在vector 里已经过载了)。在调试代码中,大家可看到这一切是如何运作的;调试代码封装在预处理器引导命令#if defined(DEBUG)和#endif(DEBUG) 之间。

现在,我们迫切需要掌握一些与CGI 有关的东西。CGI 程序用两个方式之一传递它们的输入:在GET 执行期间通过QUERY_STRING 传递(目前用的这种方式),或者在POST 期间通过标准输入。但CGI 程序通过标准输出发送自己的输出,这通常是用C 程序的printf() 命令实现的。那么这个输出到哪里去了呢?它回到了Web服务器,由服务器决定该如何处理它。服务器作出决定的依据是content-type(内容类型)头数据。这意味着假如content-type 头不是它看到的第一件东西,就不知道该如何处理收到的数据。因此,我们无论如何也要使所有CGI 程序都从content-type 头开始输出。

在目前这种情况下,我们希望服务器将所有信息都直接反馈回客户程序(亦即我们的程序片,它们正在等候给自己的回复)。信息应该原封不动,所以content-type 设为text/plain(纯文本)。一旦服务器看到这个头,就会将所有字串都直接发还给客户。所以每个字串(三个用于出错条件,一个用于成功的加入)都会返回程序片。

我们用相同的代码添加电子函件名称(用户的姓名)。但在CGI 脚本的情况下,并不存在无限循环——程序只是简单地响应,然后就中断。每次有一个CGI 请求抵达时,程序都会启动,对那个请求作出反应,然后自行关闭。所以CPU 不可能陷入空等待的尴尬境地,只有启动程序和打开文件时才存在性能上的隐患。Web 服务器对CGI 请求进行控制时,它的开销会将这种隐患减轻到最低程度。

这种设计的另一个好处是由于Pair 和CGI_vector 都得到了定义,大多数工作都帮我们自动完成了,所以只需修改main()即可轻松创建自己的CGI 程序。尽管小服务程序(Servlet)最终会变得越来越流行,但为了创建快速的CGI 程序,C++仍然显得非常方便。

71.JAVA编程思想——JAVA与CGI相关推荐

  1. 33.JAVA编程思想——JAVA IO File类

    33.JAVA编程思想--JAVA IO File类 RandomAccessFile用于包括了已知长度记录的文件.以便我们能用 seek()从一条记录移至还有一条:然后读取或改动那些记录. 各记录的 ...

  2. 35.JAVA编程思想——JAVA IO StreamTokenizer

    35.JAVA编程思想--JAVA IO StreamTokenizer 尽管StreamTokenizer并不是从 InputStream或 OutputStream衍生的,但它只随同InputSt ...

  3. Java编程思想日志

    Thinking In Java的作者是大牛!做事要站在巨人的肩膀上有助于提高效率和开阔眼界!建议学习java的小伙伴儿有时间可以抽空了解一下,以下内容为读书笔记,比较杂乱,仅供参考,推荐阅读原著: ...

  4. 70.JAVA编程思想——Web应用

    70.JAVA编程思想--Web应用 创建一个应用,令其在真实的Web 环境中运行,它将把Java 的优势表现得淋漓尽致.这个应用的一部分是在Web 服务器上运行的一个Java 程序,另一部分则是一个 ...

  5. 类的包访问权限:《Java编程思想》中一段话的困惑

    类的包访问权限:<Java编程思想>中一段话的困惑 在<java编程思想第三版>(陈昊鹏 饶若楠等译)的第五章隐藏具体实现中,5.4节的最后一段话是: "正如前面所提 ...

  6. 译者招募 | 《Java编程思想》作者Bruce Eckel新作On Java 8

    硅谷创业之父Paul Graham曾在<黑客与画家>中写道,"判断一种语言是否流行的条件是,一种免费的实现,一本相关书籍,以及语言所依附的计算机系统." 当然,Java ...

  7. 《Java编程思想》学习笔记(三)——初始化与清理

    一.初始化 初始化其实就是为变量分配内存空间,并确定其初始值的过程.想了解Java中初始化的各种情况,首先要了解Java中变量的类型.根据自己的理解,将Java中的变量类型分成以下几种,虽然可能不太准 ...

  8. Java中的泛型 --- Java 编程思想

    前言 ​ 我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的.通透理解泛型是学好基础里面中非常重要的.于是,我对<Ja ...

  9. 《Java编程思想》读书笔记 第十三章 字符串

    <Java编程思想>读书笔记 第十三章 字符串 不可变String String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以及包含修 ...

最新文章

  1. 解决报错:Can't read private key和./build-aux/cksum-schema-check: Permission denied
  2. 如何为jframe设置于右侧滑轮_如何为电脑设置屏保密码?
  3. vue项目中使用本地的json文件
  4. python 打包exe出现RuntimeError: Could not find the matplotlib data files 的解决方法
  5. 上传文件的跨域处理(转)
  6. java导出pdf_一张PDF了解JDK11 GC调优秘籍-附PDF下载
  7. eclipse设置工作空间编码为默认utf-8
  8. vue 调用webservice_调用webService的几种方式
  9. 树莓派python调用摄像头拍照
  10. python怎么改路径_python更改路径
  11. java随机生成随机整数_java生成随机整数
  12. 软件版本GA、RC、beta等含义
  13. 3D刷脸支付推动新零售行业发展
  14. Python·os.path.abspath和os.path.realpath区别
  15. 征服微信小程序视频教程-李宁-专题视频课程
  16. 基于springboot搭建的个人博客系统
  17. mysql 字符集 性能_MySQL字符集不一致导致性能下降25%,你敢信?
  18. AJAX框架眼镜穿搭夏天,20套夏天穿搭!我帮你整理出来了
  19. linux 谷歌浏览器设置代理_浏览器自带代理服务器配置脚本
  20. Borland 賣掉 CodeGear

热门文章

  1. HAProxy入门(一)
  2. 2020年起重机械指挥模拟试题及起重机械指挥模拟考试题
  3. 演练VC中的common一族
  4. [总结]CDQ分治整体二分
  5. 如何进行接口测试(一篇学会)
  6. git switch 与 git checkout的不同
  7. 大专计算机专业一般是几年制,大专需要读几年
  8. 数据结构——栈的应用
  9. 数据加密与安全专题《mbedtls工具篇,实用教程1@mbedtls简介和安装教程》
  10. 操作系统 | Mac如何更新word中的域