分布式架构学习之如何将session信息存储到数据库中

最近实践了一把SpringCloud集成注册中心、网关、配置中心、微服务,恰好电脑上的redis出现了各种问题,索性不用redis,自己写了个中间件,将存储到了MySQL中。下面是期间遇到的各种问题总结。
手先介绍基实现的本原理。我请教了下师傅,总结出了将Session持久化的几条要点。

  1. Http是无状态的协议;

所谓http是无状态协议,言外之意是说http协议没法保存客户机信息,也就没法区分每次请求的不同之处,服务器无法区分通过http发送的请求是来源于甲用户还是来源于乙用户的。关于http无状态阻碍了交互式应用程序的实现。比如记录用户浏览哪些网页、判断用户是否拥有权限访问等。于是,两种用于保持HTTP状态的技术就应运而生了,一个是Cookie,而另一个则是Session。

2.Fetch是默认不携带cookies的;

fetch默认是不携带cookie到后台的,想要携带cookie必须
添加credentials: "include"这个设置;

fetch(url, {
              method: 'GET',
              headers: myHeaders,
              -->credentials: "include"<--
            })

这个时候服务端确实拿到了cookie,但是数据返回报错:
ERROR :Fetch API cannot load http://localhost:8077/sonny/l... The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://localhost:8080' is therefore not allowed access.
这段鸟语翻译过来就是:当请求中的认证模式为‘include’时,响应的头部中的'Access-Control-Allow-Origin'不能设置为通配符“*”,这是因为cookie在设置的时候是不允许跨域的,如何在协达cookie的前后端分离项目中实现类似通配符”*“的功能将在下面讲述。因为报错了,因此得在后端添加:

response.setHeader(“Access-Control-Allow-Credentials”,”true”);
response.setHeader(“Access-Control-Allow-Origin”, “http://192.168.0.1");

这样就解决了跨域传递session的问题。

3.Cookis是不允许跨域的;

Cookie一班是不允许跨域传递的,解决方案除了上面那种,还可以通过Nginx;
解决cookie跨域问题之nginx反向代理

4.浏览器是通过OPTION请求获取服务器对请求的支持信息;

OPTIONS 方法比较少见,该方法用于请求服务器告知其支持哪些其他的功能和方法。通过 OPTIONS 方法,可以询问服务器具体支持哪些方法,或者服务器会使用什么样的方法来处理一些特殊资源。可以说这是一个探测性的方法,客户端通过该方法可以在不访问服务器上实际资源的情况下就知道处理该资源的最优方式。

5.既然比较少见,什么情况下会使用OPTION这个方法呢?

当发送的请求为简单请求的时候,服务器会自动在响应的头部添加添加一个Origin字段,其值将被自动设置为对应的请求域,Origin: http://api.bob.com因此可以说,对与简单请求是不存在跨域问题的。

A)、什么是简单请求呢?
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。


只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同时满足上面两个条件,就属于非简单请求。
浏览器对这两种请求的处理,是不一样的。


凡是不同时满足上面两个条件,就属于非简单请求。 浏览器对这两种请求的处理,是不一样的。

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

跨域资源共享 CORS 详解

6.Session是通过Cookies中的信息来识别的;

当解决了跨域和cookie携带的问题,以后每次浏览器发送请求就会携带一个cookie的信息,那么Session和JSESessionId是如何产生联系的?

首先我们需要知道Session在何时创建呢?
sessionid如何产生?由谁产生?保存在哪里?


当然还是在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同创建Session的方法,而在Java中是通过调用HttpServletRequest的getSession方法(使用true作为参数)创建的。在创建了Session的同时,服务器会为该Session生成唯一的Session id,而这个Session id在随后的请求中会被用来重新获得已经创建的Session;在Session被创建之后,就可以调用Session相关的方法往Session中增加内容了,而这些内容只会保存在服务器中,发到客户端的只有Session id;当客户端再次发送请求的时候,会将这个Session id带上。


服务器上为每个用户都保存了一个session,那当用户请求过来的时候是怎么知道某一个用户应该对应哪个session呢?


这时jsessionid就派上用场了。每一个session都有一个id来作为标识,这个id会传到客户端,每次客户端请求都会把这个id传到服务器,服务器根据id来匹配这次请求应该使用哪个session。jsessionid就是客户端用来保存sessionid的变量,主要是针对j2ee实现的web容器,没有研究过其他语言是用什么变量来保存的。一般对于web应用来说,客户端变量都会保存在cookie中,jsessionid也不例外。不过与一般的cookie变量不同,jsessionid是保存在内存cookie中的,在一般的cookie文件中是看不到它的影子的。内存cookie在打开一个浏览器窗口的时候会创建,在关闭这个浏览器窗口的时候也同时销毁。这也就解释了为什么session变量不能跨窗口使用,要跨窗口使用就需要手动把jsessionid保存到cookie里面。


下面看代码:

package com.spring.boot.gp4zj.util;

import javax.servlet.http.HttpServletRequest;

