Servlet核心接口
首先,Servlet API提供了一个抽象类GenericServlet, 它提供了一种Servlet的通用实现,与具体的网络应用层协议无关。也就是说,不必须是HTTP。而其子类HttpServlet类才是我们研究的重点。正常情况下,我们的Servlet都继承自这个类。
HTTP协议把客户请求分为了GET、POST、PUT、DELETE等方式。HttpServlet为每种请求方式都提供了相应的服务方法,如doGet()、doPost()、doPut()、doDelete()。而服务入口方法service()则基本干一件事:根据请求方式调用对应的doxxx()方法。
一般情况下,Servlet最多支持GET和POST。可以只在一个服务方法里处理请求,然后别的服务方法调用这个服务方法就可以了。
HttpServletRequest和HttpServletResponse
所有的服务方法都接受这两个对象作为参数。很明显,一个代表请求输入,一个代表返回输出。通过操作这两个对象就可以完成Servlet的基本功能。下面通过一个列表来展示这两个类互相对应的常见方法:
HttpServletRequest方法 | 操作对象 | HttpServletResponse方法 |
---|---|---|
getContentType() | Body的MIME类型 | getContentType() setContentType() |
getCharacterEncoding() | Body的字符编码 | setCharacterEncoding() |
getCookies() | Cookie | addCookie() |
getHeader() | 请求/响应头 | setHeader() |
下面是HttpServletRequest的一些特殊方法:
方法 | 描述 |
---|---|
getRequestURI() | 返回请求的URL |
getMethod() | 返回请求的 HTTP 方法的名称 |
getSession() | 返回与该请求关联的当前 session 会话 |
getParameter() | 以字符串形式返回请求参数的值 |
getAttribute() setAttribute() removeAttribute() | 操作已命名属性 |
下面是HttpServletResponse的一些特殊方法:
方法 | 描述 |
---|---|
setStatus() | 设置HTTP响应的状态代码 |
sendError() | 设置特定错误的HTTP响应代码 |
getOutputStream() | 返回一个ServletOutputStream对象,用来输出二进制的正文数据。 |
getWriter() | 返回一个PrintWriter对象,用来输出字符串形式的正文数据。 |
flushBuffer() | 把缓冲区的正文数据发送给客户端。 |
resetBuffer() | 清空缓冲区中的正文数据。 |
reset() | 清空缓冲区以及响应状态代码和响应头数据。 |
Servlet API高级主题
读写Cookie
Cookie的运行机制是由HTTP协议规定的,由服务器和客户端(浏览器)共同遵守。下面是一个典型的设置Cookie的HTTP返回头:
HTTP/1.1 200 OK
Date: Fri, 04 Feb 2000 21:03:38 GMT
Server: Apache/1.3.9 (UNIX) PHP/4.0b3
Set-Cookie: username=Tom; expires=Friday, 04-Feb-07 22:03:38 GMT;
path=/; domain=w3cschool.cc
...
可以发现,Cookie包含以下几种内容:
- 一个名称-值对
- 过期时间
- 有效domain(域)和path(路径)
如果浏览器被设置了Cookie,它将会保留此信息直到过期时间。当浏览器再次访问该Cookie匹配的路径和域的页面时,它会就会把Cookie的内容也包含到发送到服务器的请求中。
Servlet可以对Cookie做的操作就一目了然了:
- 可以调request.getCookies()和response.addCookie()
- 通过Cookie.setMaxAge()设置过期时间。大于0表示存到硬盘直到过期;等于0删除Cookie;小于0表示关闭浏览器时删除Cookie.
- 可以调setDomain()和setPath()设置Cookie作用范围。默认情况下只对同一个WebApp生效。
管理Session
可以参考菜鸟教程
HTTP是无状态的协议。客户端请求网页到收到响应后,TCP连接就会被关闭,服务器不会保留这个请求的任何记录。但是显然保存客户数据是很有必要的,典型如购物网站的购物车。
在Web开发领域,会话机制是用于跟踪用户状态的普遍解决方案。会话指的是在一段时间内,单个客户端与Web应用的一连串交互过程。在一个会话中,客户可能会多次访问Web应用的同一个网页,也可能访问Web应用的多个网页。
在会话(Session)被引入之前,有以下三种小聪明的方法用来“模拟会话的功能”:
- 服务器可以分配一个唯一的Session ID作为每个客户端的cookie,对于客户端的后续请求可以使用接收到的cookie来识别。
- 服务器可以发送一个隐藏的HTML表单字段,其值是Session ID. 这样在收到表单提交请求时可以获得Sessio ID.
- 在每个URL末尾追加一些额外的数据来标识Session,如w3cschool.cc/file.htm;sessionid=12345
终于Session唱着“不用麻烦了,不用麻烦了” 出场了。
可以调用 HttpSession session = request.getSession(); 获得Session.
调用getAttribute(), setAttribute(), removeAttribute(), getAttributeNames()来利用Session存取对象。
调用invalidate()销毁会话;setMaxInactiveInterval()设置会话不活动时间。
其实,Session实际上默认也是通过Cookie记Session ID来实现的。如果客户端禁用了Cookie,则要通过重写URL来解决。使用response.encodeURL().
最后,可以用Servlet API定义的HttpSessionLisener接口来统计在线用户人数。该接口定义了sessionCreated(), sessionDestroyed() 两个方法,顾名思义。
文件上传
可以参考 菜鸟教程
使用如下的form表单,点击上传时客户端发送的HTTP请求正文将是“multipart/form-data”类型,它表示复杂的包括多个子部分的复合表单。
Apache提供了commons-fileupload包来帮助处理文件上传。需要引入这个包及其依赖包commons-io包。
核心代码如下:
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// 配置上传参数
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置内存临界值 - 超过后将产生临时文件并存储于临时目录中
factory.setSizeThreshold(MEMORY_THRESHOLD);
// 设置临时存储目录
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置最大文件上传值
upload.setFileSizeMax(MAX_FILE_SIZE);
// 设置最大请求值 (包含文件和表单数据)
upload.setSizeMax(MAX_REQUEST_SIZE);
// 构造临时路径来存储上传的文件
// 这个路径相对当前应用的目录
String uploadPath = getServletContext().getRealPath("./") + File.separator + UPLOAD_DIRECTORY;
// 如果目录不存在则创建
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdir();
}
try {
// 解析请求的内容提取文件数据
@SuppressWarnings("unchecked")
List formItems = upload.parseRequest(request);
if (formItems != null && formItems.size() > 0) {
// 迭代表单数据
for (FileItem item : formItems) {
// 处理不在表单中的字段
if (!item.isFormField()) {
String fileName = new File(item.getName()).getName();
String filePath = uploadPath + File.separator + fileName;
File storeFile = new File(filePath);
// 在控制台输出文件的上传路径
System.out.println(filePath);
// 保存文件到硬盘
item.write(storeFile);
request.setAttribute("message",
"文件上传成功!");
}
}
}
} catch (Exception ex) {
request.setAttribute("message",
"错误信息: " + ex.getMessage());
}
...
}
转发、包含和重定向
Web应用在响应客户端的一个请求时,有可能响应过程很复杂,需要多个Web组件共同协作,才能生成响应结果。
Servlet规范为servlet之间的协作提供了两种途径:
- 请求转发:Servlet源组件先对客户请求做一些预处理操作,然后把请求转发给其他Web组件。调用getRequestDispatcher().forward()方法。
- 包含: Servlet源组件把其他Web组件生成的响应结果包含到自身的响应结果中。调用getRequestDispatcher().include()方法。
其中:源组件和目标组件共享同一个HttpServletRequest对象和HttpServletResponse对象;目标组件可以是Serlet、JSP或者HTML文档。
调用
PrintWriter out = response.getWriter();
out.println("这些内容不会出现在响应中。");
RequestDispatcher dispatcher = context.getRequestDispatcher("/output");
dispatcher.forward(request, response);
时, 会清空response中的正文数据缓冲区,然后调用目标组件的service()方法,把响应的结果返回客户端。因此源组件的响应结果不会被发送到客户端。
而如果调用下面代码:
PrintWriter out = response.getWriter();
out.println("MainServlet ");
out.println("");
RequestDispatcher headerDispatcher = context.getRequestDispatcher("/header.html");
RequestDispatcher greetDispatcher = context.getRequestDispatcher("/greet");
RequestDispatcher footerDispatcher = context.getRequestDispatcher("/footer.html");
// 包含header.html
headerDispatcher.forward(request, response);
// 包含GreetServlet输出的HTML片段
greetDispatcher.forward(request, response);
// 包含footer.html
footerDispatcher.forward(request, response);
out.println("");
out.close();
程序就会把三种目标组件的输出全部包含到最终输出中来。结果如下:
重定向就是告诉客户端老娘不伺候了,去找那个小碧池吧!调用response.sendRedirect()即可。这时候客户端浏览器就会乖乖地去找另一个网页。