HTTP是一种无状态协议,简单点来说就是一次请求只会对应一次响应,连接关闭后,Web服务器不会保存这次请求响应过程中的任何状态信息,当客户端发送下一个请求时,Web服务器会把这次请求看成一个新的连接,与前次请求无任何关联。
前面提到了HTTP的无状态,也就是协议对于事务处理没有记忆能力,不保存状态信息意味着如果后续处理仍然需要前面的信息,则必须将之前的信息重传。假如先前信息很多,会导致服务器的应答速度变慢,而且将相同的数据多次传输,这本就是不必要的性能消耗。
因此,产生了两种保持HTTP连接状态的技术Cookie和Session。
Cookie是通过客户端保持状态的一种解决方案,通俗来说,cookie是保存在客户端硬盘上的一个文件,这不是用户手动操作的,而是Web服务器通过客户端上的浏览器的权限来创建与保存文件的。
Cookie文件内的形式是名/值(name/value)的文本信息。
常见的应用场景:自动登录,网络购物车功能;
我们以购物车添加商品为例
假设说我在网上购物,看见想买的我就会把它添加进购物车,这个时候我看这个苹果看着不错,我点了一份加入了购物车。
当我点击加入购物车的按钮后,程序会发生类似于这样的的流程(实际比这复杂的多)
正常预期就是,查看购物车时,就会有苹果和香蕉两种商品在里面。
如果说我再将另一个香蕉商品加入购物车,在没有cookie的情况下会是这样的:
我们会发现两次请求两次响应确实都完成了,但HTTP是无状态的啊,也就是说前面我完成了一次添加苹果这种商品请求与响应流程,但第二次我添加香蕉这种商品时的请求响应与第一次是毫无关系的,就像是猴子掰玉米,掰一个掉一个,除非你把每次购物车的更新数据都纳入数据库中,用数据库来返回响应数据(不考虑这些具体情况),否则你只能在购物车里看到一件商品。
在我们第一次点击加入购物车时,发送请求给服务端,服务端负责处理这一请求的Servlet会根据程序员的代码生成键值对格式数据的cookie,我们以一种简单形式如“苹果=1”表示,生成的Cookie会附着在响应头中随响应返回给客户端,客户端的浏览器会解析响应头并在浏览器里的内存中生成cookie。
在第二次点击加入购物车按钮,将香蕉加入到购物车中时,发送请求时,会将原本在浏览器内存里的cookie附着在请求头中随请求一起发送。服务端中的Servlet会判断请求头中是否有cookie有则解析出对应的cookie【苹果=1】,同时生成请求本来需要生成的cookie【香蕉=1】。再将这两个cookie一起附着到响应头中随响应返回给客户端。浏览器解析响应头,并刷新内存,这时候内存里就会有两个cookie,购物车也就能有两个商品了。以此类推后续过程。
我们首先创建一个Servlet类,启动服务器后去访问这个Servlet类,使服务器在响应头里附上苹果和香蕉两个简单的cookie
package com.budiu.servlet;
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("/cookieDemo")
public class CookieDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置页面编码为utf-8
resp.setContentType("text/html;charset=utf-8");
Cookie apple_cookie = new Cookie("apple","1" );
//这是将cookie携带给响应头并保存在客户端的关键API
resp.addCookie(apple_cookie);
Cookie banana_cookie = new Cookie("banana","1" );
resp.addCookie(banana_cookie);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
我们通过访问这个Servlet来使服务器在控制台打印这些cookie
package com.budiu.servlet;
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("/cookieDemo1")
public class CookieDemoServlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//从请求头中获取cookie
Cookie[] cookies=req.getCookies();
//在控制台中打印出来
for (int i = 0; i < cookies.length; i++) {
Cookie cookie=cookies[i];
System.out.println(cookie);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
我们访问第一个Servlet类,并进行抓包后发现,它的响应头里有了set-cookie的字眼
而后面的键值对就是我们创建的cookie,也就是说,现在内存里已经将苹果和香蕉都放进去了。
我们再访问第二个Servlet来抓包并输出所有cookie,可以在请求标头中看到cookie的字眼,而它的后面就有我们自己设置的苹果和香蕉的cookie,还有一个IDEA自动生成的cookie。
控制台也输出了这三个cookie对象的地址
1.创建Cookie:
Cookie cookie = new Cookie("key", "value");
response.addCookie(cookie);
2.获取Cookie:
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("key")) {
String username = cookie.getValue();//获取cookie里的value值
//进行某些业务处理
}
}
}
3.删除Cookie:
Cookie cookie = new Cookie("key", "");//用来覆盖原来的键为key的cookie
cookie.setMaxAge(0);//设置为0表示删除cookie
response.addCookie(cookie);
4.设置Cookie的过期时间:
Cookie cookie = new Cookie("key", "value");
cookie.setMaxAge(60 * 60 * 24 * 365); // 1 年后自动销毁
response.addCookie(cookie);
5.设置Cookie的domain和path:
//创建一个新的 Cookie 对象,名为 "key",值为 "value"
Cookie cookie = new Cookie("key", "value");
// 设置此 Cookie 的域名,使其对 example.com 域名有效
// 这意味着此 Cookie 将被包含在发送到 example.com 的所有请求中
cookie.setDomain("example.com");
// 设置 Cookie 的路径为 "/",这表示该 Cookie 对整个网站都有效
// 也就是说,无论用户访问 example.com 的哪个页面,浏览器都会发送此 Cookie
cookie.setPath("/");
// 将此 Cookie 添加到响应中,使其可以被客户端(通常是浏览器)存储
// 当客户端之后发送请求到匹配的域名和路径时,它将包含此 Cookie
response.addCookie(cookie);
一般情况下,Cookie只能在同一域名下共享,如果需要在不同域名下共享,需要使用第三方库或者其他技术实现。
新创建的Cookie 通常放在 HTTP 响应头中,而不是放在请求头中?
当时还不太理解,因为我忘了写Servlet是针对服务器而写的特性,那么请求就是客户端发送服务器的,而响应就是发送给客户端的。前面既然说了,cookie是存储在客户端上的小文件,,这也就好理解为什么cookie是附在响应头里的了,其次,如果 Cookie 放在请求头中,任何中间人攻击者都可以读取或修改这些信息。通过将 Cookie 放在响应头中,客户端可以确保只有服务器能够修改或读取它们。
不过传递的cookie是可以放在请求头里的。