我的上一篇关于 从 applet 中执行 POST 操作的技巧在读者中引出了许多问题其中最突出的问题是如何显示由 Web 服务器上的 POST CGIbin 处理程序返回的 HTML 文档?在这篇技巧中我们将探索这一问题的解决方法并深入研究几个很棒的服务器端的 Java 问题
注意本技巧假定您知道读者提出的有关通过 Java 执行 POST 操作的一些基本问题如果您还不熟悉这些概念请参考Java 技巧
那么我们如何显示来自 applet 的 POST 的结果呢?这一问题有四个答案按照难易程度递增的顺序排列依次为
无法显示
别执行 POST 操作
使用 bean
作弊
正如在Java 技巧 中讨论的那样浏览器目前所用的安全性管理器不允许在浏览器中显示由 applet 生成的 HTML浏览器仅允许我们将它指向 URLURL 将代表我们将它显示出来这种情况难以令人满意!
我们通过 难以置信! 不使用 POST 就可以避开显示 POST 结果的这种限制我们可以将一些信息编码在 URL 中然后再将编码后的 URL 提供给 showDocument() 方法这些信息可作为 GET 请求的参数传递给 Web 服务器不幸的是这存在一些缺陷只能传输数量有限的数据 此外在这个过程中 URL 被更改这样做相当笨拙稍后我们会看到采用这种编码方式的一个示例
最近Sun 的 JavaSoft 分公司发布了 HTML renderer bean(还有几个别的商用软件)这样将这个 bean 作为 applet 的一部分并用它来显示网页就成为可能有什么缺点?大小兼容性和成本这个 bean 当然不小它需要支持 bean 的浏览器而且不是免费的当然我们完全可以花时间来编写自己的翻译组件但那是一种愚蠢的做法
这个问题的一种有趣而有建设性的解决方案就是作弊在这个特例中我们让服务器端的代码(例如 CGIbin 脚本)与我们的 applet 共同作弊基本思想很简单将 POST 与随后的 GET 结合起来使用这个过程如下所示
applet 仍然通过 POST 操作将信息发送给服务器
服务器利用 POST 信息生成 HTML
服务器将 HTML 保存到 Web 服务器上的文件中
服务器向 applet 返回一个魔力键
applet 将这个键编码在 URL 中并返回给服务器
applet 通过在 showDocument() 调用中使用生成的 URL 来通知浏览器显示网页
服务器接收 GET 请求并提取魔力键参数
服务器检索与此魔力键相关的文件
服务器将文件中的 HTML 内容返回给浏览器
浏览器将 HTML 内容显示出来
这种返回处理无疑比其他解决方案更复杂但现在这种处理适用于客户机和服务器的广泛组合方式这种处理的缺点在于完成一个完整的事务必须执行多个 HTTP 请求我们必须在多个请求的之间维护状态信息以便能跟蹤正在进行的事务(回忆一下HTTP 是一种无状态的请求/响应协议)稳健地处理这些必要的状态信息可能相当具有挑战性Tcl 脚本语言及 Sprite 分布式操作系统之父 John Ousterhout 曾经说过在分布式计算中状态是第二麻烦的问题不它是最麻烦的问题
服务器部分最复杂所以让我们先来看一下 applet:) 这个 applet 与以前的Java 技巧 中所用的 Happy applet 仅有几点区别到服务器的 POST 操作是相同的但我们必须修改读取服务器响应的的部分input = new DataInputStream (urlConngetInputStream ());
String str = null;
String firstLine = null;
while (null != ((str = inputreadLine())))
{
if (null == firstLine)
firstLine = str;
Systemoutprintln (str);
textAreaappendText (str + \n);
}
inputclose ();
经过许可服务器返回魔力键作为第一行魔力键是一段状态信息此信息用来唯一标识 applet 所涉及的与此服务器有关的事务 如果在处理 POST 请求的过程中遇到任何问题服务器通过以下方式将这一情况通知 applet返回 nil 字符串并紧接着返回这一问题的文本描述applet 现在所要做的唯一操作就是构建 URL并调用 showDocument() 来显示 HTMLif (null != firstLine)
{
url = new URL (// +
((getCodeBase())getHost())toString() +
/poster? + firstLine);
(getAppletContext())showDocument (url _blank);
}
一定要注意URL 参数必须是 URL 编码的在上面的代码段中因为来自服务器的魔力键已被安全编码所以我们只需添加问号将基准 URL 与所传递的参数分隔开
现在我们已讨论了 applet 部分下面该研究服务器了在以前有关 POST 的一篇 Java 技巧中服务器端的代码是用 Perl 编写的传统 CGIbin 脚本Perl 是一种不错的解决方案但是您难道不想用 Java 编写服务器端的代码吗?我们可以用 Java 编写 CGIbin 脚本(请参阅参考资源部分)但还有更好的解决办法那就是作为 Web 服务器本身一部分的 Java这种服务器端的 Java 称为 servlet本文所提供的解决方案将是一个 servlet是按照 Java Servlet API 编写的 尽管通过 CGIbin 脚本(用 PerlTclJava 或其他语言编写)也能实现同样的解决方案
请注意对 servlet 编程和管理的介绍不属于本文的讨论范围我们仅讨论与 POST 解决方案有直接关系的主要问题
PosterServlet 代码包含大量的注释以便指导您阅读代码代码包含大量的错误处理和额外检查用来处理过多的可能出现的问题拒绝服务攻击等但是多数时候您可以忽略这些代码(稍后我会更深入地讨论安全性问题)这个 servlet 是针对 Java x API 编写的(而 applet 代码是针对 Java 编写的)
doPost() 方法处理 POST 请求 即doPost() 负责前三种服务器职责它根据通过 POST 发送来信息生成 HTML 文档然后将文档保存到一个临时的磁盘文件中并将魔力键返回给 applet魔力键用来标识 HTML 文档并适合直接嵌入随后的 GET 请求中
下面是代码的核心部分(要查看完整的代码请参阅实际的源文件)实现说明魔力键实际上是为此事务生成的 HTML 文档的文件名 文件名是使用 javautilRandom 类生成的一个 Long 值 protected void doPost (HttpServletRequest request
HttpServletResponse response)
throws ServletException IOException
{
responsesetContentType (text/plain);
// 构建输出文件名
String fileName = (new Long (randomizernextLong()))toString();
File file = null;
try
{
file = new File (posterTempDir + Fileseparator +
fileName + posterTempExt);
}
catch (Exception e)
{
sendPostFailure (response
Unable to build output file path!);
return;
}
// 打开输出文件
PrintWriter output = null;
try
{
output = new PrintWriter
( new BufferedWriter
( new FileWriter (file)));
}
catch (IOException e)
{
sendPostFailure (response Unable to open output file!);
return;
}
outputprintln (< html>);
outputprint (< head>< title>Poster Servlet Generated Output);
outputprintln (< /title>< /head>);
outputprintln (< body>);
// 现在循环检查请求标头并将它们写入文件中
String headerName = null;
Enumeration headers = requestgetHeaderNames();
if (headershasMoreElements())
{
outputprintln (< h>CGI headers:< /h>< hr>);
outputprintln (< ul>);
while (headershasMoreElements())
{
headerName = (String) headersnextElement();
outputprint (< li>< b>);
outputprint (headerName);
outputprint ( = );
outputprint (requestgetHeader (headerName));
outputprintln (< /b>< /li>< br>);
}
outputprintln (< /ul>< hr>< br>);
}
// 处理 POST 内容
if ( <requestgetContentLength())
{
String line = null;
// 将所有输入字节转换为字符
BufferedReader in = new BufferedReader
( new InputStreamReader (requestgetInputStream()));
outputprintln (< h>POST contents:< h>< hr>);
outputprintln (< p>< pre>);
// 读取输入的每一行并将其写入输出文件中
HttpUtils httpUtils = new HttpUtils();
try
{
while (null != (line = inreadLine()))
{
try
{
Hashtable data = (line);
String keyName = null;
Enumeration keys = datakeys();
while (keyshasMoreElements())
{
String[] values = null;
keyName = (String) keysnextElement();
outputprint (keyName);