001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.web.session;
017
018import io.jboot.Jboot;
019import io.jboot.components.cache.JbootCache;
020import io.jboot.components.cache.JbootCacheManager;
021
022import javax.servlet.http.*;
023import java.util.Enumeration;
024import java.util.HashMap;
025import java.util.Map;
026import java.util.UUID;
027
028
029public class JbootServletRequestWrapper extends HttpServletRequestWrapper {
030
031    private static JbootSessionConfig config = Jboot.config(JbootSessionConfig.class);
032
033    private static int maxInactiveInterval = config.getMaxInactiveInterval();
034    private static String cookieName = config.getCookieName();
035    private static String cookiePath = config.getCookieContextPath();
036    private static String cookieDomain = config.getCookieDomain();
037    private static int cookieMaxAge = config.getCookieMaxAge();
038    private static String cacheName = config.getCacheName();
039    private static String useCacheName = config.getUseCacheName();
040
041    private static JbootCache jbootCache = JbootCacheManager.me()
042            .getCache(useCacheName);
043
044
045    private HttpServletResponse response;
046    private HttpServletRequest originRequest;
047    private HttpSession originSession;
048    private JbootHttpSession jbootSession;
049
050
051    public JbootServletRequestWrapper(HttpServletRequest request, HttpServletResponse response) {
052        super(request);
053        this.response = response;
054        this.originRequest = request;
055        this.originSession = request.getSession(false);
056    }
057
058    @Override
059    public HttpSession getSession() {
060        return getSession(true);
061    }
062
063
064    @Override
065    public HttpSession getSession(boolean create) {
066        if (jbootSession != null) {
067            return jbootSession;
068        }
069
070        String sessionId = getCookie(cookieName);
071        if (sessionId != null) {
072            jbootSession = new JbootHttpSession(sessionId, originRequest.getServletContext(), createSessionStore(sessionId), originSession);
073            jbootSession.setMaxInactiveInterval(maxInactiveInterval);
074        } else if (create || originSession != null) {
075            sessionId = UUID.randomUUID().toString().replace("-", "");
076            jbootSession = new JbootHttpSession(sessionId, originRequest.getServletContext(), createSessionStore(sessionId), originSession);
077            jbootSession.setMaxInactiveInterval(maxInactiveInterval);
078            setCookie(cookieName, sessionId, cookieMaxAge);
079        }
080
081        return jbootSession;
082    }
083
084    private Map<String, Object> createSessionStore(String sessionId) {
085        Map<String, Object> store = jbootCache.get(cacheName, sessionId);
086        if (store == null) {
087            store = new HashMap<>();
088            syncOriginSessionData(store);
089            jbootCache.put(cacheName, sessionId, store);
090        }
091        return store;
092    }
093
094
095    /**
096     * 同步上层 session 到 sessionStore
097     * @// TODO: 2021/6/3 若上层动态修改了 上层自己的 session,会导致 Controller 的 session 和 上层 session 不同步的情况
098     * @// TODO: 2021/6/3 临时的解决方案需要用户手动通过 Controller 来修改 session 数据
099     * @param store
100     */
101    private void syncOriginSessionData(Map<String, Object> store) {
102        if (this.originSession != null) {
103            Enumeration<String> names = originSession.getAttributeNames();
104            while (names.hasMoreElements()) {
105                String name = names.nextElement();
106                store.put(name, originSession.getAttribute(name));
107            }
108        }
109    }
110
111
112    /**
113     * http请求结束时,更新session信息,包括:刷新session的存储时间,更新session数据,清空session数据等
114     */
115    public void refreshSession() {
116        if (jbootSession == null) {
117            return;
118        }
119
120        //session 已经被整体删除,用户调用了session.invalidate()
121        if (!jbootSession.isValid()) {
122            jbootCache.remove(cacheName, jbootSession.getId());
123            setCookie(cookieName, null, 0);
124        }
125
126        //session 已经被修改(session数据的增删改查)
127        else if (jbootSession.isDataChanged()) {
128            Map<String, Object> snapshot = jbootSession.snapshot();
129            // 数据已经全部被删除了
130            if (snapshot.isEmpty()) {
131                jbootCache.remove(cacheName, jbootSession.getId());
132                setCookie(cookieName, null, 0);
133            } else {
134                jbootCache.put(cacheName, jbootSession.getId(), snapshot, maxInactiveInterval);
135            }
136        }
137
138        //更新session存储时间
139        else {
140            jbootCache.setTtl(cacheName, jbootSession.getId(), maxInactiveInterval);
141        }
142    }
143
144
145    /**
146     * Get cookie value by cookie name.
147     */
148    private String getCookie(String name) {
149        Cookie cookie = getCookieObject(name);
150        return cookie != null ? cookie.getValue() : null;
151    }
152
153    /**
154     * Get cookie object by cookie name.
155     */
156    private Cookie getCookieObject(String name) {
157        Cookie[] cookies = originRequest.getCookies();
158        if (cookies != null) {
159            for (Cookie cookie : cookies) {
160                if (cookie.getName().equals(name)) {
161                    return cookie;
162                }
163            }
164        }
165        return null;
166    }
167
168    /**
169     * @param name
170     * @param value
171     * @param maxAgeInSeconds
172     */
173    private void setCookie(String name, String value, int maxAgeInSeconds) {
174        if (!response.isCommitted()) {
175            Cookie cookie = new Cookie(name, value);
176            cookie.setMaxAge(maxAgeInSeconds);
177            cookie.setPath(cookiePath);
178            if (cookieDomain != null) {
179                cookie.setDomain(cookieDomain);
180            }
181            cookie.setHttpOnly(true);
182            response.addCookie(cookie);
183        }
184    }
185
186
187    public HttpServletRequest getOriginRequest() {
188        return originRequest;
189    }
190}