4.3.3 执行其他的网页
ASP 3.0和IIS 5.0的新特性之一就是引入了可编程的服务器端重定向(server-side redirection)的概念。这意味着,可以把一个网页的控制和执行转到另外一个网页,而不需要在客户端使用Response.Rdedirect方法。
1. 客户端重定向带来的问题
ASP编程人员通常使用Response.Redirect语句把一个页面载入到当前正在执行的网页。然而,许多人没有意识到这条语句不会自动地使服务器立即装入和执行新的网页。其真正做的是把一个HTTP重定向报头(redirection header)增加到由Web服务器发送给客户的输出流中。这个报头如下:
HTTP/1.1 302 Object Moved
Location newpage.asp
在这个报头中的标准HTTP状态信息“302 Object Moved”,告知浏览器所要求的资源已经发生移动。Location报头提供相应的网页地址。当然这个地址不一定是真实的,现在正在做的事情就是“欺骗”浏览器,使浏览器认为可在另一个位置上找到所需要的网页。实际发生的是,服务器将执行所请求的网页,但是通知浏览器需要的网页已经发生移动。这就是在发送任何页面的内容到浏览器之前必须执行Redirect方法的原因。
当一个浏览器接受到“302 Object Moved”信息时,中断当前的请求并为Location值中指定的网页发送一个新的请求。这与在网页的<HEAD>段使用一个META HTTP-EQUIV标记时的工作方式相同,前面给出的HTTP报头还可写为:
<META HTTP-EQUIV=”REFRESH” CONTENT=”0;URL=newpage.asp”>
因此重定向实际上发生在客户机端,而不是在服务器上。如果在这个连接的客户端有一个代理服务器在使用的话,可能会引起显示虚假消息。这就是在使用Response.Redirect时,“The object you requested has been moved and can be found here”消息经常在客户机上显示的原因,正确地使用缓冲通常可以防止这个问题。
在IIS 4.0或更早的版本中使用Response.Redirect时,应该在ASP网页的开头打开缓冲,然后在执行Response.Redirect方法之前调用Response.Clear。当然,在ASP 3.0中网页缓冲的缺省状态为打开,因此这不成问题。只要在执行该语句之前使用Response.Clear,以前产生的输出将不会发送给客户。
2. 在ASP 3.0中服务器端的重定向
在ASP 3.0和IIS 5.0中,在几乎所有情况下,通过使用两个新的Server对象方法Execute和Transfer,可以避免使用客户端重定向。这两个方法使控制立即转到另一个网页,该网页可以是一个ASP网页或者是任何其他的资源,例如一个HTTP网页、压缩文件或其他类型的文件。
它们之间的不同之处是:Execute方法“调用”另一个的网页,与在脚本代码中调用一个子程序或函数非常相似。当另一个网页或资源已经执行完毕或传送到客户端时,控制返回到原网页中调用Execute方法的语句的下一条语句,并继续执行。而使用Transfer方法时,控制不再返回到原页面中,在控制传送到的网页或资源的末尾处,执行过程停止。
当前网页的环境也传送给了目标网页或资源,因此这两个方法更有用。网页环境包含了原有的ASP对象中的所有变量的值,例如Request、Response和Session对象的集合以及它们的所有属性。即使该网页不在同一个虚拟应用程序中,也将传送Application对象的环境。
结果是浏览器认为它仍在接收原先的页面,它并不了解服务器所做的事情。浏览器的地址栏一直显示相同的URL,并且Back、Forward和Refresh按钮正常地工作。在使用客户端重定向时,尤其是使用HTML META元素时,情况通常不是这样的。
传送到新的页面或资源的环境包括所有现存的事务状态(transaction state)。当前网页的环境用ASP的ObjectContext对象(在第1章中已经讨论过)进行封装。如果需要将这个对象作为一个正在进行的事务的一部分,可以在传送控制的目的页面中使用这个对象。
(1) Server对象的Execute和Transfer方法的使用
在前面的示例页面中,可以试验使用Excute和Transfer方法。该页面包含了在示例中已经提供的另一个文件名字another_page.asp,它作为这两个方法的缺省参数值,
单击Server.Execute和Server.Transfer方法的按钮,提交到此窗体并重新装载该窗体。在这个页面顶部的脚本代码查看是哪个按扭被单击。如果是cmdExecute或cmdTransfer按钮,则把当前网页的路径写入到输出流中,然后调用相应的方法,并传送与该按钮相联系的文本框中的值,然后再把当前页面的路径写到输出流中。
…
If Len(Request.Form("cmdExecute")) Then
strPath = Request.Form("txtExecPath")
Response.Write "Currently executing the page: <B>" _
& Request.ServerVariables("SCRIPT_NAME") & "</B><BR>"
Server.Execute (strPath)
Response.Write "Currently executing the page: <B>" _
& Request.ServerVariables("SCRIPT_NAME") & "</B><BR>"
End If
If Len(Request.Form("cmdTransfer")) Then
strPath = Request.Form("txtTransferPath")
Response.Write "Currently executing the page: <B>" _
& Request.ServerVariables("SCRIPT_NAME") & "</B><BR>"
Server.Transfer (strPath)
End If
…
当单击Server.Excute方法的按钮时,会看到当前页面的路径,这是由上面代码中的第一条Response.Write语句创建并显示的。后面接着的内容是来自被执行的网页(another_page.asp)的一些输出内容。在这之后是第二个Response.Write语句的输出内容,这表明控制又回到了原先的网页
页面的两条水平线之间的段落(显示当前执行的网页为show_server.asp)来自原先的网页。在接下来的段落来自被执行的网页another_page.asp。下面是该页面的完整代码:
<%@ LANGUAGE=VBSCRIPT %>
<HR>
Currently executing the page: <B>another_page.asp</B><BR>
However the value of <B>Request.ServerVariables("SCRIPT_NAME")</B> is still <BR>
<B><% = Request.ServerVariables("SCRIPT_NAME") %></B>
because the <B>Request</B> collections hold<BR>
the same values as they had in the page that executed this one.<BR>
<FORM ACTION="<% = Request.ServerVariables("HTTP_REFERER") %>" METHOD="POST">
<INPUT TYPE="SUBMIT" NAME="cmdOK" VALUE=" ">
Return to the previous page<P>
</FORM>
<HR>
注意,该页面执行时,不能使用Request.ServerVariables(“SCRIPT_NAME”)获取它的路径,因为环境仍然是原网页的。我们不得不把页面名作为文本写入,因为实在没有办法可以从ASP环境中直接获取。
这里包括了一个返回前一个网页的按钮的原因是,通过在主网页中单击相对应的按钮,可以使用Server.Transfer方法调用这个页面。这次看到了完全相同的输出,只是没有第二次路径输出,因为是“传送”这个页面而不是“执行”该页面,所以控制不会回传给原先的网页
(2) 从ASP执行SSI网页
目前有了一个方法,如果需要的话可在ASP网页中成功地使用SSI指令。虽然这种要求不常出现,但可实现。过去的问题是,由于在SSI网页(文件扩展名是.stm、.shtml和.shtm)中不能包含ASP代码,所以程序不能“无缝”地重定向回到原先的网页,必须增加一个按钮或链接,以装载原先的或另外的ASP网页。
现在,由于有了Server.Execute方法,可以执行一个SSI网页并且将控制自动返回到原先的网页,客户端意识不到这些过程正在进行。客户端只是看到原先的ASP网页和执行结果。来自于SSI网页的任何输出都“无缝”地插入到流中。当然,如果在SSI网页完成后,不想使原先的网页继续执行,可以使用Server.Transfer方法。
为了看到这个技术的执行,把前面使用过的CGI-SSI例子网页的虚拟路径输入到Server.Excute方法(或Server.Transfer方法)的文本框中。这个路径是“../ssi_cgi/ssi_cgi.stm”。在单击按钮对Execute或Transfer方法进行调用以后,将看到.stm网页已经执行,其中有SSI指令的结果。在来自ssi_cgi.stm的内容之后出现的是原先的网页的其余部分,虽然在图4-16中看不到,但可通过滚动条看到该内容。
3. SSI #exec指令的不足
遗憾的是Execute和Transfer方法一般不能与SSI的#exec指令一起工作,因为包含这个指令的.stm网页会在调用它的ASP网页的环境中运行。在大多数情况下,它需要运行于直接引用该网页的一个独立的环境中。
存在这样的限制真是遗憾,如果没有这种限制,我们通过Server.Execute执行的网页可以“不可见地”包含来自于ASP网页的#exec指令。对前面的通过net stop和net start命令停止和启动Indexing Service的示例来说,它可能是一种理想的解决方案。
但是,我们必须求助于老的和已经验证的方法。当用户单击一个按钮时,简单地使用Response.Redirect方法来打开相关的网页:
<%
‘Look for a command sent from the FORM section buttons
If Len(Request.Form(“cmdStop”)) Then
Response.Redirect(“exec/stop_cisvc.stm”)
End If
If Len(Request.Form(“cmdStart”)) Then
Response.Redirect(“exec/start_cisvc.stm”)
End If
%>
可以试着把使用#exec指令的一个SSI网页的虚拟路径输入到示例页面的Server.Execute和Server.Transfer方法的文本框中。前面使用过的#exec示例的虚拟路径是“../ssi_cgi/exe/start_cisvc.stm”和“../ssi_cgi/exec/stop_cisvc.stm”。