- 3.1 从容器到 HttpServlet
Web容器做了什么
当浏览器请求HTTP服务器时,是使用HTTP来传送请求与相关信息(标头、请求、参数、Cookie等)。HTTP是基于TCP/IP之上的协议,信息基本上都是通过文字信息来传送的,然而Servlet本质上市个Java对象,运行于Web容器中。
当请求来到HTTP服务器,而HTTP服务器转交给容器时,容器会创建一个代表当次请求的HttpServletRequest对象,并将请求相关信息设置给该对象。同时容器会创建一个HttpServletResponse对象,作为稍后要对客户端进行响应的Java对象。
接着,容器会根据读取的@WebServlet标注或web.xml的设置,找出处理该请求的Servlet,调用它的service()方法,将创建的HttpServletRequest对象、HttpServletResponse对象传入作为参数,service方法中会根据HTTP请求的方式,调用对应的doXXX()方法。例如,若为GTE则调用doGet()。
接着在doGet()中,可以使用HttpServletRequest对象、HttpServletResponse对象。例如使用getParameter()取得请求参数,使用getWriter()取得输出用的PrintWriter对象,并进行各项响应处理。对PrintWriter做的输出操作,最后由容器转换为HTTP响应,再由HTTP服务器对浏览器进行响应。之后容器将HttpServletRequest对象、HttpServletResponse对象销毁回收,该次请求响应结束。
像这类请求/响应对象的创建与销毁,也就是有关请求/响应对象的生命周期管理,也是Web容器提供的功能。
doXXX()方法
Servlet接口的service()方法签名其实接受的是ServletRequest、ServletResponse:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException;
请求/响应对象的基本行为是规范在ServletRequest、ServletResponse(包是javax.servlet),而与HTTP相关行为则分别由两者的子接口HttpServletRequest、HttpServletResponse(包是javax.servlet.http)定义。
Web容器创建的确实是HttpServletRequest、HttpServletResponse的实现对象,而后调用Servlet接口的service()方法。在HttpServlet中实现service()如下:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest)req; response = (HttpServletResponse)res; } catch (ClassCastException var6) { throw new ServletException("non-HTTP request or response"); } this.service(request, response); }
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); long errMsg; if(method.equals("GET")) { errMsg = this.getLastModified(req); if(errMsg == -1L) { this.doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader("If-Modified-Since"); if(ifModifiedSince < errMsg / 1000L * 1000L) { this.maybeSetLastModified(resp, errMsg); this.doGet(req, resp);//处理HTTP GET请求 } else { resp.setStatus(304); } } } else if(method.equals("HEAD")) { errMsg = this.getLastModified(req); this.maybeSetLastModified(resp, errMsg); this.doHead(req, resp);//处理HTTP HEAD请求 } else if(method.equals("POST")) { this.doPost(req, resp);//处理HTTP POST请求 } else if(method.equals("PUT")) { this.doPut(req, resp);//处理HTTP PUT请求 } else if(method.equals("DELETE")) { this.doDelete(req, resp); } else if(method.equals("OPTIONS")) { this.doOptions(req, resp); } else if(method.equals("TRACE")) { this.doTrace(req, resp); } else { String errMsg1 = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[]{method}; errMsg1 = MessageFormat.format(errMsg1, errArgs); resp.sendError(501, errMsg1); } }
如果客户端发出了没有实现的请求:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_get_not_supported"); if(protocol.endsWith("1.1")) { resp.sendError(405, msg); } else { resp.sendError(400, msg); } }
如果在继承HttpServlet之后,没有重新定义doGet()方法,而客户端对该servlet发出了GET请求,则会收到错误信息。
在GET与POST都要相同处理的情境下,通常可以继承HttpServlet之后,在doGet()、doPost()中都调用一个自定义的processRequest()。如:
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { processRequest(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { processRequest(req, resp); } protected void processRequest(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException { //处理请求 }
- 3.2 关于HttpServletRequest
处理请求参数与标头
getParameter():指定请求参数名称来取得对应的值。
String username = request.getParameter("name");
getParameter()返回的是String对象,若传来的是像“123”这样的字符串值,而需要的是基本数据类型,则必须使用Integer.parseInt()这类的方法将之剖析为基本类型。若请求中没有所指定的请求参数名称,则返回null。
getParameterValue():如果窗体上有可复选的元件,如复选框、列表等,则同一个请求参数名称会有多个值(param=10¶m=20¶m=30),此时可以用可以用getParameterValues()取得一个String数组,数组元素代表所有被选取选项的值。
String[] values = request.getParameterValues("param");
getParameterNames():请求中有多少个请求参数,返回一个Enumeration对象,其中包括所有的请求参数名称。
Enumeration<String> e = req.getParameterNames(); while(e.hasMoreElements()) { String param = e.nextElement(); ... }
getParameterMap():将请求参数以Map对象返回,Map中的键(Key)是请求参数名称,值(Value)的部分是请求参数值,以字符串数组类型String[]返回。
对于HTTP的标头(Header)信息
getHeaders():使用方式与getParameterValues()类似,指定标头名称后可返回Enumeration,元素为字符串
getHeaderNames():使用方式与getParameterValues()类似,取得所有标头名称,以Enumeration返回,内含所有标头字符串名称。
package cc.openhome; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/header.view") public class HeaderServlet extends HttpServlet{ /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>HeaderServlet</title>"); out.println("</head>"); out.println("<body>"); //取得应用程序环境路径 out.println("<h1>HeaderServlet at " + req.getContextPath() + "</h1>"); //取得所有标头名称 Enumeration<String> names = req.getHeaderNames(); while (names.hasMoreElements()) { String name = names.nextElement(); //取得标头值 out.println(name + ":" + req.getHeader(name) + "<br>"); } out.println("</body>"); out.println("</html>"); out.close(); } }
请求参数编码处理
1、POST请求参数编码处理
如果客户端没有在Content-Type标头中设置字符编码信息,此时使用HttpServletRequest的getCharacterEncoding()返回值回事null。这个情况下,容器若使用的是默认编码处理是ISO-8859-1,而客户端使用UTF-8发送非ASCII字符的请求参数,Servlet直接使用getParameter()等方法取得该请求参数值,就会乱码。可以使用HttpServletRequest的setCharacterEncoding()方法指定取得POST请求参数时使用的编码(req.setCharacterEncoding("UTF-8")相当于String text = java.net.URLDecoder.decode("%E6%9E%97", "UTF-8"))
2、GET请求参数编码处理
在HttpServletRequest的API文件中,对setCharacterEncoding()的说明清楚提到:
Overrides the name of the character encoding used in the body of this request.
这个方法对于请求Body中的字符编码才有作用,也就是基本上这个方法只对POST产生作用,当请求时用GET发送时,则没有定义这个方法是否会影响Web容器处理编码的方式。(主要是因为处理URL的是HTTP服务器,而非Web容器)。若使用Tomcat并采用GET,或并没有设置setCharacterEncoding(),且已取得一个请求参数字符串,另外一个处理编码的方式,则是通过String的getBytes()指定编码来取得该字符串的字节数组,然后再重新构造为正确编码的字符串。
例如,若浏览器使用UTF-8处理字符,Web容器默认使用ISo-8859-1编码,则正确处理编码的方式为:
String name = req.getParameter("name"); String name = new String(name.getBytes("ISO-8859-1"), "UTF-8");
相当于
String text = java.net.URLDecoder.encode("林", "UTF-8");
package cc.openhome; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/encoding") public class EncodingServlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //GET的编码处理 String name = req.getParameter("nameGet"); name = new String(name.getBytes("ISO-8859-1"), "UTF-8"); System.out.println("GET: " + name); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //POST的编码处理 resp.setCharacterEncoding("UTF-8"); String name = req.getParameter("namePost"); System.out.println("POST:" + name); } }
getReader()、getInputStream()读取Body内容
package cc.openhoem; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; /** * Created by Administrator on 2016/4/14. * 上传文件 */ public class BodyServlet extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String body = readBody(req); PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet BodyView</title>"); out.println("<head>"); out.println("<body>"); out.println(body); out.println("</body>"); out.println("/html"); } private String readBody(HttpServletRequest req) throws IOException { BufferedReader reader = req.getReader(); String input = null; String requestBody = ""; while(null != (input = reader.readLine())) { requestBody = requestBody + input + "<br>"; } return requestBody; } }
对应的窗体
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title></title> </head> <body> Hello <form action="BodyServlet" method="post" enctype="multipart/form-data"> 选择文件:<input type="file" name="filename" value="" /><br> <input type="submit" value="Upload" name="upload"> </form> </body> </html>
getPart()、getParts()取得上传文件
<html> <head> <title></title> </head> <body> Hello <form action="UploadServlet" method="post" enctype="multipart/form-data"> 上传照片:<input type="file" name="photo" /><br><br> <input type="submit" value="上传" name="upload"> </form> </body> </html>
/** * @Title: UploadServlet.java * @Package cc.openhome * @Description: TODO(文件上传) * @author rocky * @date 2016年4月18日 下午5:22:42 */ package cc.openhoem; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import java.io.*; @MultipartConfig //使用getPart()处理上传的文件 public class UploadServlet extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part part = req.getPart("photo"); //使用getPart()取得Part对象 String filename = getFileName(part); writeTo(filename, part); } //取得上传文件名存在问题 private String getFileName(Part part) { String header = part.getHeader("Content-Disposition"); String fileName = header.substring(header.indexOf("fileName="") + 10, header.lastIndexOf(""")); return fileName; } //存储文件 private void writeTo(String fileName, Part part) throws IOException, FileNotFoundException{ InputStream in =part.getInputStream(); OutputStream out = new FileOutputStream("D:/ProgramData/" + fileName); byte[] buffer = new byte[1024]; int length = -1; while (-1 != (length = in.read(buffer))) { out.write(buffer, 0, length); } out.close(); in.close(); } }
@MultipartConfig标注可用来设置Servlet处理上传文件的相关信息,属性如下:
location:字符串设置,设置写入文件时的目录,如果设置这个属性,则缓存文件就是写到指定的目录,也可以搭配Part的write()方法使用,默认为空字符串。
maxFileSize:限制上传文件大小,默认值为-1L,表示不限制大小。
maxRequestSize:限制multipart/form-data请求个数,默认值为-1L,表示不限制个数。
fileSizeThreshold:整数值设置,若上传文件超过设置门槛,会写入缓存文件,默认值为0。
multipart/form-data发送的每个内容区段,都会有以下的标头信息:
Content-Disposition:form-data;name="fileName";fileName="caterpillar.jpg"
Content-Type:image/jpeg......
如果想取得这些标头信息,可以使用Part对象的getHeader()方法,指定标头名称来取得对应的值。所以想要取得上传文件名称,就是取得Content-Disposition标头的值,然后取得filename属性的值。最后,再利用Java I/O API写入文件中。
Part有个方便的write()方法,可以直接将上传文件指定文件名写入磁盘中,write()可指定文件名,写入的路径是相对于@MultipartConfig的location设置的路径。
多个文件上传:
<form action="UploadServlet3" method="post" enctype="multipart/form-data"> 文件1:<input type="file" name="file1" /><br><br> 文件2:<input type="file" name="file2" /><br><br> 文件3:<input type="file" name="file3" /><br><br> <input type="submit" value="上传" name="upload"> </form>
/** * @Title: UploadServlet.java * @Package cc.openhome * @Description: TODO(文件上传) * @author rocky * @date 2016年4月18日 下午5:22:42 */ package cc.openhoem; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import java.io.IOException; @MultipartConfig(location = "D:/ProgramData") //使用getPart()处理上传的文件 @WebServlet("/upload3.do") public class UploadServlet3 extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); for (Part part : req.getParts()) { //迭代Collection中所有Part对象 if (part.getName().startsWith("file")) { //只处理上传文件区段 String fileName = getFileName(part); part.write(fileName); } } } //取得上传文件名 private String getFileName(Part part) { String header = part.getHeader("Content-Disposition"); String fileName = header.substring(header.indexOf("fileName="") + 10, header.lastIndexOf(""")); return fileName; } }
<!--web.xml--> <servlet> <servlet-name>UploadServlet3</servlet-name> <servlet-class>cc.openhoem.UploadServlet3</servlet-class> <!--设置路径--> <multipart-config> <location>D:/ProgramData</location> </multipart-config> </servlet>
使用RequestDispatcher调派请求
在Web应用程序中,经常需要多个Servlet来完成请求。例如,将另一个Servlet的请求处理流程包含(Include)进来,或将请求转发(Forward)给别的Servlet处理。如果有这类的请求,可以使用HttpServletRequest的getRequestDispatcher()方法取得RequestDispatcher接口的实现对象实例,调用时指定转发或包含的相对URL网址。
RequestDispatcher dispatcher = req.getRequestDispatcher("upload.do");
1、使用include()方法
可以将另一个Servlet的操作流程包括至目前Servlet操作流程中
package cc.openhoem; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * Created by Administrator on 2016/4/19. */ @WebServlet("/some.view") public class Some extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); out.println("Some do one..."); RequestDispatcher dispatcher = req.getRequestDispatcher("other.view?data=123456"); dispatcher.include(req, resp); out.println("Some do two..."); out.close(); } }
2、请求范围属性
在include()或forword()时包括请求参数的做法,仅适用于传递字符串给另一个Servlet,在调派请求的过程中,如果有必须共享的“对象”,可以设置给请求对象称为属性,称为请求范围属性(Request Scope Attribute)。
setAttribute():指定名称与对象设置属性
getAttribute():指定名称取得属性
getAttributeNames():取得所有属性名称
3、使用forward()方法
调用时同样传入请求与响应对象,这表示你要将请求处理转发给别的Servlet,“对客户端的响应同时也转发给另一个Servlet”
package cc.openhoem; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * Created by Administrator on 2016/4/20. */ @WebServlet("/helloController.do") public class HelloController extends HttpServlet{ private HelloModel model = new HelloModel(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //收集请求参数 String name = req.getParameter("user"); //PrintWriter out = resp.getWriter(); //out.print(name); //委托HelloModel对象处理 String message = model.doHello(name); //将结果信息设置至请求对象成为属性 req.setAttribute("message", message); //转发给hello.view进行响应 req.getRequestDispatcher("hello.view").forward(req, resp); } }
package cc.openhoem; import java.util.HashMap; import java.util.Map; /** * Created by Administrator on 2016/4/20. */ public class HelloModel { private Map<String, String> messages= new HashMap<String, String>(); public HelloModel() { messages.put("caterpillar", "Hello"); messages.put("Justin", "Welcome"); messages.put("momor", "Hi"); } public String doHello(String user) { //取值 String message = messages.get(user); return message + ", " + user + "!"; } }
package cc.openhoem; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * Created by Administrator on 2016/4/20. */ @WebServlet("/hello.view") public class HelloView extends HttpServlet{ private String htmlTemplate = "<html>" +" <head>" +" <meta http-equiv='Content-Type'" +" content='text/html; charset=UTF-8'" +" <title>%s</title>" +" <body>" +" <h1>%s</h1>" +" </body>" +"</html>"; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //取得请求参数 String user = req.getParameter("user"); //取得请求属性 String message = (String) req.getAttribute("message"); //产生HTML结果 String html = String.format(htmlTemplate, user, message); //输出HTML结果 resp.getWriter().print(html); } }
- 3.3 关于HttpServletResponse
可以使用HttpServletResponse来对浏览器进行响应。大部分情况使用setContentType()设置响应类型,使用getWriter()取得PrintWriter对象,然后使用PrintWriter的println()等方法输出HTML内容。
还可以进一步使用setHeader()、addHeader()等方法进行响应标头的设置,或者是使用sendRedirect()、sendError()方法,对客户端要求重定向网页,或是传送错误状态信息。若必要,也可以使用getOutputStream()取得ServletOutputStream,直接使用串流对象对浏览器进行字节数据的响应。
设置响应标头、缓冲区
setHeader()、addHeader()来设置相应标头,setHeader()设置标头名称与值,addHeader()则可以在同一个标头名称上附加值。如果标头的值是整数,则可以使用setIntHeader()、addIntHeader()方法,如果标头的值是日期,则使用setDateHeader()、addDateHeader()方法。
容器可以对响应内容进行缓冲,通常默认缓冲。缓冲方法:
getBufferSize()、setBufferSize()、isCommited()、reset()、resetBuffer()、flushBuffer()
在缓冲区未满之前,设置的响应相关内容都不会真正传至客户端,可以使用isCommitted()看看是否响应已确认。如果想要重置所有响应内容,但不会清除已设置的标头内容。
flushBuffer()会清楚(flush)所有缓冲区中已设置的响应信息至客户端,reset()、resetBuffer()必须在未响应前调用。
使用getWriter()输出字符
如果要对浏览器输出HTML,在先前的范例中都会通过HttpServletResponse的getWriter()取得PrintWriter对象,然后指定字符串进行输出。
PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<head>");
1、设置Locale
浏览器如果有发送Accept-Language标头,则可以使用HttpServletRequest的getLocale()来取得一个locale对象,代表客户端可接受的语系。
可以使用HttpServletResponse的setLocale()来设置地区(locale)信息,地区信息就包括了语系与编码信息。语系信息通常通过响应标头Content-Language来设置,而setLocale()也会设置HTTP响应的Content-Language标头
resp.setLocale(Locale.TAIWAN);
这会将HTTP响应的Content-Language设置为zh-TW,而字符编码处理设置为BIG5.可以使用HttpServletResponse的getCharacterEncoding()方法取得编码设置。
<!--web.xml--> <locale-encoding-mapping-list> <locale-encoding-mapping> <locale>zh_TW</locale> <encoding>UTF-8</encoding> </locale-encoding-mapping> </locale-encoding-mapping-list>
2、使用setCharacterEncoding()或setContentType()
也可以调用HttpServletResponse的setCharacterEncoding()设置字符编码:
resp.setCharacterEncoding("UTF-8");
或者在使用HttpServletResponse的setContentType()时,指定charset,charset的值会自动用来调用setCharacterEncoding()。例如,以下不仅设置内容类型为text/html,也会自动调用setCharacterEncoding(),设置编码为UTF-8:
resp.setContentType("text/html; charset=UTF-8");
如果使用了setCharacterEncoding()或setContentType()时指定了charset,则setLocale()就会被忽略。
因为浏览器需要知道如何处理你的响应,所以必须告知内容类型,setContentType()方法在响应中设置content-type响应标头,你只要指定MIME(multipurpose Internet Mail Extensions)类型就可以了。由于编码涉资与内容类型通常都要设置,所以调用setContentType()设置内容类型时,同时指定charset属性是个方便且常见的做法。
常见的设置有text/html、application/pdf、application/jar、application/x-zip、image/jpeg等。
<mime-mapping> <!--设置文件后缀--> <extension>pdf</extension> <!--设置对应的MIME类型名称--> <mime-type>application/pdf</mime-type> </mime-mapping>
发送中文请求参数值,Servlet可正确接收处理并显示在浏览器中。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>宠物类型大调查</title> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> </head> <body> <form action="pet.do" method="post"> 姓名:<input type="text" name="user"><br> 邮件:<input type="text" name="email"><br> 你喜爱的宠物代表:<br> <select name="type" size="6" multiple="true"> <option value="猫">猫</option> <option value="狗">狗</option> <option value="鱼">鱼</option> <option value="鸟">鸟</option> </select><br> <input type="submit" value="送出"> </form> </body> </html>
package cc.openhoem; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * Created by Administrator on 2016/4/20. */ @WebServlet("/pet.do") public class Pet extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //设置请求对象字符编码 req.setCharacterEncoding("UTF-8"); //设置内容类型 resp.setContentType("text/html; cahrset=UTF-8"); //取得输出对象 PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>感谢填写</title>"); //取得请求参数值 out.println("联系人:<a href='mailto:"+req.getParameter("email") + "'>" + req.getParameter("user") + "</a>"); out.println("<br>喜爱的宠物类型"); out.println("<ul>"); //取得复选项请求参数值 for (String type : req.getParameterValues("type")) { out.println("<li>" + type + "</li>"); } out.println("</ul>"); out.println("</body>"); out.println("</html>"); out.close(); } }
使用getOutputStream()输出二进制字符
在大部分情况下,会从HttpServletResponse取得PrintWriter实例,使用println()对浏览器进行字符输出。然而有时候需要直接对浏览器进行字节输出,这时可以使用HttpServletResponse的gteOutputStream()方法取得ServletOutputStream实例。
package cc.openhoem; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Created by Administrator on 2016/4/20. */ public class DownLoad extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String passwd = req.getParameter("passwd"); if ("123456".equals(passwd)) { //设置内容类型 resp.setContentType("application/pdf"); //取得输入串流 InputStream in = getServletContext().getResourceAsStream("/WEB-INF/jdbc.pdf"); //取得输出串流 OutputStream out = resp.getOutputStream(); //读取PDF并输出至浏览器 writeBytes(in, out); } } private void writeBytes(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024]; int length = -1; while(-1 != (length = in.read(buffer))) { out.write(buffer, 0, length); } in.close(); out.close(); } }
使用sendRedirect()、sendError()
forward()会将请求转发至指定的URL,这个动作是在Web容器中进行,浏览器并不会知道请求被转发,地址栏也不会有变化。
在转发过程中,都还是在同一个请求周期,这也是为什么RequestDispatcher是由调用HttpServletRequest的getRequestDispatcher()方法取得,所以在HttpServletRequest中使用setAttribute()设置的属性对象,都可以在转发过程中共享。
可以使用HttpServletResponse的sendRedirect()要求浏览器重新请求另一个URL,又称为重定向(Redirect),使用时可指定绝对URL或相对URL。
reponse.sendRedirect("http://openhoem.cc");
这个方法会在响应中设置HTTP状态码301以及Location标头,浏览器接收到这个标头,会重新使用GET方法请求指定URL,因此,地址栏上会发生URL变更。
如果在处理请求的过程中发现一些错误,而你想要传送服务器默认的状态与错误信息,可以使用sendError()方法。例如,如果根据请求参数必须返回的资源根本不存在,则可以发出错误信息:
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "笔记文件");
- 3.4 综合练习
微博应用程序功能概述
微博首页、微博会员注册、会员注册失败画面、会员注册成功画面、会员登陆成功画面
实现会员注册功能
<form>标签
<form method="post" action="register.do">
邮件地址字段
<input type="text" name="username" size="25" maxlength="100">
名称字段
<input type="text" name="username" size="25" maxlength="16">
密码与确认密码字段
<input type="password" name="password" size="25" maxlength="16">
<input type="password" name="confirmedPasswd" size="25" maxlength="16">
package cc.openhoem.controller; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Created by Administrator on 2016/4/21. */ @WebServlet("/register.do") public class Register extends HttpServlet{ private final String USERS = "D:/ProgramData"; private final String SUCCESS_VIEW = "success.view"; private final String ERROR_VIEW = "error.view"; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //取得请求参数 String email = req.getParameter("emial"); String username = req.getParameter("username"); String password = req.getParameter("password"); String confirmPasswd = req.getParameter("confirmPasswd"); List<String> errors = new ArrayList<String>(); if (isInvalidEmail(email)) { errors.add("未填写邮件或邮件格式不正确"); } if (isInvalidUsername(username)) { errors.add("用户名称为空或已存在"); } if (isInvalidPassword(password, confirmPasswd)) { errors.add("请确认莫玛符合格式并再次确认密码"); } String resultPage = ERROR_VIEW; //窗体验证出错误,设置收集错误的List为请求属性 if (!errors.isEmpty()) { req.setAttribute("errors", errors); } else { resultPage = SUCCESS_VIEW; //创建用户资料 createUserData(email, username, password); } req.getRequestDispatcher(resultPage).forward(req, resp); } private boolean isInvalidEmail(String email) { return email == null || !email.matches( "^[_a-z0-9-]+([.]" + "[_a-z0-9-]+)*@[a-z0-9-]+([.][a-z0-9-]+)*$"); } //检查用户资料夹是否创建来确认用户是否已注册 private boolean isInvalidUsername(String username) { for (String file : new File(USERS).list()) { if (file.equals(username)) { return true; } } return false; } private boolean isInvalidPassword(String password, String confirmedPasswd) { return password == null || password.length() < 6 || password.length() > 16 || !password.equals(confirmedPasswd); } //创建用户资料,在profile中存储邮件与密码 private void createUserData(String email, String username, String password) throws IOException { File userhome = new File(USERS + "/" +username); userhome.mkdir(); BufferedWriter writer = new BufferedWriter(new FileWriter(userhome + "/profile")); writer.write(email + " " + password); writer.close(); } }
package cc.openhoem.controller; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.List; /** * Created by Administrator on 2016/4/21. */ @WebServlet("/error.view") public class Error extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //设置响应编码 resp.setContentType("text/html; charset=UTF-8"); PrintWriter out = resp.getWriter(); out.println("<!DOCTYPE HTML PUBLIC" + "'-//W3C//DTD HTML 4.01 Transitional//EN>'"); out.println("<html>"); out.println("<head>"); out.println("<meta http-equiv='Content-Type'" + "content='text/html;charset=UTF-8'>"); out.println(" <title>新增会员失败</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>新增会员失败</h1>"); out.println("u1 style='color: rgb(255, 0, 0);'>"); //取得请求属性 List<String> errors = (List<String>) req.getAttribute("errors"); //显示错误信息 for (String error : errors) { out.println(" <li>" + error + "</li>"); } out.println("</u1>"); out.println("<a href='register.html'>返回注册页面</a>"); out.println("</body>"); out.println("</html>"); out.close(); } }
要注意到,为了显示中文的错误信息,使用HttpServletResponse的setContentType()时顺便指定了charset属性,由于只有在失败时才会转发到这个页面,并在请求中带有error属性,于是使用HttpServletRequest的getAtrribute()取得属性,并逐一显示错误信息。
package cc.openhoem.controller; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * Created by Administrator on 2016/4/21. */ @WebServlet("/success.view") public class Success extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF-8"); PrintWriter out = resp.getWriter(); //.... out.println("<!DOCTYPE HTML PUBLIC" + "'-//W3C//DTD HTML 4.01 Transitional//EN>'"); out.println("<html>"); out.println("<head>"); out.println("<meta http-equiv='Content-Type'" + "content='text/html;charset=UTF-8'>"); out.println(" <title>新增会员失败</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>会员 " + req.getParameter("username") + " 注册成功</h1>"); out.println("<a href='index.html'>回首页登录</a>"); out.println("</body>"); out.println("</html>"); out.close(); } }
实现会员登录功能
<form>标签
<form method="post" action="login.do">
名称字段
<input type="text" name="username">
密码字段
<input type="password" name="password">
package cc.openhoem.controller; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; /** * Created by Administrator on 2016/4/21. */ @WebServlet("/login.do") public class Login extends HttpServlet{ private final String USERS = "D:/ProgramData"; private final String SUCCESS_VIEW = "member.view"; private final String ERROR_VIEW = "index.html"; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); } private boolean cheakLogin(String username, String password) throws IOException { if (username != null && password != null) { //读取用户资料夹中的profile for (String file : new File(USERS).list()) { if (file.equals(username)) { BufferedReader reader = new BufferedReader( new FileReader(USERS + "/" + file + "/profile")); String passwd = reader.readLine().split(" ")[1]; if (password.equals(password)) { return true; } } } } return false; } }
检查登录基本上就是查看用户名是否有对应的资料夹,并且看看profile文件中存放的密码是否符合,注意先前创建profile时,邮件与密码中间是用“ ”字符分隔。如果名称与密码不符就重新定向回首页,让用户可以重新登录,登陆信息正确就转发会员网页。
package cc.openhoem.controller; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * Created by Administrator on 2016/4/21. */ @WebServlet("/member.view") public class Member extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); out.println("<!DOCTYPE HTML PUBLIC" + "'-//W3C//DTD HTML 4.01 Transitional//EN>'"); out.println("<html>"); out.println("<head>"); out.println("<meta http-equiv='Content-Type'" + "content='text/html;charset=UTF-8'>"); out.println(" <title>新增会员失败</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>会员 " + req.getParameter("username") + " 您好!</h1>"); out.println("</body>"); out.println("</html>"); out.close(); } }