再谈通过Java执行POST操作

我的上一篇关于从applet中执行POST操作的技巧在读者中引出了许多问题。其中最突出的问题是:“如何显示由Web服务器上的POSTCGI-bin处理程序返回的HTML文档?”。在这篇技巧中,我们将探索这一问题的解决方法,并深入研究几个很棒的服务器端的Java问题。
  注意:本技巧假定您知道读者提出的有关通过Java执行POST操作的一些基本问题。如果您还不熟悉这些概念,请参考“Java技巧34”。
  
  那么,我们如何显示来自applet的POST的结果呢?这一问题有四个答案。按照难易程度递增的顺序排列,依次为:
  
  无法显示。
  别执行POST操作。
  使用bean。
  作弊。
  正如在“Java技巧34”中讨论的那样,浏览器目前所用的安全性管理器不允许在浏览器中显示由applet生成的HTML;浏览器仅允许我们将它指向URL,URL将代表我们将它显示出来。这种情况难以令人满意!
  
  我们通过--难以置信!--不使用POST就可以避开显示POST结果的这种限制。我们可以将一些信息编码在URL中,然后再将编码后的URL提供给showDocument()方法。这些信息可作为GET请求的参数传递给Web服务器。不幸的是,这存在一些缺陷:只能传输数量有限的数据--此外,在这个过程中URL被更改。这样做相当笨拙。稍后我们会看到采用这种编码方式的一个示例。
  
  最近,Sun的JavaSoft分公司发布了HTMLrendererbean。(还有几个别的商用软件。)这样,将这个bean作为applet的一部分并用它来显示网页就成为可能。有什么缺点?大小、兼容性和成本。这个bean当然不小,它需要支持bean的浏览器,而且不是免费的。当然,我们完全可以花时间来编写自己的翻译组件,但那是一种愚蠢的做法。
  
  这个问题的一种有趣而有建设性的解决方案就是作弊。在这个特例中,我们让服务器端的代码(例如,CGI-bin脚本)与我们的applet共同作弊。基本思想很简单:将POST与随后的GET结合起来使用。这个过程如下所示:
  
  applet仍然通过POST操作将信息发送给服务器。
  服务器利用POST信息生成HTML。
  服务器将HTML保存到Web服务器上的文件中。
  服务器向applet返回一个魔力键。
  applet将这个键编码在URL中并返回给服务器。
  applet通过在showDocument()调用中使用生成的URL来通知浏览器显示网页。
  服务器接收GET请求并提取魔力键参数。
  服务器检索与此魔力键相关的文件。
  服务器将文件中的HTML内容返回给浏览器。
  浏览器将HTML内容显示出来。
  这种返回处理无疑比其他解决方案更复杂,但现在这种处理适用于客户机和服务器的广泛组合方式。这种处理的缺点在于,完成一个完整的事务必须执行多个HTTP请求。我们必须在多个请求的之间维护“状态”信息,以便能跟踪正在进行的事务(回忆一下,HTTP是一种无状态的请求/响应协议)。稳健地处理这些必要的状态信息可能相当具有挑战性。Tcl脚本语言及Sprite分布式操作系统之父JohnOusterhout曾经说过:“在分布式计算中,状态是第二麻烦的问题。不,它是最麻烦的问题。”
  
  服务器部分最复杂,所以让我们先来看一下applet。:-)这个applet与以前的“Java技巧34”中所用的Happyapplet仅有几点区别。到服务器的POST操作是相同的,但我们必须修改读取服务器响应的的部分:input=newDataInputStream(urlConn.getInputStream());
  
  Stringstr=null;
  StringfirstLine=null;
  while(null!=((str=input.readLine())))
  {
  if(null==firstLine)
  firstLine=str;
  
  System.out.println(str);
  textArea.appendText(str "/n");
  }
  
  input.close();
  经过许可,服务器返回魔力键作为第一行。魔力键是一段状态信息,此信息用来唯一标识applet所涉及的、与此服务器有关的事务。如果在处理POST请求的过程中遇到任何问题,服务器通过以下方式将这一情况通知applet:返回"nil"字符串,并紧接着返回这一问题的文本描述。applet现在所要做的唯一操作就是构建URL,并调用showDocument()来显示HTML:if(null!=firstLine)

{
  url=newURL("http://"
  ((getCodeBase()).getHost()).toString()
  "/poster?" firstLine);
  (getAppletContext()).showDocument(url,"_blank");
  }
  一定要注意,URL参数必须是URL编码的。在上面的代码段中,因为来自服务器的魔力键已被安全编码,所以我们只需添加问号将基准URL与所传递的参数分隔开。
  
  现在我们已讨论了applet部分,下面该研究服务器了。在以前有关POST的一篇Java技巧中,服务器端的代码是用Perl编写的传统CGI-bin脚本。Perl是一种不错的解决方案,但是您难道不想用Java编写服务器端的代码吗?我们可以用Java编写CGI-bin脚本(请参阅参考资源部分),但还有更好的解决办法:那就是作为Web服务器本身一部分的Java。这种服务器端的Java称为servlet。本文所提供的解决方案将是一个servlet,是按照JavaServletAPI编写的--尽管通过CGI-bin脚本(用Perl、Tcl、Java或其他语言编写)也能实现同样的解决方案。
  
  请注意,对servlet编程和管理的介绍不属于本文的讨论范围;我们仅讨论与POST解决方案有直接关系的主要问题。
  
  PosterServlet代码包含大量的注释,以便指导您阅读代码。代码包含大量的错误处理和额外检查,用来处理过多的可能出现的问题、拒绝服务攻击等。但是多数时候您可以忽略这些代码。(稍后我会更深入地讨论安全性问题。)这个servlet是针对Java1.1.xAPI编写的(而applet代码是针对Java1.0.2编写的)。
  
  doPost()方法处理POST请求--即,doPost()负责前三种服务器职责:它根据通过POST发送来信息生成HTML文档,然后将文档保存到一个临时的磁盘文件中,并将魔力键返回给applet,魔力键用来标识HTML文档,并适合直接嵌入随后的GET请求中。
  
  下面是代码的核心部分。(要查看完整的代码,请参阅实际的源文件。)实现说明:魔力键实际上是为此事务生成的HTML文档的文件名。文件名是使用java.util.Random类生成的一个Long值。protectedvoiddoPost(HttpServletRequestrequest,
  HttpServletResponseresponse)
  throwsServletException,IOException
  {
  response.setContentType("text/plain");
  
  //构建输出文件名。
  StringfileName=(newLong(randomizer.nextLong())).toString();
  Filefile=null;
  try
  {
  file=newFile(posterTempDir File.separator
  fileName posterTempExt);
  }
  catch(Exceptione)
  {
  sendPostFailure(response,
  "Unabletobuildoutputfilepath!");
  return;
  }
  
  //打开输出文件。
  PrintWriteroutput=null;
  try
  {
  output=newPrintWriter
  (newBufferedWriter
  (newFileWriter(file)));
  }
  catch(IOExceptione)
  {
  sendPostFailure(response,"Unabletoopenoutputfile!");
  return;
  }
  
  output.println("<html>");
  output.print("<head><title>PosterServletGeneratedOutput");
  output.println("</title></head>");
  output.println("<body>");
  
  
  //现在,循环检查请求标头并将它们写入文件中。
  StringheaderName=null;
  Enumerationheaders=request.getHeaderNames();
  if(headers.hasMoreElements())
  {
  output.println("<h1>CGIheaders:</h1><hr>");
  output.println("<ul>");
  while(headers.hasMoreElements())
  {
  headerName=(String)headers.nextElement();
  output.print("<li><b>");
  output.print(headerName);
  output.print("=");
  output.print(request.getHeader(headerName));
  output.println("</b></li><br>");
  }
  output.println("</ul><hr><br>"); 

}
  
  //处理POST内容。
  if(0< request.getContentLength())
  {
  Stringline=null;
  
  //将所有输入字节转换为字符。
  BufferedReaderin=newBufferedReader
  (newInputStreamReader(request.getInputStream()));
  
  output.println("<h1>POSTcontents:<h1><hr>");
  output.println("<p><pre>");
  
  
  //读取输入的每一行,并将其写入输出文件中。
  HttpUtilshttpUtils=newHttpUtils();
  try
  {
  while(null!=(line=in.readLine()))
  {
  try
  {
  Hashtabledata=httpUtils.parseQueryString(line);
  StringkeyName=null;
  Enumerationkeys=data.keys();
  
  while(keys.hasMoreElements())
  {
  String[]values=null;
  
  keyName=(String)keys.nextElement();
  output.print(keyName);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值