本章中我们将讨论servlet链,它是JvavServer体系结构的高级特征之一。
与UNIX和DOS命令中的管道类似,你也可以将多个servlet以特定顺序链接起来。在servlet链中,一个servlet的输出被当作下一个servlet的输入,而链中最后一个servlet的输出被返回到浏览器。
让我们马上就看看如何编写一个可以被用于链接的servlet吧。这个表过滤器servlet将分析另一个servlet的输出,查找含有特殊表格式指令的HTML注释,这些指令包括表有多少列、是否显示表头等等。在该表格式指令之后的所有行将会被格式化成一个HTML表格。这样,链中的前一个servlet只要简单地将数据用逗号分割,数据就可以一行一行地直接输出了,而无须将这些数据格式化成HTML表格。同样,当你决定修改这个表格的格式时,你就不必修改产生数据的servlet,而直接修改这个表过滤器servlet就可以了。
这个表过滤器servlet实现了HTTP服务的方法。首先,它必须重复(echo)上一个servlet中设置的头信息。这些信息包括内容类型、调用的URL、远程主机等等。
package javaservlets.samples;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class TableFilter extends HttpServlet
{
/**
* Performs an HTTP service request
*
* @param req The request from the client
* @param resp The response from the servlet
*/
public void service(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, java.io.IOException
{
// Get all headers set by the previous servlet and echo them
java.util.Enumeration e = req.getHeaderNames();
while (e.hasMoreElements()) {
String header = (String)e.nextElement();
String value = req.getHeader(header);
resp.setHeader(header, value);
}
接下来的步骤是取得一个可以从前一个servlet的输出流中讲读取数据的输入流,如果该输入流的内容类型是某种我们可以分析的类型(如HTML),我们就继续下一步骤;否则,我们就从输入流中读出所有字节并毫不修改地把它们写回浏览器。
// Get the input and output streams
ServletInputStream in = req.getInputStream();
ServletOutputStream out = resp.getOutputStream();
// Only process if this is a recognized MIME type
String type = req.getContentType();
if (type.equals("text/html") ||
type.equals("text/table") ||
type.equals("application/x-www-form-urlencoded")) {
resp.setContentType("text/html");
// Create a buffered reader that we can use to read
// a single line at a time
BufferedReader br =
new BufferedReader(new InputStreamReader(in));
boolean inTable = false;
int tableCols = 0;
boolean headerRow = false;
// Read until no more data exists
while (true) {
String s = br.readLine();
// null indicates end of file
if (s == null) {
break;
}
// If we are in the middle of a table command, process
// the line
if (inTable) {
// Search for the end of the table
if (s.startsWith("<!--end table")) {
out.println("</table></center>");
inTable = false;
}
else {
// We've got a row of a table - format it
s = formatRow(s, tableCols, headerRow);
headerRow = false;
}
}
else {
// Search for the start of a table
if (s.startsWith("<!--table")) {
int pos = s.indexOf("columns=");
cols = cols.substring(0, endPos);
}
tableCols = Integer.parseInt(cols);
}
// Get the header flag. If 'yes' the first
// row of data is actually a header pos = s.indexOf("header=");
if(pos >= 0) {
String flag = s.substring(pos + 7);
headerRow = flag.startsWith("yes");
}
// If we have a valid number of columns, format
// the table
if (tableCols > 0) {
out.println(s);
s = "<center><table border>";
inTable = true;
}
}
}
out.println(s);
}
} else {
// Unsupported MIME type; echo the contents unchanged
while (true) {
int b = in.read();
if (b == -1) {
break;
}
out.write(b);
}
}
out.close();
}
/**
* <p>Formats the given line into a table row
*/
private String formatRow(String line, int cols, boolean header)
{
String s = "<tr>";
int pos = line.indexOf(",");
int lastPos = 0;
// Loop for each column
for (int i = 0; i < cols; i++) {
if (pos < 0) {
pos = line.length();
}
// Insert the proper HTML tag
if (header) {
s += "<th>";
}
else {
s += "<td>";
}
// Find the next column data
if (pos > 0) {
s += line.substring(lastPos, pos);
lastPos = pos;
if (pos < line.length()) {
lastPos = pos + 1;
pos = line.indexOf(",", lastPos);
}
else {
pos = 0;
}
}
// Insert the proper HTML tag
if (header) {
s += "</th>";
}
else {
s += "</td>";
}
}
// Return the formatted line
return s;
}
/**
* <p>Initialize the servlet. This is called once when the
* servlet is loaded. It is guaranteed to complete before any
* requests are made to the servlet
*
* @param cfg Servlet configuration information
*/
public void init(ServletConfig cfg)
throws ServletException
{
super.init(cfg);
}
/**
* <p>Destroy the servlet. This is called once when the servlet
* is unloaded.
*/
public void destroy()
{
super.destroy();
}
/**
* <p>Returns information about this servlet
*/
public String getServletInfo() {
return "Table Filter for Chaining";
}
}
接下来的输入流分析就简单得多了。我们只要一行一行地读入表格中的数据,直至找到文件结束标记为止。对每一行,通过查找逗号来取得每一个域,然后将它们格式化成表格的一行。
在你将要链接在一起的servlet组织好之后,你可以通过别名、MIME类型或者HTML请求来触发这个servlet链。每一种方式都其特殊的配置,下面我就让我们分别看看如何用Java Web服务器和Live Software的JRun来配置这些触发方法。其他服务器的设置也大致相同。
servlet别名使你可以设置一个servlet名字或别名来表示一个或多个servlet。servlet链可以用servlet列表表示,该列表中的servlet用逗号分开,并按调用次序的先后排列在一起。
Java Web Server
在配置servlet别名以触发servlet链之前,一定要确认servlet链接功能已经被启动。这里,你可以启用servlet链接功能。
增加一个servlet别名是非常直截了当的事。当服务器接收到对“/Elemetns”的请求,它将调用“javaservlets.samples.Elements”servlet,取得输出后将其传给servlet“javaservlets.samples.TableFilter”作为输入,最后将TableFilter的输出返回给浏览器。你只要简单地将servlet名用逗号分隔开来,就可以将任意数量的servlet链接在一起。
注意,在本书写作时,Java Web Server 1.1还不能完全正确地支持servlet链接。该问题将在后续版本中得到解决。
JRun
在JRun中,servlet链接的配置是通过设置servlet映射来实现的。JRun中servlet别名与servlet是一一对应的,一个servlet只能与惟一的别名对应,所以servlet别名不能支持servlet链接。而servlet映射可以让你将一个名字与一系列servlet或者servlet别名项对应。
servlet别名链接的例子:Elements
为了说明如何使用servlet别名来触发servlet链接,我们先编一个用HTML表格列出元素周期表的servlet。Elements
Servlet要实现doGet()来响应HTML GET命令。我们还要设置内容类型然后输出HTML首部信息。Elements
Servlet没有格式化输出HTML表格,我们将让它输出表过滤器servlet所需的表格格式信息,并简单地一行一行输出用逗号分隔的数据,
package javaservlets.samples;
import javax.servlet.*;
import javax.servlet.http.*;
/**
* <p>This is a simple servlet that will return a list of
* periodic elements.
*/
public void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, java.io.IOException
{
// Create a PrintWriter to write the response
java.io.PrintWriter out =
new java.io.PrintWriter(resp.getOutputStream());
// Set the content type of the response
resp.setContentType("text/html");
// Print the HTML header
out.println("<html>");
out.println("<head>");
out.println("<title>Java Servlets Sample - " +
"Periodic Elements</title>");
out.println("</head>");
out.println("<h2><center>");
out.println("The Periodic Elements</center></h2>");
out.println("<br>");
// Output special table formatting instructions for
// the TableFilter servlet
out.println("<!--table columns=2 header=yes-->");
// Output the table
out.println("Symbol,Element");
out.println("Ac,Actinium");
out.println("Ag,Silver");
out.println("Al,Aluminum");
//Etc...
out.println("Y,Yttrium");
out.println("Yb,Ytterbium");
out.println("Zn,Zinc");
out.println("Zr,Zirconium");
out.println("<!--end table-->");
// Wrap up
out.println("</html>");
out.flush();
out.close();
}
在你浏览器的URL中输出“/Elements”调用我们在Jrun中配置的servlet映射。
在刷新时,浏览器向Web服务器发出URL请求,Web服务器找到与这个URL信息对应的servlet映射,然后调用Elemetns
Servlet。Elements Servlet处理GET请求并返回未格式化的元素周期表数据给Web服务器。之后,Web服务器发现存在servlet链接,于是将Elements
Servlet的输出重定向为servlet链接中下一个servlet,也就是表过滤器的输入。表过滤器重新设置所有HTTP首部,以适应表过滤器的需要,然后读入所有元素周期表数据,表过滤器使用指定的表格格式信息来分析和处理这些数据,最后生成一个格式化的元素周期表。
触发servlet链接的另外一种方法是将一个servlet与特定Mime类型联系起来。当这种Mime类型的应答产生时,输出就会被发送给与之相联系的servlet。由于MIME类型是在servlet向输出流中写入时才确定的,所以用这种方法你可以轻易地将servlet的输出重定向到其他servlet。
Java Web Server
如前所述,在所有的工作之前,你必须确认servlet链接功能已经启用。截止到本书发稿,还没有可以管理MIME类型和servlet映射的图形用户接口(GUI),所以你不得不手工编辑“mimeservlets.properties”文件。这个文件位于目录“/<server_root>/properties/server/javawebserver/webpageservice”。值得注意的是,MIME类型所映射的servlet名字实际上是该servlet的别名。
# This file maps mime-types to the servlets which process them
# This is used by the filter manager to set up chains of servlets
# where the ouput of one servlet gets piped to the input of
# another servlet based on the mime-type that the servlet specifies
# with setContentType("mime-type")
#
# The default servlet for all mime-types is file.Do not set this
# explicitly.
#
# Entries in this file should be of the form
# mime-type/servletname
# ie.
# foo/bar=fooServlet
# where fooServlet is defined in servlets.properties
java-internal/parsed-html=ssi
java-internal/template-content=template
JRun
在JRun中,你可以通过系统管理应用程序设置MIME类型映射。你可以把一个servlet和特定的MIME类型联系起来。
MIME类型链接的例子:Indy 500
为了说明如何通过MIME类型来触发servlet链接,让我们编写一个列出Indianapolis 500自1911年起的所有优胜者。就像Elements
Servlet一样,我们直接将输入用逗号分隔的各行数据,并用表过滤器将其格式化成HTML表格形式输出。惟一的不同在于我们设置了一个不同的MIME类型,通过这个MIME类型,Web服务器将Indy
500 Servlet的输出重定向为表过滤器servlet的输入。
package javaservlets.samples;
import javax.servlet.*;
import javax.servlet.http.*;
/**
* <p>This is a simple servlet that will return a list of
* past Indianapolis 500 winners
*/
public class Indy500 extends HttpServlet
{
/**
* <p>Performs the HTTP GET operation
*
* @param req The request from the client
* @param resp The response from the servlet
*/
public void doGet(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, java.io.IOException{
// Create a PrintWriter to write the response
java.io.PrintWriter out =
new java.io.PrintWriter(resp.getOutputStream());
// Set the content type of the response
resp.setContentType("text/table");
// Print the HTML header
out.println("<html>");
out.println("<head>");
out.println("<title>Java Servlets Sample - " +
"Past Indianapolis 500 Winners</title>");
out.println("</head>");
out.println("<h2><center>");
out.println("Past Indianapolis 500 Winners</center></h2>");
out.println("<br>");
// Output special table formatting instructions for
// the TableFilter servlet
out.println("<!--table columns=3 header=yes-->");
out.println("Year,Driver,Average Speed");
out.println("1997,Arie Luyendyk,145.827");
out.println("1996,Buddy Lazier,147.956");
out.println("1995,Jacques Villenueve,153.616");
//Etc...
out.println("1912,Joe Dawson,78.719");
out.println("1911,Ray Harroun,74.602");
out.println("<!--end table-->");
// Wrap up
out.println("</html>");
out.flush();
out.close();
}
通过使用JRun中配置的MIME类型映射,调用Indy 500 Servlet的结果将会是格式化了的Indianapolis
500优胜者列表。值得注意的是,我们只要设置servlet的别名就可以了,而无须指定它的全名。
再次重申,Web浏览器向Web服务器发送的是含有servlet名字的HTTP请求,Web服务器调用了servlet(Indy
500),servlet设置了MIME类型“text/table”,而我们已经将这个MIME类型映射到表过滤器servlet。于是,Indy
500所产生的输出将被重定向为表过滤器servlet的输入,表过滤器servlet将数据格式化为HTML表格的形式,并将输出返回给Web服务器。最后Web服务器把这些HTML页发送给浏览器。
触发servlet链接的另一种方法是在HTTP请求中指定servlet链接。不过不是所有的Web服务器都支持这种方法。为了说明HTTP请求中的servlet链接,我们再来编写一个简单的servlet(Solar
System),它将返回我们太阳系中所有行星的信息。
package javaservlets.samples;
import javax.servlet.*;
import javax.servlet.http.*;
/**
* <p>This is a simple servlet that will return a list of
* the planets in our solar system
*/
public class SolarSystem extends HttpServlet{
/**
* <p>Performs the HTTP GET operation
*
* @param req The request from the client
* @param resp The response from the servlet
*/
public void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, java.io.IOException{
// Set the content type of the response
resp.setContentType("text/html");
// Create a PrintWriter to write the response
java.io.PrintWriter out = new java.io.PrintWriter(resp.getOutputStream());
// Print the HTML header
out.println("<html>");
out.println("<head>");
out.println("<title>Java Servlets Sample - " +
"Planets In Our Solar System</title>");
out.println("</head>");
out.println("<h2><center>");
out.println("Planets In Our Solar System</center></h2>");
out.println("<br>");
// Output special table formatting instructions for
// the TableFilter servlet
out.println("<!--table columns=5 header=yes-->");
out.println("Planet,Avg. Distance from Sun," +
"Time to orbit,Time to spin,Moons");
out.println("Mercury,58 million km,88 days,58.6 days,0");
out.println("Venus,108 million km,225 days,243 days,0");
out.println("Earth,150 million km,365.25 days,24 hours,1");
out.println("Mars,228 million km,687 days,24.62 hours,2");
out.println("Jupiter,778 million km,11.9 years,9.83 hours,16");
out.println("Saturn,1427 million km,29.5 years,10.65 hours,19");
out.println("Uranus,2870 million km,84 years,17.23 hours,15");
out.println("Neptune,4497 million km,164.8 years,16 hours,8");
out.println("Pluto,5913 million km,248 years,6.375 days,1");
out.println("<!--end table-->");
// Wrap up
out.println("</html>");
out.flush();
out.close();
}
/**
* <p>Initialize the servlet. This is called once when the
* servlet is loaded. It is guaranteed to complete before any
* requests are made to the servlet
*
* @param cfg Servlet configuration information
*/
public void init(ServletConfig cfg)
throws ServletException{
super.init(cfg);
}
/**
* <p>Destroy the servlet. This is called once when the servlet
* is unloaded.
*/
public void destroy()
super.destroy();
}
}
和前面的servlet一样,Solar System Servlet将直接输出无格式的数据而将格式化的工作交由表过滤器servlet来完成。由于Java
Web Server不支持用HTTP请求触发servlet链接,我们将使用JRun。
请注意调用servlet时使用的URL,在这种情况下,URL中包含了一个链接在一起的servlet名字,它们之间用逗号来间隔。
在本章中,我们讨论了servlet链接,这是JavaServer体系结构的高级特征之一。servlet链接提供了将一个servlet的输出重定向为另一个servlet的输入的能力。这样,你就可以划分工作,从而使用一系列servlet来实现它。另外,你还可以将servlet组织在一起以提供新的功能。
接下来,我们将致力于JavaServer体系结构的另一个高级特征:Server-Side Include。这一特征使你可以在HTML文档中嵌入servlet。