Servlet 之 request、respone 详解
Request
(一) 概述
request 是 Servlet.service() 方法的一个参数,在客户端发出每个请求时,服务器都会创建一个 request 对象,并把请求数据封装到 request 中,然后在调用 Servlet.service() 方法时传递给 service() 方法
HttpServletRequest 对象代表客户端的请求,当客户端通过 HTTP 协议访问服务器时,HTTP 请求头中的所有信息都封装在这个对象中,开发人员通过这个对象的方法,可以获得客户这些信息
(二) 常用方法
(1) 域方法
存储
// 用来存储一个对象,也可以称之为存储一个域属性
void setAttribute(String name, Object value)
Eg:servletContext.setAttribute(“xxx”,“XXX”)
// 在 ServletContext 中保存了一个域属性,域属性名称为 xxx,域属性的值为 XXX
获取
// 用来获取 ServletContext 中的数据
Object getAttribute(String name)
// 获取名为 xx 的域属性
Eg:String value = (String)servletContext.getAttribute(“xxx”);
// 获取所有域属性的名称;Enumeration getAttributeNames()
移除
// 用来移除 ServletContext 中的域属性
void removeAttribute(String name)
(2) 获取请求头数据
// 获取指定名称的请求头
String getHeader(String name)
// 获取所有请求头名称
Enumeration getHeaderNames()
// 获取值为 int 类型的请求头
int getIntHeader(String name)
(3) 获取请求相关的其他方法
// 获取请求体的字节数,GET 请求没有请求体,没有请求体返回 -1;int getContentLength()
/*
获取请求类型,如果请求是 GET,那么这个方法返回 null;如果是 POST 请求,那么默认
为 application/x-www-form-urlencoded,表示请求体内容使用了 URL 编码;*/
String getContentType()
// 返回请求方法,例如:GET/POST
String getMethod()
// 返回当前客户端浏览器的 Locale。java.util.Locale 表示国家和言语,这个东西在国际化中很有用;Locale getLocale()
/*
获取请求编码,如果没有 setCharacterEncoding(),那么返回 null,表示使用
ISO-8859- 1 编码;*/
String getCharacterEncoding()
/*
设置请求编码,只对请求体有效!注意,对于 GET 而言,没有请求体!!!所以此方法
只能对 POST 请求中的参数有效!*/
void setCharacterEncoding(String code)
// 返回上下文路径,例如:/Dmoe1
String getContextPath()
// 返回请求 URL 中的参数,例如:username=zhangSan
String getQueryString()
// 返回请求 URI 路径,例如:/Demo1/ServletDemo1
String getRequestURI()
/*
返回请求 URL 路径,例如:http://localhost/Demo1/ServletDemo1 即返回除了参数
以外的路径信息;*/
StringBuffer getRequestURL()
// 返回 Servlet 路径,例如:/ServletDemo1
String getServletPath()
// 返回当前客户端的 IP 地址
String getRemoteAddr()
// 返回当前客户端的主机名,但这个方法的实现还是获取 IP 地址
String getRemoteHost()
// 返回请求协议,例如:http
String getScheme()
// 返回主机名,例如:localhost
String getServerName()
// 返回服务器端口号,例如:8080
int getServerPort()
为了方便记忆,我们画一张图辅助记忆
(4) 案例练习
案例一:防盗链
顾名思义,就是说让用户只能在我们站内访问对应网页,而通过复制链接到地址栏以及贴链接到别人的网站进行盗链,则全部跳转回自己的链接页面
注意:有一部分响应代码未接触,可先照着敲,大致体会,后期回来看
先看一下效果:
这是我们所制定的网站,简单理解为官网
在官网中,正常点击链接访问,页面跳转正常
如果我们本地写一个页面,直接绕过 a.html 去访问 http://localhost:8080/web-001/ServletDemo3"
此时页面就会跳转回 a.html 中去,也就会回到了我们的官网,并且控制台输出:非法盗链,已经跳回原页面访问!
下面是具体的代码实现
- a.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/web-001/ServletDemo3"> 葫芦娃最新资源!!!</a>
</body>
</html>
- ServletDemo3
package cn.ideal.web.request;
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("/ServletDemo3")
public class RequestDemo1 extends HttpServlet {public RequestDemo1() {super();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取网页来源
String referer = req.getHeader("referer");
// 非法盗链
if (referer == null || !referer.contains("localhost:8080/web-001/a.html")) {System.out.println("非法盗链,已经跳回原页面访问!");
resp.sendRedirect("a.html");
return;
}
// 正常访问
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("是他就是他,是他就是他,我们的英雄葫芦娃!!!");
}
}
(三) request 获取请求参数
(1) GET/POST 请求的使用位置
- 浏览器地址栏直接输入:一定是 GET 请求
- 超链接:一定是 GET 请求
- 表单:可以是 GET,也可以是 POST
(2) GET/POST 请求的区别
A:GET 请求
- 请求参数会在浏览器的地址栏中显示,所以不安全
- 请求参数长度限制长度在 1K 之内
- GET 请求没有请求体,无法通过 request.setCharacterEncoding() 来设置参数的编码
B:POST 请求
- 请求参数不会显示浏览器的地址栏,相对安全
- 请求参数长度没有限制
(3) 获取请求参数的通用方式 (Get/Post 均可)
// 根据参数名称获取参数值
String getParameter(String name)
// 根据参数名称获取参数值的数组
String[] getParameterValues(String name)
// 获取所有请求的参数名称
Enumeration<String> getParameterNames()
// 获取所有参数的 map 集合
Map<String,String[]> getParameterMap()
(一)表单提交数据【通过 post 方式提交数据】
- b.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/web-001/RequestDemo3" method="post">
<table>
<tr>
<td> 用户名 </td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td> 密码 </td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td> 性别 </td>
<td><input type="radio" name="gender" value="男"> 男
<td><input type="radio" name="gender" value="女"> 女
</td>
</tr>
<tr>
<td> 爱好 </td>
<td>
<input type="checkbox" name="hobbies" value="游泳"> 游泳
<input type="checkbox" name="hobbies" value="跑步"> 跑步
<input type="checkbox" name="hobbies" value="网球"> 网球
</tr>
<input type="hidden" name="aaa" value="this is hidden text!">
<tr>
<td> 从哪来的?</td>
<td>
<select name="address">
<option value="广州"> 广州 </option>
<option value="北京"> 北京 </option>
<option value="深圳"> 深圳 </option>
</select>
</td>
</tr>
<tr>
<td> 补充说明 </td>
<td>
<textarea rows="2" cols="30" name="textarea"></textarea>
</td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
<td><input type="reset" value="重置"></td>
</tr>
</table>
</form>
</body>
</html>
- RequestDemo3
package cn.ideal.web.request;
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.Arrays;
@WebServlet("/RequestDemo3")
public class RequestDemo3 extends HttpServlet {public RequestDemo3() {super();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置 request 字符编码的格式
req.setCharacterEncoding("UTF-8");
// 通过 html 的 name 属性,获取到值
String username = req.getParameter("username");
String password = req.getParameter("password");
String gender = req.getParameter("gender");
// 复选框和下拉框有多个值,获取到多个值
String[] hobbies = req.getParameterValues("hobbies");
String[] address = req.getParameterValues("address");
// 获取到文本域的值
String description = req.getParameter("textarea");
// 得到隐藏域的值
String hiddenValue = req.getParameter("aaa");
System.out.println("username:" + username);
System.out.println("password:" + password);
System.out.println("gender:" + gender);
System.out.println("hobbies:" + Arrays.toString(hobbies));
System.out.println("address:" + Arrays.toString(address));
System.out.println("description:" + description);
System.out.println("hiddenValue:" + hiddenValue);
}
}
(二)超链接方式提交数据【通过 get 方式提交数据】
- c.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<hr/>
<form action="/web-001/RequestDemo4" method="get">
参数 1:<input type="text" name="p1"/><br/>
参数 2:<input type="text" name="p2"/><br/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
- RequestDemo4
// 省略包
@WebServlet("/RequestDemo4")
public class RequestDemo4 extends HttpServlet {public RequestDemo4() { }
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String v1 = req.getParameter("p1");
String v2 = req.getParameter("p2");
System.out.println("p1=" + v1);
System.out.println("p2=" + v2);
}
}
(四) 中文乱码问题
乱码问题主要针对 Tomcat8 以前的版本,Tomcat8 以上版本默认编码格式是 UTF-8,而不是 ISO 8859- 1 了
// 设置 request 字符编码的格式
request.setCharacterEncoding("UTF-8");
Tomcat 服务器的默认编码是 ISO 8859-1,而浏览器使用的是 UTF- 8 编码。浏览器的中文数据提交给服务器,Tomacat 以 ISO 8859- 1 编码对中文编码,当我在 Servlet 读取数据的时候自然拿到乱码。所以设置 request 的编码为 UTF-8,乱码就解决了
注意:按照上述例子中(使用 post 方式)乱码问题已经解决了,但是在 get 方式中尝试仍然是乱码。在此我们需要了解 post 方法是怎么进行参数传递的。
当我们点击提交按钮的时候,数据封装进了 Form Data 中,http 请求中把实体主体带过去了【传输的数据称之为主体】,既然 request 对象封装了 http 请求,所以 request 对象可以解析到发送过来的数据,于是只要把编码设置成 UTF- 8 就可以解决乱码问题
(对上例中 post 请求方式进行抓包)
而 get 方式不同,它的数据是从消息行带过去的,没有封装到 request 中,所以使用 request 设置编码是无效的。
解决方法:我们既然知道 Tomcat 默认的编码是 ISO 8859-1,那么 get 方式由消息体带过去给浏览器的时候肯定是用 ISO 8859- 1 编码了。
(还可以通过修改 Tomcat 服务器的配置来解决,但是不推荐,因为会太依赖服务器了)
// 此时得到的数据已经是被 ISO 8859- 1 编码后的字符串了,这个是乱码
String name = request.getParameter("username);
// 乱码通过反向查 ISO 8859- 1 得到原始的数据
byte[] bytes = name.getBytes("ISO 8859-1);
// 通过原始的数据,设置正确的码表,构建字符串
String value = new String(bytes,"UTF-8");
(五) 实现转发
服务器内部的资源跳转方式
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/web-001/RequestDemo" method="get">
<h1> 这是转发后的首页,地址栏地址也没有发生变化 </h1>
</form>
</body>
</html>
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取到 requestDispatcher 对象,跳转到 c.html
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/c.html");
// 调用 requestDispatcher 对象的 forward() 实现转发,传入 req 和 resp 方法
requestDispatcher.forward(reqt, resp);
转发的结果就是地址栏没有发生变化,但是页面已经跳转到 c.html 页面
学习 Response 后我们会学习重定向问题,到时候与转发做区分对别,请留意这一部分
(六) Servlet 之间的通讯
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {request.setAttribute("username", "admin");
// 获取到 requesetDispatcher 对象
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/servletB");
// 调用 requestDispatcher 对象的 forward() 实现转发,传入 req 和 resp 方法
requestDispatcher.forward(req, resp);
}
- ServletB
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取到存进 requeset 对象的值
String username = (String)req.getAttribute("username");
// 在浏览器输出该值
respe.getWriter().write("i am:" + username);
}
我们可以同时使用 ServletContext 和 request 实现 Servlet 之间的通讯
一般来说我们尽量使用 request,因为 ServletContext 代表着整个 web 应用,使用 ServetContext 会消耗大量的资源,而 request 对象会随着请求的结束而技术,资源会被回收,使用 request 域进行 Servlet 进行 Servlet 之间的通讯在开发中是非常频繁的
细节:
如果在调用 foreard 方法之前,在 Servlet 程序中写入的部分已经被真正地传到了客户端,forward 方法将抛出 IllegalStateException 异常,也就是说,不要在在转发之前写数据给浏览器
如果调用 forward 方法之前向 Servlet 引擎的缓冲区中写入了内容,只要写入到缓冲区中的内容还没有被真正输出到客户端,forward 方法就可以被正常执行,原来写入到缓冲区中的内容将被清空,但是已写入到 HttpServletResponse 对象中的响应头字段信息保持有效
Respone
前面学习的 Request 对象可以帮助我们获取到浏览器发过来的请求,想对应的,我们就需要学习代表响应的 response 对象,它可以帮助我们进行对客户端的响应工作
(一) 响应正文
response 作为响应对象,他提供了两个响应流对象,可以向客户端输出响应正文
// 获取字符流
l PrintWriter out = response.getWriter()
// 获取字节流
l ServletOutputStream out = response.getOutputStream()
ServletOutputStream servletOutputStream = resp.getOutputStream();
servletOutputStream.write("你好世界".getBytes());
servletOutputStream.write("Just for test".getBytes());
如果 Tomcat 版本在 8 以下 在 outputStream 中使用 print() 方法接收字符串,由于编码的问题,输出中文字符串的时候,就会出现乱码问题
原因是,outputStream 是输出二进制的数据,print() 方法先有一个将字符串转为二进制的过程,Tomcat 会使用 IOS 8859- 1 编码转换,所以出现了问题
但是使用 write() 却可以很好的解决这个问题,这是因为,write("Just for test".getBytes());
转换为 byte[] 数组的时候默认使用的是 gb2312 编码,所以不会出现问题
但是为了后续方便,我们还是要使用 UFT- 8 编码,如果我们在上一步骤中指定编码,看看如何
response.getOutputStream.write("你好世界".getBytes().getBytes("UTF-8"));
结果就是会出现乱码,这是因为客户端浏览器不知道响应数据是什么编码的,那么如何解决这个问题呢
解决方案:
A:设置消息头
// 设置头信息,告诉浏览器我回送的数据是 UTF- 8 的
response.setHeader("Content-Type","text/html;charset=UTF-8");
response.getOutputStream.write("你好世界".getBytes().getBytes("UTF-8"));
B:使用 html 标签模拟一个 http 消息头
ServletOutputStream servletOutputStream = resp.getOutputStream();
// 使用 meta 标签模拟 http 消息头,告诉浏览器回送数据的编码和格式
servletOutputStream.write("<meta http-equiv='content-type'content='text/html;charset=UTF-8>".getBytes());
servletOutputStream.write("你好世界".getBytes().getBytes("UTF-8"));
C:推荐方法
// 设置浏览器用 UTF- 8 编码显示数据
resp.setContentType("text/html;charset=UTF-8");
// 获取到 printWriter 对象
PrintWriter printWriter = resp.getWriter();
printWriter.writer("你好世界")
好处 :不只会调用 response.setCharaceterEncoding(“utf-8”)
,还会设置 content-type 响应头(客户端浏览器会使用 content-type 头来解读响应数据)
总结 :响应正文内容为字符,那么使用 respone.getWriter()
,如果响应内容是字节,例如下载文件,可以使用 response.getOutputStream()
注意 :在同一个请求中,不能同时使用这两个流,否则会抛出 IllegalStateException 异常
getWriter() 的缓冲区问题
它的类型是 PrintWriter 类型的,所以它有缓冲区,缓冲区的默认大小为 8KB,在限定代销范围以内,数据先存放在缓冲区,等到超过范围后,服务器刷新流,缓冲区中的数据发送倒客户端,如果想要响应数据马上发送到客户端,可以调用 response.flushBuffer() 方法来手动刷新缓冲区
(二) 设置响应头信息、状态码以及其他
(1) 设置响应头
使用 response 对象的 setHeader() 方法来设置响应头
// 设置 content-type 响应头, 告诉浏览器响应内容为 html 类型,编码为 utf-8。而且同时会设置 response 的字符流编码为 utf-8,即 response.setCharaceterEncoding(“uaav tf-8”);response.setHeader(“content-type”,“text/html;charset=utf-8”)
// 5 秒后自动跳转到指定主页
response.setHeader("Refresh","5; URL=http://www.xxx.com"):
(2) 设置状态码
// 设置状态码
response.setStatus(200)
// 当发送错误状态码时,跳转到指定错误页面,但可以显示错误信息
response.sendError(404,“您要查找的资源不存在”)
(3) 其他
// 等同于 response.setHeader(“content-type”,“text/html;charset=utf-8”)
response.setContentType("text/html;charset=utf-8")
// 设置字符响应流的字符编码为 UTF-8
response.setCharacterEncoding(“utf-8”)
// 下例表示定时刷新,3 秒后跳转页面
response.setHeader("Refresh", "3;URL=Bservlet");
(三) 重定向
当你访问 www.xxx.com
的时候,页面被跳转到了另一个页面,并且浏览器地址栏中的 URL 也发生了变化,这种技术就叫做重定向
完成重定向有两个关键的地方
- 设置响应码
- 设置 Location 头
响应码 200 的意思是响应成功,而重定向对应的响应码为 302,所以我们需要设置响应码
因为重定向的原理为,发出二次请求,所以你需要给浏览器指定第二次请求的 URL,所以需要蛇者 Location 头
注意:同服务器下可以使用相对路径
response.setStatus(302);
response.setHeader("Location", "www.xxx.com");
简单的写法
response.sendRedirect("www.xxx.com");
(四) 转发和重定向的区别与使用场景
(1) 区别
(一) 实际发生未知不同,地址栏不同
A:转发是发生在服务器的
B:转发是由服务器进行跳转的 ,转发时,浏览器的地址栏是没有发生变化的,(访 问了 Servlet1 后即使页面跳转到了 Servlet2,但浏览器的地址还是 Servlet1 的)也就是说浏览器是不知道该跳转的动作,实现转发只是一次的 http 请求,一次转 发中 request 和 response 对象都是同一个,这也解释了为什么可以使用 request 作为域对象进行 Servlet 之间的通讯
C:重定向是发生在浏览器的
D:重定向是由浏览器进行跳转的 ,进行重定向跳转的时候,浏览器的地址会发生变化,实现重定向的原理是由 response 的状态码和 location 头组合而实现的,这 是由浏览器进行的页面跳转实现会发出两个 http 请求,request 域对象是无效的,因为它不是同一个 request 对象
(二) 用法不同
原则 :给服务器用的直接从资源名开始写,给浏览器用的要把应用名协写上
Requst.getRequestDispatcher(“/ 资源名 URL”).forward(request,response);
转发时“/”代表的是本应用程序的根目录(web-01)
Response.send(“/web 应用 / 资源名 URL”);
重定向时“/”代表的是 webapps 目录
(三) 能够去往的 URL 的范围不同
转发是服务器跳转,只能去往当前 web 应用的资源
重定向是服务器跳转,可以去往任何的资源
(四) 传递数据的类型不同
转发的 request 对象可以传递各种类型的数据,包括对象
重定向只能传递字符串
(五) 跳转的时间不同
转发时:执行到跳转语句就会立刻跳转
重定向:整个页面执行完后才会执行跳转
(2) 应用场景
总结:转发是带着转发前的请求的参数。重定向时新的请求
典型的应用场景:
1:转发:访问 Servlet 处理业务逻辑,然后转发到 jsp 中去处理结果,浏览器里 URL 不变
2:重定向:提交表单,处理成功后重定向到另一个 jsp,防止表单重复提交,浏览器里的 URL 变了
结尾:
如果内容中有什么不足,或者错误的地方,欢迎大家给我留言提出意见, 蟹蟹大家!^_^
如果能帮到你的话,那就来关注我吧!(系列文章均会在公众号第一时间更新)
在这里的我们素不相识,却都在为了自己的梦而努力 ❤
一个坚持推送原创 Java 技术的公众号:理想二旬不止