用C++写的CGI措施
当前位置:以往代写 > JAVA 教程 >用C++写的CGI措施
2019-06-14

用C++写的CGI措施

用C++写的CGI措施

颠末前面的进修,各人应该可以或许按照例子用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气势气魄,所以各人阅读领略代码时要显得轻松一些:

 

//: CGITools.h
// Automatically extracts and decodes data
// from CGI GETs and POSTs. Tested with GNU C++ 
// (available for most server machines).
#include <string.h>
#include <vector> // STL vector
using namespace std;

// A class to hold a single name-value pair from
// a CGI query. CGI_vector holds Pair objects 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);
  }
  const char* name() const { return nm; }
  const char* value() const { return val; }
  // Test for "emptiness"
  bool empty() const {
    return (nm == 0) || (val == 0);
  }
  // Automatic type conversion for boolean test:
  operator bool() const {
    return (nm != 0) && (val != 0);
  }
  // The following constructors & destructor are
  // necessary for bookkeeping in C++.
  // Copy-constructor:
  Pair(const Pair& 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:
    delete nm;
    delete val;
    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
    delete nm; // 0 value OK
    delete val;
  }
  // If you use this method outide this class, 
  // you're responsible for calling 'delete' on
  // the pointer that's returned:
  static char* 
  decodeURLString(const char* URLstr) {
    int len = strlen(URLstr);
    char* result = new char[len + 1];
    memset(result, len + 1, 0);
    for(int i = 0, j = 0; i <= len; i++, j++) {
      if(URLstr[i] == '+')
        result[j] = ' ';
      else if(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];
    }
    return result;
  }
  // Translate a single hex character; used by
  // decodeURLString():
  static char translateHex(char hex) {
    if(hex >= 'A')
      return (hex & 0xdf) - 'A' + 10;
    else
      return hex - '0';
  }
};

// Parses any CGI query and turns it
// into an STL vector of Pair objects:
class CGI_vector : public vector<Pair> {
  char* qry;
  const char* start; // Save starting position
  // Prevent assignment and copy-construction:
  void operator=(CGI_vector&);
  CGI_vector(CGI_vector&);
public:
  // const fields must be initialized in the C++
  // "Constructor initializer list":
  CGI_vector(char* query) :
      start(new char[strlen(query) + 1]) {
    qry = (char*)start; // Cast to non-const
    strcpy(qry, query);
    Pair p;
    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 when there's 
  // no more query string left:
  Pair nextPair() {
    char* name = qry;
    if(name == 0 || *name == '\0')
      return Pair(); // End, return null Pair
    char* value = strchr(name, '=');
    if(value == 0)
      return Pair(); // 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
    }
    return Pair(name, value);
  }
}; ///:~

#p#分页标题#e#

在#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++里写出真正“结实”的类,但这需要需要一个较量费力的进程(注释⑤)。

⑤:我的《Thinking in C++》(Prentice-Hall,1995)用了一整章的处所来接头这个主题。若需更多的辅佐,请务必看看那一章。

只要将一个工具按值传入或传出函数,就会自动挪用副本构建器Pair(const Pair&)。也就是说,对付筹备为其建造一个完整副本的谁人工具,我们禁绝备在函数框架中通报它的地点。这并不是Java提供的一个选项,由于我们只能通报句柄,所以在Java里没有所谓的副本构建器(假如想建造一个当地副本,可以“克隆”谁人工具——利用clone(),拜见第12章)。雷同地,假如在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 for boolean test:
  operator bool() const {
    return (nm.length() != 0) 
      && (val.length() != 0);
  }

#p#分页标题#e#

(另外,对这个类decodeURLString()会返回一个string,而不是一个char*)。我们不愿界说副本构建器、operator=可能粉碎器,因为编译器已帮我们做了,并且做得很是好。但纵然有些工作是自动举办的,C++措施员也必需相识副本构建以及赋值的细节。
Pair类剩下的部门由两个要领组成:decodeURLString()以及一个“辅佐器”要领translateHex()——将由decodeURLString()利用。留意translateHex()并不能防御用户的恶意输入,好比“%1H”。分派好足够的存储空间后(必需由粉碎器释放),decodeURLString()就会个中遍历,将所有“+”都换成一个空格;将所有十六进制代码(以一个“%”打头)换成对应的字符。
CGI_vector用于理会和容纳整个CGI GET呼吁。它是从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措施中利用,就象下面这样:

 

//: Listmgr2.cpp
// CGI version of Listmgr.c in C++, which 
// extracts its input via the GET submission 
// from the associated applet. Also works as
// an ordinary CGI program with HTML forms.
#include <stdio.h>
#include "CGITools.h"
const char* dataFile = "list2.txt";
const char* notify = "[email protected]";
#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());
} ///:~

#p#分页标题#e#

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++仍然显得很是利便。

    关键字:

在线提交作业