概述
Servlet是Server Applet的縮寫,即在服務(wù)器端運行的小程序,而Servlet框架則是對HTTP服務(wù)器(Servlet Container)和用戶小程序中間層的標準化和抽象。這一層抽象隔離了HTTP服務(wù)器的實現(xiàn)細節(jié),而Servlet規(guī)范定義了各個類的行為,從而保證了這些“服務(wù)器端運行的小程序”對服務(wù)器實現(xiàn)的無關(guān)性(即提升了其可移植性)。在Servlet規(guī)范有以下幾個核心類(接口):
ServletContext:定義了一些可以和Servlet Container交互的方法。
Registration:實現(xiàn)Filter和Servlet的動態(tài)注冊。
ServletRequest(HttpServletRequest):對HTTP請求消息的封裝。
ServletResponse(HttpServletResponse):對HTTP響應(yīng)消息的封裝。
RequestDispatcher:將當前請求分發(fā)給另一個URL,甚至ServletContext以實現(xiàn)進一步的處理。
Servlet(HttpServlet):所有“服務(wù)器小程序”要實現(xiàn)了接口,這些“服務(wù)器小程序”重寫doGet、doPost、doPut、doHead、doDelete、doOption、doTrace等方法(HttpServlet)以實現(xiàn)響應(yīng)請求的相關(guān)邏輯。
Filter(FilterChain):在進入Servlet前以及出Servlet以后添加一些用戶自定義的邏輯,以實現(xiàn)一些橫切面相關(guān)的功能,如用戶驗證、日志打印等功能。
AsyncContext:實現(xiàn)異步請求處理。
AsyncContext
在Servlet 3.0中引入了AsyncContext,用于實現(xiàn)一個請求可以暫停處理,然后在將來的某個時候重新處理該請求,以釋放當前請求處理過程中占用的線程。在使用時,當發(fā)現(xiàn)請求需要等待一段時間后才能做進一步處理時,可以調(diào)用ServletRequest.startAsync()方法,返回AsyncContext實例,使用自己的線程池啟動一個線程來做接下來的處理或者將其放入一個任務(wù)隊列中,以由一個線程不斷的檢查它的可用狀態(tài),以實現(xiàn)最后的返回處理,或調(diào)用dispatch方法將其分發(fā)給其他URL做進一步響應(yīng)處理。這項功能對SocketConnector沒有多大意義,因為即使Servlet的servic俄方發(fā)退出了,其所占用的線程會繼續(xù)等待,并不會被回收,只有對SelectChannelConnector來說才有效果,因為它的等待不在HttpConnection的handleRequest方法中(AsyncContinuation的scheduleTimeout方法中),而是將timeout的信息提交給SelectSet,它內(nèi)部會有Acceptors個線程對timeout進行檢查。AsyncContext接口定義如下:public interface AsyncContext {
// 原始請求相關(guān)信息的屬性名,即dispatch方法所基于的計算信息。
static final String ASYNC_REQUEST_URI = "javax.servlet.async.request_uri";
static final String ASYNC_CONTEXT_PATH = "javax.servlet.async.context_path";
static final String ASYNC_PATH_INFO = "javax.servlet.async.path_info";
static final String ASYNC_SERVLET_PATH = "javax.servlet.async.servlet_path";
static final String ASYNC_QUERY_STRING = "javax.servlet.async.query_string";
// AsyncContinuation是Jetty對AsyncContext的實現(xiàn),它是一個有限狀態(tài)機。
// 1. 在初始化時,它處于IDLE狀態(tài),initial狀態(tài)為true。
// 2. 調(diào)用其handling()方法使其進入處理模式:若它處于IDLE狀態(tài),則設(shè)置initial狀態(tài)為false,將狀態(tài)轉(zhuǎn)移到DISPATCHED,清除AsyncListener,返回true;若它處于REDISPATCH狀態(tài),將狀態(tài)轉(zhuǎn)移到REDISPATCHED,返回true;若它處于COMPLETING狀態(tài),狀態(tài)轉(zhuǎn)移到UNCOMPLETED,返回false;若它處于ASYNCWAIT狀態(tài),返回false。對其他狀態(tài),拋出IllegalStateException。
// 3. 如果當前請求因為某些原因無法進一步處理時,可以調(diào)用ServletRequest.startAsync方法讓當前請求進入ASYNCSTARTED狀態(tài),即調(diào)用AsyncContinuation.suspend方法,只有它處于DISPATCHED、REDISPATCHED狀態(tài)下才能調(diào)用suspend方法,即在調(diào)用handling()方法之后。此方法還會更新AsyncEventState字段的信息,以及調(diào)用已注冊的AsyncListener的onStartAsync方法,并清除已注冊的AsyncListener。
// 4. 調(diào)用unhandle()方法判斷這個當前請求是否不需要做進一步處理而可以退出handleRequest中的循環(huán):對ASYNCSTARTED狀態(tài),將其狀態(tài)設(shè)置為ASYNCWAIT,并向SelectChannelHttpConnection中schedle一個timeout時間,如果此時它還是處于ASYNCWAIT狀態(tài)(因為對非SelectChannelConnector,它會一直等待下一個dispatch/complete/timeout事件的到來,更新當前狀態(tài),并取消等待),則返回true,否則如果它變?yōu)镃OMPLETING狀態(tài),則設(shè)置狀態(tài)為UNCOMPLETED,返回true,否則設(shè)置其狀態(tài)為REDISPATCHED,并返回false;對DISPATCHED、REDISPATCHED狀態(tài),設(shè)置狀態(tài)為UNCOMPLETED,返回true;對REDISPATCHING狀態(tài),設(shè)置為REDISPATCHED狀態(tài),返回false;對COMPLETING狀態(tài),設(shè)置為UNCOMPLETED,返回true;對其他狀態(tài),拋出異常。
// 5. 當進入異步狀態(tài)的請求完成后,需要將當前處理交由Container做進一步處理,如由另一個path完成進一步處理等,調(diào)用AsyncContext的dispatch方法,將當前請求分發(fā)回Container:如果當前AsyncContinuation處于ASYNCWAIT狀態(tài)并且沒有超時,設(shè)置狀態(tài)為REDISPATCH,并cancelTimeout()、scheduleDispatch();對已經(jīng)處于REDISPATCH狀態(tài),直接返回;對處于ASYNCSTARTED狀態(tài),設(shè)置為REDISPATCHING,并返回。
// 6. 如果當前AsyncContinuation超時,調(diào)用其expired方法:對于處于ASYNCSARTED、ASYNCWAIT狀態(tài),觸發(fā)AsyncListener的onTimeout事件,調(diào)用complete方法,并scheduleDispatch
// 7. 當完成異步請求處理時,調(diào)用其complete方法:如果處于ASYNCWAIT狀態(tài),設(shè)置狀態(tài)為COMPLETING,如果沒有超時,scheduleTimeout、scheduleDispatch;當前狀態(tài)為ASYNCSTARTED,設(shè)置狀態(tài)為COMPLETING;對其他狀態(tài),拋出異常。
// 8. 當退出handleRequest方法時,如果當前AsyncContinuation處于UNCOMPLETE狀態(tài),調(diào)用其doComplete方法,將其狀態(tài)設(shè)置為COMPLETE,如果出現(xiàn)異常,注冊javax.servlet.error.exception, javax.servlet.error.message屬性,并觸發(fā)AsyncListener的onError事件,否則觸發(fā)onComplete事件。
// 9. 對狀態(tài)為ASYNCSTARTED、REDISPATCHING、COMPLETING、ASYNCWAIT,表示處于suspend狀態(tài)。
// 10. 對狀態(tài)為ASYNCSTARTED、REDISPATCHING、REDISPATCH、ASYNCWAIT,表示其處于異步請求開啟的狀態(tài)。
// 11. 對狀態(tài)不是IDLE、DISPATCHED、UNCOMPLETED、COMPLETED,表示當前正處于異步請求狀態(tài)。
// 在調(diào)用ServletRequest.startAsync方法中使用的ServletRequest、ServletResponse實例。在調(diào)用ServletRequest.startAsync方法時,內(nèi)部調(diào)用AsyncContinuation的suspend方法,
// 傳入ServletContext、ServletRequest、ServletResponse實例,在有在AsyncContinuation實例處于DISPATCHED、REDISPATCHED狀態(tài)下才能調(diào)用suspend方法。此時將_expired、_resumed狀態(tài)設(shè)置為false,更新AsyncEventState中的AsyncContext、ServletContext(_suspendedContext, _dispatchedContext)、ServletRequest、ServletResponse、Path等信息(即如果傳入的Request、Response、_suspendContext和當前已保存的實例不同或_event實例為null,則重新創(chuàng)建AsyncEventState實例,否則清除_event中的_dispatchedContext和_path字段)。將當前AsyncContinuation的狀態(tài)設(shè)置為ASYNCSTARTED,保存已注冊的AsyncListener列表(_asyncListeners)到_lastAsyncListeners,清除_asyncListeners列表,并觸發(fā)_lastAsyncListeners中的onStartAsync事件(該事件中可以決定是否需要將自己注冊回去)。
// 對于AsyncContext實例,如果已經(jīng)調(diào)用suspend方法,則返回_event中的ServletRequest、ServletResponse,否則返回HttpConnection中的ServletRequest、ServletResponse。
public ServletRequest getRequest();
public ServletResponse getResponse();
// 當前AsyncContext是否使用原始的request、response實例進行初始化。
public boolean hasOriginalRequestAndResponse();
public void dispatch();
public void dispatch(String path);
public void dispatch(ServletContext context, String path);
public void complete();
public void start(Runnable run);
public void addListener(AsyncListener listener);
public void addListener(AsyncListener listener, ServletRequest servletRequest, ServletResponse servletResponse);
public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException;
public void setTimeout(long timeout);
public long getTimeout();
}
在Server的handleAsync()方法中,他使用HttpConnection的Request字段的AsyncEventState中的ServletRequest、ServletResponse作為Handler調(diào)用handle方法的參數(shù),如果AsyncEventState中有path值,則會用該值來更新baseRequest中的URI相關(guān)信息。// 原始請求相關(guān)信息的屬性名,即dispatch方法所基于的計算信息。
static final String ASYNC_REQUEST_URI = "javax.servlet.async.request_uri";
static final String ASYNC_CONTEXT_PATH = "javax.servlet.async.context_path";
static final String ASYNC_PATH_INFO = "javax.servlet.async.path_info";
static final String ASYNC_SERVLET_PATH = "javax.servlet.async.servlet_path";
static final String ASYNC_QUERY_STRING = "javax.servlet.async.query_string";
// AsyncContinuation是Jetty對AsyncContext的實現(xiàn),它是一個有限狀態(tài)機。
// 1. 在初始化時,它處于IDLE狀態(tài),initial狀態(tài)為true。
// 2. 調(diào)用其handling()方法使其進入處理模式:若它處于IDLE狀態(tài),則設(shè)置initial狀態(tài)為false,將狀態(tài)轉(zhuǎn)移到DISPATCHED,清除AsyncListener,返回true;若它處于REDISPATCH狀態(tài),將狀態(tài)轉(zhuǎn)移到REDISPATCHED,返回true;若它處于COMPLETING狀態(tài),狀態(tài)轉(zhuǎn)移到UNCOMPLETED,返回false;若它處于ASYNCWAIT狀態(tài),返回false。對其他狀態(tài),拋出IllegalStateException。
// 3. 如果當前請求因為某些原因無法進一步處理時,可以調(diào)用ServletRequest.startAsync方法讓當前請求進入ASYNCSTARTED狀態(tài),即調(diào)用AsyncContinuation.suspend方法,只有它處于DISPATCHED、REDISPATCHED狀態(tài)下才能調(diào)用suspend方法,即在調(diào)用handling()方法之后。此方法還會更新AsyncEventState字段的信息,以及調(diào)用已注冊的AsyncListener的onStartAsync方法,并清除已注冊的AsyncListener。
// 4. 調(diào)用unhandle()方法判斷這個當前請求是否不需要做進一步處理而可以退出handleRequest中的循環(huán):對ASYNCSTARTED狀態(tài),將其狀態(tài)設(shè)置為ASYNCWAIT,并向SelectChannelHttpConnection中schedle一個timeout時間,如果此時它還是處于ASYNCWAIT狀態(tài)(因為對非SelectChannelConnector,它會一直等待下一個dispatch/complete/timeout事件的到來,更新當前狀態(tài),并取消等待),則返回true,否則如果它變?yōu)镃OMPLETING狀態(tài),則設(shè)置狀態(tài)為UNCOMPLETED,返回true,否則設(shè)置其狀態(tài)為REDISPATCHED,并返回false;對DISPATCHED、REDISPATCHED狀態(tài),設(shè)置狀態(tài)為UNCOMPLETED,返回true;對REDISPATCHING狀態(tài),設(shè)置為REDISPATCHED狀態(tài),返回false;對COMPLETING狀態(tài),設(shè)置為UNCOMPLETED,返回true;對其他狀態(tài),拋出異常。
// 5. 當進入異步狀態(tài)的請求完成后,需要將當前處理交由Container做進一步處理,如由另一個path完成進一步處理等,調(diào)用AsyncContext的dispatch方法,將當前請求分發(fā)回Container:如果當前AsyncContinuation處于ASYNCWAIT狀態(tài)并且沒有超時,設(shè)置狀態(tài)為REDISPATCH,并cancelTimeout()、scheduleDispatch();對已經(jīng)處于REDISPATCH狀態(tài),直接返回;對處于ASYNCSTARTED狀態(tài),設(shè)置為REDISPATCHING,并返回。
// 6. 如果當前AsyncContinuation超時,調(diào)用其expired方法:對于處于ASYNCSARTED、ASYNCWAIT狀態(tài),觸發(fā)AsyncListener的onTimeout事件,調(diào)用complete方法,并scheduleDispatch
// 7. 當完成異步請求處理時,調(diào)用其complete方法:如果處于ASYNCWAIT狀態(tài),設(shè)置狀態(tài)為COMPLETING,如果沒有超時,scheduleTimeout、scheduleDispatch;當前狀態(tài)為ASYNCSTARTED,設(shè)置狀態(tài)為COMPLETING;對其他狀態(tài),拋出異常。
// 8. 當退出handleRequest方法時,如果當前AsyncContinuation處于UNCOMPLETE狀態(tài),調(diào)用其doComplete方法,將其狀態(tài)設(shè)置為COMPLETE,如果出現(xiàn)異常,注冊javax.servlet.error.exception, javax.servlet.error.message屬性,并觸發(fā)AsyncListener的onError事件,否則觸發(fā)onComplete事件。
// 9. 對狀態(tài)為ASYNCSTARTED、REDISPATCHING、COMPLETING、ASYNCWAIT,表示處于suspend狀態(tài)。
// 10. 對狀態(tài)為ASYNCSTARTED、REDISPATCHING、REDISPATCH、ASYNCWAIT,表示其處于異步請求開啟的狀態(tài)。
// 11. 對狀態(tài)不是IDLE、DISPATCHED、UNCOMPLETED、COMPLETED,表示當前正處于異步請求狀態(tài)。
// 在調(diào)用ServletRequest.startAsync方法中使用的ServletRequest、ServletResponse實例。在調(diào)用ServletRequest.startAsync方法時,內(nèi)部調(diào)用AsyncContinuation的suspend方法,
// 傳入ServletContext、ServletRequest、ServletResponse實例,在有在AsyncContinuation實例處于DISPATCHED、REDISPATCHED狀態(tài)下才能調(diào)用suspend方法。此時將_expired、_resumed狀態(tài)設(shè)置為false,更新AsyncEventState中的AsyncContext、ServletContext(_suspendedContext, _dispatchedContext)、ServletRequest、ServletResponse、Path等信息(即如果傳入的Request、Response、_suspendContext和當前已保存的實例不同或_event實例為null,則重新創(chuàng)建AsyncEventState實例,否則清除_event中的_dispatchedContext和_path字段)。將當前AsyncContinuation的狀態(tài)設(shè)置為ASYNCSTARTED,保存已注冊的AsyncListener列表(_asyncListeners)到_lastAsyncListeners,清除_asyncListeners列表,并觸發(fā)_lastAsyncListeners中的onStartAsync事件(該事件中可以決定是否需要將自己注冊回去)。
// 對于AsyncContext實例,如果已經(jīng)調(diào)用suspend方法,則返回_event中的ServletRequest、ServletResponse,否則返回HttpConnection中的ServletRequest、ServletResponse。
public ServletRequest getRequest();
public ServletResponse getResponse();
// 當前AsyncContext是否使用原始的request、response實例進行初始化。
public boolean hasOriginalRequestAndResponse();
public void dispatch();
public void dispatch(String path);
public void dispatch(ServletContext context, String path);
public void complete();
public void start(Runnable run);
public void addListener(AsyncListener listener);
public void addListener(AsyncListener listener, ServletRequest servletRequest, ServletResponse servletResponse);
public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException;
public void setTimeout(long timeout);
public long getTimeout();
}
RequestDispatcher
在Servlet中RequestDispatcher用于將請求分發(fā)到另一個URL中,或向響應(yīng)中包含更多的信息。一般用于對當前請求做一些前期處理,然后需要后期其他Servlet、JSP來做進一步處理。在Jetty中使用Dispatcher類實現(xiàn)該接口,其接口定義如下:public interface RequestDispatcher {
// 在Dispatcher類中包含了ContextHandler、uri、path、dQuery、named字段,其中ContextHandler是當前Web Application配置的Handler鏈用于將請求分發(fā)給當前Container(調(diào)用handle()方法)做進一步處理、dipatch后請求的全URI、path表示uriInContext、dQuery表示新傳入的parameter、named表示可以使用Servlet名稱創(chuàng)建Dispatcher,即將當前請求分發(fā)到一個命名的Servlet中。
// 在Dispatcher類中有三個方法:forward、error、include。對forward、error來說,如果Response已經(jīng)commit,會拋出IllegalStateException。
// 其中forward和error只是DispatcherType不一樣(FORWARD、ERROR),其他邏輯一樣:清除ServletResponse中所有響應(yīng)相關(guān)的字段,如Content Buffer、locale、ContentType、CharacterEncoding、MimeType等信息,設(shè)置ServletRequest的DispatchType;對named方式的Dispatcher,直接調(diào)用ContextHandler的handle方法,其target參數(shù)即為傳入的named;如果dQuery字段不為null,將該dQuery中的包含的參數(shù)合并到當前請求中;更新Request的URI、ContextPath,并在其Request屬性中添加原始請求的pathInfo、queryString、RequestURI、contextPath、servletPath信息,分別對應(yīng)該接口中定義的字段,如果這是第二次forward,則保留最原始的請求相關(guān)的信息;最后調(diào)用ContextHandler的handle方法,target為path屬性;在調(diào)用結(jié)束后,將RequestURI、ContextPath、ServletPath、PathInfo、Attributes、Parameters、QueryString、DispatcherType屬性設(shè)置為原來的值。
// 對include方法,它不會清除Response中的Buffer等信息:首先設(shè)置DispatcherType為INCLUDE,HttpConnection中的include字段加1,表示正處于INCLUDE的dispatch狀態(tài),從而阻止對ServletResponse響應(yīng)頭的設(shè)置、發(fā)送重定向響應(yīng)、發(fā)送Error響應(yīng)等操作,該include字段會在該方法結(jié)束是調(diào)用HttpConnection的included方法將其減1;同樣對于named設(shè)置的Dispatcher實例,直接調(diào)用ContextHandler的handle方法,target為named值;對以path方式的include,首先合并傳入的dQuery參數(shù)到Request中,更新Request中屬性的requestURI、contextPath、pathInfo、query等,后調(diào)用ContextHandler的handle方法,target為path,在handle方法完成后,將請求Attributes、Parameters、DispatcherType設(shè)置會原有值。
// 在forward中,原始請求對應(yīng)信息使用的屬性名。
static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri";
static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path";
static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info";
static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string";
// 在include中,原始請求對應(yīng)信息使用的屬性名。
static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
// 在error中,原始請求對應(yīng)信息使用的屬性名。
public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
public static final String ERROR_MESSAGE = "javax.servlet.error.message";
public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException;
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException;
}
// 在Dispatcher類中包含了ContextHandler、uri、path、dQuery、named字段,其中ContextHandler是當前Web Application配置的Handler鏈用于將請求分發(fā)給當前Container(調(diào)用handle()方法)做進一步處理、dipatch后請求的全URI、path表示uriInContext、dQuery表示新傳入的parameter、named表示可以使用Servlet名稱創(chuàng)建Dispatcher,即將當前請求分發(fā)到一個命名的Servlet中。
// 在Dispatcher類中有三個方法:forward、error、include。對forward、error來說,如果Response已經(jīng)commit,會拋出IllegalStateException。
// 其中forward和error只是DispatcherType不一樣(FORWARD、ERROR),其他邏輯一樣:清除ServletResponse中所有響應(yīng)相關(guān)的字段,如Content Buffer、locale、ContentType、CharacterEncoding、MimeType等信息,設(shè)置ServletRequest的DispatchType;對named方式的Dispatcher,直接調(diào)用ContextHandler的handle方法,其target參數(shù)即為傳入的named;如果dQuery字段不為null,將該dQuery中的包含的參數(shù)合并到當前請求中;更新Request的URI、ContextPath,并在其Request屬性中添加原始請求的pathInfo、queryString、RequestURI、contextPath、servletPath信息,分別對應(yīng)該接口中定義的字段,如果這是第二次forward,則保留最原始的請求相關(guān)的信息;最后調(diào)用ContextHandler的handle方法,target為path屬性;在調(diào)用結(jié)束后,將RequestURI、ContextPath、ServletPath、PathInfo、Attributes、Parameters、QueryString、DispatcherType屬性設(shè)置為原來的值。
// 對include方法,它不會清除Response中的Buffer等信息:首先設(shè)置DispatcherType為INCLUDE,HttpConnection中的include字段加1,表示正處于INCLUDE的dispatch狀態(tài),從而阻止對ServletResponse響應(yīng)頭的設(shè)置、發(fā)送重定向響應(yīng)、發(fā)送Error響應(yīng)等操作,該include字段會在該方法結(jié)束是調(diào)用HttpConnection的included方法將其減1;同樣對于named設(shè)置的Dispatcher實例,直接調(diào)用ContextHandler的handle方法,target為named值;對以path方式的include,首先合并傳入的dQuery參數(shù)到Request中,更新Request中屬性的requestURI、contextPath、pathInfo、query等,后調(diào)用ContextHandler的handle方法,target為path,在handle方法完成后,將請求Attributes、Parameters、DispatcherType設(shè)置會原有值。
// 在forward中,原始請求對應(yīng)信息使用的屬性名。
static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri";
static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path";
static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info";
static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string";
// 在include中,原始請求對應(yīng)信息使用的屬性名。
static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
// 在error中,原始請求對應(yīng)信息使用的屬性名。
public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
public static final String ERROR_MESSAGE = "javax.servlet.error.message";
public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException;
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException;
}
HttpSession
Http的請求是無狀態(tài)的,這種方式的好處是邏輯簡單,因為服務(wù)器不需要根據(jù)當前服務(wù)器的狀態(tài)做一些特殊處理,然而在實際應(yīng)用中,有些時候希望一系列的請求共享一些數(shù)據(jù)和信息,在HTTP中可以有兩種方式實現(xiàn)這種需求:一種是cookie,所有這些共享的數(shù)據(jù)和信息都使用cookie在發(fā)送請求時發(fā)送給服務(wù)器,在響應(yīng)請求時將最新的信息和狀態(tài)通過set-cookie的方式重新發(fā)送回客戶端,這種方式可以使服務(wù)器依然保持簡單無狀態(tài)的處理邏輯,然而它每次都要來回傳送這種狀態(tài)信息,會占用帶寬,而且cookie本身有大小限制,有些客戶端處于安全的因素會禁止cookie使用,另外cookie采用明文方式,對有些數(shù)據(jù)來說是不適合的;另一種方式則是采用服務(wù)器端Session的方法,即使用SessionId將一系列的請求關(guān)聯(lián)在一起,可以向Session存儲這些請求共享的信息和數(shù)據(jù),Session方式的好處是這些數(shù)據(jù)保存在服務(wù)器端,因而它是安全的,而且不需要每次在客戶端和服務(wù)器端傳輸,可以減少帶寬,而它不好的地方是會增加服務(wù)器負擔,因為如果Session過多會占用服務(wù)器內(nèi)存,另外它也會增加服務(wù)器端的邏輯,服務(wù)器要有一種機制保證相同的SessionId確實屬于同一個系列的請求。在Servlet種使用HttpSession抽象這種服務(wù)器端Session的信息。它包含了SessionId、CreationTime、LastAccessedTime、MaxInactiveInterval、Attributes等信息,在Servlet中的可見范偉是ServletContext,即跨Web Application的Session是不可見的。在Jetty中使用SessionManager來管理Session,Session可以存儲在數(shù)據(jù)庫中(JDBCSessionManager),也可以存在內(nèi)存中(HashSessionManager)。在Jetty中使用AbstractSessionManager的內(nèi)部類Session來實現(xiàn)HttpSession接口,并且該實現(xiàn)是線程安全的。HttpSession的接口定義如下:
public interface HttpSession {
// HttpSession創(chuàng)建的時間戳,從1970-01-01 00:00:00.000開始算到現(xiàn)在的毫秒數(shù)。
public long getCreationTime();
// 當前Session的ID號,它用來唯一標識Web Application中的一個Session實例。在Jetty的實現(xiàn)中,有兩種ID:NodeId和ClusterId,在SessionIdManager創(chuàng)建一個ClusterId時,可以使用一個SecureRandom的兩次nextLong的36進制的字符串相加或者兩次當前SessionIdManager的hashCode、當前可用內(nèi)存數(shù)、random.nextInt()、Request的hashCode左移32位的異或操作的36進制字符串相加,并添加workName前綴,如果該ID已經(jīng)存在,則繼續(xù)使用以上邏輯,直到找到一個沒有被使用的唯一的ID號。如果請求中的RequestedSessionId存在并在使用,則使用該值作為SessionID;如果當前請求已經(jīng)存在一個正在使用的SessionId(在org.eclipse.jetty.server.newSessionId請求熟悉中),則使用該ID。而對與NodeId,它會在ClusterId之后加一個".workerName",可以通過SessionIdManager設(shè)置workName或在HashSessionIdManager中使用org.eclipse.jetty.ajp.JVMRoute請求屬性設(shè)置。在AbstractSessionManager中設(shè)置NodeIdInSessionId為true來配置使用NodeId作為SessionId,默認使用ClusterId作為SessionId。
public String getId();
// 返回最后一次訪問時間戳。在每一次屬于同一個Session的新的Request到來時都會更新該值。
public long getLastAccessedTime();
// 返回該Session對應(yīng)的ServletContext。
public ServletContext getServletContext();
// 設(shè)置Session的Idle時間,以秒為單位。
public void setMaxInactiveInterval(int interval);
public int getMaxInactiveInterval();
// Attribute相關(guān)操作。在設(shè)置屬性時,如果傳入value為null,則移除該屬性;如果該屬性已存在,則替換該屬性;如果屬性值實現(xiàn)了HttpSessionBindingListener,則它在替換時會觸發(fā)其valueUnbound事件,屬性設(shè)置時會觸發(fā)valueBound事件;如果HttpSession中注冊了HttpSessionAttributeListener,則會觸發(fā)響應(yīng)的attributeAdded、attributeReplaced事件。而removeAttribute時,也會觸發(fā)相應(yīng)的valueUnbound事件以及attributeRemoved事件。
public Object getAttribute(String name);
public Enumeration<String> getAttributeNames();
public void setAttribute(String name, Object value);
public void removeAttribute(String name);
// Session失效,它移除所有和其綁定的屬性,并且將Session實例從SessionManager中移除。
public void invalidate();
// true表識客戶端沒有使用session。此時客戶端請求可能不需要使用session信息或者它使用cookie作為session的信息交互。
public boolean isNew();
}
在Jetty中使用SessionIdManager來創(chuàng)建管理SessionId信息,默認實現(xiàn)有HashSessionIdManager和JDBCSessionIdManager:// HttpSession創(chuàng)建的時間戳,從1970-01-01 00:00:00.000開始算到現(xiàn)在的毫秒數(shù)。
public long getCreationTime();
// 當前Session的ID號,它用來唯一標識Web Application中的一個Session實例。在Jetty的實現(xiàn)中,有兩種ID:NodeId和ClusterId,在SessionIdManager創(chuàng)建一個ClusterId時,可以使用一個SecureRandom的兩次nextLong的36進制的字符串相加或者兩次當前SessionIdManager的hashCode、當前可用內(nèi)存數(shù)、random.nextInt()、Request的hashCode左移32位的異或操作的36進制字符串相加,并添加workName前綴,如果該ID已經(jīng)存在,則繼續(xù)使用以上邏輯,直到找到一個沒有被使用的唯一的ID號。如果請求中的RequestedSessionId存在并在使用,則使用該值作為SessionID;如果當前請求已經(jīng)存在一個正在使用的SessionId(在org.eclipse.jetty.server.newSessionId請求熟悉中),則使用該ID。而對與NodeId,它會在ClusterId之后加一個".workerName",可以通過SessionIdManager設(shè)置workName或在HashSessionIdManager中使用org.eclipse.jetty.ajp.JVMRoute請求屬性設(shè)置。在AbstractSessionManager中設(shè)置NodeIdInSessionId為true來配置使用NodeId作為SessionId,默認使用ClusterId作為SessionId。
public String getId();
// 返回最后一次訪問時間戳。在每一次屬于同一個Session的新的Request到來時都會更新該值。
public long getLastAccessedTime();
// 返回該Session對應(yīng)的ServletContext。
public ServletContext getServletContext();
// 設(shè)置Session的Idle時間,以秒為單位。
public void setMaxInactiveInterval(int interval);
public int getMaxInactiveInterval();
// Attribute相關(guān)操作。在設(shè)置屬性時,如果傳入value為null,則移除該屬性;如果該屬性已存在,則替換該屬性;如果屬性值實現(xiàn)了HttpSessionBindingListener,則它在替換時會觸發(fā)其valueUnbound事件,屬性設(shè)置時會觸發(fā)valueBound事件;如果HttpSession中注冊了HttpSessionAttributeListener,則會觸發(fā)響應(yīng)的attributeAdded、attributeReplaced事件。而removeAttribute時,也會觸發(fā)相應(yīng)的valueUnbound事件以及attributeRemoved事件。
public Object getAttribute(String name);
public Enumeration<String> getAttributeNames();
public void setAttribute(String name, Object value);
public void removeAttribute(String name);
// Session失效,它移除所有和其綁定的屬性,并且將Session實例從SessionManager中移除。
public void invalidate();
// true表識客戶端沒有使用session。此時客戶端請求可能不需要使用session信息或者它使用cookie作為session的信息交互。
public boolean isNew();
}
public interface SessionIdManager extends LifeCycle {
public boolean idInUse(String id);
public void addSession(HttpSession session);
public void removeSession(HttpSession session);
public void invalidateAll(String id);
public String newSessionId(HttpServletRequest request,long created);
public String getWorkerName();
public String getClusterId(String nodeId);
public String getNodeId(String clusterId,HttpServletRequest request);
}
而HttpSession的創(chuàng)建和管理則使用SessionManager,默認有HashSessionManager和JDBCSessionManager兩個實現(xiàn):public boolean idInUse(String id);
public void addSession(HttpSession session);
public void removeSession(HttpSession session);
public void invalidateAll(String id);
public String newSessionId(HttpServletRequest request,long created);
public String getWorkerName();
public String getClusterId(String nodeId);
public String getNodeId(String clusterId,HttpServletRequest request);
}
public interface SessionManager extends LifeCycle {
// 在AbstractSessionManager定義了Session內(nèi)部類實現(xiàn)了HttpSession接口,使用SessionIdManager來生成并管理SessionId,可以注冊HttpSessionAttributeListener和HttpSessionListener(在HttpSession創(chuàng)建和銷毀時分別觸發(fā)sessionCreated、sessionDestroyed事件)。另外它還實現(xiàn)了SessionCookieConfig內(nèi)部類,用于使用Cookie配置Session的信息,如Name(默認JSESSIONID)、domain、path、comment、httpOnly、secure、maxAge等。在HashSessionManager和JDBCSessionManager中還各自有一個線程會檢查Session的expire狀態(tài),并Invalidate已經(jīng)expired的Session。最后,AbstractSessionManager還包含了Session相關(guān)的統(tǒng)計信息。
// 在SessionManager中定義的一些屬性,可以使用該方法定義的一些屬性在ServletContext的initParam中設(shè)置,即web.xml文件中的init-param中設(shè)置。
// 創(chuàng)建并添加HttpSession實例。
public HttpSession newHttpSession(HttpServletRequest request);
// 根據(jù)SessionId獲取HttpSession實例。
public HttpSession getHttpSession(String id);
// 獲取Cookie作為SessionTrackingMode時,該Cookie是否屬于httpOnly(用來阻止某些cross-script攻擊)
public boolean getHttpOnly();
// Session的最大Idle時間,秒為單位
public int getMaxInactiveInterval();
public void setMaxInactiveInterval(int seconds);
public void setSessionHandler(SessionHandler handler);
// 事件相關(guān)操作
public void addEventListener(EventListener listener);
public void removeEventListener(EventListener listener);
public void clearEventListeners();
// 在使用Cookie作為SessionTrackingMode時,獲取作為Session Tracking的Cookie
public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure);
public SessionIdManager getIdManager();
public void setIdManager(SessionIdManager idManager);
public boolean isValid(HttpSession session);
public String getNodeId(HttpSession session);
public String getClusterId(HttpSession session);
// 更新Session的AccessTime。
public HttpCookie access(HttpSession session, boolean secure);
public void complete(HttpSession session);
// 使用URL作為SessionTrackingMode時,在URL中作為SessionId的parameter name。
public void setSessionIdPathParameterName(String parameterName);
public String getSessionIdPathParameterName();
// 使用URL作為SessionTrackingMode時,在URL中SessionId信息的前綴,默認為:;<sessionIdParameterName>=
public String getSessionIdPathParameterNamePrefix();
public boolean isUsingCookies();
public boolean isUsingURLs();
public Set<SessionTrackingMode> getDefaultSessionTrackingModes();
public Set<SessionTrackingMode> getEffectiveSessionTrackingModes();
public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes);
public SessionCookieConfig getSessionCookieConfig();
public boolean isCheckingRemoteSessionIdEncoding();
public void setCheckingRemoteSessionIdEncoding(boolean remote);
}
// 在AbstractSessionManager定義了Session內(nèi)部類實現(xiàn)了HttpSession接口,使用SessionIdManager來生成并管理SessionId,可以注冊HttpSessionAttributeListener和HttpSessionListener(在HttpSession創(chuàng)建和銷毀時分別觸發(fā)sessionCreated、sessionDestroyed事件)。另外它還實現(xiàn)了SessionCookieConfig內(nèi)部類,用于使用Cookie配置Session的信息,如Name(默認JSESSIONID)、domain、path、comment、httpOnly、secure、maxAge等。在HashSessionManager和JDBCSessionManager中還各自有一個線程會檢查Session的expire狀態(tài),并Invalidate已經(jīng)expired的Session。最后,AbstractSessionManager還包含了Session相關(guān)的統(tǒng)計信息。
// 在SessionManager中定義的一些屬性,可以使用該方法定義的一些屬性在ServletContext的initParam中設(shè)置,即web.xml文件中的init-param中設(shè)置。
// 創(chuàng)建并添加HttpSession實例。
public HttpSession newHttpSession(HttpServletRequest request);
// 根據(jù)SessionId獲取HttpSession實例。
public HttpSession getHttpSession(String id);
// 獲取Cookie作為SessionTrackingMode時,該Cookie是否屬于httpOnly(用來阻止某些cross-script攻擊)
public boolean getHttpOnly();
// Session的最大Idle時間,秒為單位
public int getMaxInactiveInterval();
public void setMaxInactiveInterval(int seconds);
public void setSessionHandler(SessionHandler handler);
// 事件相關(guān)操作
public void addEventListener(EventListener listener);
public void removeEventListener(EventListener listener);
public void clearEventListeners();
// 在使用Cookie作為SessionTrackingMode時,獲取作為Session Tracking的Cookie
public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure);
public SessionIdManager getIdManager();
public void setIdManager(SessionIdManager idManager);
public boolean isValid(HttpSession session);
public String getNodeId(HttpSession session);
public String getClusterId(HttpSession session);
// 更新Session的AccessTime。
public HttpCookie access(HttpSession session, boolean secure);
public void complete(HttpSession session);
// 使用URL作為SessionTrackingMode時,在URL中作為SessionId的parameter name。
public void setSessionIdPathParameterName(String parameterName);
public String getSessionIdPathParameterName();
// 使用URL作為SessionTrackingMode時,在URL中SessionId信息的前綴,默認為:;<sessionIdParameterName>=
public String getSessionIdPathParameterNamePrefix();
public boolean isUsingCookies();
public boolean isUsingURLs();
public Set<SessionTrackingMode> getDefaultSessionTrackingModes();
public Set<SessionTrackingMode> getEffectiveSessionTrackingModes();
public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes);
public SessionCookieConfig getSessionCookieConfig();
public boolean isCheckingRemoteSessionIdEncoding();
public void setCheckingRemoteSessionIdEncoding(boolean remote);
}
SessionHandler
SessionHandler繼承子ScopedHandler,它主要使用SessionManager在doScope方法中為當前Scope設(shè)置Session信息。1. 如果使用Cookie作為SessionId的通信,則首先從Cookie中向Request設(shè)置RequestedSessionId。
2. 否則,從URL中計算出RequestedSessionId,并設(shè)置到Request中。
3. 如果SessionManager發(fā)生變化,則更新Request中SessionManager實例以及Session實例。
4. 如果Session發(fā)生變化,則更新Session的AccessTime,并將返回的cookie寫入Response中。
5. 在退出時設(shè)置回Request原有的SessionManager和Session實例,如果需要的話。