目录
2.1 HttpServletRequest 获取请求中传来的必要参数
2.1.1 获取 POST 请求中 application/x-www-form-urlencoded 格式的参数
2.1.2 获取 POST 请求中 application/json 格式的参数
1. HttpServlet
方法名称
| 调用时机 |
init | 只会在该 Servlet 类第一次被使用的时候调用到, 相当于是用来初始化 |
destroy
| 在 Servlet 对象被销毁的时候,才会调用到,相当于收尾操作。 |
service
| 每次收到请求 (无论是啥方法) 都会调用 service. 默认父类的 service 就会根据方法来调用 doGet, doPost, doPut....... |
doGet
|
收到
GET
请求的时候调用
(
由
service
方法调用
)
|
doPost |
收到
POST
请求的时候调用
(
由
service
方法调用
)
|
doPut/doDelete/doOptions/... |
收到其他请求的时候调用
(
由
service
方法调用
)
|
上述方法中, 最常用的还是 doXXX系列的, init, destroy, 这些其实很少用...
【问题】上一篇博客我们已经通过浏览器输入 URL 构造 GET 请求了, 那么 POST 和 其他请求如何构造 ??
如果使用 form 表单的话, 就只能构造 GET 和 POST 请求, 所以这里我们使用 ajax 来构造.
doXXX代码:
@WebServlet("/hello") // 注解, 约定 Http 请求的 URL 是什么 path, 才会调用到当前的这个 Servlet 类
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 打印在服务器的控制台上
System.out.println("doGet");
// 此处是吧 "hello world" 字符串作为响应报文的 body 了, 浏览器就会把这个 body 显示到压面中
resp.getWriter().write("doGet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost");
resp.getWriter().write("doPost");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPut");
resp.getWriter().write("dpPut");
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doDelete");
resp.getWriter().write("doDelete");
}
}
ajax代码:
<div class="one">
</div>
<button id="doGet">get</button>
<button id="doPost">post</button>
<button id="doPut">put</button>
<button id="doDelete">delete</button>
<!-- 加载 jQuery -->
<script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
<script>
// GET 请求
let doGetBtn = document.querySelector("#doGet");
doGetBtn.onclick = function() {
$.ajax({
type: 'get', // 请求类型
url: 'hello', // 相对路径
success: function(body) {
// console.log(body);
let div = document.querySelector(".one");
div.innerHTML = body;
}
});
}
// POST 请求
let doPostBtn = document.querySelector("#doPost");
doPostBtn.onclick = function() {
$.ajax({
type: 'post',
url: 'hello', // 相对路径
success: function(body) {
// console.log(body);
let div = document.querySelector(".one");
div.innerHTML = body;
}
});
}
// PUT 请求
let doPutBtn = document.querySelector("#doPut");
doPutBtn.onclick = function() {
$.ajax({
type: 'put',
url: 'hello',
success: function(body) {
let div = document.querySelector(".one");
div.innerHTML = body;
}
});
}
// DELETE 请求
let doDeleteBtn = document.querySelector("#doDelete");
doDeleteBtn.onclick = function() {
$.ajax({
type: 'delete',
url: 'hello',
success: function(body) {
let div = document.querySelector(".one");
div.innerHTML = body;
}
});
}
</script>
1. 我们把 html 文件放到 webapp 目录下, 此时该 html 也就相当于是 webapp 的一个部分了, 通过浏览器访问这个页面的时候, 就必须要带上这里的 Content Path.
2. 通过浏览器输入 URL 构造的请求得到的响应内容, 就是直接被浏览器渲染到界面上, 而通过 ajax 拿到的响应数据, 是由回调函数来处理的, 既可以直接渲染到界面, 也可以输出在控制台, 也可以不输出.
3. ajax 代码中的 url 写成 hello 是相对路径的写法, 指明我们要访问的是 /hello_servlet2/hello 这个路径, 而 ajax 代码是处在 hello_servlet2/test.html 这个路径下, 此时相当于两者在同级目录中, 所以直接写 hello 即可, 千万不要加 /.
运行结果:
可以通过 fiddler 抓包查看请求报文, 理解过程:
1.1 Postman 构造请求
上述通过 ajax 构造请求固然可以, 但是还是要写代码的, 还是不够方便,于是我们可以使用 Postman 来构造请求, 我们只需要输入 URL , 并指定请求类型, 然后点击发送, 就看可以看到服务器返回的响应了.
此处考察的就是什么时机, 调用什么方法.1. 首次使用, 先调用一次 init .2. 每次收到请求, 就会调用 service , 在 service 内部通过方法决定调用哪个 doXXX 方法.3. 销毁之前调用 destroy 方法.
2. HttpServletRequest
HttpServletRequest 表示一个 http 请求
http 请求中主要有
1. 方法
2. url
3. 版本号
4. 各种 header
5. body
这些信息都包含在 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 对象. |
通过上述这些方法可以获取到一个请求中的各个方面的信息.
代码示例:
@WebServlet("/showRequest")
public class ShowRequestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(req.getProtocol());
stringBuilder.append("<br>");
stringBuilder.append(req.getMethod());
stringBuilder.append("<br>");
stringBuilder.append(req.getRequestURI());
stringBuilder.append("<br>");
stringBuilder.append(req.getContextPath());
stringBuilder.append("<br>");
stringBuilder.append(req.getQueryString());
stringBuilder.append("<br>");
Enumeration<String> headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
stringBuilder.append(name + ": " + req.getHeader(name));
stringBuilder.append("<br>");
}
// 指定响应报文的数据格式
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write(stringBuilder.toString());
}
}
运行结果:
上述代码获取到请求信息, 返回响应之前, 要记得指定响应报文的数据格式 (和字符编码) , 否则返回的响应在控制台上就不会一行一行的显示, 而是挤在一堆, 可以通过 fiddler 抓包进行查看响应报文中是否有 Content-Type.
2.1 HttpServletRequest 获取请求中传来的必要参数
虽然我们前面通过 HttpServleRequest 的一些方法可以获取到一个请求中的各方面信息, 但更多情况下, 是希望通过 api 拿到请求中传来的必要参数.
假设客户端给服务器发送形如 /studentInfo?classId=123&studentId=101, 服务器这边就需要知道传过来的这两关键信息是啥, 然后通过班级 id, 学生 id 就可以确定班里的某一个人.
代码如下
@WebServlet("/studentInfo")
public class StudentInfoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 假设客户端发来的请求形如: / studentInfo?ClassId=10&studentId=20
// 通过 getParameter 方法来拿到这两个参数
String queryString = req.getQueryString();
System.out.println(queryString);
String classId = req.getParameter("classId");
String studentId = req.getParameter("studentId");
// 输出在服务器的控制台
System.out.println("classId: " + classId + " studentId: " + studentId);
// 输出到界面
resp.getWriter().write("classId: " + classId + " studentId: " + studentId);
}
}
在浏览器中构造 URL 请求时添加具体的 query string ,形如 : http://localhost:8080/hello_servlet2/studentInfo?classId=123&studentId=101
getParameter 的作用就是获取 query string 中的键值对, 根据 key 获取 value, 如果没有这个 key ,就返回 null.
前端除了通过 query string 来传参, 还有其他的传参方式, 例如通过 POST 请求的 body 传参到服务器, 两种数据格式 : 1.application/x-www-form-urlencoded 2.application/json.
2.1.1 获取 POST 请求中 application/x-www-form-urlencoded 格式的参数
服务器代码:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 正对这种格式, 获取到值得方式, 仍然是 getParameter
String classId = req.getParameter("classId");
String studentId = req.getParameter("studentId");
System.out.println("classId " + classId + " studentId: " + studentId);
resp.getWriter().write("classId " + classId + " studentId: " + studentId);
}
🍁Postman 构造 POST 请求
然后通过 fiddler 抓包进行查看请求报文:
本质上 urlencoded 的数据格式还是以键值对的方式进行构造...
🍁form 表单构造 POST 请求
<form action="studentInfo" method="post">
<input type="text" name="classId">
<input type="text" name="studentId">
<input type="submit" value="提交">
</form>
不仅要会 Postman 构造 POST 请求, 也要熟悉 form 表单构造出 POST 请求, 可以通过 fiddler 抓包进行查看:
2.1.2 获取 POST 请求中 application/json 格式的参数
🍁Postman 构造 POST 请求
显然我们解析不到这俩参数, 因为我们还是按照 urlencoded 的格式对数据进行解析的.
通过 fiddler 进行抓包查看:
Content-Type 确实是 json , body 中数据也是按照 json 的格式来组织, 现在主要的问题就是如何在 服务器端把 body 中的键值对给获取到....
但是 json 这种格式解析起来并不容易, 如果还是通过写代码按照字符串的方式获取到这里的键值对, 将会非常费劲!!, 因为 json 格式支持一些更复杂的结构, 它的格式之间还可以嵌套, value 的类型不仅仅可以是数字, 字符串, 还可以是 json, 还可以是数组, 形如下图:
所以, 更靠谱的处理方式, 就是使用 Spring 官方推荐使用的 Jackson 第三方库来解决. 在 maven 仓库中找到 Jackson Databind , 随便选中一个版本, 将其拷贝到 pom.xml 文件中的 <dependencies> 标签中进行下载即可.
获取 json 中参数的代码:
// 此处这个类的属性的命名,要和 json 的 key 保持一致
// 并且属性的访问权限是 public, 或者提供公开的 getter 方法
class Student {
public int classId;
public int studentId;
}
@WebServlet("/studentInfo")
public class StudentInfoServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理 JSON 格式的请求, 从请求 body 中进行读取, 并解析
ObjectMapper objectMapper = new ObjectMapper();
// 使用 readValue 来把 json 字符串转成 Java 对象
// 第一个参数是一个 String 或者 InputStream , 第二个参数是 json 转换的结果对应的 Java 类对象
Student student = objectMapper.readValue(req.getInputStream(), Student.class);
System.out.println(student.classId + ", " + student.studentId);
resp.getWriter().write(student.classId + ", " + student.studentId);
// objectMapper.writeValue(); Java 对象转成 json 字符串
}
}
通过上述代码发现 jackson 的使用方法还是非常简单的, 只需要掌握两个操作:
1. 把 json 格式的字符串转成 Java 对象
2. 把 Java 对象转成 json 字符串
这两个操作对应到 ObjectMapper 类提供的两个方法.
3. HttpServletRespnse
方法
| 描述 |
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 中写入二进制格式数据. |
3.1 代码案例1 - 设置状态码
@WebServlet("/status")
public class StatusCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setStatus(404);
}
}
部署程序, 在浏览器中通过 URL http://127.0.0.1:8080/hello_servlet2/status 访问, 可以看到如下界面:
通过 fiddler 抓包查看:
3.2 代码案例2 - 自动刷新
@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置响应报头
resp.setHeader("Refresh", "1");
// 观察时间戳的变化
resp.getWriter().write(System.currentTimeMillis() + "");
}
}
部署程序, 在浏览器中通过 URL http://127.0.0.1:8080/hello_servlet2/refresh 访问, 可以看到如下界面, 时间戳每秒钟都在变化..
通过 fiddler 抓包查看:
3.3 代码案例3 - 重定向
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.设置状态码为 3xx (典型: 302)
resp.setStatus(302);
// 2.给 header 里设置一个 Location ,表示跳转到的页面
resp.setHeader("Location", "https://www.sogou.com");
// 上面两行代码可以被下面一行代码代替
// resp.sendRedirect("https://www.sogou.com");
}
}
部署程序, 在浏览器中通过 URL http://127.0.0.1:8080/hello_servlet2/redirect 访问, 可以看到如下界面:
通过 fiddler 抓包查看: