15.6 Java 与CGI 的沟通
15.6 Java 与CGI 的沟通
代码也要简单一些,毕竟用
这个例子也阐示了如何在程序片(Applet)和
在这个版本中,我们将收集名字和电子函件地址,并用下述形式将其保存到文件中:
First Last <email@domain.com>;
这对任何
<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>
上述代码创建了两个数据输入字段(区
http://www.myhome.com/cgi-bin/Listmgr2.exe?name=First+Last&email=email@domain.com&submit=Submit
③:在
当然,上述
到这时,大家也许会对“+”
John+%26+Marsha+Smith
也就是说,特殊字符会转换成一个“%”,并在后面跟上它的十六进制
幸运的是,
//: EncodeDemo.java
// Demonstration of URLEncoder.encode()
import java.net.*;
public class EncodeDemo {
public static void 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);
}
} ///:~
该程序将获取一些命令行参数,把它们合并成一个由多个词构成的字串,各词之间用空格分隔(最后一个空格用
为调用一个
程序片实际要比
//: NameSender2.java
// An applet that sends an email address
// via a CGI GET, using Java 1.02.
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;
}
} ///:~
和往常一样,程序片设置了自己的用户界面(这次是两个输入字段,不是一个
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);
// ...
到这时,一些
我们发出任何形式的请求后,一般都会得到一个回应的
- 从程序片里显示一个
Web 页
程序亦可将
getAppletContext().showDocument(u);
其中,
//: ShowHTML.java
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;
}
} ///:~
经过前面的学习,大家应该能够根据例子用
④:
为避免第一次就提出过多的新概念,这个程序并未打算成为一个“纯”
这个程序同时也非常有趣,因为它演示了
在这儿使用
若提到缺点,就一定不要忘记
//: 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);
}
}; ///:~
在
using namespace std;
接下来的三个方法属于常规编码,在
⑤:我的《Thinking in C++
只要将一个对象按值传入或传出函数,就会自动调用副本构造器
但事情并没有完。char*
,事情就要变得简单得多(当然,并不是所有编译器都提供了对
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);
}
(此外,对这个类
现在基本工具已定义好,它们可以简单地在一个
//: 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 = "Bruce@EckelObjects.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());
} ///:~
现在,我们迫切需要掌握一些与
在目前这种情况下,我们希望服务器将所有信息都直接反馈回客户程序(亦即我们的程序片,它们正在等候给自己的回复
我们用相同的代码添加电子函件名称(用户的姓名
在许多应用程序中使用
对一个用来控制
//: POSTtest.cpp
// CGI_vector works as easily with POST as it
// does with GET. Written in "pure" C++.
#include <iostream.h>
#include "CGITools.h"
void main() {
cout << "Content-type: text/plain\n" << endl;
// For a CGI "POST," the server puts the length
// of the content string in the environment
// variable CONTENT_LENGTH:
char* clen = getenv("CONTENT_LENGTH");
if(clen == 0) {
cout << "Zero CONTENT_LENGTH" << endl;
return;
}
int len = atoi(clen);
char* query_str = new char[len + 1];
cin.read(query_str, len);
query_str[len] = '\0';
CGI_vector query(query_str);
// Test: dump all names and values
for(int i = 0; i < query.size(); i++)
cout << "query[" << i << "].name() = [" <<
query[i].name() << "], " <<
"query[" << i << "].value() = [" <<
query[i].value() << "]" << endl;
delete query_str; // Release storage
} ///:~
到这个时候,我们得到的查询字串与
为测试这个程序,必须把它编译到主机
<HTML>
<HEAD>
<META CONTENT="text/html">
<TITLE>A test of standard HTML POST</TITLE>
</HEAD>
Test, uses standard html POST
<Form method="POST" ACTION="/cgi-bin/POSTtest">
<P>Field1: <INPUT TYPE = "text" NAME = "Field1"
VALUE = "" size = "40"></p>
<P>Field2: <INPUT TYPE = "text" NAME = "Field2"
VALUE = "" size = "40"></p>
<P>Field3: <INPUT TYPE = "text" NAME = "Field3"
VALUE = "" size = "40"></p>
<P>Field4: <INPUT TYPE = "text" NAME = "Field4"
VALUE = "" size = "40"></p>
<P>Field5: <INPUT TYPE = "text" NAME = "Field5"
VALUE = "" size = "40"></p>
<P>Field6: <INPUT TYPE = "text" NAME = "Field6"
VALUE = "" size = "40"></p>
<p><input type = "submit" name = "submit" > </p>
</Form>
</HTML>
填好这个表单并提交出去以后,会得到一个简单的文本页,其中包含了解析出来的结果。从中可知道
为建立与服务器的一个直接连接,必须取得自己创建的
//: POSTtest.java
// An applet that sends its data via a CGI POST
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;
public class POSTtest extends Applet {
final static int SIZE = 10;
Button submit = new Button("Submit");
TextField[] t = new TextField[SIZE];
String query = "";
Label l = new Label();
TextArea ta = new TextArea(15, 60);
public void init() {
Panel p = new Panel();
p.setLayout(new GridLayout(t.length + 2, 2));
for(int i = 0; i < t.length; i++) {
p.add(new Label(
"Field " + i + " ", Label.RIGHT));
p.add(t[i] = new TextField(30));
}
p.add(l);
p.add(submit);
add("North", p);
add("South", ta);
}
public boolean action (Event evt, Object arg) {
if(evt.target.equals(submit)) {
query = "";
ta.setText("");
// Encode the query from the field data:
for(int i = 0; i < t.length; i++)
query += "Field" + i + "=" +
URLEncoder.encode(
t[i].getText().trim()) +
"&";
query += "submit=Submit";
// Send the name using CGI's POST process:
try {
URL u = new URL(
getDocumentBase(), "cgi-bin/POSTtest");
URLConnection urlc = u.openConnection();
urlc.setDoOutput(true);
urlc.setDoInput(true);
urlc.setAllowUserInteraction(false);
DataOutputStream server =
new DataOutputStream(
urlc.getOutputStream());
// Send the data
server.writeBytes(query);
server.close();
// Read and display the response. You
// cannot use
// getAppletContext().showDocument(u);
// to display the results as a Web page!
DataInputStream in =
new DataInputStream(
urlc.getInputStream());
String s;
while((s = in.readLine()) != null) {
ta.appendText(s + "\n");
}
in.close();
}
catch (Exception e) {
l.setText(e.toString());
}
}
else return super.action(evt, arg);
return true;
}
} ///:~
⑥:我不得不说自己并没有真正理解这儿都发生了什么事情,这些概念都是从
信息发送到服务器后,我们调用