servlet是server applet的縮寫,即在伺服器端運作的小程式,而servlet架構則是對http伺服器(servlet container)和使用者小程式中間層的标準化和抽象。這一層抽象隔離了http伺服器的實作細節,而servlet規範定義了各個類的行為,進而保證了這些“伺服器端運作的小程式”對伺服器實作的無關性(即提升了其可移植性)。
在servlet規範有以下幾個核心類(接口):
servletcontext:定義了一些可以和servlet container互動的方法。
registration:實作filter和servlet的動态注冊。
servletrequest(httpservletrequest):對http請求消息的封裝。
servletresponse(httpservletresponse):對http響應消息的封裝。
requestdispatcher:将目前請求分發給另一個url,甚至servletcontext以實作進一步的處理。
servlet(httpservlet):所有“伺服器小程式”要實作了接口,這些“伺服器小程式”重寫doget、dopost、doput、dohead、dodelete、dooption、dotrace等方法(httpservlet)以實作響應請求的相關邏輯。
filter(filterchain):在進入servlet前以及出servlet以後添加一些使用者自定義的邏輯,以實作一些橫切面相關的功能,如使用者驗證、日志列印等功能。
asynccontext:實作異步請求處理。
在servlet 3.0中引入了asynccontext,用于實作一個請求可以暫停處理,然後在将來的某個時候重新處理該請求,以釋放目前請求處理過程中占用的線程。在使用時,當發現請求需要等待一段時間後才能做進一步處理時,可以調用servletrequest.startasync()方法,傳回asynccontext執行個體,使用自己的線程池啟動一個線程來做接下來的處理或者将其放入一個任務隊列中,以由一個線程不斷的檢查它的可用狀态,以實作最後的傳回處理,或調用dispatch方法将其分發給其他url做進一步響應處理。這項功能對socketconnector沒有多大意義,因為即使servlet的servic俄方發退出了,其所占用的線程會繼續等待,并不會被回收,隻有對selectchannelconnector來說才有效果,因為它的等待不在httpconnection的handlerequest方法中(asynccontinuation的scheduletimeout方法中),而是将timeout的資訊送出給selectset,它内部會有acceptors個線程對timeout進行檢查。asynccontext接口定義如下:
public interface asynccontext {
// 原始請求相關資訊的屬性名,即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的實作,它是一個有限狀态機。
// 1. 在初始化時,它處于idle狀态,initial狀态為true。
// 2. 調用其handling()方法使其進入處理模式:若它處于idle狀态,則設定initial狀态為false,将狀态轉移到dispatched,清除asynclistener,傳回true;若它處于redispatch狀态,将狀态轉移到redispatched,傳回true;若它處于completing狀态,狀态轉移到uncompleted,傳回false;若它處于asyncwait狀态,傳回false。對其他狀态,抛出illegalstateexception。
// 3. 如果目前請求因為某些原因無法進一步處理時,可以調用servletrequest.startasync方法讓目前請求進入asyncstarted狀态,即調用asynccontinuation.suspend方法,隻有它處于dispatched、redispatched狀态下才能調用suspend方法,即在調用handling()方法之後。此方法還會更新asynceventstate字段的資訊,以及調用已注冊的asynclistener的onstartasync方法,并清除已注冊的asynclistener。
// 4. 調用unhandle()方法判斷這個目前請求是否不需要做進一步處理而可以退出handlerequest中的循環:對asyncstarted狀态,将其狀态設定為asyncwait,并向selectchannelhttpconnection中schedle一個timeout時間,如果此時它還是處于asyncwait狀态(因為對非selectchannelconnector,它會一直等待下一個dispatch/complete/timeout事件的到來,更新目前狀态,并取消等待),則傳回true,否則如果它變為completing狀态,則設定狀态為uncompleted,傳回true,否則設定其狀态為redispatched,并傳回false;對dispatched、redispatched狀态,設定狀态為uncompleted,傳回true;對redispatching狀态,設定為redispatched狀态,傳回false;對completing狀态,設定為uncompleted,傳回true;對其他狀态,抛出異常。
// 5. 當進入異步狀态的請求完成後,需要将目前處理交由container做進一步處理,如由另一個path完成進一步處理等,調用asynccontext的dispatch方法,将目前請求分發回container:如果目前asynccontinuation處于asyncwait狀态并且沒有逾時,設定狀态為redispatch,并canceltimeout()、scheduledispatch();對已經處于redispatch狀态,直接傳回;對處于asyncstarted狀态,設定為redispatching,并傳回。
// 6. 如果目前asynccontinuation逾時,調用其expired方法:對于處于asyncsarted、asyncwait狀态,觸發asynclistener的ontimeout事件,調用complete方法,并scheduledispatch
// 7. 當完成異步請求處理時,調用其complete方法:如果處于asyncwait狀态,設定狀态為completing,如果沒有逾時,scheduletimeout、scheduledispatch;目前狀态為asyncstarted,設定狀态為completing;對其他狀态,抛出異常。
// 8. 當退出handlerequest方法時,如果目前asynccontinuation處于uncomplete狀态,調用其docomplete方法,将其狀态設定為complete,如果出現異常,注冊javax.servlet.error.exception, javax.servlet.error.message屬性,并觸發asynclistener的onerror事件,否則觸發oncomplete事件。
// 9. 對狀态為asyncstarted、redispatching、completing、asyncwait,表示處于suspend狀态。
// 10. 對狀态為asyncstarted、redispatching、redispatch、asyncwait,表示其處于異步請求開啟的狀态。
// 11. 對狀态不是idle、dispatched、uncompleted、completed,表示目前正處于異步請求狀态。
// 在調用servletrequest.startasync方法中使用的servletrequest、servletresponse執行個體。在調用servletrequest.startasync方法時,内部調用asynccontinuation的suspend方法,
// 傳入servletcontext、servletrequest、servletresponse執行個體,在有在asynccontinuation執行個體處于dispatched、redispatched狀态下才能調用suspend方法。此時将_expired、_resumed狀态設定為false,更新asynceventstate中的asynccontext、servletcontext(_suspendedcontext, _dispatchedcontext)、servletrequest、servletresponse、path等資訊(即如果傳入的request、response、_suspendcontext和目前已儲存的執行個體不同或_event執行個體為null,則重新建立asynceventstate執行個體,否則清除_event中的_dispatchedcontext和_path字段)。将目前asynccontinuation的狀态設定為asyncstarted,儲存已注冊的asynclistener清單(_asynclisteners)到_lastasynclisteners,清除_asynclisteners清單,并觸發_lastasynclisteners中的onstartasync事件(該事件中可以決定是否需要将自己注冊回去)。
// 對于asynccontext執行個體,如果已經調用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調用handle方法的參數,如果asynceventstate中有path值,則會用該值來更新baserequest中的uri相關資訊。
在servlet中requestdispatcher用于将請求分發到另一個url中,或向響應中包含更多的資訊。一般用于對目前請求做一些前期處理,然後需要後期其他servlet、jsp來做進一步處理。在jetty中使用dispatcher類實作該接口,其接口定義如下:
public interface requestdispatcher {
// 在dispatcher類中包含了contexthandler、uri、path、dquery、named字段,其中contexthandler是目前web application配置的handler鍊用于将請求分發給目前container(調用handle()方法)做進一步處理、dipatch後請求的全uri、path表示uriincontext、dquery表示新傳入的parameter、named表示可以使用servlet名稱建立dispatcher,即将目前請求分發到一個命名的servlet中。
// 在dispatcher類中有三個方法:forward、error、include。對forward、error來說,如果response已經commit,會抛出illegalstateexception。
// 其中forward和error隻是dispatchertype不一樣(forward、error),其他邏輯一樣:清除servletresponse中所有響應相關的字段,如content buffer、locale、contenttype、characterencoding、mimetype等資訊,設定servletrequest的dispatchtype;對named方式的dispatcher,直接調用contexthandler的handle方法,其target參數即為傳入的named;如果dquery字段不為null,将該dquery中的包含的參數合并到目前請求中;更新request的uri、contextpath,并在其request屬性中添加原始請求的pathinfo、querystring、requesturi、contextpath、servletpath資訊,分别對應該接口中定義的字段,如果這是第二次forward,則保留最原始的請求相關的資訊;最後調用contexthandler的handle方法,target為path屬性;在調用結束後,将requesturi、contextpath、servletpath、pathinfo、attributes、parameters、querystring、dispatchertype屬性設定為原來的值。
// 對include方法,它不會清除response中的buffer等資訊:首先設定dispatchertype為include,httpconnection中的include字段加1,表示正處于include的dispatch狀态,進而阻止對servletresponse響應頭的設定、發送重定向響應、發送error響應等操作,該include字段會在該方法結束是調用httpconnection的included方法将其減1;同樣對于named設定的dispatcher執行個體,直接調用contexthandler的handle方法,target為named值;對以path方式的include,首先合并傳入的dquery參數到request中,更新request中屬性的requesturi、contextpath、pathinfo、query等,後調用contexthandler的handle方法,target為path,在handle方法完成後,将請求attributes、parameters、dispatchertype設定會原有值。
// 在forward中,原始請求對應資訊使用的屬性名。
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中,原始請求對應資訊使用的屬性名。
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中,原始請求對應資訊使用的屬性名。
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;
http的請求是無狀态的,這種方式的好處是邏輯簡單,因為伺服器不需要根據目前伺服器的狀态做一些特殊處理,然而在實際應用中,有些時候希望一系列的請求共享一些資料和資訊,在http中可以有兩種方式實作這種需求:一種是cookie,所有這些共享的資料和資訊都使用cookie在發送請求時發送給伺服器,在響應請求時将最新的資訊和狀态通過set-cookie的方式重新發送回用戶端,這種方式可以使伺服器依然保持簡單無狀态的處理邏輯,然而它每次都要來回傳送這種狀态資訊,會占用帶寬,而且cookie本身有大小限制,有些用戶端處于安全的因素會禁止cookie使用,另外cookie采用明文方式,對有些資料來說是不适合的;另一種方式則是采用伺服器端session的方法,即使用sessionid将一系列的請求關聯在一起,可以向session存儲這些請求共享的資訊和資料,session方式的好處是這些資料儲存在伺服器端,因而它是安全的,而且不需要每次在用戶端和伺服器端傳輸,可以減少帶寬,而它不好的地方是會增加伺服器負擔,因為如果session過多會占用伺服器記憶體,另外它也會增加伺服器端的邏輯,伺服器要有一種機制保證相同的sessionid确實屬于同一個系列的請求。
在servlet種使用httpsession抽象這種伺服器端session的資訊。它包含了sessionid、creationtime、lastaccessedtime、maxinactiveinterval、attributes等資訊,在servlet中的可見範偉是servletcontext,即跨web application的session是不可見的。在jetty中使用sessionmanager來管理session,session可以存儲在資料庫中(jdbcsessionmanager),也可以存在記憶體中(hashsessionmanager)。在jetty中使用abstractsessionmanager的内部類session來實作httpsession接口,并且該實作是線程安全的。httpsession的接口定義如下:
public interface httpsession {
// httpsession建立的時間戳,從1970-01-01 00:00:00.000開始算到現在的毫秒數。
public long getcreationtime();
// 目前session的id号,它用來唯一辨別web application中的一個session執行個體。在jetty的實作中,有兩種id:nodeid和clusterid,在sessionidmanager建立一個clusterid時,可以使用一個securerandom的兩次nextlong的36進制的字元串相加或者兩次目前sessionidmanager的hashcode、目前可用記憶體數、random.nextint()、request的hashcode左移32位的異或操作的36進制字元串相加,并添加workname字首,如果該id已經存在,則繼續使用以上邏輯,直到找到一個沒有被使用的唯一的id号。如果請求中的requestedsessionid存在并在使用,則使用該值作為sessionid;如果目前請求已經存在一個正在使用的sessionid(在org.eclipse.jetty.server.newsessionid請求熟悉中),則使用該id。而對與nodeid,它會在clusterid之後加一個".workername",可以通過sessionidmanager設定workname或在hashsessionidmanager中使用org.eclipse.jetty.ajp.jvmroute請求屬性設定。在abstractsessionmanager中設定nodeidinsessionid為true來配置使用nodeid作為sessionid,預設使用clusterid作為sessionid。
public string getid();
// 傳回最後一次通路時間戳。在每一次屬于同一個session的新的request到來時都會更新該值。
public long getlastaccessedtime();
// 傳回該session對應的servletcontext。
public servletcontext getservletcontext();
// 設定session的idle時間,以秒為機關。
public void setmaxinactiveinterval(int interval);
public int getmaxinactiveinterval();
// attribute相關操作。在設定屬性時,如果傳入value為null,則移除該屬性;如果該屬性已存在,則替換該屬性;如果屬性值實作了httpsessionbindinglistener,則它在替換時會觸發其valueunbound事件,屬性設定時會觸發valuebound事件;如果httpsession中注冊了httpsessionattributelistener,則會觸發響應的attributeadded、attributereplaced事件。而removeattribute時,也會觸發相應的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來建立管理sessionid資訊,預設實作有hashsessionidmanager和jdbcsessionidmanager:
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的建立和管理則使用sessionmanager,預設有hashsessionmanager和jdbcsessionmanager兩個實作:
public interface sessionmanager extends lifecycle {
// 在abstractsessionmanager定義了session内部類實作了httpsession接口,使用sessionidmanager來生成并管理sessionid,可以注冊httpsessionattributelistener和httpsessionlistener(在httpsession建立和銷毀時分别觸發sessioncreated、sessiondestroyed事件)。另外它還實作了sessioncookieconfig内部類,用于使用cookie配置session的資訊,如name(預設jsessionid)、domain、path、comment、httponly、secure、maxage等。在hashsessionmanager和jdbcsessionmanager中還各自有一個線程會檢查session的expire狀态,并invalidate已經expired的session。最後,abstractsessionmanager還包含了session相關的統計資訊。
// 在sessionmanager中定義的一些屬性,可以使用該方法定義的一些屬性在servletcontext的initparam中設定,即web.xml檔案中的init-param中設定。
// 建立并添加httpsession執行個體。
public httpsession newhttpsession(httpservletrequest request);
// 根據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);
// 事件相關操作
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繼承子scopedhandler,它主要使用sessionmanager在doscope方法中為目前scope設定session資訊。
1. 如果使用cookie作為sessionid的通信,則首先從cookie中向request設定requestedsessionid。
2. 否則,從url中計算出requestedsessionid,并設定到request中。
3. 如果sessionmanager發生變化,則更新request中sessionmanager執行個體以及session執行個體。
4. 如果session發生變化,則更新session的accesstime,并将傳回的cookie寫入response中。
5. 在退出時設定回request原有的sessionmanager和session執行個體,如果需要的話。