四步檢查法輕鬆搞定示波器測量高速訊號
狀態管理
現有問題
- Http 協議是無狀態的,不能儲存每次提交的資訊。
- 如果使用者發來一個新的請求,伺服器知道它是否與上次的請求有聯絡。
- 對於那些需要多次提交的資料才能完成的Web 操作,比如說登入來說,就成問題了。
狀態管理概念:
將瀏覽器與web 伺服器之間多次互動當做一個整體來處理,並且將多次互動所涉及的資料(即狀態)儲存下來。
簡單來說:就是 記錄客戶端和伺服器之間多次互動的狀態。
狀態管理分類:
- 客戶端狀態管理:將狀態儲存在客戶端,代表性的是 Cookie 技術。
- 伺服器狀態管理技術:將狀態儲存在伺服器端,代表性的是 Session 技術(伺服器傳遞 sessionID 時需要使用Cookie 的方式 和 application)
Cookie
1、什麼是 Cookie
- Cookie 是在瀏覽器訪問 web 伺服器的某個資源時,由 web 伺服器在 HTTP 響應頭中附帶傳送給 瀏覽器的一小段資料。
- 一旦 瀏覽器 儲存了某個Cookie ,那麼它 以後每次訪問該 web 伺服器時,都應該在 Http 請求頭中將這個 Cookie 回傳給伺服器。
- 一個 Cookie 主要有 該資訊的名稱 (name)和 值(value)組成。
建立 Cookie ,然後響應的時候攜帶,讓客戶端儲存,再次請求的時候,攜帶 Cookie 資訊。讓請求的 Servlet 獲取。
2、建立 Cookie
// 建立 Cookie Cookie cookie = new Cookie("code",code); // 設定 Cookie 的路徑(哪些可以訪問) cookie.setMaxAge(-1); // 取值有三種:>0 單位:秒; =0 瀏覽器關閉; <0 記憶體儲存,預設 -1 ; // 將 Cookie 響應給客戶端,可以是一個也可以是多個 response.addCookie(cookie); // 新增到 response 物件中,響應時傳送給客戶端
3、獲取 Cookie
// 通過 request 物件獲取所有的 cookie Cookie[] cookies = req.getCookies(); // 通過 迴圈遍歷陣列Cookie /* for (Cookie cookie : cookies) { System.out.println(cookie.getName()+":"+cookie.getValue()); }*/ // 但是有可能傳入 的cookie 是一個空,那麼如何遍歷呢,或者說如何避免空指標異常呢? // 簡單:加個判斷就好 if (cookies != null){ for (Cookie cookie : cookies) { System.out.println(cookie.getName() + ":" + cookie.getValue()); } }
3、修改 Cookie
只需要保證 Cookie 的名稱和路徑是一致的。這時會認為修改操作。
// 建立 Cookie
Cookie cookie = new Cookie("code","code");
cookie.setPath("/webs"); // 必須是一致的,不一樣就新建了個 cookie
cookie.setMaxAge(-1);
response.addCookie(cookie);
【注意】:如果改變 name 和有效路徑會新建 cookie;而改變 cookie 的 value 、有效期,會覆蓋原有的 cookie。
很簡單。。。
4、Cookie 的編碼與解碼
Cookie 預設不支援中文,只能包含 ASCII 字元,所以 cookie 需要對 Unicode 字元進行編碼,否則會出現亂碼。
- 編碼使用
java.net.URLEncoder
類中的,encode(String str,String encoding)
方法。(對指定的字串,用指定的編碼格式編碼) - 解碼使用
java.net.URLDecoder
類的decode(String str,String encoding)
方法。
建立帶中文的 cookie(編碼)
Cookie cookie = new Cookie("姓名", "張三"); // cookie 不支援
cookie.setPath("/webproject/get");
cookie.setMaxAge(60 * 60);
resp.addCookie(cookie);
需要設定 編碼格式
Cookie cookie = new Cookie(
URLEncoder.encode("姓名","utf-8"),
URLEncoder.encode("張三","utf-8"));
cookie.setPath("/webproject/get");
cookie.setMaxAge(60 * 60);
resp.addCookie(cookie);
讀取帶中文的 cookie (解碼)
同時也需要設定,解碼方式:(不然出現的是%E5%A7%93%E5%90%8D:%E5%BC%A0%E4%B8%89)
if (cookies != null) {
for (Cookie cookie : cookies) {
System.out.println(
URLDecoder.decode(cookie.getName(), "utf-8")
+ ":" + URLDecoder.decode(cookie.getValue(), "utf-8"));
}
}
總結
優點:
- 可配置到期規則
- 簡單性:Cookie 是一種基於文字的輕量結構,包含簡單的鍵值對。
- 資料永續性:cookie 預設在過期之前是可以一直存在客戶端瀏覽器上。
缺點:
- 大小收到限制:大多數瀏覽器對cookie 的大小有 4k,到8k 位元組的限制。
- 有些使用者配置禁用,就會限制 cookie 功能
- 潛在安全風險,cookie 可能會被篡改,會對安全性造成風險或者導致依賴於cookie 的應用程式失敗。
Session
概述
- Session 用於記錄使用者的狀態,Session 指的是在一段時間內,單個客戶端與 web 伺服器的一連串相關的互動過程。
- 在一個 Session 中,客戶可能會多次請求訪問同一個資源,也有可能請求訪問各種不同的伺服器資源。
Session 原理
- 伺服器會為每一次會話分配一個 Session 物件。
- 同一個瀏覽器發起的多次請求,同屬於一次會話。
- 首次使用到 Session 時,伺服器會自動建立 Session,並建立 Cookie 儲存 SessionID ,並且回傳給客戶端。
注意:Session 是服務端建立的。
首次請求:在響應中設定 cookie
未關閉瀏覽器,再次請求:請求中攜帶 Session
Session 的使用
session 是伺服器的一個 map 集合。
- Session 作用域:擁有儲存資料的空間,作用範圍是一次會話有效。
- 一次會話:是使用統一瀏覽器傳送的多次請求,一旦瀏覽器關閉,則會話結束。
- 可以將資料存入 Session 中,在一次會話的任意位置進行獲取。
- 可傳遞任何資料(基本資料型別,物件,集合,陣列)
瀏覽器在沒有關閉的情況下,訪問同一個資源,拿到的是同一個 SessionID
1、獲取 Session 物件
// 獲取 Session 物件
HttpSession session = request.getSession();
// 唯一標識
System.out.println("session :"+session.getId());
2、Session 儲存資料
setAttribute(屬性名,Object)儲存資料到 session 中
session.setAttribute("key",value); // 以鍵值對形式儲存在 session 作用域中
3、Session 獲取資料
getAttribute(屬性名);
獲取 session 中的資料
session.setAttribute("key"); // 通過String 型別的key 訪問Object 型別的 value
4、Session 移除資料
removeAttribute(屬性名);
從session 中移除資料
removeAttribute("key"); // 通過鍵移除session 作用域中的值
Session 和 Request 應用區別
區別
-
request 是一次請求有效,請求改變,則 request 改變。
-
session 是一次會話有效,只要瀏覽器不關閉,那麼 Session 是不會改變的。
例項
@WebServlet(value = "/ss")
public class SessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 獲取 Session 物件
HttpSession session = req.getSession();
System.out.println("session :"+session.getId());
session.setAttribute("name", "Asia");
// 用 request 域傳遞資料
req.setAttribute("password", "123");
resp.sendRedirect("/webproject/getvalue");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
獲取值的 Servlet
@WebServlet(value = "/getvalue")
public class GetValue extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 獲取 Session 物件
HttpSession session = req.getSession();
// 獲取 Session 資料
String name = (String) session.getAttribute("name");
System.out.println("name:" + name);
// 獲取 request 域中的 資料,這裡因為採取的是定向的方式,所以是獲取不到的
String password = (String) req.getAttribute("password");
System.out.println(password);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
結果:(先訪問 /ss,然後重定向到 /getvalue )以證明request 和 session 的區別。
session :F41D767BD803DDEBDE5E9AB2F6C9B819
name:Asia
null
Session 的生命週期
- 開始:第一次使用到Session 的請求產生,則建立 Session
- 結束:
- 瀏覽器關閉,失效
- Session 超時,失效
session.setMaxInactiveInterval(60 * 60);
- 手動銷燬,失效
-
session.invalidate();
// 登入退出、登出
-
@WebServlet(value = "/gets")
public class getSessionLife extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 獲取 Session 物件
HttpSession session = req.getSession();
// 設定 session 有效時間
// session.setMaxInactiveInterval(10); // 以秒為單位
System.out.println("第一次獲取 sessionID:" + session.getId());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
@WebServlet(value = "/secget")
public class SecondGetLife extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 獲取 Session 物件
HttpSession session = req.getSession();
System.out.println("第二次獲取 sessionID :" + session.getId()); // 因為我是在一次會話請求的這個 Servlet,同時我還設定了 10 秒超時,所以 10 秒後,超時了,第二次獲取到的 session 和第一次是不一樣的。
// 手動銷燬失效 手動銷燬後,在此獲取到 session 是 新session
session.invalidate();
/*
* 第一次獲取 sessionID:969168F6CD8A47168209EB8D13E438E0
第二次獲取 sessionID :969168F6CD8A47168209EB8D13E438E0
再次訪問第一次獲取的 servlet: sessionID:99C5CAFCE6BD0287F7CBC8B6621A92AB
* */
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
瀏覽器禁用 Cookie 解決方案
瀏覽器禁用 cookie 的後果
現在大家都很喜歡將 cookie 全部禁用了,防止在看學習資料的時候,被別人發現。
伺服器預設情況下,會使用 cookie 的方式將 sessionID 傳送給瀏覽器,我們禁止了,sessionID 就不會儲存,那我們多次獲取到的 sessionID 就是不同了,這就屬於一次會話,多個 sessionID ,這當然是不行的了。
解決方法:URL 重寫
瀏覽器在訪問伺服器上的某個地址時,不再使用原來的那個地址,而是使用經過改寫的地址(即在原來的地址後面加上了 sessionID)
實現:
response.encodeRedirectURL(String url)
生成重寫的 URL
@WebServlet(value = "/gets")
public class getSessionLife extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 獲取 Session 物件
HttpSession session = req.getSession();
// 設定 session 有效時間
// session.setMaxInactiveInterval(10); // 以秒為單位
// 生成 重寫 URL
String newURI = resp.encodeRedirectURL("/webproject/secget");
System.out.println("第一次獲取 sessionID:" + session.getId());
System.out.println(newURI);
resp.sendRedirect(newURI);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
/*
第一次獲取 sessionID:62DFA4B0640D24471A0B00FAB4D3C948
/webproject/secget;jsessionid=62DFA4B0640D24471A0B00FAB4D3C948
第二次獲取 sessionID :62DFA4B0640D24471A0B00FAB4D3C948
*/
做到了 即使客戶端禁用了 cookie ,我仍舊可以傳 serssionID 來保證,兩次請求都屬於同一個 session 會話。
Session 應用場景
1、儲存使用者登入資訊
寫一個管理員的實體類,mapper,service,controller,挨個寫,在 controller 中做判斷。
if (mgr != null){
// 登入成功
// 將管理員資訊存在 Session 裡面 (這樣我可以在任何位置,訪問管理員的資訊)
// 有效期是一次會話
HttpSession session = req.getSession();
session.setAttribute("mgr", mgr);
// 跳轉,目標,方式(通過重定向的方式,跳轉到 showallController)
resp.sendRedirect("/webproject/showAllController");
} else {
// 登入失敗
resp.sendRedirect("/webproject/loginMgr.html");
}
在 showAllController 中,也要做判斷,如果登入過的話,可以直接訪問,沒有登入,重定向到登入頁面,這樣做是為了防止,使用者直接訪問 showAllController.
// 通過 HttpSession 完成許可權的控制
HttpSession session = req.getSession();
Manager mgr = (Manager) session.getAttribute("mgr");
// 如果登入了,執行業務,沒有登入,則登入失敗,跳轉到登入頁面。
if (mgr != null){
System.out.println("進入 /showAllController doGet");
AdminService adminService = new AdminServiceImpl();
List<User> userList = adminService.showAllUser();
// request 作用域存資料
req.setAttribute("user", userList);
// 通過轉發,跳轉到顯示結果的 servlet
req.getRequestDispatcher("/showAlljsp").forward(req, resp);
} else{
// 登入失敗,轉到登入頁面登入
resp.sendRedirect("/webproject/loginMgr.html");
}
同時因為是一次會話,所以我們可以在任意位置,獲取,session 的資訊,session 存的是管理員資訊,所以可以做到,顯示 welcome ,管理員! 這樣的效果。
2、儲存驗證碼
- 匯入 validateCode.jar
- 建立生成驗證碼 的 servlet
思路:
建立生成驗證碼的 servlet ,建立驗證碼圖片和響應給客戶端,這時候,在 logMgr.html 頁面已經有驗證碼了,接下來,我們就需要在 loginMrgController 裡面首先收參,然後判斷驗證碼,是否正確,接著再進行後續業務的比較。
【注意】:建立 驗證碼 生成的 servlet 的時候,將 驗證碼獲取,然後存在 session 裡面,然後在 loginMgrController 裡面,獲取session 中存的 驗證碼,然後進行比較。
login.html
<form action="/webproject/loginMgr" method="post">
使用者名稱:<input type="text" name="username"><br>
密 碼:<input type="password" name="password"><br>
驗證碼:<input type="text" name="inputVcode"/><img src="/webproject/createCode"><br> <!--這裡已經對createCode 做了一次請求,響應。再次點選登入,那就是兩次請求,所以,不能用request 存驗證碼資料-->
<input type="submit" value="登入">
</form>
驗證碼生成 servlet
@WebServlet(value = "/createCode")
public class CreateCodeController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 建立驗證碼圖片
ValidateCode vc = new ValidateCode(150, 30, 5, 10);
// 2. 驗證碼圖片響應給客戶端
vc.write(resp.getOutputStream());
// 3. 建立session 儲存 驗證碼
// 獲取驗證碼生成 的5個字元 在 ValidateCode中 有個 getcode方法
String codes = vc.getCode();
HttpSession session = req.getSession();
session.setAttribute("code",codes);
}
loginMgrController servlet
@WebServlet(value = "/loginMgr")
public class LoginMgrController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 處理亂碼
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
// 2. 收參
String username = req.getParameter("username");
String password = req.getParameter("password");
String inputVcode = req.getParameter("inputVcode");
// 首先判斷,驗證碼,是否正確,然後,接著判斷業務邏輯
// 從session 中 獲取隨機生成的驗證碼,然後我們在呼叫業務方法,進行後續的比較
String code = (String) req.getSession().getAttribute("code");
if (inputVcode != null && inputVcode.equalsIgnoreCase(code)) {
// 3. 呼叫業務方法
ManagerServiceImpl managerService = new ManagerServiceImpl();
Manager mgr = managerService.login(username, password);
// 4. 處理結果,流程跳轉
if (mgr != null) {
// 登入成功
// 將管理員資訊存在 Session 裡面 (這樣我可以在任何位置,訪問管理員的資訊)
// 有效期是一次會話
HttpSession session = req.getSession();
session.setAttribute("mgr", mgr);
// 跳轉,目標,方式(通過重定向的方式,跳轉到 showallController)
resp.sendRedirect("/webproject/showAllController");
} else {
// 登入失敗
resp.sendRedirect("/webproject/loginMgr.html");
}
}else {
resp.sendRedirect("/webproject/loginMgr.html");
}
}