HTTP概述
-
概念:Hyper Text Transfer Protocol 超文本传输协议
-
传输协议:定义了客户端和服务器端通信时发送数据的格式,服务器和客户端之间传递的是请求信息和响应消息,而http正是对这一过程所规定的一种规则,为了使传输过程更加规范
-
特点:
- 基于tcp/ip的高级协议
- 默认端口号是:80,如果web项目把http port设置为80,那么访问服务器的资源时只需要输入虚拟路径即可,不需要输入端口号
- 基于请求/响应模型的,一次请求对应一次响应,不能出现客户端给服务器发送一次请求,然后服务器进行多次响应的效果,那样是不遵守http协议的
- 无状态的协议:每次请求之间相互独立,不能相互交互数据,多个客户端之间不能交换数据,与服务器之间建立的连接是独立的
-
历史版本:
- 1.0版本
- 由于一个网页资源全部加载完毕,需要向服务器进行多次请求,这里面有可能包括js文件、html文件、单个图片,该版本就是每一次有请求就建立一个新的连接,这样做会让页面加载时间长
- 1.1版本
- 由于采用复用连接的方法,较1.0版本在传输速度上有了很大的提升,复用连接规则:如果旧连接传输资源文件完了之后,会等待一段非常小的时间,如果在这段时间内有新的连接请求,那么就复用这个旧连接,这样大大节省了因为建立新连接而消耗的时间
- 1.0版本
请求消息数据格式
请求消息数据格式:
-
请求行
请求方式 请求url 请求协议/版本
GET /login.html HTTP/1.1
- 请求方式:
- HTTP协议有7中请求方式,常用的有2种
- GET:
- 请求参数在请求行中,在url后。
- 请求的url长度有限制的
- 不太安全
- POST:
- 请求参数在请求体中
- 请求的url长度没有限制的
- 相对安全
- GET:
- HTTP协议有7中请求方式,常用的有2种
- 请求方式:
-
请求头:客户端浏览器告诉服务器一些信息
请求头名称: 请求头值
- 常见的请求头:
- User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息
- 可以在服务器端获取该头的信息,解决浏览器的兼容性问题
- Referer:
http://localhost/login.html
- 告诉服务器,我(当前请求)从哪里来?
- 作用:
- 防盗链:前些年存在这一现象,个体户创建自己的网站,然后将自己的网站指向别的大型网站,像一些视频网站,由于视频的版权费很昂贵,个体户网站通过这一方式盗用别的大视频网站的连接,会导致大网站缺失一些应有的流量,所以就有了防盗链这一说
- 统计工作:比如网站想统计从各个网站进入自己网站的数量多少,可以通过referer来判断
- User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息
- 常见的请求头:
-
请求空行
空行,就是用于分割POST请求的请求头,和请求体的。
-
请求体(正文):
字符串格式:
POST /login.html HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
Connection: keep-alive
Referer: http://localhost/login.html
Upgrade-Insecure-Requests: 1
username=张三
Request
request对象和response对象的原理
- request和response对象是由服务器创建的。我们来使用它们
- request对象是来获取请求消息,response对象是来设置响应消息
request对象继承体系结构
ServletRequest -- 接口
| 继承
HttpServletRequest -- 接口
| 实现
org.apache.catalina.connector.RequestFacade 类(tomcat)
request功能
获取请求消息数据
-
获取请求行数据
GET /day14/demo1?name=zhangsan HTTP/1.1
-
方法:
-
获取请求方式 :GET
String getMethod()
-
获取虚拟目录:
/day14
String getContextPath()
-
获取Servlet路径:
/demo1
String getServletPath()
-
获取get方式请求参数:
name=zhangsan
String getQueryString()
-
(*)获取请求URI:
/day14/demo1
String getRequestURI()
:/day14/demo1
StringBuffer getRequestURL()
:http://localhost/day14/demo1
URL:统一资源定位符 : `http://localhost/day14/demo1` 中华人民共和国 URI:统一资源标识符 : `/day14/demo1` 共和国
-
获取协议及版本:HTTP/1.1
String getProtocol()
-
获取客户机的IP地址:
String getRemoteAddr()
-
-
-
获取请求头数据
- 方法:
String getHeader(String name)
: 通过请求头的名称获取请求头的值Enumeration<String> getHeaderNames()
: 获取所有的请求头名称
- 方法:
@WebServlet("/requestDemo2")
public class RequestDemo2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//演示获取请求头数据
//1. 获取所有请求头名称
Enumeration<String> headerNames = request.getHeaderNames();
//2. 遍历Enumeration
while(headerNames.hasMoreElements()){
String name = headerNames.nextElement();
//根据名称来获取请求头的值
String value = request.getHeader(name);
System.out.println(name+"---"+value);
}
- 获取请求体数据:
- 请求体:只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数
- 步骤:
- 获取流对象
BufferedReader getReader()
:获取字符输入流,只能操作字符数据ServletInputStream getInputStream()
:获取字节输入流,可以操作所有类型数据
- 再从流对象中拿数据
- 获取流对象
- 步骤:
- 请求体:只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数
其他功能
-
获取请求参数,
- 通用方式:不论get还是post请求方式都可以使用下列方法来获取请求参数
String getParameter(String name)
: 根据参数名称获取参数值 username=zs&password=123String[] getParameterValues(String name)
: 根据参数名称获取参数值的数组 hobby=xx&hobby=gameEnumeration<String> getParameterNames()
: 获取所有请求的参数名称Map<String,String[]> getParameterMap()
: 获取所有参数的map集合
- 通用方式:不论get还是post请求方式都可以使用下列方法来获取请求参数
-
中文乱码问题:
- get方式:tomcat 8 已经将get方式乱码问题解决了
- post方式:会乱码
- 解决:在获取参数前,设置request的编码
request.setCharacterEncoding("utf-8");
-
转发请求:一种在服务器内部的资源跳转方式
-
步骤:
- 通过request对象获取请求转发器对象:
RequestDispatcher getRequestDispatcher(String path)
- 使用RequestDispatcher对象来进行转发:
forward(ServletRequest request, ServletResponse response)
- 通过request对象获取请求转发器对象:
-
特点:
- 浏览器地址栏路径不发生变化
- 只能转发到当前服务器内部资源中。
- 转发是一次请求
-
共享数据:
- 域对象:一个有作用范围的对象,可以在范围内共享数据
- request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
- 方法:
void setAttribute(String name,Object obj)
: 存储数据Object getAttitude(String name)
: 通过键获取值void removeAttribute(String name)
: 通过键移除键值对
-
获取ServletContext:
ServletContext getServletContext()
补充:BeanUtils
BeanUtils工具类,简化数据封装
用于封装JavaBean的
导jar包:commons-beanutils-1.8.0.jar
-
JavaBean:标准的ava类
- 要求∶
- 类必须被public修饰
- 必须提供空参的构造器
- 成员变量必须使用private修饰
- 提供公共setter和getter方法
- 功能︰封装数据
- 要求∶
-
概念∶
- 成员变量:
- 属性: setter和getter方法截取后的产物
- 例如: getUsername() -->Username–> username
-
方法:
- setProperty()
- getproperty()
- populate(object obj , Map map) : 将map集合的键值对信息,封装到对应的JavaBean对象中
方法 | 描述 | |
---|---|---|
populate(Object bean, Map<String,String[]>properties) | 将Map数据封装到指定Javabean中,一般用于将表单的所有数据封装到javabean | |
BeanUtils对象 | setProperty(Object obj,String name,Object value) | 设置属性值 |
getProperty(Object obj,String name) | 获得属性值 |
// 获取请求参数
String username = req.getParameter("username");
String password = req.getParameter("password");
// 封装loginUser对象(普通封装)
User loginUser = new User();
loginUser.setUsername(username);
loginUser.setPassword(password);
---------------------
// 获取所有请求参数
Map<String, String[]> parameterMap = req.getParameterMap();
User loginUser = new User();
// 使用BeanUtils进行数据封装
try {
BeanUtils.populate(loginUser,parameterMap);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
响应消息数据格式
数据格式:
-
响应行
- 格式:
协议/版本 响应状态码 状态码描述
例如:HTTP/1.1 200 OK
- 响应状态码:服务器告诉客户端浏览器本次请求和响应的一个状态。
- 状态码都是3位数字
- 分类:
- 1xx:服务器就收客户端消息,但没有接受完成,等待一段时间后,发送1xx多状态码
- 2xx:成功。代表:200
- 3xx:重定向。代表:302(重定向),304(访问缓存)
- 4xx:客户端错误。
- 代表:
- 404(请求路径没有对应的资源)
- 405:请求方式没有对应的doXxx方法
- 代表:
- 5xx:服务器端错误。代表:500(服务器内部出现异常)
- 格式:
-
响应头
- 格式:
头名称: 值
- 常见的响应头:
- Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式
- Content-disposition:服务器告诉客户端以什么格式打开响应体数据
- 值:
- in-line:默认值,在当前页面内打开
- attachment;filename=xxx:以附件形式打开响应体。文件下载
- 值:
- 格式:
-
响应空行
-
响应体:传输的数据
响应字符串格式:
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 90
Date: Sun, 18 Aug 2019 03:09:13 GMT
<html>
<head>
<title>$Title$</title>
</head>
<body>
hello , response
</body>
</html
Response
功能:设置响应消息
- 设置响应行
- 格式:HTTP/1.1 200 ok
- 设置状态码:
setStatus(int sc)
- 设置响应头:
setHeader(String name, String value)
- 设置响应体:
- 使用步骤:
- 获取输出流
- 字符输出流:
PrintWriter getWriter()
- 字节输出流:
ServletOutputStream getOutputStream()
- 字符输出流:
- 使用输出流,将数据输出到客户端浏览器
- 获取输出流
- 使用步骤:
重定向
资源跳转的方式(会改变路径)
// 设置状态码为302
resp.setStatus(302);
// 设置响应头
resp.setHeader("location","/MyResponse/responseDemo2");
resp.sendRedirect("/MyResponse/responseDemo2");
注意其与转发的区别(forward和redirect区别):
- 重定向的特点:(redirect)
- 地址栏发生变化
- 重定向可以访问其他站点(服务器)的资源
- 重定向是两次请求,不能使用request对象来共享数据
- 转发的特点:(forward)
- 转发地址栏路径不变
- 转发只能访问当前服务器下的资源
- 转发是一次请求,意味着可以使用request对象来共享数据
路径写法
路径分类
- 相对路径:通过相对路径不可以确定唯一资源
- 如:
./index.html
- 不以/开头,以
.
开头路径 - 规则:找到当前资源和目标资源之间的相对位置关系
./
: 当前目录../
: 后退一级目录- 例如一个页面:
http://localhost:8080/day15/htmls/index.html
,在这个页面里面访问下面那个路径只需要在超链接href值写上../responseDemo1
即可
- 不以/开头,以
- 如:
http://localhost:8080/day15/responseDemo1
-
绝对路径:通过绝对路径可以确定唯一资源
- 如:
http://localhost/day15/responseDemo2
/day15/responseDemo2
- 以
/
开头的路径为绝对路径
- 如:
-
规则:判断定义的路径是给谁用的?判断请求将来从哪儿发出
-
给客户端浏览器使用:需要加虚拟目录(项目的访问路径)
-
建议虚拟目录动态获取:
request.getContextPath()
<a> , <form> 重定向...
-
-
给服务器使用:不需要加虚拟目录
- 转发资源的时候所需要的路径
-
虚拟目录动态获取
//动态获取虚拟目录,放置以后修改服务器的虚拟目录之后,所有的路径都要修改,太麻烦
String contextPath = request.getContextPath();
//补充:简单的重定向方法,只需要输入资源路径即可
response.sendRedirect(contextPath + "/responseDemo2");
中文乱码问题
tomcat默认字符集是ISO-8859-1,有可能与浏览器的解码字符集不一样,所以会产生乱码,在获取流之前,设置该流的默认编码
解决方式:
- 获取浏览器的默认字符集,使浏览器和服务器的字符流编码一样,告诉浏览器,服务器发送的消息体数据的编码,建议浏览器在响应体使用该字符集解码
// 告诉浏览器,服务器发送的消息体数据的编码,建议浏览器使用该字符集解码
response.setHeader("content-type","text/html;charset=utf-8");
//1. 获取字符输出流
PrintWriter pw=response.getWriter();
//2. 输出数据
pw.write("<h1>你好 response</h1>");
也可以把上面那段代码简单写成:
//简单的形式,设置编码,是在获取流之前设置
response.setContentType("text/html;charset=utf-8");
实例:验证码
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("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int width = 100;
int height = 50;
// 创建一个对象,在内存中的图片(验证码图片)
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
// 美化图片
Graphics graphics = image.getGraphics(); // 获取画笔对象
// 填充颜色
graphics.setColor(Color.PINK);
graphics.fillRect(0, 0, width, height);
// 画边框
graphics.setColor(Color.BLUE);
graphics.drawRect(0, 0, width - 1, height - 1);
// 随机生成字符
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
for (int i = 1; i <= 4; i++) {
int index = random.nextInt(str.length());
char c = str.charAt(index);
// 写验证码
graphics.drawString(c + "", width / 5 * i, height / 2);
}
// 画干扰线
graphics.setColor(Color.green);
for (int i = 0; i < 10; i++) {
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);
}
// 将图片输出到页面上展示
ImageIO.write(image, "jpg", resp.getOutputStream());
}
}
<!-- checkcode.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>验证码</title>
<script>
/*
分析:
点击超链接或者图片,需要换一张
1. 给超链接和图片绑定单击事件
2. 重新设置图片的src属性值
*/
window.onload = function () {
//1. 获取图片对象
var img = document.getElementById("checkCode");
img.onclick = function () {
//加时间戳
var date = new Date().getTime();
img.src = "/MyResponse/checkCodeServlet?" + date;
};
var not_clear = document.getElementById("not_clear");
not_clear.onclick = function () {
//加时间戳
var date = new Date().getTime();
img.src = "/MyResponse/checkCodeServlet?" + date;
}
}
</script>
</head>
<body>
<img src="/MyResponse/checkCodeServlet" alt="" id="checkCode">
<a href="" id="not_clear">看不清?点击换下一张</a>
</body>
</html>
ServletContext
概念
ServletContext官方叫servlet上下文。服务器会为每一个工程创建一个对象,这个对象就是ServletContext对象。这个对象全局唯一,而且工程内部的所有servlet都共享这个对象。所以叫全局应用程序共享对象。
代表整个web应用,可以和程序的容器(服务器)来通信
获取
不管通过哪种方式获取这个对象,它始终是唯一的,因为服务器是唯一的:
- 通过request对象获取
request.getServletContext()
- 通过HttpServlet获取
this.getServletContext()
功能
获取MIME类型:
-
MIME类型 : 在互联网通信过程中定义的一种文件数据类型
-
格式: 大类型/小类型 text/html image/jpeg
-
获取:
String getMimeType(String file)
域对象:共享数据
setAttribute(String name,Object value)
getAttribute(String name)
removeAttribute(String name)
ServletContext对象范围:所有用户所有请求的数据,一般不用该方法,因为在服务器中非常占用内存,而且也不怎么安全
获取文件的真实(服务器)路径
- 方法:
String getRealPath(String path)
//1. 通过HttpServlet来获取
ServletContext context2 = this.getServletContext();
// 获取文件的服务器路径
String realPath = context2.getRealPath("/b.txt"); //web目录下的资源访问
System.out.println(realPath);
//File file=new File(realPath);
String c = context2.getRealPath("/WEB-INF/c.txt"); //WEB-INF目录下的资源文件访问
System.out.println(c);
String a = context2.getRealPath("/WEB-INF/classes/a.txt"); // src目录下的资源你访问
System.out.println(a);
实例:文件下载
需求:
- 页面显示超链接
- 点击超链接后弹出下载提示框
- 完成图片文件下载
分析:
-
超链接指向的资源如果能够被浏览器解析,则在浏览器中展示,如果不能解析则弹出下载提示框
-
任何资源都满足下载提示框
-
使用响应头来设置资源的打开方式:
context-disposition:attachment;filename=xxx
以附件打开
步骤:
- 定义页面,编辑超链接href属性,指向一个Servlet,传递资源的名称filename
- 定义Servlet
- 获取文件名称
- 使用字节输入流加载文件进内存
- 指定response的响应头:
context-disposition:attachment;filename=xxx
以附件打开 - 将数据写出到response输出流
存在的问题:
要注意解决中文乱码的问题,在下载提示框内,文件名称不会正常显示
解决思路:
- 获取客户端使用的浏览器版本信息
- 根据不同的客户端浏览器版本信息,设置filename的编码方式的不同
这里注意java 9版本以后Base64Encoder和Base64Decoder不再使用,替换为import java.util.Base64下的Encoder和Decoder,所以如果在Idea中导入import sun.misc.BASE64Encoder已经无法导入
import java.util.Base64;
import java.util.Base64.*;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class DownLoadUtils {
public static String getFileName(String agent, String filename) throws UnsupportedEncodingException {
if (agent.contains("MSIE")) {
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
} else if (agent.contains("Firefox")) {
// 火狐浏览器
Encoder base64Encoder = Base64.getEncoder();
filename = "=?utf-8?B?" + base64Encoder.encodeToString(filename.getBytes("utf-8")) + "?=";
} else {
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件下载</title>
</head>
<body>
<a href="/MyResponse/downloadServlet?filename=p1.jpg">图片一</a>
<a href="/MyResponse/downloadServlet?filename=文档.docx">word文档</a>
</body>
</html>
import cn.web.utils.DownLoadUtils;
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;
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.获取请求参数
String filename = req.getParameter("filename");
//2.使用字节流加载文件进内存
//2.1找到文件的服务器路径
ServletContext servletContext = this.getServletContext();
String realPath = servletContext.getRealPath("/img/"+filename);
// 2.2用字节流关联
FileInputStream fis = new FileInputStream(realPath);
//3. 指定response的响应头:`context-disposition:attachment;filename=xxx` 以附件打开
//3.1 设置响应头类型
String mimeType = servletContext.getMimeType(filename);
resp.setContentType(mimeType);
// 解决中文文件名问题
// 获取user-agent请求头
String agent = req.getHeader("user-agent");
// 使用工具类方法编码文件名
filename = DownLoadUtils.getFileName(agent, filename);
//3.2 设置响应头打开方式:content-disposition
resp.setHeader("content-disposition", "attachment;filename=" + filename);
//4. 将数据写出到response输出流
ServletOutputStream sos = resp.getOutputStream();
byte[] bytes = new byte[1024 * 8];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
sos.write(bytes, 0, len);
}
fis.close();
}
}