笔者最近看了Spring,MyBatis,Maven和Git等技术的相关资料,想自己试着从头搭建一个web服务,以巩固并加深学到的知识,于是便有了这一系列文章。这一系列文章将主要记录整个搭建过程,碰到的问题以及解决方案。注意本系列文章并不是各类技术的入门使用教程,需要读者对技术本身有一定的了解。
开发环境
- Tomcat:9.0.16
- Maven:3.6.0
- Git:2.20
- 操作系统:windows10
项目需求
因为暂时没想好要做什么服务,那么就以最简单的登录功能开始吧。
创建项目
- 首先在github上创建了一个dizzydwarf项目
- 然后在eclipse中创建了一个Maven项目,并将两者关联起来
git remote add origin https://github.com/xuben/dizzydwarf.git
问题1:Maven中创建web项目
这里笔者在创建Maven项目的目录结构时没有选择web项目的模板,因此需要自己将其改造成一个web项目。
- 首先需要告诉Maven这是一个web项目,最后需要打包成war包,因此修改pom.xml
<packaging>war</packaging>
- 然后在src/main目录下新建如下目录结构
src
main
webapp
WEB-INF
web.xml
设计并实现
一开始的设计思路是这样的,用Tomcat作为web容器,写一个servlet,将所有的url请求都交给这个servlet处理,由servlet决定不同的url应该调用哪个类的哪个方法。
- 写了个DispatchServlet,继承
HttpServlet
,重写doGet
和doPost
,根据getRequestURI
判断请求的url,并把/login
请求交给LoginAction处理 - 在web.xml中配置servlet
- 写了个index.html页面,里面只有一个表单,提交的action为
/login
-
mvn package
生成一个war包 - 将这个war包放到Tomcat安装目录的webapps子目录下并启动Tomcat
问题2:Invalid <url-pattern>
Tomcat启动时提示Invalid <url-pattern>,笔者在web.xml中是这样配置servlet-mapping的
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>*</url-pattern>
</servlet-mapping>
到底什么样的url-pattern是合法的,具体规则可以参见servlet specification的Mapping Requests to Servlets部分。如果我们只是想要匹配所有url,可以把*
改成/*
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
问题3:Tomcat在命令行输出中文乱码
- 找到注册表项
HKEY_CURRENT_USER\Console\Tomcat
,如果没有则新建。 - 新建CodePage,类型为DWORD(32位)值,值为fde9,也就是65001
问题4:web项目的地址
笔者在浏览器中输入的访问地址是http://localhost:8080/index.html
,结果跳到了Tomcat自带的web项目。原来要访问这个新添加的web项目需要输入相对于host的路径。
这里webapps是host的根目录,而新添加的war包被解压到了webapps/dizzydwarf-0.0.1-SNAPSHOT目录,因此访问的时候需要输入http://localhost:8080/dizzydwarf-0.0.1-SNAPSHOT/index.html
,虽然有够麻烦的,不过暂时不是什么大问题,毕竟我们可以在必要的时候将其部署到host根目录。
问题5:url-pattern匹配
输入正确的地址,还是没有看到表单。原来是笔者将servlet的url-pattern配置为了/*
,因此就算是请求静态的html页面,也会被匹配到然后交给servlet处理。查了下网上的资料,似乎并没有什么简单的方法可以在url-pattern中排除指定类型的文件,无奈之下把url-pattern改回了/login
问题5:表单action的绝对路径和相对路径问题
再次输入正确的地址,终于看到表单了。点击登录按钮,返回404错误,怎么回事?仔细一看浏览器的地址栏,发现地址栏变成了http://localhost:8080/login
。
原来表单的action属性的值为/login
,而这个url地址也是相对于host根目录,而不是当前web应用根目录的地址,正确的值应该是login
。
问题6:getRequestURI获得的路径
把action改成login,重新打包部署后提交表单还是一片空白,在servlet中输出getRequestURI
结果一看,发现url是/dizzydwarf-0.0.1-SNAPSHOT/login
,而不是预期的/login
。因为在servlet配置中已经有url映射了,所以这里删除这部分的代码,改成直接处理请求。
至此,用一个servlet处理所有url请求,然后在servlet内部分发的想法算是彻底破灭。但是事实上,关于url的映射通过web.xml方式配置也有其灵活性,只不过需要写多个servlet类处理。
JSP中的解决方案
对于前端请求使用绝对路径的问题,在jsp中可以用${pageContext.request.contextPath}
表示web根目录的路径
struts2的解决方案
简单看了下struts2的StrutsPrepareAndExecuteFilter
类,关于uri的处理调用了RequestUtils.getUri
方法。大致的思路是用到了getServletPath
方法,这个方法返回的是与url-pattern匹配的部分,不包括通过*匹配的部分。结合getServletPath
、getRequestURI
和getPathInfo
方法最终得到相对于web服务根目录的uri路径。
另外struts2中的请求都以.do结尾,因此可以很好的排除对静态html文件的匹配。
结论
- 前端url请求的绝对路径是相对于host根目录,后端url-pattern则是相对于web服务的根目录
- url-pattern无法简单排除所有的静态html文件,同时servlet中也无法简单获得相对于web服务根目录的url请求路径,因此用单个servlet处理所有请求并在内部进行分发的想法实现起来并不简单。解决办法可以参考struts2。