在前面几章中,我们已经初步学习了servlet编程的一些基础知识。现在是将基础知识组合起来,开发实际应用程序的时候了。本章着重讲解HTML表单,它主要是用于在交互式Web应用程序中收集用户数据。
我们都很清楚HTML是在Web上获取信息的极佳方式。那么对于收集数据并将这些数据返回给服务器这样的功能实现,什么是最好的实现方式呢?在以后的几章我们将会看到,Java
Applet是创建具有非常强大功能的交互式Web应用程序的一个途径。然而,这样做要付出一些沉重的代价,基本这一就是applet的长度。随着起来越多的函数的加入(就如同其他类型的编程语言一样),applet的长度将以一种令人吃惊的速度增长。同时,你也应当注意到,applet的最后运行情况会随着浏览器的变化而发生改变。
另一个途径——HTML表单,这种方式提供了具有丰富设置选项的交互元件并且可以被几乎任何一个浏览器支持。大多数人都发现HTML语言编写起来非常简单,同时还有大量的可利用的开发软件使得主页制作更加容易。与applet相比,HMTL表单还有着更小的“足迹”(应用程序的下载大小)。这就使得人们可以更快地通过Internet下载它们,极大地缩短了用户的等待时间。与服务器端程序(如一个servlet程序)结合的表单是一种提供健壮的、交互式的Web应用程序的极好途径。
同其他的HTML标识符相同,表单有一个开始标识符和一个结束标识符。在每个表单内你可以加入任意个数的输入元素,诸如文本输入域、按钮、下拉菜单、复选框或可点击图片。你也可以使用HTML语言标识符,在标识符之间插入一般的HTML内容,像文本和图片。文本可以像表单标签和提示一样,用来提供如何填写表单的帮助。
当用户填写完表单以后,他或她单击Submit按钮,表单就会执行相应的动作。这就像在调用一个服务器端的进程(比如一个servlet程序),同时还发了一封电子邮件(email)。当Submit按钮被单击的时候,浏览器将会把所有从用户那里收集的数据打包并把它传给一个服务进程。服务器将会把这个包发送到适当的目的地,在那里处理数据,创建一个响应(通常是另一个HTML页面)并将响应返回给浏览器。
一个表单可以被放置在HTML页面正文部分的任何一个地方,在一个HTML页面中也可以放置多个表单(虽然表单不允许嵌套)。浏览器把表单元素放置在格式化好的HTML页面上,以使小图片嵌入到文本中。表单元素没有什么特殊的布局规则,因此,你要使用HTML语言的格式化元素(如表格)来控制具体输入元素在HTML页面上的布局。
图8.1说明了表单标识符的基本语法结构。
<FORM ACTION="url" METHOD="POST" or GET">
FORM contents
</FORM>
图8.1 基本HTML表单标识符语法结构
你至少要定义两个表单属性:ACTION(行为),它定义要调用的服务器端的进程名称;METHOD(方式),它定义参数是以何种方式传送给服务器端的。表8.1提供了一个表单标识符所有属性的清单。
表8.1 表单标识符属性
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
属性 是否必需 描述
─────────────────────────────────
ACTION 是 服务器端进程的统一资源定位符(URL),当表单被提交时,
相应进程将用于接收提交的数据
ENCTYPE 否 指定数据如何被编码以可靠的传输
METHOD 是 控制数据以何种方式发送到服务器端
─────────────────────────────────
注意,Microsoft和Netscape还定义了一些扩展的表单标识符的属性。但在这里,我们将主要讲解那些在HTML规范中定义的属性。
ACTION属性
ACTION属性作为必要属性,定义了服务器端进程的统一资源定位符(URL),当数据被提交时相应进程将用于接收提交的数据。在传统的环境(没有SERVLET)中这个URL指向一个可以在“cgi-bin”目录中找到的公共网关接口(CGI)脚本。由于我们将使用SERVLET,对我们来说,URL将指向在一个特定的服务器上的一个SERVLET。注意,这个URL可以包含一个真实的SERVLET名称,一个SERVLET别名,一个经由前缀调用的SERVLET或后缀映射表。
下面是一个有关ACTION属性的例子:
<FORM ACTION="http://www.myhost.com/servlet/myServlet"...>
</FOMR>
当然你也可以将一个电子邮件地址放在URL中。为什么要这样做呢?一些Internet服务商不允许你将脚本程序放置在Web站点,这就不能通过表单来调用一个CGI脚本或一个servlet了。然而,我们可以在ACTION属性中使用“mailto”URL。这样所有的表单参数和数据都会被发送到URL所指定的地址中。邮件扪收者就可以按照需要处理相应的表单数据。
下面是一个包含有一个电子邮件URL的ACTION属性的例子:
<FORM ACTION="mailto:karlmoss@mindspring.com"...>
</FORM>
电子邮件的正文部分包括窗体参数和它的值成对的出现:
name=Bruce Wayne
address=1 bat Cave
注意,如果你在ACTION属性中使用了电子邮件URL,你要注意以下几点:
·你的表单将只能在支持“mailto”URL的浏览器上工作。
·当一个表单被提交给一个电子邮件URL后,用户应该对可能会发生的任何事情都不感到惊奇。将数据提交给一个脚本程序或是一个servlet,相应的程序将会反馈回某种类型的确认HTML页面。而将数据提交给电子邮件URL就与它们不同,用户将只能注视着自己完成的表单。你可以使用JavaScript语言来解决这个问题。
·你将希望将ENCTYPE属性(我们将在下一部分讲述)设置成“text/plain”,这样可以确保表单参数和它们的值是可读的。
·你将不得不按某种方式来处理在电子邮件中的表单参数值。这就需要一些批处理程序来读取每个电子邮件,分析出各参数的值并执行相应的动作。
ENCTYPE属性
Web浏览器在表单数据发往服务器之前会对它们进行编码。服务器将会对发来的参数进行解码或者直接将这些参数发给相应的应用程序。缺省的编码方式是Internet媒体方式叫做“application/x-www-form-urlencoded”。你可以在表单标识符ENCTYPE属性中修改这个缺省编码方式。表8.2列出了有效编码方式清单。
表8.2 表单标识符中ENCTYPE属性值
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
值 描述
─────────────────────────────────
application/x-www-form-urlencoded 缺省的编码方式。这种编码方式
将任何表单参数值中的空格替换
为加号(+)将不是数字或字母的字
符用百分号(%)加两位十六进制的
字符的ASCII值替换,将有多个行
的值中的换行符用%0D%0A(回车/
换行符)替换
multipart/form-data 只用于包含文件选择域的表单中。数据
通过一个有多个域的单独文件传送
text/plain 只用在要经由电子邮件方式传送表单
参数和数据的情况,表单中的每一个元
素对应传输文件中的一行。这一行中,
参数名和它的值用等号隔开。有多个行
的值中的换行符用%0D%0A(回车符/换
行符)替换
─────────────────────────────────
大多数情况下你可能不会修改缺省值“application/x-www-form-urlencoded”,这样ENCTYPE属性就可以省略掉。
METHOD属性
METHOD属性是一个必须的属性,它指定Web浏览器将表单数据传送到服务器过程中所用的方法。在传送表单数据上有两种方法可供选择:POST和GET。
POST方法会使Web浏览器分两步传送数据。浏览器首先尝试与在ACTION属性中指定的服务器进行连接,当连接完成,再将表单数据通过这个特定传输通道(separate
transmission)传送给服务器。服务器要以标准方式读取表单的参数。
GET方法会使Web浏览器连接服务器并通过这个单一传输通道传送表单数据。浏览器将会把表单数据追加到ACTION所指定的URL之后(就像命令行参数一样)并用问号将URL与参数值分隔开。
你要用哪一种方法呢?这里有一个指定方针:
·如果表单只有少量的输入域,那么就使用GET方法。由于所有的数据都是通过单独传输方式发送的,这样也就可以得到很高的效率。
·由于一些服务器限制命令行参数的长度(这是GET方法的工作方式),对于很大的表单或是含有超长字符串值参数的表单,我们将只能选用POST方法。
·如果你很注重安全问题,那么就使用POST方法。由于GET方法把表单数据就像命令行参数一样加在URL后传送,因此通过网络探测器或是分析服务器的记录文件都可以很容易地获取所传数据的具体值。而对于POST方法来说,数据将通过特定传输通道传送。
传送附加参数
你可以把附加参数非常方便地通过ACTION属性所指定的统一资源地址传送出去。你只要对附加变量进行编码并把人他们按命令行参数的方式添加上去。让我们举个例子,如果你有两个参数名为“a”“b”,你可以按照“application/x-www-form-urlencoded”方法(参看表8.2)将这些参数编码如下:
a=3&b=24
一个使用了上述附加参数的URL将会像下面的这个样子。
<FORM ACTION="http://www.myhost.com/servlet/myServlet?a=3&b=24"...>
</FORM>
注意,这里有一个要点。&符号是一个HTML语言的保留符号,它用来指定字符串的插入点。为了解决&符号的传输问题,你需要将所有&符号用它的字符数码值代替,也就是替换为&或&。
<FORM ACTION="http://www.myhost.com/servlet/myServlet?a=3&b=24"...>
</FORM>
为了避免混淆,一些Web服务器要求你使用分号(;)分隔参数。
INPUT标识符用来定义表单中的输入域,如文本输入域和按钮。图8.2显示了input标识符的基本语法的结构。
TYPE属性作为必要属性指定所使用输入域类型(本部分将在下小节讲述)。而同是必要属性的NAM属性则是指定相应域提供给服务器时的名称。注意,你应当很小心地命名各个输入域。我建议你要避免使用任何一个特殊字符(下划线除外)并且参数的首字符要使用字母。特别注意不要使用+、&或%这些符号。它们在使用“application/x-www-form-rulencoded”编码方式时都具有特殊的意义。
注意,Microsoft Internet Explorer和Netscape Navigator还定义了一些扩展的属性。但在这里,我们将主要讲解那些在HTML规范中定义的标准属性。
<INPUT TYPE=input type
NAME=parameter name
[additional attributes]>
图8.2 HTML语言input标识符基本语法结构
button类型
通过使用按钮类型(button input tpye),你可以创建一个可以被表单用户单击的按钮,但单击后并不会提交表单或是生置表单。表8.3列出了按钮类型的属性。
表8.3 按钮的属性
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
属性 是否必需 描述
─────────────────────────────────
NAME 是 按钮的名称
VALUE 是 按钮上的标签
─────────────────────────────────
你可能要问,既然一个按钮既不能提交也不能清空表单,那么有什么用呢?好的,除非你使用JavaScript执行一些运作,这个问题的答案是确实毫无用处。尽管如此,图8.3是一个HTML源代码,它向用户介绍含有多个按钮的实例。图8.4显示了在实际运行中这些按钮的情况。
<html> <head> <title>HTML Form Input - Buttons</title> </head> <body> <form> <input type=button name=action value="Next"> <input type=button name=action value="Previous"> </form> </body> </html>图8.3 使用按钮的HTML源代码示例
注意,当提交上述表单的时候,一个名为“action”的参数将会被传送到服务器上,它的值就是按钮上的标识。
check box 类型
file 类型
文件类型(file input type)允许用户选择一个在他或她的本地机上的文件,当Submit按钮被按下时被选文件也将被发送到服务器上。Web浏览器将创建一个文本输入域,它将接受用户输入,同时还将创建一个Browse按钮,当Brower按钮被按下时,它将提供给用户一个依赖所用平台的对话框用来选择一个文件。表8.5列出了文件类型的属性。
表8.5 文件选择类型属性
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
属性 是否必需 描述
─────────────────────────────────
ACCEPT 否 设置要选文件的类型。用户可以通过一个用逗号分隔的文
件后缀列表选择特定类型文件,比如“image/”选择所有
的图片文件
MAXLENGTH 否 文件名气最大长度(以字符为单位)
NAME 是 文件输入域的名称
SIZE 否 输入域的长度(以字节为单位)
VALUE 否 缺省文件名
─────────────────────────────────
图8.7显示了一个使用文件选择类型的HTML示例文件。图8.8显示了它在浏览器中的运行情况。
<html> <head> <title>HTML Form Input - File Selection</title> </head> <body> <form> My favorite file is: <input type=file name=myfile size=25> </form> </body> </html>图8.7 文件选择类型的HTML示例
hidden 类型
image 类型
图像类型(image input type)将创建可单击的图像按钮,这个按钮由用户指定的图像创建出来,当用户单击它的时候,它将提交表单并在同时将鼠标单击时在图像上的位置的X、Y坐标传送给服务器。X、Y坐标值将会以<name>.x和<name>.y的方式传送。也就是说,如果创建了一个名为“map”的图像类型,鼠标所在位置的X、Y坐标将会以map.x和map.y方式传送给服务器。
表8.7列出了单击图像按钮的属性。图8.12显示了一个使用图像按钮类型的HTML示例文件。图8.13显示了这个示例的运行情况。
表8.7 图像按钮类型
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
属性 是否必需 描述
─────────────────────────────────
ALIGN 否 图像与文字的位置关系:TOP,TEXTTOP,MIDDLE,
ABSMIDDLE,CENTER,BOTTOM,BASELINE,
ABSBOTTOM
BORDER 否 以像素为单位指定图像边框的粗细程度
NAME 是 图像按钮的名称
SRC 是 图像的URL
─────────────────────────────────
<html> <head> <title>HTML Form Input - Image Button</title> </head> <body> <form> Click bere to submit the form: <input type=image name=submit src="submit.gif" align=middle> </form> </body> </html>
password 类型 radio button类型 reset 类型 submit button 类型 text 类型
servlet程序EchoSurvey是了解如何将通过一个表单提交传送上来的参数值反馈回去的很好的例子。Servlet API使这件事情变得非常容易,得到数据只需要调用一个方法。这个名为getParameter()的方法需要一个字符串参数,这个参数也就是相应的参数名。要记住在HTML表单中的所有的输入类型都需要一个名字,这就是传给服务器时的参数名。getParameter()方法为给定参数值返回一个字符串,如果相应的参数名没有找到就返回null。我们知道列表框输入类型可以进行多项选择。它会传送给服务器在单一参数名下的多个参数值。对于接收可能包含多个参数值的参数,我们可以使用getParameterValues()方法,它可以返回一个参数值的字符串数组。对于只有一个值的参数,这个方法将相应的值放在数组中第一个数组元素中(即0号数组元素)。
让我们来看一下这个servlet程序的代码,如图8.32所示。它的完整代码可以在本书附带的光盘中找到。
package javaservlets.samples; import javax.servlet.*; import javax.servlet.http.*; /** * <p>This is a simple servlet that will echo survey information * that was entered into an HTML form. */ public class EchoSurvey extends HttpServlet { /** * <p>Performs the HTTP POST operation * * @param req The request from the client * @param resp The response from the servlet */ public void doPost(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 a standard header out.println("<html>"); out.println("<head>"); out.println("<title>Survey Complete</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1><center>Your survey has been processed!"); out.println("</center></h1><hr><br>"); out.println("Values were:"); out.println("<dir>"); // Get the name String name = req.getParameter("name"); out.println("Name=" + name + "<br>"); // Get the email address String email = req.getParameter("email"); out.println("Email=" + email + "<br>"); // Get the age String age = req.getParameter("age"); out.println("Age=" + age + "<br>"); // Get the operating system. There could be more than one // value String values[] = req.getParameterValues("os"); out.print("Operating Systems="); if (values != null) { for (int i = 0; i < values.length; i++) { if (i > 0) out.print(", "); out.print(values[i]); } } out.println("<br>"); // Get the 'more information' flag String more = req.getParameter("more"); out.println("More information=" + more + "<br>"); // Get the comments String comments = req.getParameter("comments"); out.println("Comments:<br>"); out.println("<dir>"); // Comment lines are separated by a carriage return/line feed // pair - convert them to an HTML line break <br> out.println(toHTML(comments)); out.println("</dir>"); out.println("</dir>"); // Wrap up out.println("</body>"); out.println("</html>"); out.flush(); } /** * <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>Convert any carriage return/line feed pairs into * an HTML line break command (<br>) * * @param line Line to convert * @return line converted line */ private String toHTML(String line) { String s = ""; if (line == null) { return null; } // Cache the length of the line int lineLen = line.length(); // Our current position in the source line int curPos = 0; // Loop through the line and find all of the carriage // return characters (0x0D). If found, convert it into // an HTML line break command (<br>). If the following // character is a line feed (0x0A) throw it away while (true) { // Make sure we don't run off the end of the line if (curPos >= lineLen) { curPos = 0; break; } int index = line.indexOf(0x0D, curPos); // No more characters found if (index == -1) { break; } // Add any data preceding the carriage return if (index > curPos) { s += line.substring(curPos, index); } // Add the line break command s += "<br>"; // Adjust our position curPos = index + 1; // If the next character is a line feed, skip it if (curPos < lineLen) { if (line.charAt(curPos) == 0x0A) { curPos++; } } } // Be sure to add anything after the last carriage return // found if (curPos > 0) { s += line.substring(curPos); } return s; } }图8.2 servlet程序EchoSurvey代码清单
注意,servlet程序EchoSurvey继承了HttpServlet类并实现了doPost()方法。这个方法是在HTTP
POST过程完成时由Web服务器调用。也就是说当用户单击我们的调查表单上的提交按钮时,这个方法被调用。在doPost()方法中,我们从HTTP
response对象中获得一个输出流。这个HTTP response对象用来书写返回给用户的HTML主页。一旦我们获得了输出流,恢复所有的表单数据并将所有值写入主页这项工作就非常简单了。
有一点需要记住就是对具有多行文本的输入域的处理。行与行之间被一个回车/换行对(0x0D0A)分割开。在我们的例子中,我们将这些ACSII控制符替换为一个HTML换行命令(<br>)。
不要忘记你需要设置你所使用的服务器,以使用servlet程序EchoSurvey。
在这一章中,我们深入研究了HTML表单和可用于输入的多种类型,包括button,checkbox,radio button,list
box和text类型。对于每一种输入类型我们都进行了详细的讲述并提供了可执行的例子。之后我们建立了用户调查表单,它综合了HTML输入类型中的多个类型。我们还用HTML制表符格式化表单,使表单的各个行可以被适当的安排好。我们使用了一个简单的servlet程序EchoSurver来说明当提交按钮被单击后送到服务器的数据是如何被处理的。
现在我们已经掌握了编写HTML表单的基础知识。下面我们将要研究在servlet中如何在服务器端使用数据库。下一章将主要讲解如何利用JDBC接收和更新数据库信息。也就是如何管理数据库连接。