文章目录
Servlet
1.Servlet简单引入
目前的一个问题,只能请求静态资源,没法访问到java代码。Servlet可以让我们的请求能够访问到java代码了。
Serlet介绍:
Servlet是Server Applet的简称,称为服务端小程序,是JavaEE平台下的技术标准,基于Java语言编写的服务端程序。Web容器或应用服务器实现了Servlet标准所以Servlet需运行在Web容器或应用服务器中。Servlet主要功能在于能在服务器中执行并生成数据。
也就是说用Servlet必须得有Web容器,在Web容器中使用Servlet。
Servlet技术特点:
Servlet使用单进程多线程的方式运行
Servlet在应用程序中的位置:
静态资源和动态资源
静态资源:每次访问都不需要运算,直接就可以返回的资源,如HTML CSS JS 多媒体文件等等,每次访问获得的资源都是一样的。
动态资源:每次访问都需要都需要运算代码生成的资源,如Servlet JSP,每次访问获得的结果都可能是不一样的。
Servlet作为一种动态资源技术,是我们后续学习框架的基础。
前端工程师主要开发静态资源+jsp,Servlet属于后端技术了。
Servlet在程序中到底处于一个什么地位?
Servlet是可以接受HTTP请求并做出响应的一种技术,是JAVA语言编写的一种动态资源。
Servlet是前后端衔接的一种技术,不是所有的JAVA类都可以接收请求和做出响应,Servlet可以。
在MVC模式中,Servlet可以作为Controller层(控制层)的主要实现手段,主要用于和浏览器完成数据交互,控制交互逻辑。
2.第一个Servlet程序
**练习:**当浏览器请求时,在后台随机生成一个整数,如果是奇数,返回’happy new year~‘,如果是偶数,返回’happy birthday!’
Servlet开发流程
1.创建一个JAVAWEB项目,并在项目中开发一个自己的Servlet,继承HttpServlet类
2.在MyServlet类中重写service方法
3.在service方法中定义具体的功能代码
4.在web.xml中配置Servlet的映射路径
为了让idea在通过maven导入依赖的时候,不只导入jar包保证程序的运行(字节码文件),还要方便我们看源码,把源码下载下载,我们在maven中做如下配置:勾选Sources & Documentation & Annotations。配置好后需要重启idea,配置才会生效。
这里还需要注意一点,我们的maven的下载jar包的地址已经改成了公司的pixel.sankuai.com,所以导入依赖时需要连接vpn。
经过上面这两步操作,终于能看源码了qaq
可以清晰的看到前后对比:之前我们的外部依赖External Libraries中只能看到jar包,现在因为下载了源码,目录结构也都能看到了
HttpServlet中的service方法
/**
* Receives standard HTTP requests from the public
* <code>service</code> method and dispatches
* them to the <code>do</code><i>XXX</i> methods defined in
* this class. This method is an HTTP-specific version of the
* {@link javax.servlet.Servlet#service} method. There's no
* need to override this method.
*
* @param req the {@link HttpServletRequest} object that
* contains the request the client made of
* the servlet
*
* @param resp the {@link HttpServletResponse} object that
* contains the response the servlet returns
* to the client
*
* @throws IOException if an input or output error occurs
* while the servlet is handling the
* HTTP request
*
* @throws ServletException if the HTTP request
* cannot be handled
*
* @see javax.servlet.Servlet#service
*/
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
service方法中的两个参数:
1.HttpServletRequest:代表一个http请求
2.HttpServletResponse:代表一个http响应
练习代码
package com.example.demo;
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.*;
import java.util.Random;
@WebServlet(name = "helloServlet", value = "/hello-servlet")
// 这个类可以接收浏览器的请求,并做出运算和响应(这些运算和响应需要重写service方法)
public class HelloServlet extends HttpServlet {
private String message;
public void init() {
message = "Hello World!";
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
// Hello
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>" + message + "</h1>");
out.println("</body></html>");
}
/*
* 计算逻辑写到Service方法中
* req参数:HttpServletRequest的实例 tomcat给我们准备好的http请求的封装类 req代表一个http请求
* resp参数:HttpServletResponse的实例 tomcat给我们准备好的http响应的封装类 resp代表一个http响应
* */
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//我们的需求:当浏览器请求时,在后台随机生成一个整数,如果是奇数,返回'happy new year~',如果是偶数,返回'happy birthday!'
int rand = new Random().nextInt(10);
//后端处理逻辑
String message = rand%2==0 ? "happy birthday!" : "happy new year~";
//对浏览器做出响应
//获得PrintWriter对象(该打印流指向浏览器)
PrintWriter writer = resp.getWriter();
writer.write(message);
}
public void destroy() {
}
}
目前我们的java代码还是无法被访问到 接着我们还需要在web.xml中配置servlet实现类的映射路径
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--在这里指定java代码,告诉浏览器当我们访问什么页面资源的时候 自动访问什么java代码-->
<!--配置一个Servlet,向tomcat声明一个Servlet-->
<servlet>
<!--Servlet子实现类的别名-->
<servlet-name>HelloServlet_Servlet</servlet-name>
<!--对应的Servlet子实现类的相对路径-->
<servlet-class>com.example.demo.HelloServlet</servlet-class>
</servlet>
<!--给Servlet匹配一个请求的映射路径 当我们请求的url是什么时访问到该servlet-->
<servlet-mapping>
<servlet-name>HelloServlet_Servlet</servlet-name>
<url-pattern>/HelloServlet_Servlet_do</url-pattern>
</servlet-mapping>
</web-app>
注意,更新java代码后,虽然我们选择了自动更新class文件和resources,但是我们还是需要手动点一下deploy all,才能把更新后的class文件等部署到tomcat上。
此时我们就能成功的访问到我们自定义的Servlet子实现类了
其实如果我们在servlet向前端返回信息的时候,返回的是标签语言格式的字符串,浏览器也能解析并渲染,如下:
但是我们基本不会这么用
package com.example.demo;
import org.jetbrains.annotations.NotNull;
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.*;
import java.util.Random;
@WebServlet(name = "helloServlet", value = "/hello-servlet")
// 这个类可以接收浏览器的请求,并做出运算和响应(这些运算和响应需要重写service方法)
public class HelloServlet extends HttpServlet {
private String message;
public void init() {
message = "Hello World!";
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
// Hello
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>" + message + "</h1>");
out.println("</body></html>");
}
/*
* 计算逻辑写到Service方法中
* req参数:HttpServletRequest的实例 tomcat给我们准备好的http请求的封装类 req代表一个http请求
* resp参数:HttpServletResponse的实例 tomcat给我们准备好的http响应的封装类 resp代表一个http响应
* */
@Override
protected void service(HttpServletRequest req, @NotNull HttpServletResponse resp) throws ServletException, IOException {
//我们的需求:当浏览器请求时,在后台随机生成一个整数,如果是奇数,返回'happy new year~',如果是偶数,返回'happy birthday!'
int rand = new Random().nextInt(10);
//后端处理逻辑
String message = rand%2==0 ? "happy birthday!" : "happy new year~";
//对浏览器做出响应
//获得PrintWriter对象(该打印流指向浏览器)
String decoration="<h1>"+message+"</h1>";
PrintWriter writer = resp.getWriter();
writer.write(decoration);
}
public void destroy() {
}
}
3.配置欢迎页
web项目的欢迎页:当我们访问的url为 http://ip地址:http端口号/web项目的application context名 时的默认欢迎页。如:
tomcat/conf下有一个web.xml,它是作用于全部web项目的配置文件,而每个web项目里的web.xml是这个项目特有的项目配置文件,它的优先级高于全局。
在全局的web.xml中 配置了欢迎页的相对路径地址,它作用于全部部署到tomcat的web项目上:
接着我们在自己的web项目的web.xml中设置welcome-file-list属性,来覆盖掉全局web.xml中的该属性:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--在这里指定java代码,告诉浏览器当我们访问什么页面资源的时候 自动访问什么java代码-->
<!--配置一个Servlet,向tomcat声明一个Servlet-->
<servlet>
<!--Servlet子实现类的别名-->
<servlet-name>HelloServlet_Servlet</servlet-name>
<!--对应的Servlet子实现类的相对路径-->
<servlet-class>com.example.demo.HelloServlet</servlet-class>
</servlet>
<!--给Servlet匹配一个请求的映射路径 当我们请求的url是什么时访问到该servlet-->
<servlet-mapping>
<servlet-name>HelloServlet_Servlet</servlet-name>
<url-pattern>/HelloServlet_Servlet_do</url-pattern>
</servlet-mapping>
<!--当前web项目的欢迎页地址-->
<welcome-file-list>
<!--按照声明顺序依次找-->
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>firstpage.html</welcome-file>
</welcome-file-list>
</web-app>
然后我们删除默认有的index.jsp,重新部署,再次访问时,此时的欢迎页已经变成了我们设置的firstpage.html
4.Servlet简单案例开发
案例开发需求
准备一个登录页,可以输入用户名和密码
输入完毕后向后台Servlet提交用户名和密码
Servlet接收到用户名和密码之后,校验是否正确
如果正确响应Success,如果不正确响应Fail
开发过程:1.开发登陆页 2.开发后台Servlet 3.运行测试
1.开发登陆页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--用户名/密码/提交-->
<!--action中写servlet的请求路径loginServlet.do,代表我们这个form表单的信息往这个路径(我们的servlet)中提交-->
<form method="get" action="loginServlet.do">
<table width="300px" cellpadding="0px" cellspacing="0px" border="1px" style="margin: 0px auto">
<tr>
<td>用户名</td>
<td>
<input type="text" name="username"/>
</td>
</tr>
<tr>
<td>密码</td>
<td>
<input type="password" name="pw">
</td>
</tr>
<tr align="center">
<td colspan="2">
<input type="submit" value="登陆">
</td>
</tr>
</table>
</form>
</body>
</html>
**2.开发后台Servlet **
首先要创建这个web app的artifact并部署到tomcat上。
注意:如果我们新建模块,发现该模块下没有web目录的话 参考这个-> 手动添加webapp目录 这点很重要,没有一个web目录的模块/项目不会被视为是一个web项目
2.1 修改web.xml 设置login.html为默认欢迎页,并配置我们Servlet实现类的映射
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>loginServlet</servlet-name>
<servlet-class>com.example.test.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>loginServlet</servlet-name>
<url-pattern>/loginServlet.do</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
</web-app>
我们从下图中可以看到,在我们进行了上述设置后,在输入用户名&密码并点击提交信息后。确实这个http请求是向/Te st_war_exploded/loginServlet.do请求的(它就是LoginServlet的映射)。并且携带了参数username=123&pw=123(?后携带参数,参数间的分隔符是&)整个http请求就是一个HttpServletRequest对象
ps:get方式请求,参数会直接带到url路径后。post方式请求,参数会放到请求体里,因此更加安全
2.2 在Servlet子实现类中实现逻辑
package com.example.test;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class LoginServlet extends HttpServlet {
private String message;
public void init() {
message = "Hello World!";
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
// Hello
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>" + message + "</h1>");
out.println("</body></html>");
}
//重写service方法,实现逻辑
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("login servlet invoked");
String message = null;
//TODO 1.获取请求中的数据
//整个http请求就是一个HttpServletRequest对象
String username = req.getParameter("username"); // 这里的username,pwd是前端html中设置的
String password = req.getParameter("pw");
//TODO 2.判断数据 这里只简单模拟
if("zy".equals(username) && "123456".equals(password)){
message = "success";
} else {
message = "fail";
}
//TODO 3.做出响应
resp.getWriter().write(message);
}
public void destroy() {
}
}
3.测试效果
1.输入错误用户名/密码
从图2的login servlet invoked被成功打印可以看出确实调用了我们的LoginServlet对象
2.输入正确用户名&密码
5.Request获取请求行和请求头
接下来我们抽丝剥茧的讲一下HttpServletRequest和HttpServletResponse
一次请求tomcat帮我们做的事情
其实应该就是用的反射+动态代理+多态,HttpServlet loginservlet = Class.forName(“com.example.LoginServlet”).newInstance();
然后loginservlet.service()就是我们重写的service方法
一个http请求中包含请求头、请求行、请求体。
比如我们请求一下www.baidu.com。
ps:get方式提交的请求数据通过地址栏提交,没有请求体;post方式提交的请求数据单独放在请求体中(更安全);在我们【检查】的一个http请求的载荷里,可以看到这个请求携带的数据
-- 请求行
GET / HTTP/1.1
-- 请求头(kv键值对)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
Cookie: BAIDUID=3C1A52D434C656FE6E087270CB5FC5D5:FG=1; BIDUPSID=3C1A52D434C656FE6E087270CB5FC5D5; PSTM=1686808442; BAIDUID_BFESS=3C1A52D434C656FE6E087270CB5FC5D5:FG=1; ZFY=n7aLY9Jjs19VvldbYGPg1EL3:BKV21TJcplGl7d:AI3JY:C; BD_HOME=1; ZD_ENTRY=google; BD_CK_SAM=1; PSINO=2; H_PS_PSSID=39317_39354_39223_39348_39097_39198_39294_39261_39359_39241_39233_39291_39141_26350_39239_22158; delPer=0; shifen[2199026_2102]=1694442695; COOKIE_SESSION=2198797_0_0_0_4_0_0_0_0_0_0_0_2198804_0_11_0_1694442704_0_1694442693%7C2%230_0_1694442693%7C1; shifen[2199026_91638]=1694442707; shifen[1703776721_70670]=1694442710; BCLID=9989632549040733625; BCLID_BFESS=9989632549040733625; BDSFRCVID=63COJeCmHRSvIBofpgYehFrvoeKK0gOTHlln-OOW4nRRd9uVJeC6EG0Ptf8g0KubFTPRogKK0gOTH6KF_2uxOjjg8UtVJeC6EG0Ptf8g0M5; BDSFRCVID_BFESS=63COJeCmHRSvIBofpgYehFrvoeKK0gOTHlln-OOW4nRRd9uVJeC6EG0Ptf8g0KubFTPRogKK0gOTH6KF_2uxOjjg8UtVJeC6EG0Ptf8g0M5; H_BDCLCKID_SF=tbC8VCDKJKD3qbjkq45HMt00qxby26nNJD39aJ5y-J7nhhcxDP6tK--4-f7lbJ5yL2oaLfLbQpbZql5FQP-53R0h0PJkWp5l-aCqKl0MLPb5hj6gQJoDj4TyDMnMBMPjamOnaU5o3fAKftnOM46JehL3346-35543bRTLnLy5KJYMDFRDjuBDj5WDHRabK6aKC5bL6rJabC38K5MXU6q2bDeQN3ZbTJB5jRMh4jS3to-DUO8-Rj4Dp0vWq54WbbvLT7johRTWqR4s4OhjxonDh83KNLLKUQtKJcBoKJO5hvvhb6O3M7lMUKmDloOW-TB5bbPLUQF5l8-sq0x0bOte-bQXH_EJj-DJnuD_KtQ2t5VqKDkjjQWMt_h-fuX5-CsJJ7m2hcH0b61VxOCyf5obR8sWfo30JjqaKTiaKJjBMb1DbRkqfnc3T0H0R5bqRQpWDTm_q5TtUJMeCnTDMRh-xK70b5yKMnitIv9-pPKWhQrh459XP68bTkA5bjZKxtq3mkjbPbDfn028DKu-n5jHjQ-jHtD3f; H_BDCLCKID_SF_BFESS=tbC8VCDKJKD3qbjkq45HMt00qxby26nNJD39aJ5y-J7nhhcxDP6tK--4-f7lbJ5yL2oaLfLbQpbZql5FQP-53R0h0PJkWp5l-aCqKl0MLPb5hj6gQJoDj4TyDMnMBMPjamOnaU5o3fAKftnOM46JehL3346-35543bRTLnLy5KJYMDFRDjuBDj5WDHRabK6aKC5bL6rJabC38K5MXU6q2bDeQN3ZbTJB5jRMh4jS3to-DUO8-Rj4Dp0vWq54WbbvLT7johRTWqR4s4OhjxonDh83KNLLKUQtKJcBoKJO5hvvhb6O3M7lMUKmDloOW-TB5bbPLUQF5l8-sq0x0bOte-bQXH_EJj-DJnuD_KtQ2t5VqKDkjjQWMt_h-fuX5-CsJJ7m2hcH0b61VxOCyf5obR8sWfo30JjqaKTiaKJjBMb1DbRkqfnc3T0H0R5bqRQpWDTm_q5TtUJMeCnTDMRh-xK70b5yKMnitIv9-pPKWhQrh459XP68bTkA5bjZKxtq3mkjbPbDfn028DKu-n5jHjQ-jHtD3f; BD_UPN=123253; RT="z=1&dm=baidu.com&si=ca6fbc75-3262-4a56-b0f5-cebff21849b7&ss=lnmub169&sl=4&tt=5dy&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=ars6&ul=arvo&hd=arwb"
Host: www.baidu.com
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
sec-ch-ua: "Google Chrome";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
获取请求行/请求头/请求载荷相关数据的api
package com.example.test;/**
* @Author:zhoayu
* @Date:2023/10/17 23:17
* @Description:com.example.test
* @version:1.0
*/
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
/**
* @ClassName Test
* @Description //TODO
* @Author zhaoyu
* @Date 2023/10/17
*/
public class Test extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getRequestURL()); // 返回客户端浏览器发出请求时的完整URL
System.out.println(req.getRequestURI()); // 返回请求行中指定资源部分
System.out.println(req.getRemoteAddr()); // 返回发出请求的客户机的ip地址
System.out.println(req.getLocalAddr()); // 返回WEB服务器的IP地址
System.out.println(req.getLocalPort()); // 返回WEB服务器处理http协议的连接器所监听的端口号
System.out.println("主机名" + req.getLocalName());
System.out.println("客户端port" + req.getRemotePort());
System.out.println("当前项目部署名" + req.getContextPath()); // application context
System.out.println("协议名" + req.getScheme());
System.out.println("请求方式" + req.getMethod());
//根据请求头获得请求头属性对应的值(key不区分大小写 eg:Accept,accept都可以) 例:
System.out.println(req.getHeader("Accept"));
//获得全部的请求头名 Enumeration:早期的Iterator
Enumeration<String> headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()){
String headername = headerNames.nextElement();
System.out.println(headername + ":" + req.getHeader(headername));
}
//获取get/post携带的载荷(数据)直接用req.getParameter(数据对应的key)即可
//eg:String username = req.getParameter("username")
}
}
上面的结果类似这样(类似)
6.Request获取请求参数
ps:form表单中能提交的数据必须具有name属性 eg:<intput type="text" name="username">
(用输入框提交一个输入数据)
name属性的作用是让后台拿到数据的,必须要有。id属性是用来在前端获得数据的,基本对后端无作用
我们可以在提交数据的使用选择get或post方式提交(默认是get)。get方式提交的数据只能是文本(通过url拼接提交数据),由于地址的长度一般是有限制的,所以get方式能提交的数据量不大,并且类似账号密码等信息展示到url里明显不太好,提交的数据不安全。post方式将数据单独打包放到请求体中,请求体中可以放纯文本or二进制信息,所以post方式提交的数据可以是文本或者各种文件,提交的数据量理论上没有上限,比get方式能提交的数据多得多,而且因为post方式没有将数据拼接到url上,提交的数据相对安全(但是还是容易被抓包抓到解析,只是相对安全)。
ps:如果前端标签设置了readonly(只读),hidden(隐藏),那么即使我们不能输入数据或者看不到输入框,提交请求时还是会把这个标签value属性对应的值就行提交
如果标签设置了disabled(不可用),那么即使我们输入了数据,在提交请求时数据也不会被提交。
获取请求参数
package com.example.test;/**
* @Author:zhoayu
* @Date:2023/10/18 22:52
* @Description:com.example.test
* @version:1.0
*/
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
/**
* @ClassName MyServlet
* @Description //TODO
* @Author zhaoyu
* @Date 2023/10/18
*/
public class MyServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//单参数获取
//用HttpServletRequest对象来获取http请求中的数据
//如果前端没输入数据(有key没value),那么默认提交的是个空串;如果压根就没这个key,我们却req.getParameter("xxx"),那么获得的就是null
String username = req.getParameter("username");
System.out.println("username:" + username);
String password = req.getParameter("pwd");
System.out.println("password:" + password);
String gender = req.getParameter("gender");
System.out.println("gender:" + gender);
//多参数获取:
//对于多选框 比如hobby:篮球(1),足球(2),羽毛球(3),在提交数据的时候会这样提交:hobby=1&hobby=2&hobby=3
//我们需要用getParameterValues("name")来获得,返回一个String[]。如果一个hobby都不选,不会提交hobby=xxx,这里获得的就是一个null 而不是空串
String[] hobbies = req.getParameterValues("hobby");
System.out.println("hobbies:" + Arrays.toString(hobbies));
//textarea 文本域 这是一个双标签:<textarea name="introduce" value="aa">balabala</textarea> 页面中显示/实际请求提交的文字是双标签中间的文本balabala,value属性形同虚设
System.out.println("introduce:" + req.getParameter("introduce"));
//如果不知道前端页面设置的参数名字?
//req.getParameterNames()获取所有的参数名names[],然后遍历names[]获得values[](这里为了以防一个name对应多个value的情况,把所有的value都用获取数组的方式获取)
Enumeration<String> pnames = req.getParameterNames();
while (pnames.hasMoreElements()){
String pname = pnames.nextElement();
System.out.println(pname); // 获得name
String[] values = req.getParameterValues(pname);// 获得values[]数组
System.out.println(pname + ":" + Arrays.toString(values));
}
//用map获取所有name:values[]
Map<String, String[]> pmap = req.getParameterMap();// 获得map 输出格式为 <name:[value1,value2...],name2:[value1,value2...],...>
Set<String> keys = pmap.keySet();
for (String key : keys){
String[] valuearr = pmap.get(key);
System.out.println(Arrays.toString(valuearr));
}
}
}
7.Response设置响应
Tomcat会帮我们创建HttpServletRequest对象req和HttpServletResponse对象resp,对于req对象,我们可以调用Servlet提供的api来获取数据,然后我们可以将数据设置在resp对象中,由Tomcat负责把响应返回给客户端。
一个HTTP响应可以分为三部分:响应行、响应头和响应体
1.响应行
2.响应头
和请求头类似,都是一些kv键值对,主要是指定一些设置:
3.响应体
响应体是服务器返回的具体数据,常见的数据是HTML源代码。浏览器在接收到HTTP响应后,会根据响应正文的不同类型进行不同的处理。如果响应正文是DOC文档,那么浏览器会借助安装在本机的Word程序打开这份文档;如果响应正文是RAR压缩文件,那么浏览器会弹出一个下载窗口让用户下载解压软件;如果响应正文是HTML文档,那么浏览器会在自身的窗口中展示该文档。
Tomcat支持的MIME类型在web.xml配置文件中进行了声明。extension标签声明了这种文件的后缀名,mime-type标签声明了它对应的Content-type是什么。例:
excel也支持
word也支持
支持的文件类型特别多。
如果我们故意指定错的Content-type给浏览器,比如明明传的是个html文件我们却指定text/css,那么浏览器不会正确解析,会把数据原原本本的打印出来。
如果我们的浏览器请求的是诸如html,css等静态资源的话,我们也没法自己指定返回的文件类型。但是不用担心,tomcat会帮我们设置好响应头的Content-Type。如果是我们自己写的Servlet动态资源,返回什么数据都是自己设置的,那么Content-Type也是最好自己设置一下,否则浏览器获得不到正确的Content-Type 就会把数据原本的打印出来而不是解析渲染
package com.example.test;/**
* @Author:zhoayu
* @Date:2023/10/21 19:48
* @Description:com.example.test
* @version:1.0
*/
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @ClassName MyServletTestResponse
* @Description //TODO
* @Author zhaoyu
* @Date 2023/10/21
*/
public class MyServletTestResponse extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//向HttpServletResponse对象中设置信息
//1.响应行相关信息
//这里我们设置一下响应状态码,事实我们根本不用设置它 仅练习用
resp.setStatus(200); // 默认设置的也是200(Tomcat设置的)
resp.setStatus(405,"method not supported"); // 状态码+状态描述
//2.响应头相关信息
//resp.setHeader(key,value);
resp.setHeader("Date","2023-10");// 将kv键值对设置到响应头中
//自定义响应头
resp.setHeader("aaa","abc"); // 这个kv键值对虽然能设置到响应头中,但其实是无意义的,因为浏览器能解析的kv键值对必须是它认识的
//Content-Type:代表响应给浏览器的MIME类型,服务器通过MIME告诉浏览器响应数据是什么类型的,浏览器就知道了该如何进行解析
resp.setHeader("Content-Type","text/html"); // 响应的数据是html
//ps:Content-Length 响应携带的数据的字节数,浏览器会根据这个参数判断是否完整接收到了响应中的数据
//一个英文字母/数字占1个字节,汉字在GBK中占2个字节,在UTF-8中占3个字节,在UTF-16中占2个字节
//事实上因为Content-Type这个类型比较重要,有一个专门设置它的api
resp.setContentType("text/html");
//3.响应体相关信息
//通过resp对象获得一个指向浏览器的打印流
PrintWriter writer = resp.getWriter();
writer.write("<h1>this is a tag</h1>");//浏览器会按Content-Type指定的text/html的方式来解析这段文字,而不是单纯的打印出来
}
}
8.Servlet的继承结构
Servlet继承结构
HttpServlet
public abstract class HttpServlet extends GenericServlet
GenericServlet
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable
Servlet
Servlet接口中声明的方法,体现了一个Servlet对象的生命周期
HttpServlet中的service()方法(GenericServlet中没有实现Servlet中的service方法,但是它的子抽象类HttpServlet实现了)
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
// 获得请求方法
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
//getLastModified(req)默认返回-1
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
//HEADER_IFMODSINCE = "If-Modified-Since"
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
// HttpServletResponse.SC_NOT_MODIFIED=304
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
}
//METHOD_HEAD = "HEAD"
else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
}
//METHOD_POST = "POST"
else if (method.equals(METHOD_POST)) {
doPost(req, resp);
}
//METHOD_PUT = "PUT"
else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
}
//METHOD_DELETE = "DELETE"
else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
}
//METHOD_OPTIONS = "OPTIONS"
else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
}
//METHOD_TRACE = "TRACE"
else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
//HttpServletResponse.SC_NOT_IMPLEMENTED=501 501:意思是服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
getLastModified 返回-1
protected long getLastModified(HttpServletRequest req) {
return -1;
}
doGet 获得请求行的协议,如果是1.1结尾 报错HttpServletResponse.SC_METHOD_NOT_ALLOWED(405),否则返回HttpServletResponse.SC_BAD_REQUEST(400)
访问网页显示400,其含义是你访问的页面域名不存在或者请求错误。
出现405错误代码表示资源被禁止,对于请求所标识的资源,不允许使用请求行中所指定的方法。有可能是文件目录权限不够导致的。
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
//405
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
//400
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
304状态码
什么情况下会返回304状态码
客户端是怎么知道这些内容没有更新的呢?其实这并不是客户端的事情,而是你服务器的事情,大家都知道服务器可以设置缓存机制,这个功能是为了提高网站的访问速度,当你发出一个GET请求的时候服务器会从缓存中调用你要访问的内容,这个时候服务器就可以判断这个页面是不是更新过了,如果未更新过那么他会给你返回一个304状态码。
例如:一些搜索引擎是如何知道我们的网站是否有更新。判断网页是否发生变化最直接的方法是设置页面的某一处为监控区域,每次都抓取该部分区域的内容,然后与本地保存的或最 近一次抓取内容比较,如果有差异就表明网页发生了变化,才可以进行解析。这种方法比较稳妥,几乎可达到万无一失的效果。但是,这种方式在每次扫描时都要下载页面内容,并且要去截取监控区域的内容,最后还要进行字符串比较,整个过程比较耗时。其实在众多网页中,有一部分网站的网页内容是静态页面,如图片,html,js等,这些静态页面往往可能是服务器早已准备好的,用户访问时仅仅是下载而已。那么针对这种静态页面,就可以仅仅通过304状态码来判断,内容是否发生了变化。
doPost
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
//405
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
//400
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
doPut
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_put_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
doDelete
protected void doDelete(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_delete_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
doOptions
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
Method[] methods = getAllDeclaredMethods(this.getClass());
boolean ALLOW_GET = false;
boolean ALLOW_HEAD = false;
boolean ALLOW_POST = false;
boolean ALLOW_PUT = false;
boolean ALLOW_DELETE = false;
boolean ALLOW_TRACE = true;
boolean ALLOW_OPTIONS = true;
for (int i=0; i<methods.length; i++) {
String methodName = methods[i].getName();
if (methodName.equals("doGet")) {
ALLOW_GET = true;
ALLOW_HEAD = true;
} else if (methodName.equals("doPost")) {
ALLOW_POST = true;
} else if (methodName.equals("doPut")) {
ALLOW_PUT = true;
} else if (methodName.equals("doDelete")) {
ALLOW_DELETE = true;
}
}
// we know "allow" is not null as ALLOW_OPTIONS = true
// when this method is invoked
StringBuilder allow = new StringBuilder();
if (ALLOW_GET) {
allow.append(METHOD_GET);
}
if (ALLOW_HEAD) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_HEAD);
}
if (ALLOW_POST) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_POST);
}
if (ALLOW_PUT) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_PUT);
}
if (ALLOW_DELETE) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_DELETE);
}
if (ALLOW_TRACE) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_TRACE);
}
if (ALLOW_OPTIONS) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_OPTIONS);
}
resp.setHeader("Allow", allow.toString());
}
总结
如果我们没有继承HttpServlet类实现自己的service方法,就发起请求的话,整个流程就是这样的了。
首先前端输入url,后端根据xml文件中servlet-mapping和servlet-class的配置找到对应HttpServlet实现类字节码文件的路径准备调用service()方法,大概应该是通过反射和多态完成的方法调用 eg:
HttpServlet httpServlet = Class.forName("自定义HttpServlet实现类的字节码文件路径").newInstance()
完成对象的创建,然后调用
httpServlet.service(req,resp)
但是如果我们的httpServlet对象中没有service()方法的话,就会自动调用HttpServlet类中的service(HttpRequest req,HttpResponse resp)方法,如上我们可以看到,基本这个方法一旦调用,就是根据各种逻辑去调用doxxx方法,而doxxx方法就是报错,根据不同场景返回不同的错误信息和状态码。
ps:idea中代码回退/前进的快捷键是:command+option+ <-/->