目录
前言
加油!坚持呀!
五丶Servlet API详解(下)
<1>HttpServletResponse
这个部分我们学习了在服务端去处理http响应的对象,在这个过程当中,我们会调用该对象的方法,去设置对象属性的内容。当然,最后tomcat会组织为http响应报文。
这一部分的主要方法如下:
1.void setStatus(int sc) 为该响应设置状态码。
2.void setHeader(String name,String value)设置一个带有给定的名称和
值的 header. 如果 name 已经存在,则覆盖旧的值.
3.void addHeader(Stringname, String value)添加一个带有给定的名称和
值的 header. 如果 name 已经存在,不覆盖旧的值, 并列添加新的键值对
4.void setContentType(Stringtype)设置被发送到客户端的响应的内容类型
。
5.void setCharacterEncoding(Stringcharset)设置被发送到客户端的响
应的字符编码(MIME 字符集)例如,UTF-8。
6.void sendRedirect(Stringlocation) 使用指定的重定向位置 URL 发送
临时重定向响应到客户端。
7.PrintWriter getWriter() 用于往 body 中写入文本格式数据.
8.OutputStream getOutputStream()用于往 body 中写入二进制格式数据
<2>设置状态码/设置响应头
我们在这里实现的程序内容是,用户在浏览器通过参数指定要返回的状态码。首先,我们在这里创建一个类ResponseStudyServlet。当然,老套路,我们代码的模板还是和Ruquest时候一样。
@WebServlet("/response")
public class ResponseStudyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
然后我们就在里面书写我们的后端代码,首先就是要获取queryString的响应状态码
String status = req.getParameter("status");
然后我们要把这个状态码转换成为Integer类型,为了更明确地显示响应成功,我们可以进行显示设置。
if(status != null) {
resp.setStatus(Integer.parseInt(status));
resp.getWriter().write("设置响应状态码成功");
}
然后我们抓包看看具体情况
可以看到这里响应状态码是200,响应成功。因为我们没有进行状态码的设置,所以就是响应成功默认返回200。
那么我们就要开始着手书写前端代码,进行状态码的设置,进行前后端的交接。这里还是老规矩,前端代码在VSCode书写。在webapp下,创建文件response.html,来设置我们的响应头
<h3>设置响应头</h3>
<input type="text" name="" id="status">
<br>
<button onclick="setStatus()">提交</button>
<script>
function setStatus(){
//js中发请求:(1)ajax (2)直接修改url
let status = document.querySelector("#status");
//后端会设置这个文本框输入的值为响应状态码:严格来做需要验证(省略)
window.location.href = "response?status="+status.value;
}
</script>
这里我对window.location.href
这行代码进行说明
浏览器内置的window对象里面有一个location,表示地址栏对象,地址栏对象有一个href属性
这里我们在文本框中输入的内容,就是我们设置的状态码。
这里是乱码,看不出来很正常,我们打开抓包工具看就好。
可以看到我设置的状态码此时生效了。
我们设置响应头的键值对,键可以是标准的http响应头的键。也可以是自定义的,但是只有当响应状态码是301,302,307的时候,响应头有location字段,我们才可以实现重定向。
resp.setHeader("Location","http://www.baidu.com");//这里我们没有设置响应状态码所以不会生效
resp.setHeader("uesrname","张三");
当我们在文本栏当中输入3xx对应的状态码的时候,会发生什么呢?
这个时候就会发生重定向,location重定向到百度页面。
<3>响应正文为html/设置响应体
我们响应头设置暂时完毕啦,这个时候我们设置一下Content-tyoe,也就是响应体的内容。
在IDEA中新建一个类,HTMLTypeServlet,然后老规矩,模板先搞起来。
@WebServlet("/html")
public class HTMLTypeServlet extends HttpServlet {
//html?type=...
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/
}
}
然后设置我们的响应体内容也很简单,看如下代码:
resp.setContentType("text/html;charset=utf-8");
但是在这里我们就要分情况讨论啦。我们根据url当中的type关键字来获取获得提交给我们服务端的html响应正文是简单格式还是复杂格式的。
1>如果是简单格式
如果说是简单格式的,这里我们在VScode中表明type=1,说明是简单格式。
<h3>响应正文为简单的html</h3>
<a href="html?type=1">查看</a>
这里如果是简单格式我们就没有什么特别需要说明的,就输出一个响应成功,代表我们的服务端获取成功就好。IDEA书写
//在这里,我们先拿到request请求queryString中type关键字
String type = req.getParameter("type");
if ("1".equals(type)){//返回简单的html
pw.println("<h3>获取网页成功</h3>");
}
在浏览器显示如下:
然后我们点击查看,就是给服务端提交type= 1的url,表明这就是一个简单的html响应body,对应变化如下:
2>如果是复杂格式
那为什么这里要说是复杂格式呢?简单和复杂的html区别在哪里?
没错,复杂的html是会动态变化的。
那么我们在前端代码中书写
<h3>响应正文为复杂的html(动态变化的)</h3>
<input type="text" id="username" placeholder="输入姓名">
<br>
<button onclick="toWelcome()">跳转</button>
这里我们用一个文本框来获取输入的内容,然后按键toWelcome()方法进行绑定
function toWelcome(){
let username = document.querySelector("#username");
window.location.href = "html?type=2&username=" + username.value;
}
一但按下按键,那么我们给服务端的queryString里的type关键字就会设置为2,username的值设置为我们文本框中输入的内容
对应显示内容如下:
<4>重定向 + 转发
实现一个程序, 返回一个重定向 HTTP 响应, 自动跳转到另外一个页面。实际上这段代码在上文中的跳转到百度的那行代码已经实现。而且也说了,响应头有location字段时,才是重定向。这里我们继续延展这个知识点。
继续我们的前端代码书写
<h3>重定向</h3>
<a href="goto?type=1">跳转</a>
<h3>转发到hello.html</h3>
<a href="goto?type=2">跳转</a>
点下不同的按键,往后端发送的内容里就会有不同的queryString。
这里我们两个后端代码分开写,但是都要先获取type字段的值。
String type = req.getParameter("type");
1>转发
resp.setStatus(301);
resp.setHeader("Location","http://www.sogou.com");
首先设置状态码为301,然后设置响应头重定向到百度页面。我们可以把如上代码简化为如下形式。
resp.sendRedirect("http://www.sogou.com");
2>重定向
req.getRequestDispatcher("http://www.baidu.com").forward(req,resp);
这里我们对应情况说完后,整合一下代码。
if("1".equals(type)){//重定向
resp.sendRedirect("http://www.sogou.com");
}else if("2".equals(type)){//转发
req.getRequestDispatcher("http://www.baidu.com").forward(req,resp);
}
对应页面
如果是转发
可以发现,这里没有跳转到百度页面,也是跳转到了本地文件。
3>关于重定向和转发的区别
转发如下:
1、属于转发,也是服务器跳转,相当于方法调用,在执行当前文件的过程中转向执行目标文件,两个文件(当前文件和目标文件)属于同一次请求,前后页共用一个request,可以通过此来传递一些数据或者session信息,request.setAttribute()和request.getAttribute()。
2、在前后两次执行后,地址栏不变,仍是当前文件的地址。
3、不能转向到本web应用之外的页面和网站,所以转向的速度要快。
4、URL中所包含的“/”表示应用程序(项目)的路径。
重定向如下:
1、属于重定向,也是客户端跳转,相当于客户端向服务端发送请求之后,服务器返回一个响应,客户端接收到响应之后又向服务端发送一次请求,一共是2次请求,前后页不共用一个request,不能读取转向前通过request.setAttribute()设置的属性值。
2、在前后两次执行后,地址栏发生改变,是目标文件的地址。
3、可以转向到本web应用之外的页面和网站,所以转向的速度相对要慢。
4、URL种所包含的"/"表示根目录的路径。
<5>获取一个文件–渲染展示 + 下载
1>渲染
这里是服务端接收文件,比如说像图片音乐什么的,然后用他们来渲染页面。
首先先书写我们的前端,也就是客户端代码
<h3>获取一个图片(渲染展示)</h3>
<img src="file?type=photo&show=1" alt="">
<h3>获取一个音乐(渲染展示)</h3>
<audio src="file?type=music&show=1" controls></audio>
这里根据queryString来获取文件的类型,也就是确定客户端是要是图片还是什么类型。
然后开始我们后端的代码书写:
OutputStream os = resp.getOutputStream();
先获取对应对象的输出流,然后获得返回的文件类型,和返回时候的操作。
//返回的文件类型:1.图片 2.
String type = req.getParameter("type");
//返回时的操作:1.渲染 2.下载
String show = req.getParameter("show");
这里我们返回时候会返回一个文件,所以就要先创建一个File对象file。
然后进行判断就好了
if("photo".equals(type)){//返回图片
resp.setContentType("application/octet-stream");
file = new File("D:\\JavaWeb\\img\\dog.jpg");
}else if("music".equals(type)){//返回音乐
resp.setContentType("audio/mp3");//MP3格式
file = new File("D:\\CloudMusic\\王巨星 - 拾忆(抖音版).mp3");
}
然后因为我们的前端代码里面发起了两个请求,所以图片和音乐都全了,所以这里页面上也会出现照片和音乐。这里我们的对象里的内容指定完毕,然后就可以把他转化为字节流,同时指定长度发回去了。
byte[] data = Files.readAllBytes(file.toPath());
resp.setContentLength(data.length);//=setHeader("Content-Length",xxx)
os.write(data);
最后页面显示如下:
2>下载
其实这里和上面情况差不多是一样的,但是我们要更换一下show的值。
<h3>获取一个图片(渲染展示)</h3>
<img src="file?type=photo&show=1" alt="">
<h3>获取一个音乐(渲染展示)</h3>
<audio src="file?type=music&show=1" controls></audio>
然后给上面的后端代码进行修改,加一个判断就好
if("photo".equals(type)){//返回图片
if("1".equals(show)){
resp.setContentType("image/jpeg");//jpg格式
}else{
resp.setContentType("application/octet-stream");
}
file = new File("D:\\JavaWeb\\img\\dog.jpg");
}else if("music".equals(type)){//返回音乐
if("1".equals(show)){
resp.setContentType("audio/mp3");//MP3格式
}else{
//这样只是没有设置下载的文件名,有兴趣可以自行扩展完成
resp.setContentType("application/octet-stream");
}
file = new File("D:\\CloudMusic\\王巨星 - 拾忆(抖音版).mp3");
}
判断类型,然后判断是要单纯获得一个图片还是说要下载。
我这里想特别说一下application/octet-stream
当浏览器在请求资源时,会通过http返回头中的content-type决定如何显示/处理将要加载的数据,如果这个类型浏览器能够支持阅览,浏览器就会直接展示该资源,比如png、jpeg、video等格式。在某些下载文件的场景中,服务端可能会返回文件流,并在返回头中带上Content-Type: application/octet-stream,告知浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载。
所以我们浏览器显示页面如下:
这里要注意了,下载的是个字节流,我没有指名文件的名称以及格式,所以下载之后要自己修改,笔者在这里不做过多解释。
<6>动态生成网页内容
这部分内容常用于ajax请求,返回一些数据,用于动态的填充网页。
首先在客户端发起ajax请求,书写前端代码。
<h3>获取ajax响应数据动态生成网页内容</h3>
<button onclick="gen()">试试</button>
<div id="content"></div>
我们在这里绑定按键,如果按下这个按键,就会向服务端发送请求并且获取响应。
对应gen()方法如下:
function gen(){
let content = document.querySelector("#content");
ajax({
url:"ajax-response",
method:"get",
callback:function(status,resp){
console.log(resp);//resp是一个字符串
//[{"from":"旺财","to":"咪咪","info":"来吃小鱼干"},{"from":"旺财","to":"咪咪","info":"来吃小骨头"}]
//转换为json对象
let array = JSON.parse(resp);
for(json of array){//开始遍历
//每一个json对象创建一个dom来保存信息
let p = document.createElement("p");
p.innerText = json.form + " 对 " + json.to + " 说: " + json.info;
content.appendChild(p);
}
}
})
}
当然,这里是要设置一下回调函数的。
function ajax(args){//var ajax = function(){}
let xhr = new XMLHttpRequest();
// 设置回调函数
xhr.onreadystatechange = function(){
// 4: 客户端接收到响应后回调
if(xhr.readyState == 4){
// 回调函数可能需要使用响应的内容,作为传入参数
args.callback(xhr.status, xhr.responseText);
}
}
xhr.open(args.method, args.url);
//如果args中,contentType属性有内容,就设置Content-Type请求头
if(args.contentType){//js中,if判断,除了判断boolean值,还可以判断字符串,对象等,有值就为true
xhr.setRequestHeader("Content-Type", args.contentType);
}
//如果args中,设置了body请求正文,调用send(body)
if(args.body){
xhr.send(args.body);
}else{//如果没有设置,调用send()
xhr.send();
}
}
然后就可以对我们的后端代码下手了。首先构造一个Message类,用对应的message对象来存储我们要动态添加的内容。
static class Message{
private String from;//谁
private String to;//对谁
private String info;//说了什么
public Message(String from, String to, String info) {
this.from = from;
this.to = to;
this.info = info;
}
public String getForm() {
return from;
}
public void setForm(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
接着就可以进行对象的创建啦。
List<Message> messageList = new ArrayList<>();
Message m1 = new Message("咪咪","旺财","来吃小鱼干");
Message m2 = new Message("旺财","咪咪","来吃小骨头");
messageList.add(m1);
messageList.add(m2);
用一个List对象,储存Message对象,然后接下来就把这个Message对象转化为json字符串。
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(messageList);
接着就设置ajax,进行ajax响应,返回这个json字符串。
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().println(json);
之后我们就可以在我们的页面上动态生成内容。
六丶Cookie和Session
<1> 回顾Cookie
HTTP协议自身是属于”无状态“协议。也就是说:默认情况下客户端和服务端的此次通信,和下次通信之间没有直接的联系。
但是实际上我们很多时候是知道请求之间的关联关系。比如,第二次访问网站的时候是能够知道这个网站是否之前已经访问过的。
这个令牌就保存在Cookie字段当中,此时在服务器这边就需要记录信息,以及令牌对应的用户信息。
<2>回顾Session
对于服务器来说,同一时刻收到的请求很多,为了认清那个请求是属于那个用户的。就需要记录好用户的令牌以及和用户之间的关系。
会话本质是一个hash表,存储了些键值对结构,key就是令牌的ID,也就是SessionID,而用户的信息就是Session,当然这里的Session也是一个Map对象。
1.用户登录时,服务器在Session中新增一个新纪录,并把sessionid/token返回给客户端。
(例如通过HTTP响应中的Set-cookie)字段。
2.客户端后续再次访问的时候,请求中带上sessionid/token(比如通过HTTP请求中的Cookie
字段带上)
3.服务器收到请求之后,根据请求中的sessionid/token在session信息当中收到对应的用户
信息,然后进行后续操作