以下转载和参考自Servlet进阶 - 廖雪峰的官方网站。
Tomcat、Jetty、GlassFish等Web服务器用来提供TCP连接处理、HTTP协议解析处理、Servlet容器等功能。HTTP协议解析处理包括识别正确和错误的HTTP请求(开始行、Header)、处理开始行和各个请求头等。Servlet是在Web服务器中运行的,它用来处理HTTP数据,比如将HTTP数据包装成Servlet对象后在Servlet中对HTTP数据进行处理,Servlet由Web服务器创建其实例运行,所以Web服务器也称为Servlet容器。使用Servlet可以使我们很方便的处理HTTP数据,可以将Servlet代码(实现对HTTP数据的处理)打成包以后提供给Web服务器使用。Servlet属于JavaEE中的组件。
1、简单Servlet实现
①、下面是一个简单的Servlet的代码实现,使用Servlet需要引入其Jar包,可以通过maven来下载相关的依赖,如下所示,需要注意的是Servlet的打包类型不是jar而是war(Java Web Application Archive),所以<packaging>为war,而且Servlet的依赖库是在编译的时候使用,当编译成war包给Web服务器使用的时候,因为Web服务器中包含Servlet的Jar包,所以将<scope>指定为provided表示仅编译时使用该包,但不会打包到.war文件中。我们将生产的包名设置为hello。
@WebServlet注解中可以对servlet进行配置,比如urlPatterns用来设置当前servlet的映射url(说明servlet能处理的路径),比如将映射url设置成"/test"的话,那么浏览器的请求应该是应用上下文(war包名)的全路径 + 映射url,即http://192.168.1.32:8080/warName/test,如果将映射url设置成"/"的话,那么浏览器的请求应该是http://192.168.1.32:8080/warName/(如果输入http://192.168.1.32:8080/warName的话自动跳转到http://192.168.1.32:8080/warName/)。一个Servlet可以映射多个url,如 @WebServlet(urlPatterns = { "/test", "/favicon.ico", "/static/*" }) 。
一个Servlet类在服务器中只有一个实例,但对于每个HTTP请求,Web服务器会使用多线程执行请求,即Servlet容器会使用多线程来执行doGet()或doPost()方法(因为会有多个浏览器并发浏览数据,为了提升处理多个连接请求的效率,Servlet容器会使用多线程来处理客户端的请求),所以在这两个方法中使用HelloServlet类的成员的话需要注意线程安全。参数HttpServletRequest和HttpServletResponse实例是由Servlet容器传入的局部变量,它们只能被当前线程访问,不存在多个线程访问的问题。
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(urlPatterns = "/") // WebServlet注解表示这是一个Servlet,并映射到地址/:
public class HelloServlet extends HttpServlet { //一个Servlet总是继承自HttpServlet
@Override
protected void doGet( //覆写doGet()或doPost()方法来处理HTTP请求,浏览器中输入URL默认为GET请求
HttpServletRequest req/*收到的HTTP请求*/,
HttpServletResponse resp/*要回复的HTTP响应*/)
throws ServletException, IOException {
resp.setContentType("text/html"); // 设置响应类型:
String name = req.getParameter("name"); //获取参数
if (name == null) {
name = "world";
}
PrintWriter pw = resp.getWriter(); // 获取实体数据输出流
pw.write("<h1>Hello, " + name + "!</h1>"); // 写入响应实体数据
pw.flush(); //flush强制输出
}
}
②、下面还要在项目目录下的 src/main/webapp/WEB-INF/目录下创建一个web.xml描述文件,其内容如下所示,然后就可以通过mvn clean package命令来生成war文件。类似@WebServlet注解,web.xml也用来对servlet进行配置。
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
③、安装Tomcat:先下载安装JDK或JRE,然后Windows下的话下载对应的Tomcat版本,解压到指定目录,然后按照readme和RUNNING中的说明设置环境变量CATALINA_HOME为Tomcat所在目录,设置环境变量JAVA_HOME为JDK目录(或者JRE_HOME设置为JRE目录)。也可以下载Tomcat的安装版本,这样就不用我们自己额外配置环境变量。
把hello.war放到Tomcat的webapps目录下,然后进入到Tomcat的bin目录,执行startup.sh(linux)或startup.bat(windows)来启动Tomcat服务器,也可以执行bin下的tomcat.exe来启动服务。这样Tomcat就开启了我们的Web服务,在浏览器输入http://192.168.1.32:8080/hello/ 就可以看到浏览器输出了“Hello, world!”。关闭Tomcat的话执行shutdown.sh或者shutdown.bat。以后新生成了.war包的话可以直接进行替换,不需要关闭Tomcat。
一个Web服务器允许同时运行多个Web App:如果我们有另一个Servlet包的话,比如world.war,那么同样将其放到webapps目录下,然后在浏览器输入http://192.168.1.32:8080/world/的话就可以看到world包对应的网页内容。Tomcat会定时检查webapps目录下的war包文件,为其创建一个目录来存放相关的数据,可以看到webapps目录下默认有一个ROOT目录,当我们在浏览器下输入http://192.168.1.32:8080/的话使用的就是该目录下相关的数据。我们可以删除ROOT目录(需要关闭Tomcat?),设置我们生成的war包名为ROOT,这样在浏览器下只需要输入http://192.168.1.32:8080/的话浏览器显示的就是Hello World!,即不用再输入war包名了 。Servlet容器会给每个Servlet类创建唯一实例来区分各个Web App,如下所示:
Servlet容器为每个Web App自动创建一个唯一的ServletContext
实例,这个实例就代表了Web应用程序本身。ServletContext的getContextPath()方法可以获得上下文名称,即war包名,如"hello",ROOT包的话该值为空。ServletContext的getRealPath()可以获得对应请求资源所在服务器上的目录:
@WebServlet(urlPatterns = {"/test", "/favicon.ico"})
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
//比如请求的是http://localhost/hello/test的话(hello为war包名),则strContextPath为"/hello"
ServletContext ctx = req.getServletContext();
String strContextPath = ctx.getContextPath();
//比如请求的是http://localhost/hello/favicon.ico(hello为war包名)
// 则strFileName为"/favicon.ico",filePath为war包目录路径+favicon.ico,如"D:\Program\apache-tomcat-9.0.60\webapps\hello\favicon.ico"
String strFileName = req.getRequestURI().substring(strContextPath.length());
String filePath = ctx.getRealPath(strFileName);
if(filePath != null){
Path path = Paths.get(filePath);
if (!path.toFile().isFile()) { // 文件不存在
//...
}
}
}
}
Windows下启动Tomcat后的命令行中文输出可能会乱码,解决方法为修改Tomcat 目录下的 conf 中的 logging.properties:
java.util.logging.ConsoleHandler.encoding = GBK
2、调试Servlet
因为Tomcat实际上就是个Java程序,所以我们可以通过添加其Jar包到项目中,然后在项目中开启Tomcat服务,这样就可以在项目中调试我们的Servlet代码。如下所示,我们通过修改原来的pom.xml来添加Tomcat的Jar包,因为Tomcat中自动引入了Servlet,所以就不用添加Servlet依赖,还要设置项目使用的JDK版本和使用的字符编码,如果Tomcat中这些设置与我们的不同的话就会报错。可以看到Tomcat依赖的scope被设置成了provided,表示仅编译时使用,运行时不使用,如果使用默认的compile的话生成的war包就会很大。
然后我们新增一个main方法来启动Tomcat并使用我们的HelloServlet:
import org.apache.catalina.Context;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.webresources.DirResourceSet;
import org.apache.catalina.webresources.StandardRoot;
import java.io.File;
public class Main {
public static void main(String[] args) throws Exception {
// 启动Tomcat:
Tomcat tomcat = new Tomcat();
tomcat.setPort(Integer.getInteger("port", 8080));
tomcat.getConnector();
// 创建webapp:
Context ctx = tomcat.addWebapp("", new File("src/main/webapp").getAbsolutePath());
WebResourceRoot resources = new StandardRoot(ctx);
DirResourceSet ds = new DirResourceSet(resources,
"/WEB-INF/classes",
new File("target/classes").getAbsolutePath(),
"/");
resources.addPreResources(ds);
ctx.setResources(resources);
tomcat.start();
tomcat.getServer().await();
}
}
当我们运行程序的时候需要注意,因为引入的Tomcat的依赖为provided,所以直接运行会报错找不到Tomcat相关类。所以我们应该让程序运行的时候将Tomcat相关依赖包添加到classpath,可以通过以下实现:在 工具栏-Run-Edit Configurations-Application-Main-Modify options-Use classpath of module,然后选择我们的项目名,勾选Include dependencies with "Provided" scope。这样的设置对于生成的war包大小无影响。
经我测试,上面自定义Tomcat的代码实际上是只启动了Root webapp,因为输入http://localhost:8080/也能访问数据。SpringBoot也支持在main()方法中一行代码直接启动Tomcat,并且还能方便地更换成Jetty等其他服务器,它的启动方式和我们介绍的是基本一样的。
3、HttpServletRequest和HttpServletResponse
如下为获得请求体body内容的两种方法,一种是使用HttpServletRequest的getInputStrem()获得,一种是在spring使用@RequestBody注解将body中的JSON数据直接转换为对象:
@PostMapping("/test")
@ResponseBody
public void test(HttpServletRequest request)throws IOException{
StringBuilder requestBody = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream(),
StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
requestBody.append(line);
}
}
String bodyContent = requestBody.toString();
System.out.println("Request body: " + bodyContent);
}
public class LoginRequest {
private String userName;
private String password;
public String getUsername() { return userName; }
public void setUsername(String username) { this.userName = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
@PostMapping("/test")
@ResponseBody
public void test(@RequestBody LoginRequest requestBodyData)throws IOException{
String strName = requestBodyData.getUsername();
String strPassword = requestBodyData.getPassword();
}
HttpServletRequest的getRequestURL()获得的是整个请求的URL,如 "http://http://localhost:8080/"、"http://localhost:8080/favicon.ico",getRequestURI()获得的是不包括协议、主机名和端口号的请求路径,如"/"、"/test"、"hello/test"、"/favicon.ico"。
下面为向浏览器发送一个错误响应:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html"); // 设置响应类型:
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); //400错误响应
PrintWriter pw = resp.getWriter();
pw.write("<html><body><h1>");
pw.write("error");
pw.write("</h1></body></html>");
pw.flush();
}
4、重定向
当在浏览器输入http://localhost:8080/warName/hi,使用以下代码的话会向浏览器回复了一个重定向,重定向的映射地址设置为"/warName/hello",浏览器会收到如下的302响应,然后浏览器会去http://localhost:8080/warName/hello请求资源。
@WebServlet(urlPatterns = "/hi")
public class HiServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String redirectToUrl = "/warName/hello";
resp.sendRedirect(redirectToUrl); //重定向回应
}
}
@WebServlet(urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
pw.write("<h1>Hello, world!!!</h1>");
pw.flush();
}
}
HTTP/1.1 302 Found
Location: /hello
重定向有两种:一种是302响应,称为临时重定向,一种是301响应,称为永久重定向。两者的区别是,如果服务器发送301永久重定向响应,浏览器会缓存/hi到/hello这个重定向的关联,下次请求/hi的时候,浏览器就直接发送/hello请求了。以下为实现301永久重定向 :
resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301
resp.setHeader("Location", "/hello");
当我们使用"/"作为映射路径的话,在浏览器输入http://localhost:8080/hello/或者http://localhost:8080/hello都能访问到资源,但第二种方式实际上是被Tomcat重定向到http://localhost:8080/hello/的。如果使用"test"作为映射路径的话,在浏览器只能输入http://localhost:8080/hello/test,输入http://localhost:8080/hello/test/的话访问出错。
上面重定向的地址实际上是同一个Servlet下的不同映射地址,如果要重定向不同网址的话,使用下面的做法:
@WebServlet(urlPatterns = "/test")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
String site = new String("http://192.168.70.21:8080/hello/test2"); // 要重定向的新位置
resp.setStatus(resp.SC_MOVED_TEMPORARILY); //302
resp.setHeader("Location", site);
}
}
5、转发
如果想要实现一个war包下的不同映射地址之间的转换,最好使用转发而不是重定向,因为重定向多出了两个步骤:服务给浏览器发送302,然后浏览器再重新请求。如下所示ForwardServlet会将请求转发给路径为"/test"的Servlet,即HelloServlet:
@WebServlet(urlPatterns = "/forward")
public class ForwardServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
req.getRequestDispatcher("/test").forward(req, resp);
}
}
@WebServlet(urlPatterns = "/test")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
pw.write("<h1>Hello, world!!!</h1>");
pw.flush();
}
}
转发是服务器行为,重定向是客户端行为,所以转发后浏览器地址栏不变,而重定向变成转发后的资源。
6、Session和Cookie
如下所示,当浏览器首次请求http://localhost:8080/test的时候我们创建了两个Cookie:user和pwd。如下所示,当服务端首次调用req.getSession()的时候会创建名为JSESSIONID的Cookie并将其发送给浏览器,当浏览器再次请求的时候(非首次请求),会在请求头里将JSESSIONID和其它自定义的cookie发送给服务器,这个JSESSIONID就相当于是该用户与服务端会话的ID。HttpServletRequest的getSession()方法返回的是HttpSession对象,在Servlet中总是通过HttpSession访问当前Session,第一次调用req.getSession()时,Servlet容器就会自动创建一个名为JSESSIONID的Cookie。
@WebServlet(urlPatterns = "/test")
public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String user = (String) req.getSession().getAttribute("user"); //获得名为user的Cookie值
String pwd = (String) req.getSession().getAttribute("pwd"); //获得名为pwd的Cookie值
if (user == null) { //cookie不存在,创建
req.getSession().setAttribute("user", "leon"); //创建名为user的Cookie
req.getSession().setAttribute("pwd", "123456"); //创建名为pwd的Cookie
} else { //cookie存在,将其删除
req.getSession().removeAttribute("user");
req.getSession().removeAttribute("pwd");
}
}
}
上面是直接将用户名(用户ID)作为cookie值来进行身份认证,我们可以将用户名或用户ID加密后来作为cookie值(关于加密的具体可以参考https://www.cnblogs.com/milanleon/p/8929685.html中MD5、SHA-1部分),服务器将此cookie值与对应的用户保存起来(且服务器也需要进行有效期管理)以供下次浏览页面时的身份认证。cookie认证的具体可以参考http随笔这篇文章中用户身份认证-基于表单认证部分。
下面为在Servlet下对Session的支持,下面的四个Servlet在同一个war包hello中,当浏览http://localhost:8080/hello/signin会进入如下的登录页面,输入正确的密码后点击Sign In按钮的话会进入到SignInServlet的POST请求处理,在其中验证输入的密码,正确的话保存用户名到Session,并跳转到http://localhost:8080/hello/。下次用户浏览http://localhost:8080/hello/index页面的时候,浏览器自动带了Session保存的用户名,所以会显示一个登出的链接,其链接地址为http://localhost:8080/hello/signout,在这个登出的页面中将Session保存的用户名删除:
@WebServlet(urlPatterns = "/signin")
public class SignInServlet extends HttpServlet {
// GET请求
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
pw.write("<h1>Sign In</h1>");
pw.write("<form action=\"/signin\" method=\"post\">"); //创建表单,提交动作为向"/hello/signin"发送POST请求
pw.write("<p>Username: <input name=\"username\"></p>"); //输入栏username
pw.write("<p>Password: <input name=\"password\" type=\"password\"></p>"); //密码输入栏password
pw.write("<p><button type=\"submit\">Sign In</button> <a href=\"/\">Cancel</a></p>"); //提交按钮、到"/hello"的链接按钮
pw.write("</form>");
pw.flush();
}
// POST请求
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("username");
String password = req.getParameter("password");
String expectedPassword = "123456";
if (expectedPassword != null && expectedPassword.equals(password)) { // 登录成功
req.getSession().setAttribute("user", name); //将用户名放入当前HttpSession
resp.sendRedirect("/"); //重定向到"/hello"
} else { //登录失败
resp.sendError(HttpServletResponse.SC_FORBIDDEN); //返回403错误
}
}
}
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
pw.write("<h1>Hello, world!!!</h1>");
pw.flush();
}
}
@WebServlet(urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
resp.setHeader("X-Powered-By", "JavaEE Servlet");
String user = (String) req.getSession().getAttribute("user"); //从HttpSession获取当前用户名
if (user != null) { //已登录
PrintWriter pw = resp.getWriter();
pw.write("<p><a href=\"/signout\">Sign Out</a></p>"); //显示登出链接
pw.flush();
}else{ //未登录
resp.sendRedirect("/signin"); //跳转到"/signin"
}
}
}
@WebServlet(urlPatterns = "/signout")
public class SignOutServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().removeAttribute("user"); //从HttpSession移除用户名,下次发送的Session数据不再包含它
resp.sendRedirect("/signin"); //跳转到"/signin"
}
}
上面是通过HttpServletRequest来添加Cookie的,而且默认在关闭浏览器后Cookie会失效。我们也可以通过HttpServletResponse来创建Cookie,可以自定义Cookie的有效时间等,如下所示。如果想要删除浏览器上的某个cookie的话,可以添加同名的cookie到HttpServletResponse,并且通过setMaxAge设置其有效期为0。
@WebServlet(urlPatterns = "/test")
public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String strCookieValue = "";
String strResponseBody = "";
Cookie[] cookies = req.getCookies(); // 获取请求附带的所有Cookie
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("lang")) { //存在名为lang的cookie
strCookieValue = cookie.getValue(); // 获得lang Cookie的值:
strResponseBody = "<h1>has cookie lang</h1>";
break;
}
}
}
if (strCookieValue.isEmpty()) { //不存在名为lang的cookie
strResponseBody = "<h1>No cookie</h1>";
Cookie cookie = new Cookie("lang", "en"); //创建名为lang的cookie,值为"en"
cookie.setPath("/"); // 该Cookie生效的路径范围,如果是setPath("/user/"),那么浏览器只有在请求以/user/开头的路径时才会附加此Cookie
cookie.setMaxAge(10); // 该Cookie有效期: 86400秒=1天,不调用该方法进行设置的话关闭浏览器Cookie即失效
cookie.setSecure(true); //如果是HTTPS的话才需要该行
resp.addCookie(cookie); // 将该Cookie添加到响应
}
}
}
为了防止客户端的cookie信息被窃取使用,可以设置cookie的以下属性:
①、设置HttpOnly属性,这样就不允许JS脚本读取cookie信息,防止了跨站脚本攻击(XSS)。
②、设置secure属性,这样能够可以防止通过抓包工具来获得cookie值,因为设置了该属性后cookie只会在https中发送给服务端,https协议会加密http包中数据。
③、设置samesite属性为strict或lax,防止跨站请求伪造(CSRF)。浏览器在请求A网站时,只要浏览器存储了适用于A网站的Cookie,就会带上它发起请求。在网站B中,如果有一个按钮是向网站A发起请求,那么仍然会带上你在A网站的Cookie将请求发出去。比如你打开了银行网站并进行了登录,然后你去浏览一个论坛,当你在这个论坛发帖的时候,其背后代码逻辑会向银行发送转账或购买物品请求,因为这个请求携带了你登录银行网站的cookie信息,所以银行会以为转账或购物就是你发的请求,这就是跨站请求伪造CSRF。通过给cookie设置amesite属性,这样能够禁止跨站请求携带Cookie,从而确保了能够携带Cookie的请求一定是用户在浏览器自己的网站期间发出来的。
服务器将所有用户的Session都存储在内存中,如果遇到内存不足的情况,就会把部分不活动的Session序列化到磁盘上,所以放入的Cookie对象要小,比如是一个字符串或简单的结构体对象。
上面所说的Session问题,也可以单独整一个Session服务器用来专门存储Session相关信息,所有的服务从Session服务器里获得Session信息,如下图所示。关于反向代理的更多内容,可以参考 与http协作的web服务器 里的反向代理和负载均衡部分。
cookie和web storage:
localStorage用来在计算机上存储和读取数据,使用它可以存储大容量数据,除非是通过js方法删除,或者浏览器清除缓存,否则localStorage永远不会过期。
cookie用于浏览器向服务端请求数据时的信息传递,cookie数据大小有限制,cookie默认关闭浏览器清空,也可以设置其有效期使用之成为持久cooike。
sessionStorage用于本地存储一个会话中的数据,这些数据只有在同一个会话中的页面才能访问,并且当会话结束后,数据也随之销毁。所以sessionStorage仅仅是会话级别的存储,而不是一种持久化的本地存储。
7、再谈映射URL和ROOT包
前面说过映射url的作用,比如对于下面的IndexServlet,我们输入http://localhost:8080/warName/index的话才能访问到IndexServlet页面,而对于HelloServlet,输入http://localhost:8080/warName的话就能访问到HelloServlet页面(实际上会跳转到http://localhost:8080/warName/)。映射到 "/" 的Servlet相当于映射到了"/*",我们输入不存在的地址如http://localhost:8080/warName/none的话相当于输入了http://localhost:8080/warName,当然如果该地址存在的话则会进入对应页面,如http://localhost:8080/warName/index。
@WebServlet(urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//......
}
}
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//......
}
}
如果将war包名设置为ROOT的话,那么就不用再输入war包名了,如上面的的两个Servlet,访问其页面的话只需要输入http://localhost:8080/index 和htt