目标
了解 HTTP 请求 / 响应头及常见的属性;
了解如何使用 SpringBoot 处理头信息 ;
了解如何使用 SpringBoot 处理 Cookie ;
学会如何对 Session 进行读写;
了解如何在不同请求间传递 flash 参数
一, Http 头信息
HTTP 头 (Header) 是一种附加内容, 独立于请求内容和响应内容.
HTTP 协议中的大量特性都通过 Header 信息交互来实现, 比如内容编解码, 缓存, 连接保活等等.
如下面的一个请求响应:
- Request
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
- Accept-Encoding: gzip, deflate
- Accept-Language: zh-CN,zh;q=0.9
- Cache-Control: max-age=0
- Connection: keep-alive
- Host: www.cnblogs.com
- If-Modified-Since: Wed, 18 Jul 2018 13:47:45 GMT
- Upgrade-Insecure-Requests: 1
- User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
名称 | 用途 |
---|---|
Accept | 客户端期望的 MIME 类型列表 |
Accept-Encoding | 客户端期望的编解码方式 |
Accept-Language | 客户端期望的语言 |
Cache-Control | 缓存控制 |
Connection | 连接行为 (keep-alive) |
Host | 请求访问的主机 |
If-Modified-Since | 缓存控制 |
Upgrade-Insecure-Requests | 支持安全加密标记 |
User-Agent | 用户代理 (客户端标识) |
- Response
- Cache-Control: private, max-age=10
- Connection: keep-alive
- Content-Encoding: gzip
- Content-Type: text/html; charset=utf-8
- Date: Wed, 18 Jul 2018 13:47:51 GMT
- Expires: Wed, 18 Jul 2018 13:48:01 GMT
- Last-Modified: Wed, 18 Jul 2018 13:47:51 GMT
- Transfer-Encoding: chunked
- Vary: Accept-Encoding
- X-Frame-Options: SAMEORIGIN
- X-UA-Compatible: IE=10
名称 | 用途 |
---|---|
Cache-Control | 缓存控制 |
Connection | 连接行为 (keep-alive) |
Content-Encoding | 编解码方式 |
Content-Type | 内容类型 (MIME) |
Date | 当前响应时间 |
Expires | 文档过期时间 |
Last-Modified | 最后一次更新时间 |
Transfer-Encoding | 传输编码方式 |
Vary | 需要刷新的请求 Header |
X-Frame-Options | FRAME 展示策略 (用于同源控制) |
X-UA-Compatible | IE 兼容属性 |
更多的 ** Http Header ** 可以从这里找到 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers
二, SpringBoot 处理头信息
前面的内容中已经讲过如何完成 Controller 方法及请求的映射.
在 SpringBoot 可通过 @RequestHeader 注解方式
将请求头信息映射到参数, 如下面的片段:
- @GetMapping("/some")
- @ResponseBody
- public String someHeader(@RequestHeader(value = "Host") String host,
- @RequestHeader(value = "User-Agent") String userAgent,
- @RequestHeader(value = "Cache-Control", required = false) String cacheControl,
- HttpServletResponse response) {
- logger.info("host:{}", host);
- logger.info("User-Agent:{}", userAgent);
- logger.info("Cache-Control:{}", cacheControl);
- // 设置响应头
- response.setHeader("Cache-Control", "no-cache,no-store,must-revalidate");
- response.setHeader("Pragma", "no-cache");
- response.setDateHeader("Expires", 0);
- return "OK";
- }
而响应头呢, 可以通过声明一个 HttpServletResponse 参数后,
通过该对象进行设置, 上面的代码非常容易理解.
如果希望获得全部的请求头, 可以使用 HttpHeaders 对象:
- @GetMapping("/all")
- public ResponseEntity<Map<String, List<String>>> allHeaders(@RequestHeader HttpHeaders headers) {
- Map<String, List<String>> valueMap = new HashMap<String, List<String>>();
- for (String header : headers.keySet()) {
- valueMap.put(header, headers.get(header));
- logger.info("header[{}]={}", header, headers.get(header));
- }
- // 通过 ResponseEntity 设置响应头
- ResponseEntity<Map<String, List<String>>> entity = ResponseEntity.status(HttpStatus.OK)
- .header("new header", UUID.randomUUID().toString()).body(valueMap);
- return entity;
- }
上面的一段代码中, 可以将所有请求头信息全部打印出来.
此外还须注意到, 返回响应使用了 ResponseEntity 对象, 这是一个用于直接表示
响应信息头, 内容的对象, 利用 ResponseEntity 可以很方便的设置响应头信息.
三, Cookie 处理
Cookie 一开始服务器用于辨别用户信息而记录在浏览器上的信息.
到目前为止 Cookie 作为客户端的存储有了非常多的应用场景.
SpringBoot 提供了 @CookieValue 以支持参数方式注入, 如下:
- @GetMapping("/some")
- @ResponseBody
- public String someCookie(@CookieValue(value = "counter", defaultValue = "0") int counter,
- HttpServletResponse response) {
- logger.info("counter:{}", counter);
- counter += 1;
- String newValue = counter + "";
- // 设置 Cookie
- response.addCookie(new Cookie("counter", newValue));
- return newValue;
- }
上述代码中, 访问 / some 可以获得一个 counter 的 cookie 值,
且每访问一次则自增一次, 这是一个简单的访问计数器功能.
如果希望获取全部的 Cookie, 可以参考以下代码:
- @GetMapping("/all")
- public ResponseEntity<Map<String, String>>allCookies(HttpServletRequest request, HttpServletResponse response) {
- Map<String, String> valueMap = new HashMap<String, String>();
- for (Cookie cookie : request.getCookies()) {
- valueMap.put(cookie.getName(), cookie.getValue());
- logger.info("cookie[{}]={}", cookie.getName(), cookie.getValue());
- }
- // 设置 Cookie
- response.addCookie(new Cookie("key", UUID.randomUUID().toString()));
- return new ResponseEntity<Map<String, String>>(valueMap, HttpStatus.OK);
- }
清理全部 Cookie
- @GetMapping("/clear")
- public ResponseEntity<Map<String, String>> clearCookies(HttpServletRequest request, HttpServletResponse response) {
- Map<String, String> valueMap = new HashMap<String, String>();
- for (Cookie cookie : request.getCookies()) {
- valueMap.put(cookie.getName(), cookie.getValue());
- logger.info("cookie[{}]={}", cookie.getName(), cookie.getValue());
- // 清除
- cookie.setMaxAge(0);
- response.addCookie(cookie);
- }
- return new ResponseEntity<Map<String, String>>(valueMap, HttpStatus.OK);
- }
Cookie 机制存在一定的缺陷, 尽可能在考虑一些风险后使用
安全性无法保证, 除非使用 HTTPS;
浏览器端只有 4KB 大小的存储上限;
四, Session 处理
Session 指的是会话, 是建立于 Cookie 机制上的一种身份识别方式.
由于 Cookie 自身的安全性和容量限制, 大多数应用中是在 Cookie 中存放一个唯一凭证;
服务侧通过凭证再进行身份信息的存取, 这就是会话的由来.
不同的语言, 框架采用的实现方式有些差异, 比如 JavaEE 采用 JSESSION_ID, 而 PHP 则是 PHPSESSID
Session 的交互原理可以参考下面一个图:
Springboot 内嵌了 Servlet 容器, 则是沿用的 JSESSION_ID. 因此在浏览一些 JavaWeb 站点时会发现该 Cookie.
使用 @SessionAttribute 可以将会话中的属性映射到方法参数;
如果希望对 Session 属性进行操作, 可以在 Controller 上声明 @SessionAttributes 注解以指定想要变更的属性;
之后, 通过 Model 参数进行写入即可(由框架自动检测并修改 Session)
- @SessionAttributes("seed")
- public class SessionController {
- private static final Logger logger = LoggerFactory.getLogger(SessionController.class);
- @GetMapping("/some")
- @ResponseBody
- public String someSession(@SessionAttribute(value = "seed", required = false) Integer seed, Model model) {
- logger.info("seed:{}", seed);
- if (seed == null) {
- seed = (int) (Math.random() * 10000);
- } else {
- seed += 1;
- }
- model.addAttribute("seed", seed);
- return seed + "";
- }
上面的例子与 Cookie 实现访问计数器的功能是一样的!
如果希望获取全部会话, 可以使用 HttpSession
- @GetMapping("/all")
- public ResponseEntity<Map<String, Object>> allSessions(HttpSession session) {
- Map<String, Object> valueMap = new HashMap<String, Object>();
- Enumeration<String> iSession = session.getAttributeNames();
- while (iSession.hasMoreElements()) {
- String sessionName = iSession.nextElement();
- Object sessionValue = session.getAttribute(sessionName);
- valueMap.put(sessionName, sessionValue);
- logger.info("sessoin[{}]={}", sessionName, sessionValue);
- }
- // 写入 session
- session.setAttribute("timestmap", new Date());
- return new ResponseEntity<Map<String, Object>>(valueMap, HttpStatus.OK);
- }
清除会话
- @GetMapping("/clear")
- public ResponseEntity<Map<String, Object>> clearSessions(HttpSession session) {
- Map<String, Object> valueMap = new HashMap<String, Object>();
- Enumeration<String> iSession = session.getAttributeNames();
- while (iSession.hasMoreElements()) {
- String sessionName = iSession.nextElement();
- Object sessionValue = session.getAttribute(sessionName);
- valueMap.put(sessionName, sessionValue);
- logger.info("sessoin[{}]={}", sessionName, sessionValue);
- session.removeAttribute(sessionName);
- }
- return new ResponseEntity<Map<String, Object>>(valueMap, HttpStatus.OK);
- }
五, Flash 参数传递
Flash 的意思是一瞬间, 一闪而过的, 因此很好理解, 这是一类仅用来消费一次的参数, 有些类似阅后即焚.
试想这样的场景, 你确认完购物车, 完成订单支付后进入订单管理界面, 而此时界面上提示你 "下单成功, 请等待发货".
这便可以通过 Flash 传参来实现.
Flash 的意义是用作请求之间的瞬时参数传递, 仅消费一次后便不再用.
以下是一个示例:
- /**
- * 执行跳转, 并设置传值
- *
- * @param counter
- * @param response
- * @return
- */
- @GetMapping("/first")
- public String first(final RedirectAttributes redirectAttrs) {
- logger.info("redirect start:{}");
- redirectAttrs.addFlashAttribute("flash", UUID.randomUUID().toString());
- return "redirect:/flash/second";
- }
- /**
- * 获取传值
- *
- * @param session
- * @param response
- * @return
- */
- @GetMapping("/second")
- @ResponseBody
- public String second(@ModelAttribute("flash") String flash) {
- logger.info("redirect receive {}", flash);
- return flash;
- }
交互原理
Sprintboot 中 Flash 机制也是利用 Session 实现的, 其中 FlashMapManager 接口实现了 Flash 参数的管理.
默认的实现是 SessionFlashMapManager, 可以通过 RequestContextUtils 获得上下文中的 FlashMapManager 对象.
RequestContextUtils 通过 Request Scope(请求上下文)存取对象
这也是一个本文未提及的 scope 域, Request 上下文是利用线程变量实现的, 通常用于线程内业务处理的数据交互.
小结
HTTP 头信息是一种附加内容, 用于实现 HTTP 协议中的各种特性, 在开始部分介绍了常见的头信息定义.
本文主要介绍了几种常见的 HTTP scope 信息的存取方法, 包括如何对 header,cookie 进行读取及修改.
springboot 内嵌了 Servlet 容器, 会话处理机制上沿用了 JSESSIONID, 通过代码示例介绍了会话的处理方法;
Flash 参数是一种阅后即焚的数据, 其底层实现也用了 session 的实现方案.
来源: https://www.cnblogs.com/littleatp/p/9345801.html