JavaWeb体系介绍
- web服务器
- Servlet
- JSP(轻量级Servlet)
- JDBC
Tomcat服务器(软件)
什么是Web服务器
web服务器通常也称之为web容器,提供了对javaweb项目的运行环境,内部可以实现对客户端的请求接收,通过web应用中对应的java程序进行处理(访问数据库,进行业务逻辑操作等)之后,向客户端响应运行结果。
目前javaweb开发中常见的服务器包含以下几种:
- tomcat(开源免费的web服务器)
- jetty(开源免费的web服务器,常见于一些web聊天应用)
- weblogic(Oracle开发收费的web服务器)
- jboss(开源免费的web服务器,与EJB结合良好)
服务器和服务器软件
**服务器(硬件):**实际上就是一台主机(一台电脑),一般情况下服务器配置要求要高于普通的个人电脑(PC机);在服务器中通常运行的是一些服务软件(web服务器软件,mysql数据库服务,http服务器软件等)
**服务器软件:**是运行在服务器主机上的一些应用程序,比如tomcat服务器,mysql服务器等。
Tomcat
tomcat有apache(阿帕奇)开源组织对外开源免费的一个web容器,提供了对javaweb应用中jsp和servlet等web技术的运行环境,通过http协议接收以及响应客户端请求。
tomcat下载与安装
- 访问tomcat官网:http://tomcat.apache.org/
- 下载tomcat
启动与访问
-
由于tomcat是由java技术编写而成,因此需要在主机中具备java的运行环境,即配置JAVA_HOME(具体参考java环境变量配置)
-
打开tomcat解压缩之后的目录,找到bin目录:
- 找到bin目录中,名字叫
startup.bat
文件(linux下使用startup.sh
)双击启动;
- 启动正常时,命令行没有任何错误信息,并显示如下信息
bat:windows中的批处理文件
目录结构
tomcat的目录结构如下:
以上目录中,经常操作的主要包含以下:
conf:配置目录
webapps:web项目的发布目录
注意事项:
在tomcat访问应用程序时,如果不指定具体的资源,tomcat会默认请求名为
index.html
、index.htm
、index.jsp
等相关的资源,因为在conf/web.xml配置中默认有指定,并且该配置也允许程序员修改;
常用配置
tomcat管理账号密码设置
当需要涉及到对tomcat中应用程序管理时,通常需要提供访问的账号密码,此时可以通过在conf/tomcat-user.xml
进行用户信息配置:
以上配置中:
第一行:配置角色
第二行:配置用户并指定角色
端口号配置
由于计算机中不同的网络应用需要对外通信就必须指定各自的访问端口,但是有可能端口会出现冲突;tomcat服务器默认占据的8080端口,即需要访问指定主机的服务器只需要通过主机名+端口即可;http协议默认端口是80,如果将tomcat服务器的端口配置为80,则访问时就无需手动指定端口号。
修改端口号
使用文本编辑器(推荐vscode)打开apache-tomcat-9.0.37>conf>server.xml
,第69~71行
将以上port值修改为指定数值即可
虚拟访问路径配置
所谓虚拟访问路径配置,即将主机中指定的一些资源通过tomcat服务器配置虚拟访问路径之后对外提供可访问的能力
- 准备本地的文件夹(存储的需要对外公开的资源)
- 打开tomcat服务器所在目录的conf目录,打开server.xml文件,配置以下标签:
docBase:表示对外提供的资源所在的本机绝对路径
path:设置虚拟的访问地址
- 打开浏览器,访问指定资源,浏览器地址栏中输入:localhost:/mp3/2.mp3
地址栏中的资源路径:/mp3/2.mp3通过tomcat服务器解析之后找到服务器所在主机中虚拟路径映射的绝对路劲地址:
D:\bak\2.mp3
Servlet入门
Servlet 概述
Servlet(Server+Applet),服务端小程序;是一项运行在服务器端的java程序,可以接受来自客户端的http请求,并且对请求的信息作出相应。Servlet是一项接口技术,任何时候创建一个Servlet应用都必须从Servlet接口实现,实际上就是一个java类
Hello Servlet
创建一个Servlet包含三个基本步骤:
-
创建一个类从Servlet实现(继承HttpServlet)
public class HelloServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException {} @Override public ServletConfig getServletConfig() {return null;} @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("HelloServlet...."); } @Override public String getServletInfo() {return null;} @Override public void destroy() {} }
-
实现service的方法
@Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { //实现具体的操作 }
-
配置Servlet(基于xml/基于注解)
<servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.softeem.servlets.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping>
serlvet:
配置servlet名称以及类路径
servlet-mapping:
servlet映射配置,配置servlet名称以及虚拟的访问路径
Servlet创建方式对比
- 实现javax.servlet.Servlet接口
- 继承javax.servlet.GenericServlet类
- 继承javax.servlet.HttpServlet类(推荐)
关于405错误状态码:
客户端请求服务端的method与服务端servlet接收处理的方法不一致,比如:
客户端发送的get请求,服务端使用doPost处理
Servlet执行原理
- 当web容器启动时,首先对web.xml初始化检查配置是否正确,比如url-pattern
- 客户端浏览器发起对servlet请求,通过请求的资源路径寻找对应的url-pattern
- 通过url-pattern对应的servlet-name寻找到servlet标签中的servlet-name
- 根据servlet-name所在标签找到servlet-class并且执行对应类中的service方法
Servlet生命周期
任何一个Servlet类从初始化到使用完毕销毁都有一套完整的执行流程,这一套执行流程我们称之为servlet的生命周期.
生命周期
- 当请求第一次到达时,容器会对被请求的servlet初始化,并调用init方法,完成初始操作,而且只会初始化一次(单例)
- 紧接着执行service方法,service默认会根据客户端的请求方法选择调用对应的doXXX方法
- 一旦web容器停止服务,此时所有的servlet都会被销毁,同时执行destroy完成收尾工作(先执行destroy再销毁)
load-on-startup
通过对servlet设置load-on-startup配置(默认值-1),可以控制servlet的初始化顺序,一旦为servlet指定了load-on-startup配置后,则即便,客户端不请求该servlet,web容器也会默认根据设置的数值大小对指定的servlet进行初始化,并调用init方法,关于配置方式分为两种:
- 基于注解的配置
- 基于xml文件配置(web.xml)
Servlet配置详解
基于注解的servlet配置
servlet3.0之后允许使用注解的方式配置servlet,相比在web.xml配置来说,代码得到了极大的简化,具体使用方式如下:
客户端访问方式与之前保持一致:
http://localhost/javaweb02/msg
即可
url-pattern配置
//配置访问映射地址(虚拟访问路径)
//@WebServlet("/hello")
//@WebServlet({"/hello","/helloServlet","/HelloServlet"})
//@WebServlet("/user/hello")
//@WebServlet("/user/*")
//@WebServlet("/hello.do")
@WebServlet("*.do")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Hello....get");
}
}
对于以上配置方式,对应的访问地址如下:
@WebServlet("/hello")
http://localhost/javaweb02/hello
@WebServlet({"/hello","/helloServlet","/HelloServlet"})
http://localhost/javaweb02/hello
http://localhost/javaweb02/helloServlet
http://localhost/javaweb02/HelloServlet
@WebServlet("/user/hello")
http://localhost/javaweb02/user/hello
@WebServlet("/user/*")
http://localhost/javaweb02/user/XXX
@WebServlet("/hello.do")
http://localhost/javaweb02/hello.do
注意事项:
在一个web应用中不能出现重复的url-pattern,否则服务器启动时会抛出异常
ServletConfig与初始化参数
Idea配置Tomcat热部署
- 配置tomcat服务器使用热部署模式
- 使用debug启动web应用
获取客户端提交数据
HttpServletRequest
HttpServletRequest对象表示客户端请求,当客户端通过Http协议请求到Servlet时,所有的请求数据都会封装到HttpServletRequest对象中,通过该对象提供的相关方法可以获取客户端提交过来的数据信息。
获得客户机信息:
方法 | 解释 |
---|---|
getRequestURL() | 返回客户端发出请求时的完整URL。 |
getRequestURI() | 返回请求行中的参数部分。 |
getQueryString () | 方法返回请求行中的参数部分(参数名+值) |
getRemoteHost() | 返回发出请求的客户机的完整主机名。 |
getRemoteAddr() | 返回发出请求的客户机的IP地址。 |
getPathInfo() | 返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以"/"开头。 |
getRemotePort() | 返回客户机所使用的网络端口号。 |
getLocalAddr() | 返回WEB服务器的IP地址。 |
getLocalName() | 返回WEB服务器的主机名。 |
获得客户机请求头
方法 | 解释 |
---|---|
getHeader(string name) | 以 String 的形式返回指定请求头的值。如果该请求不包含指定名称的头,则此方法返回 null。如果有多个具有相同名称的头,则此方法返回请求中的第一个头。头名称是不区分大小写的。可以将此方法与任何请求头一起使用 |
getHeaders(String name) | 以 String 对象的 Enumeration 的形式返回指定请求头的所有值 |
getHeaderNames() | 返回此请求包含的所有头名称的枚举。如果该请求没有头,则此方法返回一个空枚举 |
获得客户机请求参数
方法 | 解释 |
---|---|
getParameter(String name) | 根据name获取请求参数(常用) |
getParameterValues(String name) | 根据name获取请求参数列表(常用) |
getParameterMap() | 返回的是一个Map类型的值,该返回值记录着前端(如jsp页面)所提交请求中的请求参数和请求参数值的映射关系。(编写框架时常用) |
getParameterNames() | 获取所有请求参数的name名称 |
案例
-
表单结构
<form action="msg"> 用户名: <input type="text" name="name"><br> 密码:<input type="password" name="pwd"><br> 年龄:<input type="number" name="age"><br> <!--对于单选按钮,如果未指定value时,后台获取到的是on;如果未进行选择则获取null--> 性别:<input type="radio" name="sex" id="sex_male" checked value="男"><label for="sex_male">男</label> <input type="radio" name="sex" id="sex_famale" value="女"> <label for="sex_famale">女</label> <br> 兴趣爱好: <input type="checkbox" name="hobby" value="java">java <input type="checkbox" name="hobby" value="game">打游戏 <input type="checkbox" name="hobby" value="doudou">打豆豆 <input type="checkbox" name="hobby" value="sleep">睡觉 <br> 所在城市:<select name="city"> <option value="武汉">武汉</option> <option value="北京">北京</option> <option value="上海">上海</option> <option value="广州">广州</option> <option value="深圳">深圳</option> </select><br> 个性签名: <textarea name="mark" cols="50" rows="5"></textarea><br> 出生日期: <input type="date" name="birth"><br> <button type="submit">注册</button> </form>
-
servlet中doGet方法实现
//设置请求编码(解决提交数据时的乱码问题) request.setCharacterEncoding("utf-8"); //获取请求参数中指定参数名的参数值(参数即为表单控件的name属性) String name = request.getParameter("name"); String pwd = request.getParameter("pwd"); String age = request.getParameter("age"); String sex = request.getParameter("sex"); // String hobby = request.getParameter("hobby"); //获取多个参数名一致的参数值,并以数组的形式返回(复选框) String[] hobbies = request.getParameterValues("hobby"); String city = request.getParameter("city"); String mark = request.getParameter("mark"); String birth = request.getParameter("birth"); System.out.println(name); System.out.println(pwd); System.out.println(age); System.out.println(sex); for(String h:hobbies){ System.out.println(h); } System.out.println(city); System.out.println(mark); System.out.println(birth); System.out.println("===================================="); //获取请求中所有提交数据 Map<String, String[]> parameterMap = request.getParameterMap(); parameterMap.forEach((k,v)->{ for(String s:v){ System.out.println(k+"---->"+s); } });
重定向与转发
Redirect
重定向是指当浏览器请求一个URL时,服务器返回一个重定向指令,告诉浏览器地址已经变了,麻烦使用新的URL再重新发送新请求。
例如,我们已经编写了一个能处理/first
的FirstServlet
,如果收到的路径为/first
,希望能重定向到/second
,可以再编写一个SecondServlet
:
@WebServlet("/first")
public class FirstServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
resp.sendRedirect("/second"+(name==null?"":"?name="+name));
}
}
如果浏览器发送GET /hi
请求,RedirectServlet
将处理此请求。由于RedirectServlet
在内部又发送了重定向响应,因此,浏览器会收到如下响应:
HTTP/1.1 302 Found
Location: /hello
当浏览器收到302响应后,它会立刻根据Location
的指示发送一个新的GET /hello
请求,这个过程就是重定向:
┌───────┐ GET /hi ┌───────────────┐
│Browser│ ────────────> │RedirectServlet│
│ │ <──────────── │ │
└───────┘ 302 └───────────────┘
┌───────┐ GET /hello ┌───────────────┐
│Browser│ ────────────> │ HelloServlet │
│ │ <──────────── │ │
└───────┘ 200 <html> └───────────────┘
观察Chrome浏览器的网络请求,可以看到两次HTTP请求:
重定向有两种:一种是302响应,称为临时重定向,一种是301响应,称为永久重定向。两者的区别是,如果服务器发送301永久重定向响应,浏览器会缓存/hi
到/hello
这个重定向的关联,下次请求/hi
的时候,浏览器就直接发送/hello
请求了。
重定向有什么作用?重定向的目的是当Web应用升级后,如果请求路径发生了变化,可以将原来的路径重定向到新路径,从而避免浏览器请求原路径找不到资源。
HttpServletResponse
提供了快捷的redirect()
方法实现302重定向。如果要实现301永久重定向,可以这么写:
resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301
resp.setHeader("Location", "/second");
Forward
Forward是指内部转发。当一个Servlet处理请求的时候,它可以决定自己不继续处理,而是转发给另一个Servlet处理。
例如,我们已经编写了一个能处理/first
的FirstServlet
,继续编写一个能处理/second
的SecondServlet
:
@WebServlet("/first")
public class FirstServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("/second").forward(req,resp);
}
}
FirstServlet
在收到请求后,它并不自己发送响应,而是把请求和响应都转发给路径为/second
的Servlet,即下面的代码:
req.getRequestDispatcher("/second").forward(req, resp);
后续请求的处理实际上是由HelloServlet
完成的。这种处理方式称为转发(Forward),我们用流程图画出来如下:
┌────────────────────────┐
│ ┌───────────────┐ │
│ ────>│ FirstServlet │ │
┌───────┐ GET /first │ └───────────────┘ │
│Browser│ ──────────────> │ │ │
│ │ <────────────── │ ▼ │
└───────┘ 200 <html> │ ┌───────────────┐ │
│ <────│ SecondServlet │ │
│ └───────────────┘ │
│ Web Server │
└────────────────────────┘
转发和重定向的区别在于,转发是在Web服务器内部完成的,对浏览器来说,它只发出了一个HTTP请求:
使用Session和Cookie
在Web应用程序中,我们经常要跟踪用户身份。当一个用户登录成功后,如果他继续访问其他页面,Web程序如何才能识别出该用户身份?
因为HTTP协议是一个无状态协议,即Web应用程序无法区分收到的两个HTTP请求是否是同一个浏览器发出的。为了跟踪用户状态,服务器可以向浏览器分配一个唯一ID,并以Cookie的形式发送到浏览器,浏览器在后续访问时总是附带此Cookie,这样,服务器就可以识别用户身份。
Session
我们把这种基于唯一ID识别用户身份的机制称为Session。每个用户第一次访问服务器后,会自动获得一个Session ID。如果用户在一段时间内没有访问服务器,那么Session会自动失效,下次即使带着上次分配的Session ID访问,服务器也认为这是一个新用户,会分配新的Session ID。
JavaEE的Servlet机制内建了对Session的支持。我们以登录为例,当一个用户登录成功后,我们就可以把这个用户的名字放入一个HttpSession
对象,以便后续访问其他页面的时候,能直接从HttpSession
取出用户名:
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password= req.getParameter("password");
System.out.println(username+" : "+password);
if("admin".equals(username)&&"123".equals(password)){
//登录成功
//记录当前登录状态
req.getSession().setAttribute("currentUser",username);
resp.sendRedirect("/index");
}else{
resp.sendError(404);
}
}
}
上述LoginServlet
在判断用户登录成功后,立刻将用户名放入当前HttpSession
中:
HttpSession session = req.getSession();
session.setAttribute("currentUser", username);
在IndexServlet
中,可以从HttpSession
取出用户名:
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
HttpSession session = req.getSession();
String currentUser = null;
if(session.getAttribute("currentUser")!=null){
currentUser = session.getAttribute("currentUser").toString();
out.write("<h1>欢迎登陆 "+currentUser+"</h1>");
out.write("<h2><a href='logout'>登出</a></h2>");
}else{
resp.sendRedirect("index.html");
}
}
}
如果用户已登录,可以通过访问/logout
登出。登出逻辑就是从HttpSession
中移除用户相关信息:
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().removeAttribute("currentUser");
resp.sendRedirect("/index");
}
}
对于Web应用程序来说,我们总是通过HttpSession
这个高级接口访问当前Session。如果要深入理解Session原理,可以认为Web服务器在内存中自动维护了一个ID到HttpSession
的映射表,我们可以用下图表示:
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ ┌───────────────┐ │
┌───>│ IndexServlet │<──────────┐
│ │ └───────────────┘ ▼ │
┌───────┐ │ ┌───────────────┐ ┌────────┐
│Browser│──┼─┼───>│ LoginServlet │<────>│Sessions││
└───────┘ │ └───────────────┘ └────────┘
│ │ ┌───────────────┐ ▲ │
└───>│LogoutServlet │<──────────┘
│ └───────────────┘ │
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
而服务器识别Session的关键就是依靠一个名为JSESSIONID
的Cookie。在Servlet中第一次调用req.getSession()
时,Servlet容器自动创建一个Session ID,然后通过一个名为JSESSIONID
的Cookie发送给浏览器:
这里要注意的几点是:
JSESSIONID
是由Servlet容器自动创建的,目的是维护一个浏览器会话,它和我们的登录逻辑没有关系;- 登录和登出的业务逻辑是我们自己根据
HttpSession
是否存在一个"user"
的Key判断的,登出后,Session ID并不会改变; - 即使没有登录功能,仍然可以使用
HttpSession
追踪用户,例如,放入一些用户配置信息等。
除了使用Cookie机制可以实现Session外,还可以通过隐藏表单、URL末尾附加ID来追踪Session。这些机制很少使用,最常用的Session机制仍然是Cookie。
使用Session时,由于服务器把所有用户的Session都存储在内存中,如果遇到内存不足的情况,就需要把部分不活动的Session序列化到磁盘上,这会大大降低服务器的运行效率,因此,放入Session的对象要小,通常我们放入一个简单的User
对象就足够了:
public class User {
public long id; // 唯一标识
public String email;
public String name;
}
在使用多台服务器构成集群时,使用Session会遇到一些额外的问题。通常,多台服务器集群使用反向代理作为网站入口:
┌────────────┐
┌───>│Web Server 1│
│ └────────────┘
┌───────┐ ┌─────────────┐ │ ┌────────────┐
│Browser│────>│Reverse Proxy│───┼───>│Web Server 2│
└───────┘ └─────────────┘ │ └────────────┘
│ ┌────────────┐
└───>│Web Server 3│
└────────────┘
如果多台Web Server采用无状态集群,那么反向代理总是以轮询方式将请求依次转发给每台Web Server,这会造成一个用户在Web Server 1存储的Session信息,在Web Server 2和3上并不存在,即从Web Server 1登录后,如果后续请求被转发到Web Server 2或3,那么用户看到的仍然是未登录状态。
要解决这个问题,方案一是在所有Web Server之间进行Session复制,但这样会严重消耗网络带宽,并且,每个Web Server的内存均存储所有用户的Session,内存使用率很低。
另一个方案是采用粘滞会话(Sticky Session)机制,即反向代理在转发请求的时候,总是根据JSESSIONID的值判断,相同的JSESSIONID总是转发到固定的Web Server,但这需要反向代理的支持。
无论采用何种方案,使用Session机制,会使得Web Server的集群很难扩展,因此,Session适用于中小型Web应用程序。对于大型Web应用程序来说,通常需要避免使用Session机制。
Cookie
实际上,Servlet提供的HttpSession
本质上就是通过一个名为JSESSIONID
的Cookie来跟踪用户会话的。除了这个名称外,其他名称的Cookie我们可以任意使用。
如果我们想要设置一个Cookie,例如,记录用户选择的语言,可以编写一个LanguageServlet
:
@WebServlet(urlPatterns = "/pref")
public class LanguageServlet extends HttpServlet {
private static final Set<String> LANGUAGES = Set.of("en", "zh");
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String lang = req.getParameter("lang");
if (LANGUAGES.contains(lang)) {
// 创建一个新的Cookie:
Cookie cookie = new Cookie("lang", lang);
// 该Cookie生效的路径范围:
cookie.setPath("/");
// 该Cookie有效期:
cookie.setMaxAge(8640000); // 8640000秒=100天
// 将该Cookie添加到响应:
resp.addCookie(cookie);
}
resp.sendRedirect("/");
}
}
创建一个新Cookie时,除了指定名称和值以外,通常需要设置setPath("/")
,浏览器根据此前缀决定是否发送Cookie。如果一个Cookie调用了setPath("/user/")
,那么浏览器只有在请求以/user/
开头的路径时才会附加此Cookie。通过setMaxAge()
设置Cookie的有效期,单位为秒,最后通过resp.addCookie()
把它添加到响应。
如果访问的是https网页,还需要调用setSecure(true)
,否则浏览器不会发送该Cookie。
因此,务必注意:浏览器在请求某个URL时,是否携带指定的Cookie,取决于Cookie是否满足以下所有要求:
- URL前缀是设置Cookie时的Path;
- Cookie在有效期内;
- Cookie设置了secure时必须以https访问。
如果我们要读取Cookie,例如我们在ParseCookies读取名为lang
的Cookie以获取用户设置的语言
package com.softeem.server;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/parseCookies")
public class ParseCookie extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie [] cookies = req.getCookies();
String currentLang= "EN"; //set default
if(cookies!=null){
for (Cookie cookie : cookies) {
System.out.println(cookie.getName());
if(cookie.getName().equals("lang")){
System.out.println(cookie.getValue());
}
}
}
resp.sendRedirect("/");
}
}
可见,读取Cookie主要依靠遍历HttpServletRequest
附带的所有Cookie。