HTTP請求的無狀態性
HTTP的無狀態性是其一個重要的特徵,指的是HTTP協議本身並不保留客戶端與伺服器互動的歷史資訊,換而言之,即每次的HTTP請求都是獨立的,伺服器在處理每一個請求時都不會記住前一個請求的狀態
無狀態的含義
- 獨立性:每次的HTTP請求都是獨立的,不依賴於之前的請求,即伺服器處理每次請求時都會從頭開始,不會參照之前的狀態
E.g:假設A使用者在一個Web超市新增的一個商品到購物車中,等到再次購買時,伺服器已經無法分別判斷購買行為是屬於使用者A還是其他使用者
就是由於HTTP請求的無狀態性,伺服器無法識別其請求的上文狀態,因此人們就開發了新的技術來解決HTTP無狀態性帶來的狀態丟失問題,也就是我們接下來要講解的
Cookie
與Session
也借鑑了創造者@測試開發喵
Cookie
Cookie原意為”餅乾”,是由W3C組織提出的.目前的主流瀏覽器
IE,Google,Edge,Firefox
等等都支援了Cookie技術
Cookie的原理機制
鑑於HTTP請求協議是一個無狀態的請求協議,伺服器單從網路連線上無法得知客戶的身份.於是前輩們就想出了一個方法:給客戶端們都頒發一個通行證,這樣每次發起請求後都要攜帶自己的通行證.於是乎伺服器就可以從通行證上確認使用者的身份了
Cookie的本質
- Cookie實際上是一小串文字資訊.當客戶端請求伺服器時,若伺服器要記錄該使用者的狀態,則使用
response
向客戶端瀏覽器傳送一個Cookie.客戶端的瀏覽器會把Cookie儲存→當此瀏覽器再次申請伺服器時,瀏覽器會將請求的網站連同Cookie一起提交給伺服器→瀏覽器會檢查該Cookie,以此來辨別使用者的狀態,伺服器也可以根據需求修改Cookie中的內容
Cookie的型別
- 持久型cookie
- 以檔案方式存放在硬碟空間上的永久性cookie.持久cookie是值存放在客戶端硬碟中的cookie資訊(被設定了一段有效期)
- 當使用者訪問網站時,瀏覽器會在本地硬碟上查詢與該網站相關聯的cookie.若該cookie存在,則瀏覽器會將其和頁面請求一起傳送到使用者所在的站點,之後伺服器會比對cookie中相應的屬性值與存放在伺服器中的資訊是否一致,以此判斷”新使用者”和老使用者
- 以檔案方式存放在硬碟空間上的永久性cookie.持久cookie是值存放在客戶端硬碟中的cookie資訊(被設定了一段有效期)
- 會話型cookie
- 停留在瀏覽器記憶體中的臨時cookie.會話型cookie.僅在會話期間存在,瀏覽器一旦關閉,會話型cookie就會被銷燬
Cookie中的屬性
- Domain:指定Cookie會被哪些域名下的頁面訪問
- Path:指定Cookie可以被哪些路徑下的頁面訪問
- Secure:若設定為true,則只有在HTTPS連線下才會傳送Cookies,增加了安全性
- HttpOnly:若設定為true,則Cookie不能被
JavaScript
訪問,有利於防止跨站指令碼攻擊 - SameSite:設定Cookie是否隨著第三方請求一起傳送,有助於防止跨站偽造攻擊
JavaWeb對Cookie的操作
Java中將Cookie操作封裝到
javax.servlet.http.Cookie
類中,
Cookie物件的建立
new Cookie(String name, String value)
:設定Cookie的名稱與值- name:是Cookie的唯一標識,透過name可以區分不同的Cookie物件,一個網站可能有多個Cookie如
username,language,
等等 - value:用於儲存實際的資料,透過cookie值可以跟蹤使用者狀態
- name:是Cookie的唯一標識,透過name可以區分不同的Cookie物件,一個網站可能有多個Cookie如
Java中常用的Cookie的屬性
屬 性 名 | 描 述 |
---|---|
String name |
該Cookie的名稱。Cookie一旦建立,名稱便不可更改 |
String value |
該Cookie的值。如果值為Unicode字元,需要為字元編碼。如果值為二進位制資料,則需要使用BASE64編碼 |
int maxAge |
該Cookie失效的時間,單位秒。如果為正數,則該Cookie在maxAge秒之後失效。如果為負數,該Cookie為臨時Cookie,關閉瀏覽器即失效,瀏覽器也不會以任何形式儲存該Cookie。如果為0,表示刪除該Cookie。預設為–1 |
boolean secure |
該Cookie是否僅被使用安全協議傳輸。安全協議。安全協議有HTTPS,SSL等,在網路上傳輸資料之前先將資料加密。預設為false |
String path |
該Cookie的使用路徑。如果設定為“/sessionWeb/”,則只有contextPath為“/sessionWeb”的程式可以訪問該Cookie。如果設定為“/”,則本域名下contextPath都可以訪問該Cookie。注意最後一個字元必須為“/” |
String domain |
可以訪問該Cookie的域名。如果設定為“.google.com”,則所有以“google.com”結尾的域名都可以訪問該Cookie。注意第一個字元必須為“.” |
String comment |
該Cookie的用處說明。瀏覽器顯示Cookie資訊的時候顯示該說明 |
int version |
該Cookie使用的版本號。0表示遵循Netscape的Cookie規範,1表示遵循W3C的RFC 2109規範 |
Cookie的有效期
Cookie的maxAge決定著Cookie的有效期,單位為秒(second),Cookie中的
getMaxAge()
與setMaxAge(int maxAge)
可以用來讀寫maxAge
屬性
- 若
maxAge
為正數,則表示該Cookie會在maxAge秒後失效,瀏覽器會將maxAge為正數的Cookie持久化,即寫入到對應的Cookie檔案中.無論客戶關閉了瀏覽器還是電腦,只要在maxAge之內,訪問相應的網站仍然有效,
Cookie cookie = new Cookie("username","張三");//新建cookie
cookie.setMaxAge(Integer.MAX_VALUE);//設定生命時間為無限
resp.addCookie(cookie);//響應到客戶端
//此方法新增的cookie資訊永遠生效
- 若
maxAge
為負數時則表示此cookie只在該瀏覽器視窗以及期子視窗生效,關閉瀏覽器即失效,maxAge
為負數的Cookie為臨時Cookie,不會被持久化,即不會被寫入到檔案中,Cookie資訊只保留在瀏覽器的記憶體中,Cookie的預設值為-1,即預設為會話Cookie - 若
maxAge
為0時則表示,刪除該Cookie.Cookie的機制沒有提供刪除Cookie的方法,因此可以提供設定maxAge
為0的方法即時失效,實現刪除Cookie的效果,失效的Cookie會被瀏覽器從檔案或記憶體中清除
Cookie cookie = new Cookie("username","李四");//建立cookie
cookie.setMaxAge(0);//設定為0,即時失效
resp.addCookie(cookie);//相應到客戶端
Cookie的修改
- Cookie本身並不提供修改的操作,若要修改一個Cookie,則需要新建一個同名的Cookie,新增到response中將原本的Cookie覆蓋
- 注意點:修改時新建的Cookie除了
value,maxAge
之外的屬性,例如name,path,domian
等等都要和原先的Cookie一致才能完成修改
- 注意點:修改時新建的Cookie除了
Cookie的不可跨域名性
Cookie的不可跨域名性表示的是:Cookie通常只能由建立它的域名所訪問,這意味著一個域名下的Cookie不能被另一個域名下的Cookie訪問
-
提供設定
Domain
屬性是實現不可跨域名性:- 例如當把
Domian
設定為:.example.com
時,則該Cookie不僅可以在example.com域名上被訪問,也可以在sub.example
上被訪問
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie cookie = new Cookie("username","李四");//建立cookie cookie.setMaxAge(Integer.MAX_VALUE); cookie.setHttpOnly(true);//不可被JavaScript訪問 cookie.setSecure(true);//設定只能提供Http傳送 cookie.setDomain(".example.com");//cookie可以被example.com及其子域名訪問 resp.addCookie(cookie);//相應到客戶端 }
- 例如當把
Session
Session
也是一種解決HTTP請求無狀態性的技術,Session是伺服器使用的一種記錄客戶端狀態的機制,相比Cookie來說,Session更加簡單,但增加了伺服器的儲存壓力
Session的原理機制
Session是一種伺服器端的機制,伺服器使用一種雜湊表的結構儲存Session資訊
Session的行為
- Session是依賴於Cookie的,使用者客戶端請求申請建立一個session→伺服器首先檢查客戶端請求中是否包含了一個session的標識—
session id
若包含一個session id
則說明已經為該客戶端建立過session,伺服器會按照session id
檢索出來直接使用,若檢索不到則新建一個;若不包含session id
,則為此客戶端新建一個session id
並生成相應的session id
→新建的session id會包含在cookie中隨響應一起返回到客戶端中儲存
- 簡單來說:如果是cookie是檢查客戶端攜帶的”通行證”,來確認客戶.那麼session就是透過檢查伺服器上的”客戶端明細表”來確認客戶端身份,session相對於在伺服器上建立了一份客戶檔案.
Session的屬性
與Cookie一致的是 Session的值也是以
key-value
的形式存在的,Java中把將Session的操作封裝到javax.servlet.http.Session
類中
獲取Session
- 使用 request中的
HttpSession getSession()
方法可以獲取當前使用者的Session,若該使用者的Session不存在則返回null
- 進而衍生出了另一種傳參的
getSession()
方法HttpSession getSession(boolean create);
當create為true時,該方法會新建一個Session,再講Session返回
- 進而衍生出了另一種傳參的
public class myServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
HttpSession session1 = req.getSession(true);
}
}
Session中常用的方法
方 法 名 | 描 述 |
---|---|
void setAttribute(String attribute, Object value) |
設定Session屬性。value引數可以為任何Java Object。通常為Java Bean。value資訊不宜過大 |
String getAttribute(String attribute) |
返回Session屬性 |
Enumeration getAttributeNames() |
返回Session中存在的屬性名 |
void removeAttribute(String attribute) |
移除Session屬性 |
String getId() |
返回Session的ID。該ID由伺服器自動建立,不會重複 |
long getCreationTime() |
返回Session的建立日期。返回型別為long,常被轉化為Date型別,例如:Date createTime = new Date(session.get CreationTime()) |
long getLastAccessedTime() |
返回Session的最後活躍時間。返回型別為long |
int getMaxInactiveInterval() |
返回Session的超時時間。單位為秒。超過該時間沒有訪問,伺服器認為該Session失效 |
void setMaxInactiveInterval(int second) |
設定Session的超時時間。單位為秒 |
void putValue(String attribute, Object value) |
不推薦的方法。已經被setAttribute(String attribute, Object Value)替代 |
Object getValue(String attribute) |
不被推薦的方法。已經被getAttribute(String attr)替代 |
boolean isNew() |
返回該Session是否是新建立的 |
void invalidate() |
使該Session失效 |
Session與Cookie的關係
-
Session實現的原理與Cookie有關
-
當伺服器建立了Session物件後,首先將其存入伺服器記憶體,並把Session的唯一標識session id以Cookie的形式寫回客戶端本地檔案中(鍵名為JSESSIONID,值為該session的id)
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); Cookie cookie = new Cookie("JSESSIONID",session.getId()); cookie.setMaxAge(60*60); resp.addCookie(cookie); }
-
-
Session的儲存結構
Session是以Map資料結構儲存的key值為session id;value值為Session物件
protected Map<String, Session> sessions = new ConcurrentHashMap<>();
-
Session與Cookie的區別
- 安全性:
- Session:儲存在伺服器中,更加安全
- Cookie:儲存在客戶端,更容易受到攻擊,可以設定
HttpOnly
,和Secure
屬性來提高安全性
- 資料量:
- Session:可以儲存較大的資料量,沒有嚴格的大小限制
- Cookie:提出限制在4kb左右
- 應用場景:
- Session:用於跟蹤使用者狀態,適合儲存敏感資訊,如身份驗證等等
- Cookie:用於儲存非敏感資訊,適合短期儲存資料
- 安全性:
Filter過濾器
Filter過濾器是
Servlet
技術中最實用的部分,Web開發人員透過Filter技術管理Web伺服器的所有資源,如JSP,Servlet,HTML
等等,主要用於對客戶請求進行預處理
Filter的主要功能
- 編碼轉換:在請求資源到達資源之前轉換字元編碼,確保字元編碼的一致性
- 日誌記錄:在請求和響應中記錄相關資訊
- 預處理和後處理認任務:
- 修改請求頭或響應頭
- 新增快取控制頭
- 進行安全檢查
Filter的配置
Filter
有兩種配置方法,一種是在web.xml
檔案中配置,第二種是使用註釋的方法配置(Java Servlet 3.0 及以上版本)
web.xml配置
借鑑作者@coderland
<filter>
<filter-name>myFilter01</filter-name>//設定filter名稱
<filter-class>com.mashang.web.myFilter01</filter-class>
//指定用於指定過濾器的完整類路徑
</filter>
<filter-mapping>//設定Filter負責攔截的資源
<filter-name>myFilter01</filter-name>//需和filter-name一致
<url-pattern>/ *</url-pattern>//設定filter攔截的路徑 URL結構
</filter-mapping>
註釋配置
@WebFilter()
- 在
Filter
實現類,頂部使用@WebFilter()
進行註釋 - @WebFilter()中的屬性
String filterName() default "";
:指定Filter名稱String[] urlPatterns() default {};
:指定Filter攔截的URL,使用/*
表示所有URL都會經過此Filter-
由於
urlPatterns
以字串陣列存在,因此可以一次配置多條路徑,例如@WebFilter(filterName = "MyFilter", urlPatterns = {"/secure/*", "/admin/*"})
-
@WebInitParam()
@WebInitParam註釋可用於在@WebFilter中配置引數初始值
WebInitParam[] initParams() default {};
:是@WebFilter中的引數類別,預設為空值,表示沒有被初始化- 其有兩個引數:
name
:引數名稱;value
:引數值
- 其有兩個引數:
@WebFilter(filterName = "myFilter01",urlPatterns = "/*",
initParams ={
@WebInitParam(name = "encoding",value = "UTF-8")
//將其中的encoding 屬性配置為UTF-8,來實現編碼統一
}
)
public class myFilter01 implements Filter { }
Filter的生命週期
Filter是Java的一個介面,其有一個簡單的生命週期,
void init(FilterConfig config)
- 與Servlet程式一致,Filter的建立和銷燬都是由Web伺服器操作的,Web伺服器啟動時建立Filter物件,並呼叫
init()
方法完成初始化工作,讀取web.xml
檔案,Filter物件只會建立一次,因此init()
方法只會執行一次, FilterConfig
是 Java Servlet API 中的一個介面,它提供了獲取Filter
配置資訊的方法,在下文我們會展開講解
- 與Servlet程式一致,Filter的建立和銷燬都是由Web伺服器操作的,Web伺服器啟動時建立Filter物件,並呼叫
- **
void doFilter(ServletRequest serReq, ServletResponse** **serResp, FilterChain filterChain)**
- 完成實際過濾操作的方法.Web伺服器會在每次屌用service()方法之前呼叫Filter的
doFilter()
方法 FilterChain
:Filter鏈,在開發中會編寫多個Filter,FilterChain
是Filter的集合,如果呼叫了FilterChain物件的doFilter方法,則web伺服器會檢查FilterChain物件中是否還有filter,如果有,則呼叫第2個filter,如果沒有,則呼叫目標資源
- 完成實際過濾操作的方法.Web伺服器會在每次屌用service()方法之前呼叫Filter的
void destroy()
- Filter物件建立後會駐留在Web記憶體中,當Web用於結束伺服器停止時,Web伺服器銷燬之前的物件
- 與init()一致的是destroy()也只呼叫一次
FilterConfig介面
當Filter被初始化時,FilterConfig物件會被傳遞給
init()
方法,在init()
中透過FilterConfig,可以回去Filter的名稱,初始化引數,及相關的ServletContext
FilterConfig主要方法
-
String getFilterName():
返回Filter的名稱 -
String getIntiParameter(String name)
:獲取指定名稱的初始引數的值,若沒找到對應的初始化引數則返回null
-
Enumeration<String> getInitParameterNames()
:返回所有初始化引數名稱, -
ServletContext getServletContext()
:返回與Filter關聯的ServletContext
物件,ServletContext物件提供了整個Web應用程式的訪問public void init(FilterConfig filterConfig) throws ServletException { //獲取FilterConfig中的所有引數名 Enumeration<String> initParameterNames = filterConfig.getInitParameterNames(); while (initParameterNames.hasMoreElements()) { String name = initParameterNames.nextElement(); System.out.println("param"+":"+name); } ServletContext servletContext = filterConfig.getServletContext(); servletContext.setAttribute("name","value"); }
用Filter實現編碼統一
@WebFilter(urlPatterns = "/*")
public class myFilter01 implements Filter {
private String characterEncoding=null;
@Override
public void init(FilterConfig fC) throws ServletException {
//在初始定義中獲取characterEncoding
//若為非空,encoding也是非空 則直接獲取 encoding的值
if (fC != null && fC.getInitParameter("encoding") !=null
&& ! fC.getInitParameter("encoding").equals(""))
{
characterEncoding=fC.getInitParameter("encoding");
}
else{
characterEncoding="UTF-8";//否則置為utf8
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//將Servlet轉換為HttpServlet用於處理HTTP請求響應
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
//攔截所有請求進行統一編號
//指定request和response的編號
req.setCharacterEncoding(characterEncoding);
//將response資料響應為utf8
resp.setCharacterEncoding(characterEncoding);
//告訴瀏覽器輸出內容為HTML格式,
resp.setContentType("text/html;charset="+characterEncoding);
filterChain.doFilter(req,resp);//將請求和響應傳遞給下一個 Filter 或目標資源
}
Listener
Listener 監聽器與Servlet 程式,Filter 過濾器共稱JavaWeb的三大元件,Listener作用
application,session,request
三個物件中,Listener用於監聽特點的時事件,然後回撥函式,並做出相應的反應,Listner主要分為三個大類:ServletContext 監聽,Session監聽,Request 監聽;Listener本質是個介面
Listener的分類及使用
ServletContext監聽
ServletContext監聽是由
ServletContextListener
和ServletContextAttributeListener
介面實現的,
ServletContextListener方法
對整個Servlet上下文進行監聽(建立或銷燬)
public void contextInitialized(ServletContextEvent sce);
:執行初始化任務,如載入快取等等public void contextDestroyed(ServletContextEvent sce);
:停止時執行清除任務,釋放資源,關閉連線等等- ServletContextEvent物件的操作:
public ServletContext getServletContext();
:取得一個ServletContext(application)物件
ServletContextAttributeListener方法
對Servlet上下文屬性的監聽(增刪改屬性)
public void attributeAdded(ServletContextAttributeEvent scab);
:向ServletContext中新增屬性public void attributeRemoved(ServletContextAttributeEvent scab);
:刪除ServletContext中的屬性public void attributeRepalced(ServletContextAttributeEvent scab);
:替換SevletContext中的屬性,(重複設定無效果)- ServletContextAttributeEvent物件的操作:能獲取屬性名於值
public String getName();
:獲取屬性名public Object getValue();
:獲取屬性的值
Session監聽
於
ServletContext
類似,Session也分為兩種介面,分別為HttpSessionListener
和HttpSessionAttributeListener
HttpSessionListener方法
對Session整體狀態的監聽
public void sessionCreated(HttpSessionEvent se);
:建立Sessionpublic void sessionDestroyed(HttpSessionEvent se);
:銷燬Session- HttpSessionEvent物件的操作:
public HttpSession getSession();
:取得當前操作的session
HttpSessionAttributeListener方法
對Session屬性的監聽
public void attributeAdded(HttpSessionBindingEvent se);
:新增屬性到HttpSession
中public void attributeRemoved(HttpSessionBindingEvent se);
:將一個屬性從HttpSession
中刪除public void attributeReplaced(HttpSessionBindingEvent se);
:替換HttpSession
中的屬性- HttpSessionBindingEvent物件的操作
public String getName();
:取得屬性的名public Object getValue();
:取得屬性的值public HttpSession getSession();:
取得當前的session
Request監聽
Request監聽分為
ServletRequestListener
和ServletRequestAttributeListener
ServletRequestListener方法
public void requestInitialized(ServletRequestEvent sre);
:request初始化public void requestDestroyed(ServletRequestEvent sre);
:request銷燬- ServletRequestEvent物件的操作
public ServletRequest getServletRequest();
:取得一個ServletRequest物件public ServletContext getServletContext();
:取得一個ServletContext(application)物件
ServletRequestAttributeListener方法
public void attributeAdded(ServletRequestAttributeEvent srae);
:往當前ServletRequest
物件中增加屬性public void attributeRemoved(ServletRequestAttributeEvent srae);
往當前ServletRequest
物件中刪除屬性public void attributeReplaced(ServletRequestAttributeEvent srae);
屬性替換(第二次設定同一屬性)- ServletRequestAttributeEvent物件操作
public String getName();
:得到屬性名稱public Object getValue();
:取得屬性的值
Listener的配置
-
web.xm配置
原理於Servlet,Filter大同小異,不展開講解
<listener> <listener-class>com.listener.class</listener-class> </listener>
-
註釋@WebListener
- 往Listener實現類頂部加上
@WebListener
註釋即可
@WebListener public class myListener01 implements HttpSessionAttributeListener {}
- 往Listener實現類頂部加上
Listener的例項應用
使用HttpSessionListener統計最大線上人數
@WebListener
public class myListener01 implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent event) {
//使用Session獲取ServletContext物件
ServletContext app = event.getSession().getServletContext();
//獲取Servlet中的count屬性
int count = (int) app.getAttribute("onLineCount");
count++;//每次建立一個Session Count就會+1 用來計數
app.setAttribute("onLineCount",count);
int maxOnLineCount= (int) app.getAttribute("maxOnLineCount");
//若count>maxOnLineCount 則將maxOnLineCount更新為count的值
if (count>maxOnLineCount)
{
app.setAttribute("maxOnLineCount",count);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
app.setAttribute("date",sdf.format(new Date()));
}
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
//結束會話後會刪除Session 即減少一位客戶 count--
ServletContext app = event.getSession().getServletContext();
int count = (int)app.getAttribute("count");
count--;
app.setAttribute("count", count);
}
}