筆者最近看了Spring,MyBatis,Maven和Git等技術(shù)的相關(guān)資料,想自己試著從頭搭建一個(gè)web服務(wù),以鞏固并加深學(xué)到的知識(shí),于是便有了這一系列文章。這一系列文章將主要記錄整個(gè)搭建過(guò)程,碰到的問(wèn)題以及解決方案。注意本系列文章并不是各類(lèi)技術(shù)的入門(mén)使用教程,需要讀者對(duì)技術(shù)本身有一定的了解。
開(kāi)發(fā)環(huán)境
- Tomcat:9.0.16
- Maven:3.6.0
- Git:2.20
- 操作系統(tǒng):windows10
項(xiàng)目需求
因?yàn)闀簳r(shí)沒(méi)想好要做什么服務(wù),那么就以最簡(jiǎn)單的登錄功能開(kāi)始吧。
創(chuàng)建項(xiàng)目
- 首先在github上創(chuàng)建了一個(gè)dizzydwarf項(xiàng)目
- 然后在eclipse中創(chuàng)建了一個(gè)Maven項(xiàng)目,并將兩者關(guān)聯(lián)起來(lái)
git remote add origin https://github.com/xuben/dizzydwarf.git
問(wèn)題1:Maven中創(chuàng)建web項(xiàng)目
這里筆者在創(chuàng)建Maven項(xiàng)目的目錄結(jié)構(gòu)時(shí)沒(méi)有選擇web項(xiàng)目的模板,因此需要自己將其改造成一個(gè)web項(xiàng)目。
- 首先需要告訴Maven這是一個(gè)web項(xiàng)目,最后需要打包成war包,因此修改pom.xml
<packaging>war</packaging>
- 然后在src/main目錄下新建如下目錄結(jié)構(gòu)
src
main
webapp
WEB-INF
web.xml
設(shè)計(jì)并實(shí)現(xiàn)
一開(kāi)始的設(shè)計(jì)思路是這樣的,用Tomcat作為web容器,寫(xiě)一個(gè)servlet,將所有的url請(qǐng)求都交給這個(gè)servlet處理,由servlet決定不同的url應(yīng)該調(diào)用哪個(gè)類(lèi)的哪個(gè)方法。
- 寫(xiě)了個(gè)DispatchServlet,繼承
HttpServlet,重寫(xiě)doGet和doPost,根據(jù)getRequestURI判斷請(qǐng)求的url,并把/login請(qǐng)求交給LoginAction處理 - 在web.xml中配置servlet
- 寫(xiě)了個(gè)index.html頁(yè)面,里面只有一個(gè)表單,提交的action為
/login -
mvn package生成一個(gè)war包 - 將這個(gè)war包放到Tomcat安裝目錄的webapps子目錄下并啟動(dòng)Tomcat
問(wèn)題2:Invalid <url-pattern>
Tomcat啟動(dòng)時(shí)提示Invalid <url-pattern>,筆者在web.xml中是這樣配置servlet-mapping的
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>*</url-pattern>
</servlet-mapping>
到底什么樣的url-pattern是合法的,具體規(guī)則可以參見(jiàn)servlet specification的Mapping Requests to Servlets部分。如果我們只是想要匹配所有url,可以把*改成/*
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
問(wèn)題3:Tomcat在命令行輸出中文亂碼
- 找到注冊(cè)表項(xiàng)
HKEY_CURRENT_USER\Console\Tomcat,如果沒(méi)有則新建。 - 新建CodePage,類(lèi)型為DWORD(32位)值,值為fde9,也就是65001
問(wèn)題4:web項(xiàng)目的地址
筆者在瀏覽器中輸入的訪問(wèn)地址是http://localhost:8080/index.html,結(jié)果跳到了Tomcat自帶的web項(xiàng)目。原來(lái)要訪問(wèn)這個(gè)新添加的web項(xiàng)目需要輸入相對(duì)于host的路徑。
這里webapps是host的根目錄,而新添加的war包被解壓到了webapps/dizzydwarf-0.0.1-SNAPSHOT目錄,因此訪問(wèn)的時(shí)候需要輸入http://localhost:8080/dizzydwarf-0.0.1-SNAPSHOT/index.html,雖然有夠麻煩的,不過(guò)暫時(shí)不是什么大問(wèn)題,畢竟我們可以在必要的時(shí)候?qū)⑵洳渴鸬絟ost根目錄。
問(wèn)題5:url-pattern匹配
輸入正確的地址,還是沒(méi)有看到表單。原來(lái)是筆者將servlet的url-pattern配置為了/*,因此就算是請(qǐng)求靜態(tài)的html頁(yè)面,也會(huì)被匹配到然后交給servlet處理。查了下網(wǎng)上的資料,似乎并沒(méi)有什么簡(jiǎn)單的方法可以在url-pattern中排除指定類(lèi)型的文件,無(wú)奈之下把url-pattern改回了/login
問(wèn)題5:表單action的絕對(duì)路徑和相對(duì)路徑問(wèn)題
再次輸入正確的地址,終于看到表單了。點(diǎn)擊登錄按鈕,返回404錯(cuò)誤,怎么回事?仔細(xì)一看瀏覽器的地址欄,發(fā)現(xiàn)地址欄變成了http://localhost:8080/login。
原來(lái)表單的action屬性的值為/login,而這個(gè)url地址也是相對(duì)于host根目錄,而不是當(dāng)前web應(yīng)用根目錄的地址,正確的值應(yīng)該是login。
問(wèn)題6:getRequestURI獲得的路徑
把a(bǔ)ction改成login,重新打包部署后提交表單還是一片空白,在servlet中輸出getRequestURI結(jié)果一看,發(fā)現(xiàn)url是/dizzydwarf-0.0.1-SNAPSHOT/login,而不是預(yù)期的/login。因?yàn)樵趕ervlet配置中已經(jīng)有url映射了,所以這里刪除這部分的代碼,改成直接處理請(qǐng)求。
至此,用一個(gè)servlet處理所有url請(qǐng)求,然后在servlet內(nèi)部分發(fā)的想法算是徹底破滅。但是事實(shí)上,關(guān)于url的映射通過(guò)web.xml方式配置也有其靈活性,只不過(guò)需要寫(xiě)多個(gè)servlet類(lèi)處理。
JSP中的解決方案
對(duì)于前端請(qǐng)求使用絕對(duì)路徑的問(wèn)題,在jsp中可以用${pageContext.request.contextPath}表示web根目錄的路徑
struts2的解決方案
簡(jiǎn)單看了下struts2的StrutsPrepareAndExecuteFilter類(lèi),關(guān)于uri的處理調(diào)用了RequestUtils.getUri方法。大致的思路是用到了getServletPath方法,這個(gè)方法返回的是與url-pattern匹配的部分,不包括通過(guò)*匹配的部分。結(jié)合getServletPath、getRequestURI和getPathInfo方法最終得到相對(duì)于web服務(wù)根目錄的uri路徑。
另外struts2中的請(qǐng)求都以.do結(jié)尾,因此可以很好的排除對(duì)靜態(tài)html文件的匹配。
結(jié)論
- 前端url請(qǐng)求的絕對(duì)路徑是相對(duì)于host根目錄,后端url-pattern則是相對(duì)于web服務(wù)的根目錄
- url-pattern無(wú)法簡(jiǎn)單排除所有的靜態(tài)html文件,同時(shí)servlet中也無(wú)法簡(jiǎn)單獲得相對(duì)于web服務(wù)根目錄的url請(qǐng)求路徑,因此用單個(gè)servlet處理所有請(qǐng)求并在內(nèi)部進(jìn)行分發(fā)的想法實(shí)現(xiàn)起來(lái)并不簡(jiǎn)單。解決辦法可以參考struts2。