• web服务器——实现Session


    一、Session实现原理

        凡是搞过web开发的都知道,多数情况下浏览器请求服务器使用的是http请求,而http请求是无状态的,也就是说每次请求服务器都会新建连接,当得到响应后连接就关闭了,虽然http1.1支持持久连接(keep-alive),但是其最用主要是避免每次重建连接,而非解决用户在线状态等业务上的需求。而如果服务器想知道客户端的状态或是识别客户端,那么就不能像长连接那样通过连接本身实现,而是要通过每次请求时的数据来判断。
        我们首先来看一下下图:

        从上图我们可以很清楚的看出session是如何实现的,一般在客户端第一次请求的时候,服务器会生成一个session_id(不同服务器可能名字不同,其值是一个唯一串)作为会话标示,同时服务器会生成一个session对象,用来存储该会话相关的数据。在响应时在请求头通过Set-Cookie(用法)可在客户端cookies中添加session_id。之后的访问中,每次服务器都会检测session_是否存在并能找到对应session对象,以此来识别客户端。
        这里还有一个问题就是,如果客户端关闭了怎么办?服务器如何知道?实际上服务器并不需要去关心客户端是否失败,通常的做法是给session设置过期时间,每次请求时重置过期时间,如果在过期前一直无请求,则清除该session,这样会话就相当于结束了。这里还需注意一点是,实际情况下设置的客户端session_id一定要是临时cookie,这样在关闭浏览器时session_id会清除,否则你在过期时间内重新打开浏览器还能够继续改会话,明显是不合理(本版本就不考虑这个问题了)。

    二、功能设计

        和之前一样,我们先来设计一下应该如何在我们的项目中实现。首先,我们来确定一下数据结构。session本身就不必多说了,核心是一个map,存储数据,同时我们还需要记录每个session的最后访问时间,以便处理过期问题。
        那么session集合我们怎么存储呢?大家都知道每个web程序启动都会生成一些内置对象,session相当于会话级别的(作用范围是一个会话内),那么还有一个web应用级别的,在该web程序全局可访问。由于session集合在应用多个层次都需要访问,因此我们需要实现一个单例的ApplicationContext,处理全局数据,同时处理session的创建和访问。
        接下来我们来设计下如何处理session。首先根据上边介绍,我们应该在接收请求后即判断并生成session,以保证后续业务能获取session,因此我们应该在EHHttpHandler的handler()方法开始就完成这些操作。此外,由于之前设计的在调用controller时我们只传了一个map参数集合,这样在controller中无法获取session,因此调用controller前我们将session放入map中(这只是简单做法,比较好的做法是对参数进行封装,这样如果以后需要拓展参数类型,只需要修改封装后的类即可)。
        随后我们还有实现一个定时任务,定期清理过期session。

    三、实现代码

    /**
     * session数据
     * @author guojing
     * @date 2014-3-17
     */
    public class HttpSession {
        Map<String, Object> map = new HashMap<String, Object>();
        Date lastVisitTime = new Date(); // 最后访问时间
    
        public void addAttribute(String name, Object value) {
            map.put(name, value);
        }
    
        public Object getAttribute(String name) {
            return map.get(name);
        }
    
        public Map<String, Object> getAllAttribute() {
            return map;
        }
    
        public Set<String> getAllNames() {
            return map.keySet();
        }
    
        public boolean containsName(String name) {
            return map.containsKey(name);
        }
    
        public Map<String, Object> getMap() {
            return map;
        }
    
        public void setMap(Map<String, Object> map) {
            this.map = map;
        }
    
        public Date getLastVisitTime() {
            return lastVisitTime;
        }
    
        public void setLastVisitTime(Date lastVisitTime) {
            this.lastVisitTime = lastVisitTime;
        }
    
    }
    /**
     * 全局数据和会话相关数据,单例
     * @author guojing
     * @date 2014-3-17
     */
    public class ApplicationContext {
        private Map<String, Object> appMap = new HashMap<String, Object>(); // ApplicationContext全局数据
    
        /**
         * 这里自己也有点搞不清sessionMap是不是有必要考虑线程安全,还请指教
         */
        private ConcurrentMap<String, HttpSession> sessionMap = new ConcurrentHashMap<String, HttpSession>(); // session数据
    
        private ApplicationContext(){
        }
    
        /**
         * 内部类实现单例
         */
        private static class ApplicationContextHolder {
            private static ApplicationContext instance = new ApplicationContext();
        }
        
        public static ApplicationContext getApplicationContext() {
            return ApplicationContextHolder.instance;
        }
    
        public void addAttribute(String name, Object value) {
            ApplicationContextHolder.instance.appMap.put(name, value);
        }
    
        public Object getAttribute(String name) {
            return ApplicationContextHolder.instance.appMap.get(name);
        }
    
        public Map<String, Object> getAllAttribute() {
            return ApplicationContextHolder.instance.appMap;
        }
    
        public Set<String> getAllNames() {
            return ApplicationContextHolder.instance.appMap.keySet();
        }
    
        public boolean containsName(String name) {
            return ApplicationContextHolder.instance.appMap.containsKey(name);
        }
    
        public void addSession(String sessionId) {
            HttpSession httpSession = new HttpSession();
            httpSession.setLastVisitTime(new Date());
            ApplicationContextHolder.instance.sessionMap.put(sessionId, httpSession);
        }
    
        /**
         * 获取session
         */
        public HttpSession getSession(HttpExchange httpExchange) {
            String sessionId = getSessionId(httpExchange);
            if (StringUtil.isEmpty(sessionId)) {
                return null;
            }
            HttpSession httpSession = ApplicationContextHolder.instance.sessionMap.get(sessionId);
            if (null == httpSession) {
                httpSession = new HttpSession();
                ApplicationContextHolder.instance.sessionMap.put(sessionId, httpSession);
            }
            return httpSession;
        }
    
        /**
         * 获取sessionId
         */
        public String getSessionId(HttpExchange httpExchange) {
            String cookies = httpExchange.getRequestHeaders().getFirst("Cookie");
            String sessionId = "";
            if (StringUtil.isEmpty(cookies)) {
                cookies = httpExchange.getResponseHeaders().getFirst("Set-Cookie");
            }
            
            if (StringUtil.isEmpty(cookies)) {
                return null;
            }
    
            String[] cookiearry = cookies.split(";");
            for(String cookie : cookiearry){
                cookie = cookie.replaceAll(" ", "");
                if (cookie.startsWith("EH_SESSION=")) {
                    sessionId = cookie.replace("EH_SESSION=", "").replace(";", "");
                }
            }
            
            return sessionId;
        }
    
        /**
         * 获取所有session
         */
        public ConcurrentMap<String, HttpSession> getAllSession() {
            return ApplicationContextHolder.instance.sessionMap;
        }
    
        /**
         * 设置session最后访问时间
         */
        public void setSessionLastTime(String sessionId) {
            HttpSession httpSession = ApplicationContextHolder.instance.sessionMap.get(sessionId);
            httpSession.setLastVisitTime(new Date());
        }
    }

        可以看出这两部分代码十分简单,下边看一下handle中如何处理session:

    public void handle(HttpExchange httpExchange) throws IOException {
            try {
                String path = httpExchange.getRequestURI().getPath();
                log.info("Receive a request,Request path:" + path);
                
                // 设置sessionId
                String sessionId = ApplicationContext.getApplicationContext()
                        .getSessionId(httpExchange);
                if (StringUtil.isEmpty(sessionId)) {
                    sessionId = StringUtil.creatSession();
                    ApplicationContext.getApplicationContext().addSession(sessionId);
                }
                
                //.....其他代码省略
            } catch (Exception e) {
                httpExchange.close();
                log.error("响应请求失败:", e);
            }
        }
    
        /**
         * 调用对应Controller处理业务
         * @throws UnsupportedEncodingException 
         */
        private ResultInfo invokController(HttpExchange httpExchange) throws UnsupportedEncodingException {
            // 获取参数
            Map<String, Object> map = analysisParms(httpExchange);
            IndexController controller = new IndexController();
            
            // 设置session
            HttpSession httpSession = ApplicationContext.getApplicationContext().getSession(
                    httpExchange);
            log.info(httpSession);
            map.put("session", httpSession);
            
            return controller.process(map);
        }

     最后看一下定时任务的实现:

    /**
     * 定时清理过期session
     * @author guojing
     * @date 2014-3-17
     */
    public class SessionCleanTask extends TimerTask {
        private final Log log = LogFactory.getLog(SessionCleanTask.class);
    
        @Override
        public void run() {
            log.info("清理session......");
            ConcurrentMap<String, HttpSession> sessionMap = ApplicationContext.getApplicationContext()
                    .getAllSession();
            
            Iterator<Map.Entry<String, HttpSession>> it = sessionMap.entrySet().iterator();
            while (it.hasNext()) {
                ConcurrentMap.Entry<String, HttpSession> entry= (Entry<String, HttpSession>) it.next();
                HttpSession httpSession= entry.getValue();
                
                Date nowDate = new Date();
                int diff = (int) ((nowDate.getTime() - httpSession.getLastVisitTime().getTime())/1000/60);
                
                if (diff > Constants.SESSION_TIMEOUT) {
                    it.remove();
                }
            }
    
            log.info("清理session结束");
        }
    }

     此次改动的代码就这么多。

    四、测试

    下边我们来测试一下是否有效。由于目前controller是写死的,只有一个IndexController可用,那么我们就将就着用这个来测试吧,我们先来改一下其process方法的代码:

    public ResultInfo process(Map<String, Object> map){
        ResultInfo result =new ResultInfo();
         
        // 这里我们判断请求中是否有name参数,如果有则放入session,没有则从session中取出name放入map
        HttpSession session = (HttpSession) map.get("session");
        if (map.get("name") != null) {
            Object name = map.get("name");
            session.addAttribute("name", name);
        } else {
            Object name = session.getAttribute("name");
            if (name != null) {
                map.put("name", name);
            }
        }
         
        result.setView("index");
        result.setResultMap(map);
        return result;
    }
  • 相关阅读:
    客户端相关知识学习(三)之Android原生与H5交互的实现
    客户端相关知识学习(二)之h5与原生app交互的原理
    nslookup基础用法
    十大渗透测试演练系统
    最新的windows xp sp3序列号(绝对可通过正版验证)
    Metasploit基础命令
    msf回退一步
    验证SMB登入
    Nmap使用指南(1)
    postgreSql基础命令及linux下postgreSql命令
  • 原文地址:https://www.cnblogs.com/yinglunstory/p/6085024.html
Copyright © 2020-2023  润新知