目录
tomcat的定位
Servlet 详解
HttpServlet
核心方法
Get请求
关于乱码的问题
HttpServletRequest
核心方法
代码案例1: 打印请求信息
代码案例2: 获取GET请求中的参数
代码案例3:获取POST请求中的参数
代码案例4:获取POST请求中的参数(2)
引入JSON库解析String字符串
HttpServletResponse
核心方法
代码案例1: 设置状态码
代码案例2: 重定向
创作不易, 多多支持
我们自己的实现是在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, 并重写其中的某些方法....
方法名称 | 调用时机 |
init | 在 HttpServlet 实例化之后被调用一次 |
destory | 在 HttpServlet 实例不再使用的时候调用一次 |
service | 收到 HTTP 请求的时候调用 |
doGet | 收到 GET 请求的时候调用(由 service 方法调用) |
doPost | 收到 POST 请求的时候调用(由 service 方法调用) |
doPut/doDelete/doOptions/... | 收到其他请求的时候调用(由 service 方法调用) |
我们实际开发的时候主要重写 doXXX 方法, 很少会重写 init / destory / service
这些方法的调用时机, 就称为 "Servlet 生命周期". (也就是描述了一个 Servlet 实例从生到死的过
程)
注意: HttpServlet 的实例只是在程序启动时创建一次. 而不是每次收到 HTTP 请求都重新创建实例
创建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 {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("doget!!");
}
}
创建 testMethod.html, 放到 webapp 目录中, 形如
注意 他是和WEB-INF并列的html文件:
test
然后重启tomcat
访问testMethod.html资源:
点击 发送get请求, 就可以在控制台上面看到响应内容
分析:
如果我们在代码中写入中文, 例如:
resp.getWriter().write("GET 响应");
在浏览器访问的时候, 会看到乱码的情况:
关于 "乱码":
中文的编码方式有很多种. 其中最常见的就是 utf-8 .
如果没有显式的指定编码方式, 则浏览器不能正确识别编码, 就会出现乱码的情况
此时通过抓包可以看到, 当加上了 resp.setContentType("text/html; charset=utf-8"); 代码之后,
响应中多了 Content-Type 字段, 内部指定了编码方式. 浏览器看到这个字段就能够正确解析中文了
Post 请求
创建一个类, 实现doPost方法, 然后在里面设置如下:
@WebServlet("/myPost")
public class MethodPost extends HttpServlet {
@Override
protected 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);
}
});
}
类似的方法还可以验证doPut, doDelete
当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 对象. |
在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 {
@Override
protected 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("
");
respBody.append("请求返回http方法和名称").append(req.getMethod());
respBody.append("
");
respBody.append("请求返回协议名知道http请求的第一行查询字符串中").append(req.getRequestURI());
respBody.append("
");
respBody.append("返回指示请求上下文的请求uri部分").append(req.getContextPath());
respBody.append("
");
respBody.append("返回URL中的查询字符串").append(req.getQueryString());
respBody.append("
");
respBody.append("headers:
");
Enumeration headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
respBody.append(headerName + " ");
respBody.append(req.getHeader(headerName));
respBody.append("
");
}
resp.getWriter().write(respBody.toString());
}
}
访问127.0.0.1:8080/myMaven/ShowRequest
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 给服务器传递了好几个参数, 例如:
服务器就可以通过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 {
@Override
protected 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中传来的参数
POST请求的参数一般是通过body传递给服务器, body中的数据格式有很多种, 如果是采用form表单的形式, 仍然可以荣国getParameter来获取参数的值
在maven项目myMaven里面创建PostParameter类:
@WebServlet("/postParameter")
public class PostParameter extends HttpServlet {
@Override
protected 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文件:
testPost
重新部署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.8
userId=123&classId=456
Content-Type: application/x-www-form-urlencoded, 对应的 body 数据格式就形如
userId=123&classId=456
上面的代码案例3 是使用form表单的形式来传输数据给服务器的, 下面我们介绍使用json的格式来传递数据,
同样的我们在maven项目myMaven底下创建一个PostParameterJson类:
@WebServlet("/postParameterJson")
public class PostParameterJson extends HttpServlet {
@Override
protected 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:
testPostJson
重新部署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库进一步分析
引入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 {
@Override
protected 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;
}
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 之前, 否则设置可能失效
实现一个程序, 用户在浏览器通过参数指定要返回响应的状态码
首先在maven项目(myMaven) 中创建一个 StatusServlet类 :
@WebServlet("/statusServlet")
public class StatusServlet extends HttpServlet {
@Override
protected 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 的值, 就可以看到不同的响应结果
实现一个程序, 让浏览器每秒钟刷新一次, 并且显示当前的时间戳
创建一个AutoRefreshServlet类
@WebServlet("/autoRefreshServlet")
public class AutoRefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Refresh","1");
long timeStamp = new Date().getTime();
resp.getWriter().write("timstamp: " + timeStamp);
}
}
部署程序:
访问 :http://localhost:8080/myMaven/autoRefreshServlet
提升训练, 如何把他改成具体的日期时间,. 而不是时间戳
实现一个程序, 返回一个重定向的HTTP响应, 自动跳转另外一个页面.
创建RedirectServlet类
@WebServlet("/redirectServlet")
public class RedirectServlet extends HttpServlet {
@Override
protected 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