1. HTTP协议概述
HTTP协议全称为超文本传输协议,所谓超文本,就是指可以传输文本及其他格式的数据,如音乐,图片,视频等,是一种被广泛应用的应用层协议
对于应用层协议的解释:将数据从A端传输到B端,TCP/IP协议对应的功能是顺丰的功能,但是两端还要对数据进行加工处理或使用,所以还需要一层协议,不必关心通信时的细节,只关心应用,这层协议就是应用层协议
我们平时打开的网站就是通过HTTP协议来传输数据的,HTTP协议是基于传输层TCP协议实现(HTTP1.0,HTTP1.1,HTTP2.0都是基于TCP,HTTP3.0基于UDP实现),我们此处所讨论的以HTTP1.1为主
对于在浏览器访问一个资源(网页,图片,视频等)来说,就是基于HTTP数据包的格式,从主机A的进程传输到主机B的进程
如:在浏览器输入百度的网址(URL)时,浏览器向百度服务器发送了一个HTTP请求,百度的服务器给我们浏览器返回了一个HTTP响应,响应被浏览器解析后,就展示为页面内容
2. HTTP协议的工作过程
HTTP协议的工作过程也就是客户端与服务端交互的过程(网络通信),这里的客户端指浏览器进程,服务端指web服务器进程
如:在浏览器输入一个网址,浏览器向给对应服务器发送HTTP请求,对方收到这个请求后进行处理,处理完后返回一个HTTP响应
通常访问一个网站的时候,涉及到多次HTTP请求和响应的交互过程,可以通过浏览器的开发者工具的网络标签页,刷新页面查看详细过程
说明:百度搜索的页面是通过HTTPS协议来进行通信的,HTTPS是在HTTP及基础上做了一个加密解密的过程,在后续文章中介绍
3. 使用抓包工具观察HTTP协议格式
HTTP协议是一个文本格式的协议,可以使用抓包工具Fiddler进行抓包,以此来分析HTTP请求和响应的细节
3.1 Fiddler抓包工具
Fiddler抓包工具的使用
附上下载地址:Fiddler抓包工具下载地址,需要的小伙伴可以去下载哟!安装过程一路next即可
左侧窗口显示了所有的HTTP请求/响应,可以选中某个查看详情
右侧上方显示了HTTP请求报文的内容(Raw标签可以查看详细的数据格式)
右侧下方显示了HTTP响应报文的内容(Raw标签可以查看详细的数据格式)
抓包工具的原理
Fiddler相当于一个代理,当浏览器访问baidu.com时,就会把HTTP请求先发给Fiddler,Fiddler再把请求转发给baidu的服务器,baidu的服务器返回响应时,也是先把数据发到Fiddler,Fiddler再将数据转发给浏览器,所以Fiddler对浏览器和服务器交互的细节否是非常清楚的
上述原理相当于:代理就可以简单理解为一个跑腿小弟,你想买罐冰可乐,又不想自己下楼去超市,那么就可以把钱给你的跑腿小弟,跑腿小弟来到超市把钱给超市老板,再把冰可乐拿回来交到你手上,这个过程中,这个跑腿小弟对于 "你" 和 "超市老板" 之间的交易细节,是非常清楚的
抓包结果
HTTP请求:
HTTP响应:
3.2 HTTP协议格式
协议格式总结
HTTP请求:
首行:请求方法+url+协议版本号
Header头:请求的属性,为多个用冒号分割的键值对,每个键值独占一行
空行:表示Header头的结束
Body:空行后面的内容都是Body,Body允许空,如果Body存在,则在Header头中有一个Content-Length的属性来标识Body的长度
HTTP响应:
首行:协议版本号+响应状态码+状态码解释
Header头:请求的属性,为多个用冒号分割的键值对,每个键值独占一行
空行:表示Header头的结束
Body:空行后面的内容都是Body,Body允许空,如果Body存在,则在Header头中有一个Content-Length的属性来标识Body的长度
为什么HTTP报文中要存在空行?
HTTP协议并没有规定Header头有多少个键值对,空行就是相当于Header头结束的标记,也就是报头和正文的间隔
HTTP在传输层依赖TCP协议,TCP是面向字节流的,如果没有空行,就会出现“粘包问题”
Body是任意格式的数据,如何解析?
Header头中有两个字段:Content-Length,Content-Type
Content-Length:标识Body的长度(字节长度)
Content-Type:标识Body的数据格式,目的是告诉对方如何解析body
Content-Type的常用格式:
application/x-www-form-urlencode:表单提交的格式,键值对的形式,键=值,多个键值对用&间隔(与queryString的格式一样),只能是简单类型(数值,字符,boolean等)
image/jpeg:指定具体的一个文件类型,客户端发送请求只能上传一个文件,服务端返回相应只能返回一个图片
text/javascript,text/css,text/html
application/json
multipart/form-data:简称form-data格式,一般用于请求,不用于响应,可以发送多个字段,每个字段可以是简单类型(数值,字符,boolean等),也可是复杂类型(图片,视频等)
请求正文的格式常用表单格式,图片,视频等文件格式,都是用来上传数据到服务端
响应正文的格式常用的是text/javascript,text/css,text/html,来返回网页,css样式文件,js文件,客户端使用这些文件将图片渲染出来,播放视频,下载文件等
application/json格式,请求和响应都常用,对于请求就是输入内容提交到服务端,对于响应就是服务端返回一些数据,客户端js代码获取到响应数据后,然后填充到html中
4. 解析HTTP请求
4.1 URL
URL标识网络中某个资源的路径,俗称网址,互联网上每个文件都有一个唯一的URL
URL的格式:协议名://服务器地址:服务器端口号/带层次的资源路径?查询字符串
协议名:常见的有http,https
服务器地址:可以使用IP地址或域名,IP地址不方便记忆,使用域名更方便,而且更换服务器后,只需要将域名绑定新的IP,域名还可以使用
服务器端口号:当端口号省略时,浏览器会根据协议的类型自动决定使用哪个端口,http协议默认使用80端口,https协议默认使用443端口
带层次的资源路径:标识某个服务器中的某个资源路径,如果没有输入资源路径,就默认访问 /(称为某个web应用的根路径)
查询字符串(queryString):用=分割的键值对,键=值,多个键值对用&分割,它的作用是获取不同条件下的资源,如userId=10,当使用不同的userId时,会获取到不同的user信息
ping命令的简单使用
使用ping命令查看域名对应的IP地址:
在开始菜单输入cmd,打开命令提示符
在cmd中输入ping www.baidu.com,即看到域名解析的结果
URL中可以省略的部分
协议名:可以省略,省略后默认为http://
IP地址/域名:在HTML中可以省略(如img,link,script,a标签的src或者href属性),省略后表示服务器的IP地址/域名与HTML所属的IP地址/域名一致
端口号:可以省略,http协议默认端口80,https默认端口443
带层次的资源路径:可以省略,省略后相当于/,有些服务器发现/路径时自动访问/index.html
查询字符串:可以省略
URL encode
如果URL中包含特殊字符如中文,空格等都会转义,转义后再放在http数据包中,然后在发送http请求,浏览器的地址栏中还是显示中文,但是真实发送的http数据包是已经转义过的内容
urlencode(url编码):将url里的中文,空格等转换为16进制数据
urldecode(url解码):将url里的16进制数据转换为原始的空格,中文等
获取URL时,就得注意,因为可能获取的是编码后的内容,可能需要解码,如在后端获取到URL需要解码,在JS中,img.src=="xxx",如果xxx有中文,可能达不到预期结果,因为img.src保存的是编码后的内容
4.2 请求方法
请求方法标识具体使用什么方式来操作资源,如:获取资源,保存资源,修改资源,删除资源,属于操作资源的类型
说明:这里只是规范上的约定,具体服务端代码中要怎样实现,由程序员自己决定,GET,POST方法最常用,其他方法了解就行
4.2.1 GET方法
GET方法常用于获取服务器资源,在浏览器中输入URL,浏览器会向服务器发送一个GET请求(浏览器输入URL默认是GET方法),使用JavaScript中的ajax也能构造GET请求
GET请求的特点:
首行的方法为GET
URL的queryString可以为空,也可以不为空,数据一般存放于queryString中
body一般为空
4.2.2 POST方法
POST方法常用于将用户输入的数据提交到服务端(如登陆功能),通过HTML中的form标签能构造POST请求,使用JavaScript中的ajax也能构造POST请求
POST请求的特点:
首行的方法为POST
URL的queryString一般为空
body一般不为空,数据一般保存在body中
4.2.3 经典面试题:GET与POST的区别?
语义:GET一般用于获取服务端资源,POST一般用于提交数据到服务端
存放数据位置:GET一般存放数据在queryString中,POST一般存放数据在body中
幂等性:GET具有幂等性,POST不具有幂等性,所谓幂等性是指多次发送http相同的数据包,得到的结果一样
缓存:GET可以被缓存,POST不能被缓存,浏览器为了提高性能,把GET获取的资源提前保存在本地,下次请求直接从本地获取
4.2.4 其他方法
PUT:与POST相似,只是具有幂等特性,一般用于更新
DELETE:删除服务器指定资源
OPTIONS:返回服务器所支持的请求方法
HEAD:类似于GET,只不过响应体不返回,只返回响应头
TRACE:回显服务器端收到的请求,测试的时候会用到这个
CONNECT:预留,暂无使用
这些方法的HTTP请求也可以使用JavaScript的ajax来构造
4.3 请求报头(Header)
Header头标识数据包属性,格式为用冒号分割的键值对,键:值,每个键值对独占一行
Host:标识服务器主机的地址(域名或IP+端口)
Content-Length:标识Body长度,对方根据这个属性来解析
Content-Type:标识Body的数据格式
User-Agent:简称UA,标识浏览器和操作系统的信息,常用作判断是哪个浏览器,pc,手机
Referer:标识这个页面是从哪个页面跳转过来的
Cookie:用于请求头,浏览器自动携带本网站在本地保存的Cookie信息
Set-Cookie:用于响应头,服务端设置信息
理解登录过程
重点说明:
登陆成功后,服务端返回的响应中响应头有一个Set-Cookie属性,该属性意味着告诉浏览器要将这些信息存起来,浏览器将收到响应,将Set-Cookie的值存在本地中,客户端浏览器后续访问该网站的其他页面时,发送的HTTP请求中,请求头携带一个Cookie属性,该属性就保存了登陆的一些相关信息
4.4 Cookie和Session(面试常考)
4.4.1 Cookie
Cookie是一种客户端保存数据的技术
如何保存?
服务端响应的http数据包中,设置Set-Cookie头,客户端收到响应后将此信息保存在本地,Cookie是和网站关联,不同的网站有不同的Cookie(保存的信息如账号等不同)
如何使用?
浏览器在每次请求时,自动将保存的信息携带在Cookie头中
保存的数据是什么格式?
多组键值对(键=值,多个键值对用分号间隔)
4.4.2 Session
Session是一种服务端保存会话的技术,一次会话指登陆没有注销或者超时
由于HTTP协议是无状态的,所谓无状态,就是一次请求,一次响应,服务端无法感知之前登陆的用户,所以在服务端使用Map的数据结构来保存用户信息
4.4.3 Cookie和Session是如何一起工作的?
以登陆功能举例:
服务端校验账号密码成功后,生成一个随机字符串(sessionId,标识用户身份)及一个Session对象(标识用户的该次对话),把sessionId作为键,Session对象作为值存入Map如果需要保存用户信息就保存在Session对象Map中,相当于登陆时服务端使用Session保存用户信息
登陆响应,服务端返回给客户端的HTTP响应数据包中,Set-Cookie响应头包含sessionId=xxx
客户端收到响应后,保存Cookie信息,将响应的Set-Cookie中的内容保存在客户端本地(和此次服务器地址绑定)
客户端每次请求时,都携带sessionId=xxx在Cookie头中
服务端获取客户端请求时,先获取Cookie请求头中的内容,查找sessionId对应的值,然后从保存的Map结构中查找,如果存在就是登录用户,如果为null就是未登录
4.4.2 Cookie和Session的过期校验
Session的过期校验
服务端保存的Session信息有默认的过期时间(可通过程序设置)
服务器有Session的过期校验机制:通过单独的线程扫描,发现当前时间和Session最后一次使用的时间超时就删掉
服务器存放Session的地方,web服务器默认是存放在内存中,所以重启服务器Session也就没了,但是有些服务器把数据保存在服务器硬盘,重启就还有
如果用户注销登录,相当于服务端删除Map中的Session
所以超时后,注销后,重启服务器后需要访问页面就需要重新登陆
Cookie的过期校验
Cookie也有过期时间(可以通过程序设置)
如果Cookie过期,浏览器发请求时就不会携带这些信息,服务端验证sessionId时就会验证失败,也就是没有登陆
如果在客户端手动删除Cookie,就相当于服务端还有Session信息,但是客户端请求时也不会携带Cookie信息,服务端验证sessionId失败,也就意味没有登陆
5. 解析HTTP响应
5.1 HTTP响应状态码
状态码由三位数字构成,表示访问一个页面的结果,HTTP响应报文由服务端返回(程序可以设置内容),状态码也可以由程序设置
常见状态码
200:表示服务端对当次请求处理成功
404 Not Found:找不到请求路径url对应的资源
304 Not Modified:表示之前访问过的资源,本次请求时没有被修改过,也就是客户端直接从缓存中获取
403 Forbidden:表示访问被拒绝,一般是没有访问权限,没有登陆时就访问就会出现403
405 Method Not Allowed:方法不支持,检查前端请求方法也要检查后端的请求方法
500 Internal Server Error:服务器内部错误,要检查后端控制台异常堆栈信息
504 Gateway Timeout:请求在服务端处理超时,服务端也有返回响应的时间限制,发现处理时间超时就返回504
302 Move temporarily:临时重定向,响应报文的header部分会包含一个Location字段,表示要跳转到哪个页面
301 Moved Permanently:永久重定向,301也是通过Location字段来表示要重定向到的新地址
状态码总结
类别 |
原因 | |
1xx | Informational(信息性状态码) | 接收的请求正在处理 |
2xx | Success(成功状态码) | 请求正常处理完毕 |
3xx | Redirection(重定向状态码) | 需要进行附加操作完成请求 |
4xx | Client Error(客户端错误状态码) | 服务器无法完成请求 |
5xx | Server Error(服务器错误状态码) | 服务器处理请求出错 |
5.2 响应报头(Header)
响应报头的格式与请求报头的格式基本一致,也就是Content-Type,Content-Length等属性含义也和请求中含义相同
响应请求头中可能会有Location属性,指重定向跳转的路径
Content-Type
响应中的Content-Type常见取值有以下几种:
text/html:body数据格式是HTML
text/css:body数据格式是CSS
text/javascript : body数据格式是JavaScript
application/json:body数据格式是JSON
6. 构造HTTP请求
6.1 form表单构造HTTP请求
6.1.1 form表单的介绍
form(表单)是HTML中的一个常用标签,可用于给服务端发送GET或者POST请求
form的重要参数
action:构造HTTP请求的URL
method:构造HTTP请求的方法(GET或POST,form只支持GET或者POST)
input的重要参数
type:表示输入框的类型,text表示文本,password表示密码,submit表示提交
name:对于GET请求来说,表示构造出的HTTP请求的queryString的key,queryString的value就是输入框输入的内容,对于POST请求来说,数据从queryString转移到了body中
value:input标签的值,对于submit来说,value对于按钮上显示的文本
6.1.2 form表单构造GET请求
<body> <!-- form表单,action为请求的url,method为请求的方法 --> <form action="http://abc.com" method="GET"> <!-- name作为键,内容作为值,多个键值对用&间隔 --> <input type="text" name="username"> <Input type="password" name="password"> <input type="submit" value="提交"> </form> </body>
页面效果:
点击提交就会构造出GET请求并发出去
体会form代码和HTTP请求之间的关系:
6.1.3 form表单构造POST请求
与构造GET请求不同的是只需将form的method由GET修改为POST
<body> <!-- form表单,action为请求的url,method为请求的方法 --> <form action="http://abc.com" method="POST"> <!-- name作为键,内容作为值,多个键值对用&间隔 --> <input type="text" name="username"> <Input type="password" name="password"> <input type="submit" value="提交"> </form> </body>
页面效果:
点击提交就会构造POST请求并发出去
6.2 Ajax构造HTTP请求
从前端角度,除了浏览器地址栏能构造GET请求,form表单能构造GET和POST之外,还可以在 JavaScript中可以通过ajax的方式构造HTTP请求,并且功能更强大
6.2.1 为何使用ajax构造HTTP请求?
使用表单提交数据,URL会改变,相当于跳转到另一个页面,假如想实现页面局部内容的改变(如发送HTTP请求,用响应返回的数据生成一些内容),这个过程页面不会刷新,就要使用ajax技术
使用ajax的好处:
不刷新页面就可以发送HTTP请求,用户体验更好
相同的一个页面,动态的通过响应数据来生成局部的页面内容,如果是服务端直接返回变化后的HTML,这时数据传输量比较大,效率较低,但是使用ajax,效率较高,因为响应的数据量只有变化的数据
ajax产生的原因:
JS是单线程运行(代码一行一行的运行,不能基于多线程的方式一次运行多行代码)
ajax异步:发送ajax请求后,后边的JS代码还可以继续执行,在ajax事件发生后,由系统内核来通知执行ajax的回调函数
6.2.2 ajax构造GET请求
<script> //XMLHttpRequest对象就是ajax发送请求及处理响应的对象 let xhr = new XMLHttpRequest(); //设置一个异步回调函数到ajax对象的属性中 //发送http请求,对应事件发生,才会调用回调函数 xhr.onreadystatechange = function(){ //xhr.readyState属性: //0:请求未初始化,还没有发生http请求 //1:客户端和服务器已经建立连接 //2:服务端已经接收请求 //3:服务端已经处理请求 //4:客户端已经收到服务端返回的响应 if(xhr.readyState == 4){ //响应状态码 console.log(xhr.status); //响应正文 console.log(xhr.responseText); } //open,设置请求方法和url,此时还没有发生http请求 xhr.open("GET", "http://42.192.83.143:8089/AjaxMockServer/info"); //send,正式发送http请求,也可以设置请求正文(body)数据 xhr.send();//send(),send(body)两种方式 } </script>
6.2.3 ajax构造POST请求
对于 POST 请求,需要设置 body 的内容:
先使用setRequestHeader设置Content-Type
再通过send的参数设置body内容
<script> //XMLHttpRequest对象就是ajax发送请求及处理响应的对象 let xhr = new XMLHttpRequest(); //设置一个异步回调函数到ajax对象的属性中 //发送http请求,对应事件发生,才会调用回调函数 xhr.onreadystatechange = function(){ //xhr.readyState属性: //0:请求未初始化,还没有发生http请求 //1:客户端和服务器已经建立连接 //2:服务端已经接收请求 //3:服务端已经处理请求 //4:客户端已经收到服务端返回的响应 if(xhr.readyState == 4){ //响应状态码 console.log(xhr.status); //响应正文 console.log(xhr.responseText); } //open,设置请求方法和url,此时还没有发生http请求 xhr.open("POST", "http://42.192.83.143:8089/AjaxMockServer/info"); //设置请求头,设置body数据格式 xhr.setRequestHeader("Content-Type", "application/x-www-formurlencoded") //send,正式发送http请求,也可以设置请求正文(body)数据 xhr.send("username=abc&password=123");//send(),send(body)两种方式 } </script>
6.2.4 封装Ajax函数
原生的XMLHTTPRequest类使用并不方便,我们可以在这个基础上进行简单封装
<script> //封装ajax函数,args为一个js对象 //args对象属性如下: //method:请求方法,url:请求资源路径,contenType:请求正文格式 //body:请求正文,callback:回调函数,客户端接收到响应数据后调用 function ajax(args){ 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可以判断是否有值 xhr.setRequestHeader("Content-Type", args.contentType); } //如果args中body有内容,设置body请求正文 if(args.body){ xhr.send(args.body); }else { xhr.send(); } } </script>
使用封装的ajax函数构造GET请求
直接调用封装好的ajax函数,传入args对象即可
ajax({ method: "GET", url: "http://42.192.83.143:8089/AjaxMockServer/info", callback: function(status,responseText){ console.log(status+responseText); } });
使用封装的ajax函数构造POST请求
直接调用封装好的ajax函数,传入args对象即可
ajax({ method: "POST", url: "http://42.192.83.143:8089/AjaxMockServer/info", contentType: "application/x-www-formurlencoded", body: "username=abc&password=123", callback: function(status,responseText){ console.log(status+responseText); } });
6.2.5 ajax的跨域问题
请求当前html页面路径中的服务器地址(ip/域名+端口号)和使用ajax请求的服务器地址不同,就是跨域,跨域问题是ajax中存在的(和html,js,服务器)无关,也就是服务端还能接收到这个请求,并返回响应,但是ajax为了安全起见,如果发现响应头中没有设置允许跨域的信息,就会报错
如果想要强行跨域,需要服务器进行配合,需要在服务器的响应中设置“允许跨域”