您的位置:首页 > 编程语言 > Java开发

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

2016-05-08 21:27 429 查看
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
staticvoid
main(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
void
init() {

        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
boolean
action(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
final
String CGIProgram=
"MyCGIProgram";

    Button send =
new Button("Go");

    Label l =
new
Label();

 

    public
void
init() {

        add(send);

        add(l);

    }

 

    public
boolean
action(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
int
BSIZE = 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++仍然显得非常方便。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: