本章中我们将讨论servlet链,它是JvavServer体系结构的高级特征之一。
4.1 什么是servlet链
与UNIX和DOS命令中的管道类似,你也可以将多个servlet以特定顺序链接起来。在servlet链中,一个servlet的输出被当作下一个servlet的输入,而链中最后一个servlet的输出被返回到浏览器。
4.2 servlet链接的实例:表过滤器(Table Filter)
让我们马上就看看如何编写一个可以被用于链接的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"; } }
接下来的输入流分析就简单得多了。我们只要一行一行地读入表格中的数据,直至找到文件结束标记为止。对每一行,通过查找逗号来取得每一个域,然后将它们格式化成表格的一行。
4.3 触发一个servlet链
在你将要链接在一起的servlet组织好之后,你可以通过别名、MIME类型或者HTML请求来触发这个servlet链。每一种方式都其特殊的配置,下面我就让我们分别看看如何用Java Web服务器和Live Software的JRun来配置这些触发方法。其他服务器的设置也大致相同。
4.3.1 servlet别名
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首部,以适应表过滤器的需要,然后读入所有元素周期表数据,表过滤器使用指定的表格格式信息来分析和处理这些数据,最后生成一个格式化的元素周期表。
4.3.2 Mime类型
触发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页发送给浏览器。
4.3.3.HTTP请求
触发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名字,它们之间用逗号来间隔。
4.4 小结
在本章中,我们讨论了servlet链接,这是JavaServer体系结构的高级特征之一。servlet链接提供了将一个servlet的输出重定向为另一个servlet的输入的能力。这样,你就可以划分工作,从而使用一系列servlet来实现它。另外,你还可以将servlet组织在一起以提供新的功能。
接下来,我们将致力于JavaServer体系结构的另一个高级特征:Server-Side Include。这一特征使你可以在HTML文档中嵌入servlet。