目录
tomcat的定位
Servlet 详解
HttpServlet
核心方法
Get请求
关于乱码的问题
HttpServletRequest
核心方法
代码案例1: 打印请求信息
代码案例2: 获取GET请求中的参数
代码案例3:获取POST请求中的参数
代码案例4:获取POST请求中的参数(2)
引入JSON库解析String字符串
HttpServletResponse
核心方法
代码案例1: 设置状态码
代码案例2: 重定向
创作不易, 多多支持😘
tomcat的定位
我们自己的实现是在Tomcat基础上, 运行的
当浏览器给服务器发送请求的时候, tomcat 作为 http 服务器, 就可以接受到这个请求
http协议作为一个应用层协议, 需要底层协议栈来支持工作:
1. 接收请求:
用户在浏览器输入一个 URL, 此时浏览器就会构造一个 HTTP 请求.
这个 HTTP 请求会经过网络协议栈逐层进行 封装 成二进制的 bit 流, 最终通过物理层的硬件设备转
换成光信号/电信号传输出去.
这些承载信息的光信号/电信号通过互联网上的一系列网络设备, 最终到达目标主机(这个过程也需要
网络层和数据链路层参与).
服务器主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成
HTTP 请求. 并交给 Tomcat 进程进行处理(根据端口号确定进程)
Tomcat 通过 Socket 读取到这个请求(一个字符串), 并按照 HTTP 请求的格式来解析这个请求, 根据
请求中的 Context Path 确定一个 webapp, 再通过 Servlet Path 确定一个具体的 类. 再根据当前请
求的方法 (GET/POST/...), 决定调用这个类的 doGet 或者 doPost 等方法. 此时我们的代码中的
doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息.
2. 根据请求计算响应:
在我们的 doGet / doPost 方法中, 就执行到了我们自己的代码. 我们自己的代码会根据请求中的一
些信息, 来给 HttpServletResponse 对象设置一些属性. 例如状态码, header, body 等.
3. 返回响应:
我们的 doGet / doPost 执行完毕后, Tomcat 就会自动把 HttpServletResponse 这个我们刚设置好
的对象转换成一个符合 HTTP 协议的字符串, 通过 Socket 把这个响应发送出去.
此时响应数据在服务器的主机上通过网络协议栈层层 封装, 最终又得到一个二进制的 bit 流, 通过物
理层硬件设备转换成光信号/电信号传输出去.
这些承载信息的光信号/电信号通过互联网上的一系列网络设备, 最终到达浏览器所在的主机(这个过
程也需要网络层和数据链路层参与).
浏览器主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成
HTTP 响应, 并交给浏览器处理.
浏览器也通过 Socket 读到这个响应(一个字符串), 按照 HTTP 响应的格式来解析这个响应. 并且把
body 中的数据按照一定的格式显示在浏览器的界面上.
Servlet 详解
HttpServlet
我们在写servlet代码的时候, 首先第一步就是创建类, 继承自HttpServlet, 并重写其中的某些方法....
核心方法
方法名称 | 调用时机 |
init | 在 HttpServlet 实例化之后被调用一次 |
destory | 在 HttpServlet 实例不再使用的时候调用一次 |
service | 收到 HTTP 请求的时候调用 |
doGet | 收到 GET 请求的时候调用(由 service 方法调用) |
doPost | 收到 POST 请求的时候调用(由 service 方法调用) |
doPut/doDelete/doOptions/... | 收到其他请求的时候调用(由 service 方法调用) |
我们实际开发的时候主要重写 doXXX 方法, 很少会重写 init / destory / service
这些方法的调用时机, 就称为 "Servlet 生命周期". (也就是描述了一个 Servlet 实例从生到死的过
程)
注意: HttpServlet 的实例只是在程序启动时创建一次. 而不是每次收到 HTTP 请求都重新创建实例
Get请求
创建MethodGet.java, 创建doGet方法:
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;@WebServlet("/doGet")
public class MethodGet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("doget!!");}
}
创建 testMethod.html, 放到 webapp 目录中, 形如
注意 他是和WEB-INF并列的html文件:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>test</title>
</head><body>
<button onclick="sendGet()">发送get请求</button>
<script>function sendGet() {ajax({method: 'GET',url:'myGet',callback: function(body, status) {console.log(body);}});}function ajax(args) {var xhr = new XMLHttpRequest();xhr.onreadystatechange = function () {// 0: 请求未初始化// 1: 服务器连接已建立// 2: 请求已接收// 3: 请求处理中// 4: 请求已完成,且响应已就绪if (xhr.readyState == 4) {args.callback(xhr.responseText, xhr.status)}}xhr.open(args.method, args.url);if (args.contentType) {xhr.setRequestHeader('Content-type', args.contentType);}if (args.body) {xhr.send(args.body);} else {xhr.send();}}
</script>
</body></html>
然后重启tomcat
访问testMethod.html资源:
点击 发送get请求, 就可以在控制台上面看到响应内容
分析:
- 当浏览器中输入URL之后, 浏览器会给服务器发送了一个HTTP get请求, 来获取testMethod.html资源
- 访文到testMethod.html资源之后, 然后点击get 发送请求的按钮, 然后浏览器又通过ajax 给服务器发送了一个http get请求
- 此处应该注意这里的ajax里面的url中写的是 url: 'myGet', 这只是一个相对路径, 最终真是发送的请求URL路径为/myMaven/myGet
关于乱码的问题
如果我们在代码中写入中文, 例如:
resp.getWriter().write("GET 响应");
在浏览器访问的时候, 会看到乱码的情况:
关于 "乱码":
中文的编码方式有很多种. 其中最常见的就是 utf-8 .
如果没有显式的指定编码方式, 则浏览器不能正确识别编码, 就会出现乱码的情况
此时通过抓包可以看到, 当加上了 resp.setContentType("text/html; charset=utf-8"); 代码之后,
响应中多了 Content-Type 字段, 内部指定了编码方式. 浏览器看到这个字段就能够正确解析中文了
Post 请求
创建一个类, 实现doPost方法, 然后在里面设置如下:
@WebServlet("/myPost")
public class MethodPost extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html;charset=utf-8");resp.getWriter().write("Post 响应!!!");}
}
在testMethod.html中新增一个按钮, 和对应的点击事件处理函数
function sendPost() {ajax({method:'POST',url:'myPost',callback: function(body, status) {console.log(body);}});}
点击post, 然后查看控制台:
类似的方法还可以验证doPut, doDelete
HttpServletRequest
当Tomcat 通过Socket API 读取 HTTP请求 (字符串) , 并且 按照http协议的格式吧字符串解析成 HttpServletRequest 对象
核心方法
方法 | 描述 |
String getProtocol() | 返回请求协议的名称和版本。 |
String getMethod() | 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。 |
String getRequestURI() | 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请 求的 URL 的一部分。 |
String getContextPath() | 返回指示请求上下文的请求 URI 部分。 |
String getQueryString() | 返回包含在路径后的请求 URL 中的查询字符串。 |
Enumeration getParameterNames() | 返回一个 String 对象的枚举,包含在该请求中包含的参数的名 称。 |
String getParameter(String name) | 以字符串形式返回请求参数的值,或者如果参数不存在则返回 null。 |
String[] getParameterValues(String name) | 返回一个字符串对象的数组,包含所有给定的请求参数的值,如 果参数不存在则返回 null。 |
Enumeration getHeaderNames() | 返回一个枚举,包含在该请求中包含的所有的头名。 |
String getHeader(String name) | 以字符串形式返回指定的请求头的值。 |
String getCharacterEncoding() | 返回请求主体中使用的字符编码的名称。 |
String getContentType() | 返回请求主体的 MIME 类型,如果不知道类型则返回 null。 |
int getContentLength() | 以字节为单位返回请求主体的长度,并提供输入流,或者如果长 度未知则返回 -1。 |
InputStream getInputStream() | 用于读取请求的 body 内容. 返回一个 InputStream 对象. |
代码案例1: 打印请求信息
在maven项目myMaven中创建ShowRequest类
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.util.Enumeration;@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html;charset=utf8");StringBuilder respBody = new StringBuilder();respBody.append("请求协议的名称和版本:").append(req.getProtocol());respBody.append("<br>");respBody.append("请求返回http方法和名称").append(req.getMethod());respBody.append("<br>");respBody.append("请求返回协议名知道http请求的第一行查询字符串中").append(req.getRequestURI());respBody.append("<br>");respBody.append("返回指示请求上下文的请求uri部分").append(req.getContextPath());respBody.append("<br>");respBody.append("返回URL中的查询字符串").append(req.getQueryString());respBody.append("<br>");respBody.append("<h3>headers:</h3>");Enumeration<String> headerNames = req.getHeaderNames();while (headerNames.hasMoreElements()) {String headerName = headerNames.nextElement();respBody.append(headerName + " ");respBody.append(req.getHeader(headerName));respBody.append("<br>");}resp.getWriter().write(respBody.toString());}
}
访问127.0.0.1:8080/myMaven/ShowRequest
代码案例2: 获取GET请求中的参数
GET请求中的参数一般是通过QueryString的方式传递给服务器的, 例如,
https://search.bilibili.com/all?keyword=%E4%BD%A0%E5%A5%BD&from_source=webtop_search&spm_id_from=333.1007&search_source=5
此时浏览器通过queryString 给服务器传递了好几个参数, 例如:
- keyword = %E4%BD%A0%E5%A5%BD
- from_source = webtop_search
- spm_id_from = 333.1007
- search_source = 5
服务器就可以通过getParameter来获取到这些以&为分隔符的键值对
下面是代码案例:
在maven项目myMaven中创建GetParameter类:
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;@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html;charset=utf8");String parameter1Value = req.getParameter("parameter1");String parameter2Value = req.getParameter("parameter2");resp.getWriter().write("parameter1 :" + parameter1Value);resp.getWriter().write("parameter2 :" + parameter2Value);}
}
重新部署tomcat:
访问127.0.0.1:8080/myMaven/getParameter?parameter1=123¶meter2=456
此时服务器已收到了来自客户端的在queryString中传来的参数
代码案例3:获取POST请求中的参数
POST请求的参数一般是通过body传递给服务器, body中的数据格式有很多种, 如果是采用form表单的形式, 仍然可以荣国getParameter来获取参数的值
在maven项目myMaven里面创建PostParameter类:
@WebServlet("/postParameter")
public class PostParameter extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html;charset=utf8");String userId = req.getParameter("userId");String classId = req.getParameter("classId");resp.getWriter().write("userId :" + userId + " - - " + "classId :" + classId );}
}
在webapp中创建testPost.html文件:
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>testPost</title>
</head>
<body>
<form action="postParameter" method="POST"><input type="text" name="userId"><input type="text" name="classId"><input type="submit" value="提交">
</form>
</body>
</html>
重新部署tomcat, 然后访问:
http://localhost:8080/myMaven/testPost.html
显示如下:
输入:
发送此请求, 通过form表单, 发送给
postParameter, 然后调用相关的doPost方法, 显示:
通过fiddler抓包可以看到form表单的构造的body数据格式为:
POST http://127.0.0.1:8080/myMaven/postParameter HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 22
Cache-Control: max-age=0
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/91.0.4472.114 Safari/537.36
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,imag
e/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8080/ServletHelloWorld/testPost.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8userId=123&classId=456
Content-Type: application/x-www-form-urlencoded, 对应的 body 数据格式就形如
userId=123&classId=456
代码案例4:获取POST请求中的参数(2)
上面的代码案例3 是使用form表单的形式来传输数据给服务器的, 下面我们介绍使用json的格式来传递数据,
同样的我们在maven项目myMaven底下创建一个PostParameterJson类:
@WebServlet("/postParameterJson")
public class PostParameterJson extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json;charset=utf8");String body = readBody(req);resp.getWriter().write(body);}private String readBody(HttpServletRequest req) throws IOException {int contentLength = req.getContentLength();byte[] buffer = new byte[contentLength];InputStream inputStream = req.getInputStream();inputStream.read(buffer);return new String(buffer, StandardCharsets.UTF_8);}
}
创建testPsotJson.html:
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>testPostJson</title>
</head>
<body><button onclick="sendJson()">发送 JSON 格式 POST 请求</button><script>// 参数 args 是一个 JS 对象, 里面包含了以下属性// method: 请求方法// url: 请求路径// body: 请求的正文数据// contentType: 请求正文的格式// callback: 处理响应的回调函数, 有两个参数, 响应正文和响应的状态码function ajax(args) {var xhr = new XMLHttpRequest();xhr.onreadystatechange = function () {// 0: 请求未初始化// 1: 服务器连接已建立// 2: 请求已接收// 3: 请求处理中// 4: 请求已完成,且响应已就绪if (xhr.readyState == 4) {args.callback(xhr.responseText, xhr.status)}}xhr.open(args.method, args.url);if (args.contentType) {xhr.setRequestHeader('Content-type', args.contentType);}if (args.body) {xhr.send(args.body);} else {xhr.send();}}// 调用该函数function sendJson() {ajax({method: 'POST',url: 'postParameterJson',callback: function (body, status) {console.log(status);console.log(body);},contentType:'application/json;charset=utf-8',body: JSON.stringify({userId: 123, classId: 456 })});}</script><script !src=""></script>
</body>
</html>
重新部署tomcat然后访问: http://localhost:8080/myMaven/testPostJson.html
点击发送, 然后查看后台如下:
抓包可以看到留恋其给服务器发送的一个Post请求:
POST http://127.0.0.1:8080/ServletHelloWorld/postParameterJson HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 28
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/91.0.4472.114 Safari/537.36
Content-Type: application/json; charset=utf-8
Accept: */*
Origin: http://127.0.0.1:8080
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8080/ServletHelloWorld/testPostJson.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8{"userId":123,"classId":456}
其中最后面一行就是Json格式的数据
但是需要注意的是, 服务器拿到的这个json数据 依然是一个整体的String 类型, 如果你想哟啊获取到具体的值(123, 456). 还需要搭配JSON库进一步分析
引入JSON库解析String字符串
引入Jackson这个库, 对JSON进行解析:
首先, 访问中央仓库:
中央仓库https://mvnrepository.com/
选择版本:
复制maven地址:
粘贴到pom.xml中的dependencies标签中去:
然后刷新maven项目, 在idea右侧边栏中
如果下载速度过于慢, 我们可以在settings.xml中配置国内源
修改PostParameterJson中的代码, 修改好的如下:
import com.fasterxml.jackson.databind.ObjectMapper;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.InputStream;
import java.nio.charset.StandardCharsets;@WebServlet("/postParameterJson")
public class PostParameterJson extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// resp.setContentType("application/json;charset=utf8");
// String body = readBody(req);
// resp.getWriter().write(body);resp.setContentType("application/json;charset=utf8");String body = readBody(req);// 创建ObjectMapper对象, 这是jackson中的核心类ObjectMapper objectMapper = new ObjectMapper();// 通过readvalue 方吧body , 这个字符串转换成 JsonData 对象JsonData jsonData = objectMapper.readValue(body, JsonData.class);resp.getWriter().write("userId: " + jsonData.userId + ',' + "calssId:" + jsonData.classId);}private String readBody(HttpServletRequest req) throws IOException {int contentLength = req.getContentLength();byte[] buffer = new byte[contentLength];InputStream inputStream = req.getInputStream();inputStream.read(buffer);return new String(buffer, StandardCharsets.UTF_8);}
}// 创建一个新的类表示JSON数据, 属性的名字需要和json字符串中的key一样
class JsonData {public String userId;public String classId;
}
HttpServletResponse
HttpServlet中的doXXX方法目的就是根据请求计算得到响应,然后把相应的数据设置到HttpServletResponse对象中
然后tomcat 就会把这个HttpServletResponse对象按照HTTP协议的格式转化成一个字符串, 并且通过Socket写回给浏览器..
核心方法
方法 | 描述 |
void setStatus(int sc) | 为该响应设置状态码。 |
void setHeader(String name, String value) | 设置一个带有给定的名称和值的 header. 如果 name 已经存在, 则覆盖旧的值. |
void addHeader(String name, String value) | 添加一个带有给定的名称和值的 header. 如果 name 已经存在, 不覆盖旧的值, 并列添加新的键值对 |
void setContentType(String type) | 设置被发送到客户端的响应的内容类型。 |
void setCharacterEncoding(String charset) | 设置被发送到客户端的响应的字符编码(MIME 字符集)例如, UTF-8。 |
void sendRedirect(String location) | 使用指定的重定向位置 URL 发送临时重定向响应到客户端。 |
PrintWriter getWriter() | 用于往 body 中写入文本格式数据. |
OutputStream getOutputStream() | 用于往 body 中写入二进制格式数据. |
注意: 响应的对象是服务器要返回给浏览器的内容, 这里的重要信息都是程序员设置的, 因此上面的方法都是 着重于"写入"
注意: 对于状态码 / 响应的设置要放到 getWriter / getOutputStream 之前, 否则设置可能失效
代码案例1: 设置状态码
实现一个程序, 用户在浏览器通过参数指定要返回响应的状态码
首先在maven项目(myMaven) 中创建一个 StatusServlet类 :
@WebServlet("/statusServlet")
public class StatusServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String statusString = req.getParameter("status");if (statusString != null) {resp.setStatus(Integer.parseInt(statusString));}resp.getWriter().write("status:" + statusString);}
}
通过tomcat部署程序, 然后访问 http://localhost:8080/myMaven/statusServlet?status=200:
然后通过fiddler抓包可以看到
HTTP/1.1 200
Content-Length: 11
Date: Mon, 21 Jun 2021 08:05:37 GMT
Keep-Alive: timeout=20
Connection: keep-alive
status: 200
变换不同的 status 的值, 就可以看到不同的响应结果
代码案例2: 自动刷新
实现一个程序, 让浏览器每秒钟刷新一次, 并且显示当前的时间戳
创建一个AutoRefreshServlet类
@WebServlet("/autoRefreshServlet")
public class AutoRefreshServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setHeader("Refresh","1");long timeStamp = new Date().getTime();resp.getWriter().write("timstamp: " + timeStamp);}
}
- 通过HTTP响应报头中的Refresh字段, 可以控制浏览器 自动刷新的时机
- 通过Date 类的getTime方法, 可以获取到当前时刻的毫秒级时间戳
部署程序:
访问 :http://localhost:8080/myMaven/autoRefreshServlet
提升训练, 如何把他改成具体的日期时间,. 而不是时间戳
代码案例3 重定向
实现一个程序, 返回一个重定向的HTTP响应, 自动跳转另外一个页面.
创建RedirectServlet类
@WebServlet("/redirectServlet")
public class RedirectServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.sendRedirect("http://www.baidu.com");}
}
部署然后 直接访问:http://127.0.0.1:8080/myMaven/redirectServlet
抓包结果:
HTTP/1.1 302
Location: http://www.baidu.com
Content-Length: 0
Date: Mon, 21 Jun 2021 08:17:26 GMT
Keep-Alive: timeout=20
Connection: keep-alive