這是我在華沙大學參加的課程的一部分創建的項目。這是一個簡單的Servlet容器,該容器支持Java Servlets符合Java Servlet API。即使它是準排骨,它也可以加載和運行簡單的應用程序,這些應用程序使用servlet的基本功能,包括派遣,JSP和異步servlet。
我沒有找到許多其他小型servlet容器實現,因此可以隨意將其用作如何創建這樣的容器的示例。請記住,由於時間限制,這是一個非常不完美的越野車項目,儘管我試圖忠於Servlet API,但我在這里和那裡獲得了一些自由。
這個想法是,該容器以與Tomcat相似的方式工作。您可以使用ServletContainer :: AddRoute添加Servlet類,但首選的方法是使用組件掃描功能。也就是說,如果應用程序被劃定到warName.war中。 WAR文件並將其放置在server/src/main/resources內部的deploy目錄中,它將在服務器啟動上加載並在localhost:8000/warName下可用。
默認情況下,服務器在端口8000上運行。可以在Main函數中更改它。此功能還包含如何啟動和配置該服務器的示例。
該項目是使用Gradle構建的。它包含兩個子項目:服務器和演示應用程序(簡單書籍數據庫)。它在Linux下進行了測試,我不知道它甚至在Windows/MacOS下工作。
運行服務器使用:
./gradlew server:run
啟動服務器要求我們創建ServletContainer的實例。
ServletContainer(int threads)創建ServletContainer的新實例。給定數量的線程用於創建線程池。該池將用於處理HTTP請求。
void ServletContainer::start(int port)在給定端口上啟動servlet容器。請記住,目前尚未加載.war文件。它們僅在ServletContainer:servletScan時才加載。
void ServletContainer::stop()優雅地停止容器。
void ServletContainer::servletScan()從server/src/main/resources內部deploy目錄中加載所有.WAR文件。它也將加載.jsp文件,並立即將它們轉移到.class文件中。只有@WebServlet註釋的課程才會加載。一個警告是,我使用的.war文件具有我不確定與大多數其他.war文件相同的特定目錄結構。您可以在演示應用程序中查看war Gradle任務,以查看此結構的外觀。在開始()方法之前,該方法最多應一次調用。
void ServletContainer::servletScan(String url)與ServletContainer::servletScan()相同,但可以從給定的URL加載.war文件。
void ServletContainer::addRoute(Class<? extends HttpServlet> cls, String path)將servlet添加到容器中。 Servlet只能處理給定的URL(有關更多詳細信息,請參見常見問題解答)。
該服務器同時支持多個客戶端的處理。每個客戶端將通過ThreadPool的單獨線程處理。默認的池大小為4,可以在Main功能中更改。
該服務器將支持從Httpservlet繼承的任何類。可以使用ServletContainer::addRoute添加此類類。
這些類別的最重要功能是實施的。您可以使用(僅) PrintWriter ,設置標頭和響應狀態寫入客戶端。 httpservletrequest可以提取請求URL,http方法(獲取,發布,刪除,補丁),查詢參數和從身體的參數(在POST的情況下)。沖洗緩衝區或關閉HttpServletResponse後,數據將發送給客戶端。使用RequestDispatcher ,您可以將查詢發送到包括JSP Servlet在內的其他servlet。
有支持異步servlet。執行HttpServletRequest::startAsync之後,請求進入異步模式。使用此模式有兩種方法。任何代碼都將在某個時候執行AsyncContext::complete ,這將運行。您也可以使用AsyncConext::start(Runnable) ,在這裡您還需要在某個時候執行AsyncContext::complete 。後者與Java Servlet API兼容。它還支持超時( AsyncContext::setTimeout )和AsyncListener 。異步servlet是使用CompletableFuture實現的,因此它們將使用與同步客戶端使用的螺紋池。
如果該應用程序已被拉到.war並移至deploy文件夾,則將自動加載它。用@WebServlet註釋並從HttpServlet繼承的所有類都將添加到Servlet容器中,並將在localhost:port/warName/servletUrl上找到。每個這樣的類都必須在值屬性中的@WebServlet註釋中指定一個servletUrl 。如果應用程序使用JSP,它將自動編譯為.class並加載。可以加載許多應用程序,但是,當兩個應用程序中使用相同的類名稱時,我不知道會發生什麼。
服務器可以將.jsp文件轉換為.class。 JSP中幾乎所有語法都有支持。其中包括:
<%@ page import/include=... %><% ... %><%! ... %><%= ... %><%-- %>${...} ${}語法部分支持表達語言。可以執行簡單的算術操作,並將表單實例的任何表達式instance.property1.property2轉換為request.getAttribute("instance").getProperty1().getProperty2() 。在<% ... %>中也有out.println(...) ,直接寫給客戶。可以使用RequestDispatcher::forward顯示JSP,也可以直接在localhost:8000/warName/jspFileName.jsp顯示。可在localhost:8000/library/jsp上獲得示例JSP操作。請記住,我實施了解析自己,因此怪異的代碼格式/語法可能會破壞它。
為了演示servlet容器,我實施了一個簡單的應用程序,該應用程序模擬了書籍數據庫。可以使用HTML表格添加,刪除和更新書籍。所有書也可以在HTML表中查看。前端使用JSP完全開發。應用程序被劃入庫中。 war gradle任務,在啟動時移動到服務器上deploy並加載到服務器。
端點:
有30多次測試可以檢查大多數功能和演示應用程序。由於常見問題解答中解釋的問題,首選從Intellij運行它們。或者,它們可以使用:
./gradlew test
該項目是大學課程的一部分,除Junit以外,我不允許使用任何第三方圖書館。
插座端口已經在使用中存在問題。我從來沒有解決過它來修復它,但是大多數時候它可以從Intellij運行。
目前,Servlet應處理給定URL的分辨率非常原始。基本上,給定url服務器尋找servletUrl是url的前綴的Servlet。如果有許多前綴最長的一個。這意味著,如果servletUrl等於/app/home等於/app/home/nonexisting/ , /app/home/12345
歡迎任何人為這個項目做出貢獻。我創建了一些良好的問題,因此請隨時檢查一下:)