HTTP协议
文章目录
1.什么是HTTP
HTTP全称为超文本传输协议,是一种应用非常广泛的应用层协议
所谓"超文本"的含义,就是传输的内容不仅仅是文本,还可以是一些其他的资源,比如图片,视频,音频等二进制的数据
HTTP往往是基于传输层的TCP协议实现的,例如HTTP1.0,HTTP1.1,HTTP2.0 ,HTTP3.0基于UDP实现
当我们在浏览器中输入一个搜狗搜索的"网址"(URL)时,浏览器就给搜狗的服务器发送了一个HTTP请求,搜狗的服务器返回了一个HTTP响应
这个响应结果被浏览器解析之后,就展示成我们看到的页面内容,这个过程中浏览器可能会给服务器发送多个HTTP请求,服务器会对应返回多个响应,这些响应里面就包含了页面HTML,DCC,JavaScript,图片,字体等信息
如何理解应用层协议
应用层协议关心数据具体内容,为了解决实际问题(编程),包括协议请求要传输哪些信息,信息按照什么样的格式传输,服务器返回的响应要包含哪些信息,信息按照什么样的格式传输,这些是应用层协议需要关心的事情,不需要关心数据怎么传输,传输层,网络层,数据链路层,物理层,这下四层只关心数据的传输,而不关心数据是什么,HTTP就是现成的应用层协议,它已经规定好HTTP请求(响应)包含哪些信息,格式是什么,
HTTP协议的工作过程
网络通信中涉及到客户端和服务器,请求和响应
客户端和服务器之间的沟通存在多种模型
1、一发一收
客户端发送一个请求,服务器返回一个响应,请求和响应是一一对应的。 HTTP协议就是一发一收模型
当我们在浏览器中输入一个"网址",此时浏览器就会给对应的服务器发送一个HTTP请求,对方服务器接收到这个请求之后,经过计算处理,就会返回一个HTTP响应
2、多发一收
客户端发送多个请求,服务器返回一个响应,多个请求对应一个响应,例如:上传大文件。会将大文件分成几个部分,包装成多个请求,进行上传,上传完毕后服务器返回一个响应,上传完毕
3、一发多收
客户端发送一个请求,服务器返回多个响应,一个请求对应多个响应。例如:看直播,看开一个直播间就是发送一个看直播的请求,服务器就会返回直播的内容,就是多个响应
4、多发多收
客户端发送多个请求,服务器返回多个响应。多个请求对应多个响应
2. HTTP协议格式
使用Fiddler抓包工具(只抓HTTP协议的抓包工具),来分析HTTP请求和响应
例如打开CSDN网页
- 左侧窗口显示了所有的HTTP请求/响应,可以选中某个请求查看详情,此时选择的是左侧一个蓝色的HTTP请求,并且它的body值最大,这条HTTP请求,就是获取CSDN网页的请求,
- 右侧上方有个inspectors标签,点击这个分析器标签,就可以打开这个HTTP的请求和响应的格式
- 右上方窗口显示了HTTP请求的报文内容,切换到Raw标签页可以看到详细的数据格式
- 右下方窗口显示了HTTP响应的报文内容,切换到Raw标签页可以看到详细的数据格式
可以看到获取CSDN网页的请求类型是HTTPS,HTTPS也是基于HTTP的一种应用层协议,只是在HTTP基础上进行了加密,Fiddler会自动进行解密,还原成初始的HTTP
2.1 抓包工具的原理
Fiddler相当于一个"代理"
当浏览器访问csdn.net时,就会把HTTP请求先发给Fiddler,然后Fiddler将请求发送给CSDN服务器,CSDN服务器处理请求返回响应时,会将响应发送给Fiddler,Fiddler再将响应发给浏览器,因此Fiddler对于浏览器和CSDN服务器之间的交互细节非常清楚
2.2 抓包结果
HTTP请求
- 第一行红色框标注的是首行,首行包括方法,URL,版本号 这三个之间用空格隔开
- 绿色框标注的是请求头(Header),包含了一些属性,每个属性都是一个键值对,键与值之间使用冒号加空格分割,键值对之间使用换行分割,
- 蓝色标注框,是一个空行,这个空行用来标注请求头部分结束
- 光标处在最后一行的红色标注,是body,也就是请求内容,body可以为空,如果body存在,那么请求头中就会有Content-Length属性来标识body的长度,Content-type属性标识body的类型
HTTP响应
响应和请求的格式类似
- 首行包括版本号,状态码,状态码的描述,三者用空格分割
- 绿色标注框表示响应头(Header),其中的属性都是键值对,键与值用冒号加空格分割,键值对之间用换行分割
- Header下面有一个空行,空行用来表示Header部分结束,
- 红色标注框表示响应的内容(body),这个HTTP请求是获取CSDN网页,所以响应的内容可以看到是HTML页面,而且我们可以从Header的属性中看到Content-Length来标识body的长度,Content-type标识body的类型
2.3 协议格式小结
HTTP报文中为什么要存在"空行"
因为HTTP协议并没有规定报头部分的键值对有多少个,空行相当于是"报头的结束标记",或者是"报头和正文之间的分隔符",HTTP在传输层依赖TCP协议,TCP是面向字节流的,如果没有这个空行,就会出现"粘包问题"
3. HTTP请求
3.1 URL
URL全称:Uniform Resource Locator 统一资源定位符,URL就是平时所说的"网址",互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它
URL基本格式
一个具体的URL
user:pass:登陆信息,现在网站进行身份认证基本上不通过URL进行,一般都会省略
端口号:当端口号省略时,浏览器会根据协议类型自动决定使用哪个端口,http协议默认使用80端口,https协议默认使用443端口
/web:带层次的文件路径,表示访问服务器上的不同的资源
片段标识:主要用于页面内跳转,
URL中可以省略的部分
- 协议名:可以省略,省略后默认为http://
- ip地址/域名:在HTML中可以省略,(例如img,link,script,a标签中的src或者href属性),省略后表示服务器的ip/域名和当前HTML所属的ip/域名一致
- 端口号:可以省略,当端口号省略时,浏览器会根据协议类型自动决定使用哪个端口,http协议默认使用80端口,https协议默认使用443端口
- 带层次的文件路径:可以省略,省略后相当于/. 有些服务器会在发现/路径的时候自动访问/index,html
- 查询字符串:可以省略,是浏览器给服务器传递的一些参数,使用键值对组织,查询字符串中每个键值对表达的意思,只有设计网站的程序员知道,这些都是程序猿自己规定的
- 片段标识:可以省略
URL encode
像 / ? : 这样的字符,已经被URL当作特殊意义理解了,因此这些字符不能随意出现,例如:query string中需要带有这些特殊字符,就必须先对特殊字符进行转义
如果query string中包含了上述的这些特殊字符,就可能导致浏览器/服务器解析URL时出错
一个中文字符由UTF-8或者GBK这样的编码方式构成,虽然在URL中没有特殊含义,但是仍然需要进行转义,否则浏览器可能把UTF-8/GBK编码中的某个字节当作URL中的特殊符号
转移规则:将需要转码的字符转成16进制,然后从右到左,取4位,不足4位直接处理,每两位做一位,前面加上%,编码成%XY格式
例如搜索C++,浏览器就自动将+转义成%2B
URL decode 就是 URL encode的逆过程
3.2 方法
GET方法
GET是最常用的HTTP方法,常用于获取服务器上的某个资源
触发GET请求的方式:
- 在浏览器中直接输入URL,此时浏览器就会发送出一个GET请求
- HTML中的 link,img,script,a等标签也会触发GET请求
- form表单
- ajax
- 使用Java代码/其他的库
- 通过linux下的wget/curl
- 通过第三方工具 postman这类工具
使用Fiddler观察GET请求
打开Fiddler,访问搜狗主页,观察抓包结果
最上面的200 HTTPS www.sogou.com / 是通过浏览器地址栏发送的GET请求
有些是通过HTML中的link/script/img标签产生的,例如
有些是通过ajax的方式产生的,例如
选中第一条HTTPS观察请求的详细结果
GET请求的特点:
- 首行的第一部分位GET
- URL的query string可以为空,也可以不为空
- headr部分有若干个键值对
- body部分为空,通常清空下,GET请求的body部分为空,也可以自己构造一个body不为空的GET请求
对于GET请求中的URL长度没有任何限制,实际上URL的长度取决于浏览器的实现和HTTP服务器的实现,在浏览器端,不同的浏览器最大长度是不同的,现在的浏览器支持的长度一般都很长,在服务器端,一般这个长度是可以配置的
POST方法
POST方法也是一种常见的方法,多用于提交用户输入的数据给服务器,例如登录页面(form表单)
触发POST请求的方式
- form表单
- ajax
- 第三方工具
使用Fiddler观察POST方法
在云班课登陆页面,输入账号密码之后,就可以看到POST请求
POST请求的特点:
- 首行的第一部分为POST
- URL的query string一般为空,也可以不为空
- header部分有若干个键值对,以空行为结束标识(绿色框)
- body部分一般不为空,body内的数据格式通过header中的Content-Type指定,body的长度由header中的Content-Length指定,
此时我们可以看到body的内容是在云班课登陆页面输入的账号和密码,数据格式在header中指定:application/json,长度:52
经典面试题:GET和POST的区别
- 语义不同:GET一般用于获取数据,POST一般用于提交数据;GET也可以用于提交数据,POST也可以用于获取数据
- 数据位置不同:GET的数据一般通过在URL的query string传递(也可以放在body中传递),POST的数据一般通过body传递(也可以放在URL的query string中传递),
- GET的body一般为空,也可以不为空;POST的query string一般为空,也可以不为空
- GET请求一般是幂等的,POST请求一般是不幂等的。标准建议GET实现为幂等的,实际开发中GET也不必完全遵守这个规则,POST也可以设置为幂等的(幂等:如果多次请求得到的结果一样,就视为请求是幂等的)
- GET可以被缓存,POST不能被缓存,同样也可以通过设置来改变
- 事实上,GET和POST没有本质上的区别,都是一些习惯用法的区别
关于安全性:是否安全取决于前端在传输密码等敏感信息时是否进行加密,和GET,POST无关;例如GET一般将数据放在URL的query string中,这样用户就可以直接通过URL看到密码,POST一般将数据放在body中,但是可以通过抓包工具也能看到数据内容,
关于传输数据量:标准中没有规定GET的URL的长度,也没有规定POST的body的长度,传输数据量多少,完全取决于不同浏览器和不同服务器之间的实现区别
关于传输数据类型:GET,POST都可以传输文本数据,POST也可以传输二进制数据。GET的query string 虽然无法直接传输二进制数据,但是可以针对二进制数据进行url encode,并且GET也可以将数据放在body中,通过这两个方法,GET请求也可以传输二进制数据。所以针对传输数据类型,GET和POST没有区别,都可以传输文本数据和二进制数据
其他方法
- PUT与POST相似,只是具有幂等特性,一般用于更新
- DELETE 删除服务器指定资源
- OPTIONS 返回服务器所支持的请求方法
- HEAD类似于GET,只不过响应体不返回,只返回响应头
- TRACE 回显服务器端收到的请求,测试的时候会用到这个
- CONNECT预留,暂无使用
4. 认识请求"报头"(header)
header的整体的格式是"键值对"结构
每个键值对占一行,键和值之间使用分号加空格分割
4.1 HOST
标识服务器主机的地址和端口
4.2 Content-Length
表示请求的body中的数据长度,单位是字节
4.3 Content-Type
表示请求的body中的数据格式
常见格式选项:
1、application/x-www-form-urlencoded: form 表单提交的数据格式. 此时 body 的格式形如: title=test&content=hello
2、multipart/form-data: form 表单提交的数据格式(在 form 标签中加上enctyped=“multipart/form-data” . 通常用于提交图片/文件. body 格式形如:
Content-Type:multipart/form-data; boundary=----
WebKitFormBoundaryrGKCBY7qhFd3TrwA
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"
title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png
PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--
3、application/json: 数据为 json 格式. body 格式形如:
{"username":"123456789","password":"xxxx","code":"jw7l","uuid":"d110a05ccde64b16a861fa2bddfdcd15"}
4.4 User-Agent(UA)
表示浏览器/操作系统的属性,例如:
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36 Edg/101.0.1210.39
其中windows NT 10.0;Win64;64 表示操作系统信息
AppleWebKit后面的内容,表示浏览器信息
UA可以区分PC端和手机端
4.5 Referer
表示这个页面是从哪个页面跳转过来的
在搜狗搜索中输入"鲜花"进行搜索,通过Fiddler进行抓包,查看HTTP请求可以看到如下Referer
Referer: https://www.sogou.com/
表示这个页面是从搜狗搜索这个页面跳转过来的,
如果直接在浏览器中输入URL,或者直接通过收藏夹访问页面时,是没有Referer的
4.6 Cookie
Cookie是一个让浏览器能够存储信息的机制,考虑到安全性,浏览器是禁止网页直接访问计算机的磁盘的,但是在网页开发的时候,又需要能够在网页端保存一些数据,所以就要依赖Cookie
Cookie中存储了一个字符串,这个数据可能是客户端(网页)自行通过JS写入的,也可能来自于服务器(服务器在HTTP响应的header中通过Set-Cookie字段给浏览器返回数据),保存在网页这边的Cookie就会在后续的请求中,自动的带入到请求的报头(header)中
可以通过Cookid实现"身份标识"的功能;每个不同的域名下都可以有不同的Cookie,不同网站之间的Cookie不冲突
通过Fiddler抓包观察登陆页面的过程(以登录CSDN为例);首先要清除之前的cookie,再进行登录
请求
可以看到,首次登录时,请求的header中没有Cookie
响应
首次登录的响应中,有Set-Cookie字段,其中的属性值是一串加密后的信息,这个信息就是用户当前登录的身份标识,也称为"令牌"
后续再点击"个人中心时"(访问网站的其他页面时,请求中就会自动带上Cookie),查看请求:
可以看到,请求的header中已经包含首次登陆时服务器给浏览器返回的Cookie了
理解登录过程
5. 认识请求"正文"(body)
正文中的内容格式和header中的Content-Type密切相关,可以通过抓包来观察这几种情况
5.1 application/x-www-form-urlencoded
抓取码云上传头像请求
请求
在header中可以看到body中的格式类型为:Content-Type:application/x-www-form-urlencoded;body内容是一个键值对结构,avater就是头像,=后面就是图片的具体内容
5.2 multipart/form-data
主要用来上传文件(可以上传比较大的文件),形如这样:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8d5Rp4eJgrUSS3wT
body内容
------WebKitFormBoundary8d5Rp4eJgrUSS3wT
Content-Disposition:form-data;name="file";filename="test.pdf"
Content-Type:application/pdf
hello
------WebKitFormBoundary8d5Rp4eJgrUSS3wT
multipart/form-data表示body中的数据格式类型,boundary表示body的边界,值是一个随机的字符串,通过这个字符串,将body内容包裹起来,正文内容以它开始,以它结束
5.3 application/json
云班课登录的POST请求中,body内容的格式就是json
json格式以{}包裹,内部有若干个键值对,键值对之间用逗号分割,键和值用冒号分割
6. HTTP响应
6.1 状态码
状态码表示访问一个页面的结果,例如访问成功,访问失败,等等
常见状态码有以下几种
200 OK
这是一个最常见的状态码,表示访问成功
抓包抓到的把部分结果都是200,例如访问搜狗主页
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 10 Jun 2021 06:07:27 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Vary: Accept-Encoding
Set-Cookie: black_passportid=; path=/; expires=Thu, 01 Jan 1970 00:00:00
GMT; domain=.sogou.com
Pragma: No-cache
Cache-Control: max-age=0
Expires: Thu, 10 Jun 2021 06:07:27 GMT
UUID: 80022370-065c-49b0-a970-31bc467ff244
Content-Length: 14805
<!DOCTYPE html><html lang="cn"><head><meta name="viewport"
content="width=device-width,minimum-scale=1,maximum-scale=1,userscalable=
no"><script>window._speedMark = new Date(); window.lead_ip =
'1.80.175.234';
404 Not Found
没有找到资源
浏览器输入一个URL,目的就是为了访问服务器上的一个资源,如果这个URL标识的资源在服务器上不存在,就会出现404,
例如在浏览器中输入www.sogou.com/test.html 此时就在尝试访问sogou服务器上的/test.html这个资源,显然sogou服务器上大概率是没有这个资源的,所以就会出现404
403 Forbidden
表示访问被拒绝,有的页面通常需要用户具有一定的权限才能访问,也就是登录后才能访问,如果用户没有登录直接访问,就容易出现403
例如查看码云的私有仓库,如果不登陆,就会出现403,
405 Method Not Allowed
HTTP中所支持的方法有很多,但是服务器不一定全都支持,此时用户使用了服务器不支持的HTTP方法,就会出现405
500 Internal Server Error
服务器出现内部错误,一般是服务器的代码执行过程中遇到了一些特殊情况,服务器异常崩溃就会产生这个状态码
504 Gateway Timeout
当服务器负载比较大的时候,服务器处理单条请求的时候消耗的时间就会很长,就可能导致出现超时的情况
例如在双十一秒杀时
302 Move temporaily
临时重定向,重定向就是通过这个页面跳转到另一个页面,例如在我们登录CSDN时,输入账号密码登陆成功后,就会跳转到CSDN主页,响应报文的header部分会包含一个Location字段,表示要跳转到哪个页面
例如:码云的登录页面
可以看到登陆页面响应的首行中,状态码是403,并且在header中有一个Location字段,值就是码云的主页域名,表示要跳转到码云的主页,并且可以在左侧看到,在码云登录页面的HTTP请求下面,紧接着就出现了获取码云主页的HTTP请求
对于302这样的重定向响应来说,body不是必须的,这里的body就是一个简单的HTML,里面就只有一个a标签,万一浏览器没有自动跳转,那么用户手动点击这个a标签也可以跳转
301 Moved Permanently
永久重定向,当浏览器受到这种响应时,后续的请求都会被自动改成新的地址
301也是通过Location字段来表示要重定向到的新的地址
状态码小结
类别 | 原因 | |
---|---|---|
1XX | Informational(信息性状态码) | 接收的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求,(自动跳转页面) |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
6.2 认识响应"报头"(header)
响应报头的基本格式和请求报头的格式基本一致
类似于Content-Type , Content-Lenth等属性的含义和请求中的含义一致
Content-Type
响应中的Content-Type常见取值有以下几种:
- test/html:body数据格式是HTML
- test/css:body数据格式是CSS
- application/javascript:body数据格式是JavaScript
- application/json:body数据格式是JSON
在CSDN主页,ctrl+F5强制刷新,就能看到以上四种
- 第一个黑色字体的HTML请求对应的响应中的Content-Type的值为application/json
- 蓝色字体:test/html
- 绿色字体:application/javascript
- 紫色字体:application/css
6.3 认识响应"正文"(body)
正文的具体格式取决于Content-Type,观察上面几个抓包结果中的响应部分
test/html
可以看到body中数据格式类型是html
test/css
可以看到body中数据格式类型是css
application/javascript
可以看到body中数据格式类型是js
application/json
可以看到body中的数据格式类型是json
7. 通过form表单构造HTTP请求
form的重要参数:
- action:构造的HTTP请求的URL
- method:构造的HTTP请求的方法是GET还是POST(form只支持GET和POST)
7.1form发送GET请求
input的重要参数:
- type:表示输入框的类型,text表示文本,password表示密码,submit表示提交按钮
- name:表示构造出来的HTTP请求中query string 的key,输入框中用户输入的内容就是query string的value
- value:input标签的值,对于type为submit类型来说,value就对应了按钮上显示的文本
<body>
<form action="http://www.baidu.com">
账号<input type="text" name="账号">
密码<input type="password" name="密码" >
<input type="submit" value="提交">
</form>
</body>
在输入框随便输入后,点击提交按钮,就会构造HTTP请求并发送
构造的HTTP GET请求
可以看到URL的query string中就出现了我们输入的账号密码,
form代码和HTTP请求之间的关系
- form的action属性对应HTTP请求的URL
- form的method属性对应HTTP请求的方法
- input的name属性对应query string 的key
- 用户在input中输入的内容对应query string的value
7.2 form发送POST请求
<body>
<form action="http://www.baidu.com" method="POST">
账号<input type="text" name="账号">
密码<input type="password" name="密码" >
<input type="submit" value="提交">
</form>
</body>
构造的HTTP请求
可以看到通过form发送了POST请求,并且请求的body中包含了我们提交的数据,账号和密码,但是,当前直接给百度提交请求,百度返回的响应中状态码是302,header中Location的地址直接跳转到了出错页面,因为百度不支持像我们自己创建的"账号","密码"这样的参数。
8. 通过ajax构造HTTP请求
ajax是一种JavaScript给服务器发送HTTP请求的方式,特点是可以不需要刷新页面/页面跳转,就能进行数据传输
8.1 发送GET请求
创建test.html 在script标签中编写以下代码
<script>
// 构造一个XMLHttpRequest对象
let httpRequest = new XMLHttpRequest();
// 注册一个回调函数。处理HTTP响应,异步处理
httpRequest.onreadystatechange = function(){
// 0:请求未初始化
// 1:服务器连接已建立
// 2:请求已接收
// 3:请求处理中
// 4:请求已完成,且响应已就绪
if(httpRequest.readyState == 4){
// 打印响应状态码
console.log(httpRequest.status);
// 打印响应正文(body)
console.log(httpRequest.responseText);
}
}
// 构造一个HTTP请求
httpRequest.open('GET','http://42.192.83.143:8089/AjaxMockServer/info');
// 发送这个HTTP请求
httpRequest.send();
</script>
8.2 发送POST请求
对于POST请求,需要设置body内容
先使用setRequestHeader设置Content-Type,再通过send的参数设置body内容
发送application/x-www-form-urlencoded数据
<script>
let httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function(){
if(httpRequest.readyState == 4){
console.log(httpRequest.status);
console.log(httpRequest.responseText);
}
}
httpRequest.open('GET','http://42.192.83.143:8089/AjaxMockServer/info');
httpRequest.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
httpRequest.send('name=zhangsan&age=18');
</script>
发送application/json数据
httpRequest.setRequestHeader('Content-Type','application/json')
httpRequest.send({
name:'zhangsan',
age:'18'
});
8.3 使用jQuery
使用第三方库封装好的ajax方法来使用,
<!-- 引入jquery第三方库 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
$.ajax({
type: 'GET',
url: 'http://42.192.83.143:8089/AjaxMockServer/info',
success: function(data,status){
// status就是响应的描述信息,请求是否成功,data就是响应的body
console.log(status);
console.log(data);
}
});
</script>
在浏览器页面加载过程中,可以同时发起多个ajax请求,此时这多个ajax请求相当于是一个并发执行的关系
ajax为了保证安全性,要求发起ajax请求的页面和接收ajax请求的服务器,应该在同一个域名/地址下,默认情况下ajax不允许跨域
8.4 通过Java socket构造HTTP请求
发送HTTP请求,本质上就是按照HTTP的格式,往TCP Socket中写入一个字符串
接收HTTP响应,本质上就是从TCP Socket中读取一个字符串,再按照HTTP的格式来解析
构造一个简单的HTTP客户端程序:
public class HttpClient {
public String ip;
public int port;
public Socket socket;
public HttpClient(String ip, int port) throws IOException {
this.ip = ip;
this.port = port;
socket = new Socket(ip,port);
}
public String get(String url) throws IOException {
StringBuilder request = new StringBuilder();
//构造首行
request.append("GET"+ url + " HTTP/1.1 \n");
//构造header
request.append("HOST: "+ip+":"+port+"\n");
//空行
request.append("\n");
OutputStream outputStream = socket.getOutputStream();
//OutputStream是一个字节流,以字节为单位进行写入的,所以将刚才构造好的request转成byte[]进行发送
outputStream.write(request.toString().getBytes());
//读取响应
InputStream inputStream = socket.getInputStream();
//创建一个1M的缓冲区,用来存放响应数据
byte[] buffer = new byte[1024*1024];
//n表示实际读到的字节数
int n = inputStream.read(buffer);
return new String(buffer,0,n,"utf-8");
}
public String post(String url , String body) throws IOException {
StringBuilder request = new StringBuilder();
request.append("POST" + url + " HTTP/1.1\n");
request.append("HOST: "+ip+":"+port+"\n");
request.append("Content-Type: text/plain\n");
request.append("Content-Length: "+ body.getBytes().length+"\n");
request.append("\n");
request.append(body);
//发送请求
OutputStream outputStream = socket.getOutputStream();
outputStream.write(request.toString().getBytes());
//读取响应
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024*1024];
int n = inputStream.read(buffer);
return new String(buffer,0,n,"utf-8");
}
public static void main(String[] args) throws IOException {
HttpClient httpClient = new HttpClient("42.192.83.143",8089);
String response = httpClient.get("/AjaxMockServer/info");
System.out.println(response);
String resp = httpClient.post("/AjaxMockServer/info","这儿是POST请求的正文");
System.out.println(resp);
}
}
使用Java构造的HTTP客户端不再有"跨域"的限制了,此时也可以用来获取其他服务器的数据
跨域只是浏览器的行为,对于ajax有效,对于其他语言来说一般都和跨域无关
9. HTTPS
HTTPS也是一个应用层协议,只是在HTTP协议的基础上引入了一个加密层(SSL)
HTTP协议内容都是按照文本的方式明文传输的,这就导致在传输过程中出现一些被篡改的情况,由于我们通过网络传输的任何数据包都会经过运营商的网络设备(路由器,交换机等),那么运营商的网络设备就可以解析出你传输的数据内容,并进行篡改,不止运营商可以劫持,其他的黑客也可以用类似的手段进行劫持,来窃取用户隐私信息,或者篡改内容,在互联网上明文传输是比较危险的事情
HTTPS就是在HTTP的基础上进行了加密,来保证用户的信息安全
9.1 加密
加密就是把明文(要传输的信息)进行一系列的变换,生成密文,
解密就是把密文,再进行一系列的变换,还原成明文
在这个加密和解密的过程中,往往需要一个或者多个中间的数据,辅助进行这个过程,这样的数据称为密钥
9.2 HTTPS的工作过程
加密的方式有很多,整体可以分为两大类:对称加密和非对称加密
1. 引入对称加密
对称加密起始就是通过同一个"密钥",把明文加密成密文,也能把密文解密成明文
引入对称加密之后,即使黑客通过入侵路由器截获了数据,因为没有密钥,就无法对密文进行解密,也就不知道数据的真实内容了,
但是因为服务器是同一时刻给很多客户端提供服务的,这些客户端的密钥都不相同,所以服务器就需要维护每个客户端和每个密钥之间的关联关系,
比较理想的做法,就是在客户端和服务器建立连接的时候,双方协商确定这次的密钥是啥
但是如果直接把密钥明文传输,那么黑客也就能获得密钥了,那加密操作就毫无意义了,
所以密钥的传输也必须加密传输
但是想要对密钥进行对称加密,就仍然需要先协商确定一个"密钥的密钥",这就无限套娃了,
所以针对密钥加密需要引入非对称加密
2. 引入非对称加密
非对称加密需要用到两个密钥,一个叫"公钥",一个叫"私钥"
公钥和私钥是配对的,最大的缺点就是运算速度非常慢,比对称加密要慢很多,成本比对称加密高
通过公钥对明文进行加密,再通过私钥对密文进行解密。也可以反着用
- 客户端再本地生成对称密钥,通过公钥加密,发送给服务器
- 由于中间的网络设备没有私钥,即使截获了数据,也无法还原出内部的原文,也就无法获得对称加密的密钥
- 服务器通过私钥对密文进行解密,得到了对称加密的密钥,并且使用这个对称密钥将要给客户端返回的数据进行加密
- 后续客户端和服务器的通信都只用对称加密即可,由于该密钥只有客户端和服务器持有,其他主机/设备不知道密钥,即使数据被截获也没有影响
由于对称加密的效率比非对称加密高很多,因此只是在开始阶段协商密钥的时候使用非对称加密,后续的传输仍然使用对称加密,将对称加密和非对称加密进行结合,非对称加密用来针对"对称加密的密钥"进行加密,对称加密用来传输数据。这样既保证了安全性,降低了成本,也提高了效率
客户端如何获取到公钥呢?是在客户端和服务器连接建立之初,服务器将公钥发送给客户端,如果黑客在中间进行偷梁换柱,首先截获服务器发送给客户端的真正的公钥,再伪造一对公钥和私钥,私钥自己持有,公钥给客户端,这时客户端用这个公钥对于对称密钥进行加密,再发送给服务器时,黑客就可以用自己的私钥对密文进行解密,就知道了本次连接的对称加密的密钥,进而将这个对称密钥使用服务器真正的公钥进行加密,再发送给客户端。这时黑客知道了对称加密的密钥,对于客户端和服务器的数据交互就了如指掌了。这就是中间人攻击
3. 引入证书
为了解决中间人攻击问题,就可以在客户端和服务器刚一建立连接的时候,服务器给客户端返回一个证书,这个证书包含了公钥,也包含了网站的身份信息
这个证书可以理解成是一个结构化的字符串,包含:
- 证书发布机构
- 证书有效期
- 公钥
- 证书所有者
- 签名等
当客户端获取到这个证书之后,会对证书进行校验,防止证书是伪造的
- 判定证书的有效期是否过期
- 判定证书的发布机构是否受信任,(在操作系统中已内置的受信任的证书发布机构)
- 验证证书是否被篡改:从系统中拿到该证书发布机构的公钥,对签名解密,得到一个hash值,设为hash1,然后计算整个证书的hash值,设为hash2,对于hash1和hash2是否相等,如果相等,说明证书是没有被篡改过的
9.3 小结
HTTPS传输过程
- 客户端先从服务器获取证书,证书中包含了公钥
- 客户端对证书进行校验
- 客户端生成一个对称密钥,使用公钥对对称密钥进行加密,发送给服务器
- 服务器得到这个请求之后,使用私钥进行解密,得到对称密钥
- 客户端发出后续的请求,后续的请求都是使用这个对称密钥加密的
- 客户端服务器收到的数据也都是使用这个对称密钥进行解密的