import org.springframework.context.ApplicationContext;

import com.spring.boot.gp4zj.model.UserSession;
import com.spring.boot.gp4zj.model.Worker;
import com.spring.boot.gp4zj.service.AuthenticationService;
import com.spring.boot.gp4zj.service.impl.AuthenticationServiceImpl;

import cn.lz.cloud.common.service.ReqObject;

/**
 * 我自己封装的session工具类;
 * @author xdsm
 *
 */
public class MySession {
     //私有的静态内部类保证了单例,并且是懒汉模式的;
    private static class SingletonHolder{
      private static final MySession INSTANCE = new MySession();
    }
    private MySession(){}

    public static final MySession getInstance(){
          return SingletonHolder.INSTANCE;
    }
    private ApplicationContext applicationContext = SpringUtils.getApplicationContext();
    private AuthenticationService authenticationService = applicationContext.getBean(AuthenticationServiceImpl.class);
    /**
     * 根据请求中携带的session信息获取用‘session’中的用户户信息;
     * @return
     */
  public Worker getAttributeOfUserInfo(HttpServletRequest request) {
      //获取jseSessionId;
      String jsessionid = HttpUtils.getJsessionidFromHeader(request);// 获取请求携带的sessioId;
      //查询用户信息;
      UserSession requestAttachmentInfo = new UserSession();
      requestAttachmentInfo.setJsessionid(jsessionid);
      ReqObject<UserSession> sessionSelecCondition = new ReqObject<UserSession>();
      sessionSelecCondition.setObject(requestAttachmentInfo);
      UserSession sessionVo = authenticationService.selectUserSessionBySessionID(sessionSelecCondition);
      Worker workerInfo = new Worker();
      String workerName = sessionVo.getWorkerName();
      String workerPassword = sessionVo.getWorkerPassword();
      workerInfo.setWorkerName(workerName);
      workerInfo.setWorkerPassword(workerPassword);
      Worker sessionAttruVO = authenticationService.doGetAuthenticationInfo(workerInfo);
    return sessionAttruVO;
  }

}


package com.spring.boot.gp4zj.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.Enumeration;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;

public class HttpUtils {
    public static void main(String[] args) {
        HttpServletResponse response = null;
        responseOutWithJson(response, null);
    }

    public static String getHostIp() {
        InetAddress addr;
        String currentIP = null;
        try {
            addr = InetAddress.getLocalHost();
            currentIP = addr.getHostAddress().toString(); //获取本机ip
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return currentIP;
    }

    public static void responseOutWithJson(HttpServletResponse response, Object responseObject) {
        // Person person=new Person();
        Gson gson = new Gson();
        // Object stu = new Student("ShaHao", 21);
        String objStrJson = gson.toJson(responseObject);
        // System.out.println(objStrJson);
        response.reset();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter out = null;
        try {
            out = response.getWriter();
            out.print(objStrJson);
            out.flush();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    /**
     * 获取post请求中的Body
     *
     * @param request
     * @return
     */
    public static String getBodyString(HttpServletRequest request) {
        System.out.println("--------HttpUtils---------HttpUtils--------");
        Enumeration<?> ele = request.getHeaderNames();
        while (ele.hasMoreElements()) {
            String name = (String) ele.nextElement();
            String value = request.getHeader(name);
            System.out.println(name + " = " + value);

        }

        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            // 读取流并将流写出去,避免数据流中断;
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }

    /**
     * 从请求的头部获取用户的身份识别id;
     * 
     * @param request
     * @return
     */
    public static String getJsessionidFromHeader(HttpServletRequest request) {
        String jsessionid = null;// 识别用户身份的id;
        Enumeration<?> e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String) e.nextElement();
            String value = request.getHeader(name);
            // cookie = JSESSIONID=B926F6024438D4C693A5E5881595160C;
            // SESSION=458e80dc-e354-4af3-a501-74504a873e70
            if ("cookie".equals(name)) {
                jsessionid = value.split(";")[0].split("=")[1];
            }
        }
        return jsessionid;
    }
}

其实所有的根本就是获取有效的SessionId就可以了。参考文章都在上面了,里面结合了我的理解和参考的内容,希望可以帮助到大家。

这里写图片描述
夏天了,又到了长肉的季节了。。。。。。


Previous
父类和子类的相互转换及父类调用子类的方法 父类和子类的相互转换及父类调用子类的方法
父类和子类的相互转换及父类调用子类的方法 父类转换为子类的前提条件是:父类引用指向子类;Parent p=new Son()Son s=(Son)p; –正确Parent p=new Parent()Son s=
2018-12-04 Pursue
Next
单例模式的几种实现方式 单例模式的几种实现方式
单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了吧。但是其中的坑却不少,所以也常作为面试题来考。本文主要对几种单例写法的整理,并分析其优缺点。很多都是一些老生常谈的问题,但如果你不知道如何创建一个线程安全的单例,不知道什么是双
2018-12-04 Pursue