Response响应组成&常见状态码&ServletContext&url编码&文件下载案例
回顾
请求有哪三个的组成部分
- 请求行:提交方式 URI HTTP/1.1
- 请求头:由多个键和值组成
- 请求体:发送给服务器的数据,只有POST才有请求体
获取请求行相关的方法
HttpServletRequest对象的方法 | 功能描述 |
---|---|
String getMethod() | 获取提交方式 |
String getRequestURI() | 获取URI |
String getProtocol() | 获取协议和版本 |
String getQueryString() | 获取查询字符串,?后面的参数 |
获取请求头相关的方法
请求方法 | 功能描述 |
---|---|
String getHeader(String headName) | 通过键获取值 |
Enumeration<String> getHeaderNames() | 获取所有的请求头的名字 |
获取请求参数的方法
方法名 | 描述 |
---|---|
String getParameter(String name) | 通过参数名获取参数值 |
String[] getParameterValues(String name) | 通过一个参数名获取所有同名的值,返回数组 |
Map<String,String[]> getParameterMap() | 一次性获取所有的参数,封装成map 键是参数名,值是参数值 |
POST方式乱码解决方法 | 说明 |
---|---|
setCharacterEncoding(“编码”) | 解决POST方法的乱码问题 |
请求域有关的方法
request与域有关的方法 | 作用 |
---|---|
Object getAttribute(“键”) | 通过键获取值 |
setAttribute(“键”,Object数据) | 向请求域中添加键和值,如果键不存在就是添加,存在就是修改 |
removeAttribute(“键”) | 通过键删除键和值 |
转发与重定向的区别
区别 | 转发 | 重定向 |
---|---|---|
地址栏会不会发生变化 | 不会 | 会 |
跳转方是哪一方 | 服务器端 | 浏览器端 |
请求域中数据是否丢失 | 不会 | 会 |
根目录地址 | http://localhost:8080/项目访问地址/ | http://localhost:8080/ |
Servlet是运行在服务器端的,服务器端的 / 根目录是指的web目录,web目录在浏览器上访问的时候地址:
服务器端是包含了项目的访问地址:http://localhost:8080/项目访问地址/
网页是运行在浏览器端,浏览器端的 / 根目录是不包含项目名字的
http://localhost:8080/
学习目标
- 响应的格式
- 能够使用使用浏览器开发工具查看响应
- 能够理解响应行(状态行)的内容
- 能够理解常见的状态码
- 响应对象的编程
- 能够使用Response对象操作HTTP响应内容
- 能够处理响应乱码
- 能够完成文件下载案例
- 能够使用ServletContext域对象
学习内容
1. 响应的三个组成部分
目标
HTTP响应由哪三个组成部分
什么是HTTP响应
由服务器向浏览器发送的数据
数据准备
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<h2>用户登录</h2>
<form action="login" 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 colspan="2"><input type="submit" value="登录"/></td>
</tr>
</table>
</form>
</body>
</html>
响应信息的三个组成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0uTC9vY-1598791362482)(assets/image-20200830090313984.png)]
小结
响应有哪三个组成部分?
- 响应行(状态码)
- 响应头:由键和值组成
- 响应体:服务器返回的数据,如果是网页看到的就是源码
2. 响应行、响应头、响应体的格式
目标
- 响应行的格式
- 响应头的格式
- 响应体的格式
响应行
组成:协议和版本 状态码 描述信息
HTTP/1.1 200 OK
HTTP/1.1 302 Found
HTTP/1.1 404 Not Found
响应头
响应头信息 | 说明 |
---|---|
Location: http://www.itcast.cn | 出现在302中,表示下一个要跳转的页面 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DD1GdJ6I-1598791362499)(assets/image-20200830091153748.png)] |
Server:Apache Tomcat | 服务器的名字 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwbdFq1P-1598791362507)(assets/image-20200830091331777.png)] |
Content-Encoding: gzip | 服务器端数据压缩的格式,浏览器需要知道压缩格式从而进行解压 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OS2Q7885-1598791362566)(assets/image-20200830091634611.png)] |
Content-Length: 80 | 服务器返回的数据长度,单位是字节 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-59p8XLZN-1598791362569)(assets/image-20200830091744979.png)] |
Content-Type: text/html; charset=utf-8 | 响应的类型和编码 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V6M5Z2Bf-1598791362571)(assets/image-20200830091517641.png)] |
Refresh:秒;url=/hello.html | 过多久以后跳转到另一个页面 |
Content-Disposition: attachment; filename=文件名.扩展名 | 如果要实现文件的下载,必须指定这个响应头 |
响应体
有两种类型的数据
-
文本类型:网页,JS代码,CSS样式等
-
二进制类型:文件,图片,视频,音乐等
在Servlet中有相应的IO流:
- 文本类型:字符流,基类:Reader和Writer
- 二进制类型:字节流,基类:InputStream和OutputStream
小结:响应的组成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kvf7o1XC-1598791362574)(assets/1552645116046.png)]
3. 响应对象的方法:与状态码有关(了解)
目标
与响应行有关的方法
什么是HttpServletResponse对象
是一个接口,也就可以有不同的实现类,我们的程序就可以运行在不同的Web服务器上。
- 由谁提供实现类:由tomcat实现
- 由谁创建此对象:由tomcat创建这个对象
设置状态码的方法
HTTP/1.1 200 OK
状态码的方法 | 描述 |
---|---|
setStatus(int status) | 设置响应的状态码,比如:200 |
sendError(int sc) | 发送错误码,通常400~500以上都属于错误码 |
sendError(int sc, String msg) | 发送错误码,多了一个错误信息 |
演示代码
package com.itheima.servlet;
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("/demo1")
public class Demo1ResponseLineServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//response.setStatus(203); //直接写成状态码
//也可以使用常量
//response.setStatus(HttpServletResponse.SC_ACCEPTED); //200
//发送错误码
//response.sendError(401); //会出现错误页面
response.sendError(401, "你吃早餐了吗?"); //会出现错误页面
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
小结
状态码的方法 | 描述 |
---|---|
setStatus(int status) | 设置状态码 |
sendError(int sc) | 发送错误码,可以带信息 |
4. 常见的状态码【重点】
目标
- 响应行中常见状态码的含义
- 404与405出现的原因
常见状态码的含义
200
表示服务器正常响应
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S5dnf4pp-1598791362592)(assets/image-20200830100225791.png)]
302
表示页面在浏览器端进行了页面的跳转
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OPMwBmYT-1598791362594)(assets/image-20200830100303244.png)]
304
静态页面使用了缓存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x9XUf2pa-1598791362596)(assets/image-20200830100331603.png)]
404
-
地址栏输入错误,注:在Tomcat是区分大小写的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oMyRZTh5-1598791362598)(assets/image-20200830100419834.png)]
-
WEB-INF
不能直接访问WEB-INF目录下的资源,如果访问也会出现404错误
-
重定向或转发地址不正确
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0T9Jcrm6-1598791362600)(assets/image-20200830100820032.png)]
//request.getContextPath()的作用:获取当前项目的访问地址 System.out.println(request.getContextPath()); //地址:/day27_02_login_war_exploded response.sendRedirect(request.getContextPath() + "/failure.html");
405
-
如果使用GET方法提交,Servlet中没有doGet方法
-
如果使用POST方法提交,Servlet中没有doPost方法
如果直接在浏览器上输入访问地址,使用的是GET方法,只有一种情况是POST,就是表单提交的时候method=“POST”
500
服务器出现异常,通常是你的代码有问题
小结
状态码 | 含义 |
---|---|
200 | 服务器正常的响应 |
302 | 页面在浏览器端进行了页面跳转 |
304 | 静态页面进行了缓存 |
404 | 找不到资源 |
405 | 没有doGet或doPost方法 |
500 | 服务器代码有错误 |
5. 响应对象:响应头相关方法
目标
- 学习响应头相关的方法
- 案例:过3秒跳转到其它页面
响应头的方法
响应头的方法 | 描述 |
---|---|
void setHeader(String name, String value) | 向浏览器设置指定的响应头,指定名字和值 |
void setContentType(String type) | 相当于这个方法: setHeader(“Content-Type”,“text/html;charset=utf-8”) 因为这个响应头使用频繁,所以专门有一个方法 |
response.setContentType("text/html;charset=utf-8");
案例:设置响应头过3秒跳转
步骤
- 创建ResponseRefreshServlet
- 调用setHeader,设置消息头(“Refresh”,“3;url=http://www.itcast.cn”)
效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rdat1NTp-1598791362601)(assets/image-20200830102744446.png)]
代码
package com.itheima.servlet;
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.PrintWriter;
@WebServlet("/demo2")
public class Demo2ResponseRefreshServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("过3秒跳转到传智播客的官网");
//设置refresh响应头
response.setHeader("refresh","3;url=http://www.itcast.cn");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
案例:使用location实现页面跳转
步骤
- 只设置location响应头
- 同时设置302状态码
- 使用重定向的方法跳转
效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MjvbSB3Z-1598791362604)(assets/image-20200830105759715.png)]
代码
package com.itheima.servlet;
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("/demo4")
public class Demo4LocationServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*//设置响应头location进行页面跳转
response.setHeader("location","login.html");
//还要设置状态码为302
response.setStatus(302);*/
//使用重定向跳转,重定向本质上就是上面的2行代码
response.sendRedirect("login.html");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
小结
响应头的方法 | 描述 |
---|---|
void setHeader(String name, String value) | 设置响应头的它的值 |
void setContentType(String type) | 设置一个特殊的响应头:content-type |
6. 案例:响应头数据压缩
目标
在服务器端对数据压缩后在浏览器端显示出来
需求
使用数据压缩之后再从服务器传输数据到浏览器, 可以减少网络的传输量,提高网页的下载速度。
GZIPOutputStream类的方法
构造方法 | 说明 |
---|---|
GZIPOutputStream(OutputStream out) | 创建一个压缩字节输出流,参数是被压缩的输出流。 下面的案例中需要压缩响应的字节输出流 |
GZIPOutputStream类的方法 | 说明 |
---|---|
public void write(byte[] b) | 把字节数据输出,参数是要输出的字节数组 |
void finish() | 清空缓存,结束输出 |
步骤
-
如果需要对数据进行压缩,需要使用压缩流,将响应的输出流做为参数。
-
使用压缩流的write方法:首先会对数据进行压缩处理,然后调用传递进去OutputStream对象的write方法把压缩的数据写出去。
-
完成将压缩数据写入输出流的操作,如果没有调用结束的方法,则浏览器上看不到东西。
要点
要设置相应响应头信息,告诉浏览器目前内容是以压缩包的形式传输的,不然就会以附件的方式下载下来了。
response.setHeader("Content-Encoding","gzip")
响应头
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jal35jet-1598791362605)(assets/image-20200830104008095.png)]
代码
package com.itheima.servlet;
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.OutputStream;
import java.util.zip.GZIPOutputStream;
@WebServlet("/demo3")
public class Demo3GzipServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应头,告诉浏览器,服务器端数据是经过了压缩,压缩格式是gzip
response.setHeader("Content-Encoding","gzip");
//创建一个比较大的字节数组
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5000; i++) {
sb.append("abcdefg29374slkjdfsef");
}
//在服务器上输出数据原来的长度
System.out.println("长度是:" + sb.length());
//获取响应的输出流
OutputStream out = response.getOutputStream();
//创建压缩输出流
GZIPOutputStream outputStream = new GZIPOutputStream(out);
//直接使用字节输出流输出
//输出到浏览器上,将字符串转成一个字节数组输出
outputStream.write(sb.toString().getBytes());
//输出完毕要清空缓存
outputStream.finish();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
小结
需要设置一个响应头:
response.setHeader("Content-Encoding","gzip");
GZIPOutputStream类的方法 | 说明 |
---|---|
public void write(byte[] b) | 将压缩后的字节写到浏览器上 |
void finish() | 清空缓存 |
7. 响应体:处理响应乱码的问题【重点】
目标
- 响应体数据的两种方式
- 处理汉字乱码的问题
响应体的两种数据
- 字符流:Writer
- 字节流:OutputStream
响应体的方法 | 描述 |
---|---|
OutputStream getOutputStream() | 获取字节输出流 |
PrintWriter getWriter() | 获取字符输出流,字符流用来输出文本 |
代码
package com.itheima.servlet;
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.PrintWriter;
/**
* 打印流
*/
@WebServlet("/demo5")
public class Demo5WriterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//告诉浏览器,我服务器使用的是什么编码
//response.setContentType("text/html;charset=utf-8");
//纯文本的内容,标签是不起作用的
response.setContentType("text/plain;charset=utf-8");
/*
1. 告诉浏览器,我服务器使用的是什么编码
2. 设置了响应字符输出流的编码
3. 设置响应内容类型
*/
//字符流必须设置编码
//设置响应的编码,请求对象也有相同的方法,但浏览器并不知道你用的是什么编码
//response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
out.print("<h1>你好,打印流</h1>");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
小结
content-type响应头的作用:
- 告诉浏览器,服务器使用的编码
- 设置了响应字符输出流的编码
- 设置了响应的内容类型
8. 如何得到ServletContext对象【重点】
目标
如何得到上下文对象
什么是ServletContext
概念:每个Web模块或工程都会有一个对应的上下文对象,它们之间是一对一的关系。
实现以下三个功能:
- 读取web.xml中全局的配置参数
- 获取当前工程的资源,转成字节输入流对象
- 获取资源在服务器上真实地址
得到上下文对象(上下文域)的方法
ServletConfig接口中方法 | 描述 |
---|---|
ServletContext getServletContext() | 调用这个方法就可以获取上下文对象, 我们的Servlet中已经继承了这个方法 所以直接在类中调用就可以了。 |
回顾Servlet的继承结构
创建模块没有勾选web
-
在创建好的模块上点右键
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5CJ31WL9-1598791362607)(assets/image-20200830113230288.png)]
-
勾选web模块
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H2sBmPlS-1598791362608)(assets/image-20200830113256543.png)]
-
这时tomcat并没有自动部署,要手动去部署
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1FHHxeDC-1598791362609)(assets/image-20200830113328662.png)]
得到ServletContext的代码
package com.itheima.servlet;
import javax.servlet.ServletContext;
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("/demo1")
public class Demo1ServletContextServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取上下文对象
ServletContext application = getServletContext();
//输出到服务器上: org.apache.catalina.core.ApplicationContextFacade@48086280
//它是一个接口,实现类由Tomcat
//public class ApplicationContextFacade implements ServletContext
System.out.println(application);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
小结
-
每个Web项目对应几个上下文对象?
1个
-
如何得到ServletContext?
getServletContext(),它由tomcat实现,由tomcat实例化对象
9. ServletContext:读取全局的配置参数【了解】
目标
获取全局的配置参数
ServletContext方法
与以前学的ServletConfig接口中方法是一样的,但ServletConfig接口中的方法只能获取当前Servlet初始化的值,其它Servlet是不能获取这个参数值的
需求
在web.xml中设置2个全局的参数,username=NewBoy, age=21 在不同的Servlet中去获取这2个值,并使用上面2个方法。
代码
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--配置全局的初始参数,可以供所有的servlet使用 -->
<context-param>
<param-name>user</param-name>
<param-value>NewBoy</param-value>
</context-param>
<context-param>
<param-name>age</param-name>
<param-value>21</param-value>
</context-param>
<servlet>
<servlet-name>demo2</servlet-name>
<servlet-class>com.itheima.servlet.Demo2InitParamServlet</servlet-class>
<!--局部配置参数-->
<init-param>
<param-name>user</param-name>
<param-value>Rose</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>demo2</servlet-name>
<url-pattern>/demo2</url-pattern>
</servlet-mapping>
</web-app>
servlet代码
package com.itheima.servlet;
import javax.servlet.ServletContext;
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.PrintWriter;
import java.util.Enumeration;
/**
* 使用配置的方式
*/
public class Demo2InitParamServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
//使用ServletConfig的方法可以获取自己的参数,局部参数
String user = getInitParameter("user"); //ServletConfig的方法
out.print("局部初始参数:" + user + "<hr/>");
//获取全局的配置参数
ServletContext application = getServletContext();
//调用上下文对象的方法
String user1 = application.getInitParameter("user"); //ServletContext的方法
out.print("全局的配置参数:" + user1 + "<hr/>");
//获取所有的全局参数的名字
Enumeration<String> initParameterNames = application.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
String name = initParameterNames.nextElement(); //获取一个参数名
String value = application.getInitParameter(name); //获取参数的值
out.print("全局参数名:" + name + ",参数值:" + value + "<hr/>");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
效果
小结
方法 | 功能 |
---|---|
ServletContext getServletContext() | 获取上下文对象的方法 |
String getInitParameter(String name) | 通过参数名获取参数值,全局的初始参数 所有的Servlet都可以使用 |
Enumeration<String> getInitParameterNames() | 获取所有的参数名 |
10. ServletContext:获取当前工程的资源
目标
得到web目录下的某个图片资源在浏览器上显示出来
idea的bug
web目录下静态资源,如果是复制过去,idea不会自动部署,在out目录下没有,如果是自己新建的不会有问题。
解决方法
自己手动在out目录下再复制一份
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P3omkH3r-1598791362611)(assets/image-20200830144011571.png)]
案例:读取Web目录下的资源文件
执行效果
代码
package com.itheima.servlet;
import org.apache.commons.io.IOUtils;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* application.getResourceAsStream("/") 表示web目录
* 读取web目录或它的目录下资源,返回字节输入流
*/
@WebServlet("/demo3")
public class Demo3ResourceServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//回顾:获取src目录下资源,转成输入流
//InputStream inputStream = getClass().getResourceAsStream("/xxx");
//0.获取上下文对象
ServletContext application = getServletContext();
//1.获取一个输入流对象,这里的/是web目录
InputStream inputStream = application.getResourceAsStream("/WEB-INF/img/404.jpg");
//2.获取一个响应的输出流
OutputStream outputStream = response.getOutputStream();
//3.把输入流写到输出流中去
/*int len = 0;
byte[] buf = new byte[1024];
while ((len = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, len);
}*/
//使用工具IOUtils,用来操作IO流的静态方法
IOUtils.copy(inputStream, outputStream);
//4.响应会自动关闭流
inputStream.close();
outputStream.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
使用IOUtils工具类的方法 | 功能 |
---|---|
copy(InputStream in, OutputStream out) | 把输入流中的数据写到输出流中 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WfomllS0-1598791362612)(assets/image-20200830145341211.png)]
小结
ServletContext的方法 | 功能 |
---|---|
InputStream getResourceAsStream(String path) | 读取web或它的子目录下资源,转成字节输入流 |
11. ServletContext:获取资源在服务器的真实地址
目标
使用上下文对象得到web资源真实的地址
代码
package com.itheima.servlet;
import javax.servlet.ServletContext;
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.PrintWriter;
/**
* 获取一个资源在服务器部署的真实地址
*/
@WebServlet("/demo4")
public class Demo4RealPathServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取上下文对象
ServletContext application = getServletContext();
//2.调用方法:获取资源在服务器上部署的真实地址。参数:资源的路径名
String realPath = application.getRealPath("/WEB-INF/img/404.jpg");
//3.输出资源的真实地址
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("真实地址是:" + realPath);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
执行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uknx153Y-1598791362614)(assets/image-20200830150352672.png)]
小结
ServletContext的方法 | 功能 |
---|---|
String getRealPath(String path) | 获取指定资源部署到服务器上真实地址 |
12. 上下文域对象的方法
目标
- 上下文域的操作方法
- 得到第几个登录用户的分析
上下文域
作用域有三个:
- 请求域:一个用户的一次请求起作用,范围最小。
- 会话域:一个用户的所有请求。
- 上下文域:所有用户的所有请求。都是起作用的,范围是最大。
作用范围越大,占用资源越多,原则:在满足功能的前提下,使用尽可能小的作用域。
上下文域是服务器启动就存在,服务器关闭才销毁。
上下文域的操作方法
所有的作用域都有三个相同的方法
上下文域操作有关的方法 | 作用 |
---|---|
Object getAttribute(“键”) | 通过键获取上下文域中值 |
setAttribute(“键”,Object数据) | 向上下文域中添加键和值,如果存在就是覆盖 |
removeAttribute(“键”) | 从上下文域中删除指定的键和值 |
需求
记录当前是第几个登录成功的用户
案例流程
- 计数放在上下文域中
- 在Servlet每一次访问的时候,向上下文域中添加1个变量count=0
- 每个用户登录
- 登录成功:从上下文域中取出count的值,加1,更新上下文域
- 登录失败:输出登录失败
13. 案例:得到当前是第几个登录的用户
目标
得到当前是第几个登录的用户的代码实现
步骤
-
在init方法中得到上下文对象
-
创建count=0,并且将值放入上下文域中.
-
登录方法中得到用户名和密码
-
判断用户名和密码是否正确
-
如果正确,得到上下文对象
-
从上下文域中取出count
-
加1再更新上下文域
-
显示输出人数
-
登录失败则跳转到登录页面
代码
HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<h2>用户登录</h2>
<form action="login" 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 colspan="2"><input type="submit" value="登录"/></td>
</tr>
</table>
</form>
</body>
</html>
登录的Servlet
package com.itheima.servlet;
import javax.servlet.ServletContext;
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.PrintWriter;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 记录当前是第几个登录的用户
*/
@WebServlet("/login")
public class Demo5LoginServlet extends HttpServlet {
/*
第一个用户登录的时候执行1次
注:
因为每个Servlet只有一个对象,每个用户其实是一个线程,
所以Servlet中成员变量同时给多个用户使用的会有线程安全的问题,建议不要对成员变量进行修改。
*/
@Override
public void init() throws ServletException {
//获取上下文对象,向上下文域中添加了一个计数为0
getServletContext().setAttribute("count", 0);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
//实现用户的登录
String username = request.getParameter("username"); //获取用户名
String password = request.getParameter("password"); //获取用户名
//直接判断,登录成功
if ("admin".equals(username) && "123".equals(password)) {
//从上下文域中取出原有的值
ServletContext application = getServletContext();
//获取一个整数类型的值
int count = (int) application.getAttribute("count");
//计数加1,再放回到上下文域中
application.setAttribute("count", ++count);
//输出第几个登录的用户
out.print("登录成功,您是第" + count + "个登录的用户");
} else {
out.print("登录失败,<a href='login.html'>请重试</a>");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
小结
上下文域的范围是:所有用户的所有请求。服务器开启就会创建上下文域,服务器关闭才会销毁
ServletContext的方法 | 作用 |
---|---|
Object getAttribute(“键”) | 获取上下文域中值 |
void setAttribute(“键”,Object 数据) | 设置上下文域中键和值 |
void removeAttribute(“键”) | 删除上下文域中的键和值 |
14. URL编码和解码
目标
- 为什么需要URL编码和解码
- 如何实现URL编码和解码
为什么需要URL编码和解码
表单中提交汉字,使用GET提交,查看请求行的数据
URL的编码规则
字母数字字符 "a" 到 "z"、"A" 到 "Z" 和 "0" 到 "9" 保持不变。
特殊字符 "."、"-"、"*" 和 "_" 保持不变。
空格字符 " " 转换为一个加号 "+"。
所有其他字符都是不安全的,因此首先使用一些编码机制将它们转换为一个或多个字节。然后每个字节用一个包含 3 个字符的字符串 "%xy" 表示,其中 xy 为该字节的两位十六进制表示形式。推荐的编码机制是 UTF-8。
如:张三 编码后:%E5%BC%A0%E4%B8%89
有关的方法
URL编码和解码有关的方法 | 功能 |
---|---|
URLEncoder.encode(“字符串”,“utf-8”) | URL编码,参数1:要编码的字符串, 参数2:编码的类型,统一使用utf-8 |
URLDecoder.decode(“字符串”,“utf-8”) | URL解码:参数2:要解码的字符串, 参数2:编码的类型,统一使用utf-8 |
案例演示代码
package com.itheima.test;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
public class TestURL {
public static void main(String[] args) throws UnsupportedEncodingException {
String hz = "张三";
//URL编码,参数1:要编码的字符串,参数2:编码的类型,统一使用utf-8
String encode = URLEncoder.encode(hz, "utf-8");
System.out.println("编码前:" + hz);
System.out.println("编码后:" + encode);
//URL解码:参数2:要解码的字符串,参数2:编码的类型,统一使用utf-8
String decode = URLDecoder.decode(encode, "utf-8");
System.out.println("解码后:" + decode);
}
}
小结
URL编码和解码有关的方法 | 功能 |
---|---|
URLEncoder.encode(“字符串”,“utf-8”) | URL的编码 |
URLDecoder.decode(“字符串”,“utf-8”) | URL的解码 |
15. 案例:使用链接下载文件不足
目标
链接下载文件存在的问题
页面效果
下载页面
<!DOCTYPE html>
<html>
<head>
<title>资源下载列表</title>
<meta charset="utf-8">
</head>
<body>
<h2>文件下载页面列表</h2>
<h3>超链接的下载</h3>
<!--直接链接下载的资源,download是一个目录 -->
<a href="download/file.txt">文本文件</a><br/>
<a href="download/file.jpg">图片文件</a><br/>
<a href="download/file.zip">压缩文件</a><br/>
<hr/>
<h3>手动编码的下载方式</h3>
<!-- 注:down这里是下载的Servlet的访问地址 -->
<a href="down?filename=file.txt">文本文件</a><br/>
<a href="down?filename=file.jpg">图片文件</a><br/>
<a href="down?filename=file.zip">压缩文件</a><br/>
<!--以后不建议使用汉字的文件名-->
<a href="down?filename=美女.jpg">美女</a><br/>
</body>
</html>
小结:使用超链接下载的不足
- 有些资源是在浏览器上直接打开,不是下载。
- 会暴露资源的真实地址,容易被人盗链
- 不利于业务逻辑的控制,比较如:要登录或是会员才可以下载
16. 案例:使用Servlet下载文件【重点】
目标
- 实现使用Servlet的方式下载文件
- 文件名有汉字的处理
下载设置的响应头
设置响应头 | 参数说明 |
---|---|
Content-Disposition: attachment; filename=文件名 | Content-Disposition: 1. inline表示在浏览器中打开,默认值 2. attachment: 以附件的方式下载资源 filename: 下载的文件名 |
步骤
- 从链接上得到文件名
- 设置content-disposition头
- 得到文件的输入流
- 得到response的输出流
- 写出到浏览器端
汉字乱码原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gSak2te3-1598791362616)(assets/1552656145066.png)]
注:IE、Chrome下载中文采用的是URL编码,注:FireFox不能采用这种方式。
下载的Servlet代码
package com.itheima.servlet;
import org.apache.commons.io.IOUtils;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
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.io.OutputStream;
import java.net.URLEncoder;
@WebServlet("/down")
public class DownloadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取下载的文件名
String filename = request.getParameter("filename");
System.out.println("下载的文件名是:" + filename);
//要点:如果要下载,必须设置响应头
//因为浏览器会自动对汉字进行解码的操作,所以我们在服务器端对汉字进行编码
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename,"utf-8"));
//2.使用上下文对象获取文件的输入流
InputStream inputStream = getServletContext().getResourceAsStream("/download/" + filename);
//3.获取响应的输出流
OutputStream outputStream = response.getOutputStream();
//4.将输入流复制到输出流中,注:要导入io工具的jar包到lib目录下
IOUtils.copy(inputStream, outputStream);
//5.关闭流
inputStream.close();
outputStream.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
小结
-
下载需要设置如个响应头?
content-disposition:attachment;filename=文件名
-
如果下载文件名中有汉字使用哪个方法编码?
URLEncoder.encode("字符串","utf-8")
17. 验证码的使用
目标
- 将上面创建的验证码用在HTML页面中
- 点击验证码图片刷新验证码的功能
验证码的代码
package com.itheima.servlet;
import javax.imageio.ImageIO;
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.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
@WebServlet("/code")
public class PicCodeServlet extends HttpServlet {
//随机类
private Random random = new Random();
/**
* 随机获取一种颜色
*/
private Color getRandomColor() {
//随机得到r,g,b的取值,范围是0~255
int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
return new Color(r, g, b); //red红 green绿 blue蓝
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置MIME类型
response.setContentType("image/jpeg");
//定义宽和高的值
int width = 90;
int height = 30;
//1. 创建一张图片,参数:宽,高,图片模式
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//获取画笔对象
Graphics graphics = image.getGraphics();
//整个图片填充白色
graphics.setColor(Color.WHITE);
graphics.fillRect(0,0,width,height);
//2. 随机绘制4个验证码
char[] arr = { 'A', 'B', 'C', 'D', 'N', 'E', 'W', 'b', 'o', 'y', '1', '2', '3', '4','5','6','7','8' };
//设置字体,字体对象有三个参数:字体名字,字体样式(加粗,斜体), 大小
graphics.setFont(new Font(Font.DIALOG_INPUT, Font.BOLD + Font.ITALIC, 19));
for (int i = 0; i < 4; i++) {
//随机获取1个索引号
int index = random.nextInt(arr.length);
//随机获取字符数组的一个字符
char c = arr[index];
//每个字符的颜色不同
graphics.setColor(getRandomColor());
//写字符,参数:文字内容,x,y坐标 (把字符转成字符串)
graphics.drawString(String.valueOf(c),10+(i*20), 20);
}
//3. 绘制8条干扰线
for (int i = 0; i < 8; i++) {
//指定颜色
graphics.setColor(getRandomColor());
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
int x2 = random.nextInt(width);
int y2 = random.nextInt(height);
//画线,起点和终点
graphics.drawLine(x1,y1,x2,y2);
}
//4. 把图片输出到浏览器,参数:输出的图片对象,图片格式,响应输出流
ImageIO.write(image,"jpg", response.getOutputStream());
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
代码
<!--验证码,src要指定为servlet的访问地址-->
<img src="code" id="picCode" style="width: 90px; height: 30px; cursor: pointer;" title="看不清,点击刷新">
<script type="text/javascript">
//点击图片的事件
document.getElementById("picCode").onclick = function () {
//刷新页面 location.reload();
//再次访问服务器,将src的值重新赋值,默认会使用缓存
//每次传递不同的参数就可以修改URL地址了,参数并没有什么用
this.src = "code?t=" + new Date().getTime();
}
</script>
小结
实现点击验证码刷新的功能:每次传递不同的参数
学习总结
-
能够使用浏览器开发工具查看响应
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YGSZOHbl-1598791362618)(assets/1552645116046.png)]
-
能够理解响应行(状态行)的内容
HTTP/1.1 200 OK
- 协议和版本
- 状态码
- 状态描述
-
能够理解常见的状态码
状态码 含义 200 正常的响应 302 浏览器端进行了跳转 304 使用了缓存 404 找不到资源 405 没有重写doGet或doPost方法 500 服务器代码有错误 -
能够使用Response对象操作HTTP响应内容
响应头的方法 描述 setStatus(int status) 设置状态码 void setHeader(String name, String value) 设置某个响应头 OutputStream getOutputStream() 获取字节输出流 PrintWriter getWriter() 获取字符输出流 -
能够处理响应乱码
处理响应乱码的方法 描述 void setContentType(String type) 1. 告诉浏览器,服务器端使用的编码
2. 设置响应的编码,相当于下面这个方法
3. 设置响应的内容类型response.setCharacterEncoding(“字符集”) 设置响应的编码 -
能够完成文件下载案例
- 设置下载的响应头
- 通过上下文对象getResourceAsStream()得到输入流
- 获取响应的输出流
- IOUtils.copy(输入流,输出流)
设置响应头 参数说明 Content-Disposition: attachment; filename=文件名 设置浏览器以附件的方式下载文件
而不是在浏览器中直接打开 -
能够使用ServletContext域对象
ServletConfig接口中方法 描述 ServletContext getServletContext() 获取上下文对象 ServletContext的方法 作用 InputStream getResourceAsStream(String path) 读取web目录下的资源文件,转成输入流 String getRealPath(String path) 读取web目录下资源的部署到服务器上的真实地址 setAttribute() getAttribute() removeAttribute()