HTML 协议与应用

HTML 协议介绍与常见应用

协议介绍

发送请求与spring集成

@RequestBody

该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上。再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上。
Spring Boot目前没有注解同时兼容form(application/x-www-form-urlencoded)与json两种请求格式。json格式用@RequestBody,form用的是@RequestParam。要解决这个问题有两种方法:

  1. 通过自己实现一个Converter去处理请求。
  2. javax.servlet.http.HttpServletRequest,通过原生的HttpServletRequest去捕捉参数,自行进行解析

当前台界面使用GET或POST方式提交数据时,数据编码格式由请求头的Content-Type指定。分为以下几种情况:

  1. application/x-www-form-urlencoded,这种情况的数据@RequestParam、@ModelAttribute可以处理。(参数通过url或者body传输,非json格式数据)
  2. multipart/form-data,@RequestBody不能处理这种格式的数据。(form表单里面有文件上传时,必须要指定enctype属性值为multipart/form-data,意思是以二进制流的形式传输文件。)
  3. application/json、application/xml等格式的数据, 必须 使用@RequestBody来处理。

@ResponseBody

该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。

返回的数据(如json、xml等)。

常用必备知识储备

Content-Type

application/x-www-form-urlencoded

最常见的 POST 提交数据的方式,浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。key 和 val 进行 URL 转码。表单数据编码为键值对,&分隔。如果是GET请求参数拼接在URL后面,如果是POST请求参数将会放在BODY中传递。Ajax提交数据,也可以使用这种方式。

multipart/form-data

我们使用表单上传文件时,必须让 form 的 enctyped 等于这个值

application/json

除了低版本IE外的浏览器基本都原生支持 JSON.stringify 。对于复杂嵌套对象可以自行进行转换成字符串传输

text/xml

客户端告诉服务器,我是用XML方式提交数据。还有个远程调用方案XML-RPC(XML Remote Procedure Call)

  • 用于告知服务端两个请求是否来自同一浏览器。Cookie 曾一度用于客户端数据的存储。

  • 服务器发送的响应报文包含 Set-Cookie 首部字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中

  • Domain 标识指定了哪些主机可以接受 Cookie

  • 标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用,避免跨站脚本攻击XSS攻击

  • 为了保证安全浏览器可以禁用 Cookie

分块传输

Chunked Transfer Coding

chunked

chunk编码格式如下:

1
[chunk size][\r\n][chunk data][\r\n][chunk size][\r\n][chunk data][\r\n][chunk size = 0][\r\n][\r\n]

如果一个HTTP消息(请求消息或应答消息)的Transfer-Encoding消息头的值为chunked,那么,消息体由数量未定的块组成,并以最后一个大小为0的块为结束。每一个非空的块都以该块包含数据的字节数(字节数以十六进制表示)开始,跟随一个CRLF (回车及换行),然后是数据本身,最后块CRLF结束。在一些实现中,块大小和CRLF之间填充有白空格(0x20)。最后一块是单行,由块大小(0),一些可选的填充白空格,以及CRLF。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。消息最后以CRLF结尾。

例子:
编码的应答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

25
This is the data in the first chunk

1C
and this is the second one

3
con

8
sequence

0

编码应答的解释
前两个块的数据中包含有显式的\r\n字符。

1
2
3
4
5
"This is the data in the first chunk\r\n"      (37 字符(包括\r\n) → 十六进制: 0x25)
"and this is the second one\r\n"            (28 字符(包括\r\n) → 十六进制: 0x1C)
"con"                              ( 3 字符 → 十六进制: 0x03)
"sequence"                          ( 8 字符 → 十六进制: 0x08)
应答需要以0长度的块( "0\r\n\r\n".)结束。

解码的数据

1
2
3
This is the data in the first chunk
and this is the second one
consequence

boundary

一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔,每个部分都可以有首部字段。

例如,表单上传文件时使用如下方式:

1
2
3
4
5
6
7
8
9
10
11
12
Content-Type: multipart/form-data; boundary=WebKitFormBoundaryrSDFR23f

--WebKitFormBoundaryrSDFR23f
Content-Disposition: form-data; name="test"

Hello
--WebKitFormBoundaryrSDFR23f
Content-Disposition: form-data; name="file"; filename="a.txt"
Content-Type: text/plain

... contents of a.txt ...
--WebKitFormBoundaryrSDFR23f--
  • boundary 用于分割不同的字段
  • 每部分都是以 --boundary 开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)
  • 消息主体最后以 --boundary-- 标示结束

HTTPS

HTTPS 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率

认证

校验对方身份,CA

HTTP相关基础概念

HTTP的状态

Keep-Alive 是指TCP连接不断开,在最后一个交互结束后还保持一段时间。不用每次请求都重新建立TCP连接。
http协议无状态中的【状态】到底指的是什么?!
所以【在服务器端开辟一块缓存区】才是真正的条件,也就是说,它确实等价于【有状态】。通过在服务器端开辟一块缓存区,存储、记忆、共享一些临时数据,你就可以:
协议对于事务处理有记忆能力【事物处理】【记忆能力】
对同一个url请求有上下文关系【上下文关系】
每次的请求都是不独立的,它的执行情况和结果与前面的请求和之后的请求是直接关系的【不独立】【直接关系】
服务器中保存客户端的状态【状态】
cookie和session应该是完全实现了有状态这个功能
TCP一直有状态,HTTP一直无状态,但是应用为了有状态,就给HTTP加了cookie和session机制,让使用http的应用也能有状态,但http还是无状态

URI

URI:Uniform Resource Identifier,即统一资源标志符,用来唯一的标识一个资源。

URL:Uniform Resource Locator,统一资源定位符。即URL可以用来标识一个资源,而且还指明了如何locate这个资源。

URN:Uniform Resource Name,统一资源命名。通过名字来表示资源。

请求与响应报文

GET请求报文

1
2
3
4
5
6
7
8
9
10
GET /admin/device?_search=false&nd=15532&pageSize=10&pageNo=1 HTTP/1.1
Host: 47.101.188.88:8888
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36
Referer: http://47.101.188.88:8888/admin/modules/analyse/device.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=9945ffba-927e-4c29-9fd2-3886805dabfe

HTTP请求组成:

  • 第一部分:请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本
  • 第二部分:请求头部,紧接着请求行(第一行)之后的部分,用来说明服务器要使用的附加信息
  • 第三部分:空行,请求头部后面必须有空行
  • 第四部分:请求数据也叫主体,可以添加任意数据

GET响应报文

1
2
3
4
5
6
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Date: Fri, 22 Mar 2019 07:12:26 GMT
Transfer-Encoding: chunked

{"msg":"success","code":0,data:{"totalCount":0}}

状态码200代表成功

POST请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /hems/device.do?reqCode=insertItem HTTP/1.1
Host: 192.168.33.11:88
Connection: keep-alive
Content-Length: 19
Origin: https://192.168.33.11:88
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
Referer: https://192.168.33.226:7443/hems/monitor.do?reqCode=init
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: account=dev; JSESSIONID=ACC630D4ADF84514D8A640F45EF7988C

id=10000001&name=12

Content-Length 标识请求主体内容长度

响应报文

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
Date: Fri, 22 Mar 2019 07:27:29 GMT

{"msg":"添加成功!","success":true}

HTTP之状态码

  • 1xx:指示信息 表示请求已接收,继续处理
  • 2xx:成功 表示请求已被成功接收、理解、接受
  • 3xx:重定向 要完成请求必须进行更进一步的操作
  • 4xx:客户端错误 请求有语法错误或请求无法实现
  • 5xx:服务器端错误 服务器未能实现合法的请求

HTTP请求方法

HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法

HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法

  • GET 请求指定的页面信息,并返回实体主体。
  • HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头,主要用于确认 URL 的有效性以及资源更新的日期时间等
  • POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中
  • PATCH 从客户端向服务器传送的数据取代指定的文档的内容。PATCH 允许部分修改
  • PUT 由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法
  • DELETE 请求服务器删除指定的资源。
  • CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输
  • OPTIONS 查询指定的 URL 能够支持的方法
  • TRACE 服务器会将通信路径返回给客户端。

HTTP 请求头参数

通用首部字段

首部字段名 说明
Cache-Control 控制缓存
Connection 控制不再转发给代理的首部字段、管理持久连接
Date 创建报文的日期时间
Transfer-Encoding 指定报文主体的传输编码方式
Via 代理服务器的相关信息

请求首部字段

首部字段名 说明
Accept 用户可处理的媒体类型
Accept-Charset 优先的字符集
Accept-Encoding 优先的内容编码
Accept-Language 优先的语言(自然语言)
Host 请求资源所在服务器包括域名和端口号
Origin 用来说明请求从哪里发起的,包括,且仅仅包括协议和域名。CORS跨域请求中会用到,response有对应的header:Access-Control-Allow-Origin
If-Match 比较实体标记(ETag)
Max-Forwards 最大传输逐跳数
Proxy-Authorization 代理服务器要求客户端的认证信息
Range 实体的字节范围请求
Referer 原始请求URI 包括:协议+域名+查询参数
User-Agent HTTP 客户端程序的信息
Content-Length 标识请求主体内容长度

 

响应首部字段

首部字段名 说明
Accept-Ranges 是否接受字节范围请求
Age 推算资源创建经过时间
ETag 资源的匹配信息
Server HTTP 服务器的安装信息
Vary 代理服务器缓存的管理信息
Access-Control-Allow-Credentials 是否允许后续请求携带认证信息
Access-Control-Allow-Origin 指定允许其他域名访问,设置*是最简单粗暴的

编码

一般使用js框架进行post发送数据的时候都会对参数进行encodeURIComponent。如果不想进行编码可以修改框架源码,或者使用原生的Ajax请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var params1 = {
username: username,
passwrod: password
};
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var data = xhr.responseText;
data = JSON.prase(data);
console.log(data);
}
}
xhr.open("POST","/url",true);
xhr.setRequestHeader('Content-Type', 'multipart/x-www-form-urlencoded; charset=UTF-8');
xhr.send(params1);

// ------------------
// 进行encodeURL的方法
function $params(obj) {
var str = [];
for (var p in obj) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
}
xhr.send($params(params1));

最常用的encodeURI和encodeURIComponent

  • encodeURI方法不会对下列字符编码 ASCII字母、数字、~!@#$&*()=:/,;?+'
  • encodeURIComponent方法不会对下列字符编码 ASCII字母、数字、~!*()'
  • encodeURIComponent比encodeURI编码的范围更大。

过程:

UTF-8编码→UTF-8(iso-8859-1)编码→iso-8859-1解码→UTF-8解码

encodeURL函数主要是来对URI来做转码,它默认是采用的UTF-8的编码
UTF-8编码的格式:一个汉字来三个字节构成,每一个字节会转换成16进制的编码,同时添加上%号

模拟请求

设置超时时间

HttpClient 4.5

1
2
3
4
5
6
7
8
9
CloseableHttpClient httpclient = HttpClients.createDefault();  
HttpGet httpGet = new HttpGet("http://www.baidu.com/");
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000).setConnectionRequestTimeout(1000)
.setSocketTimeout(5000).build();
httpGet.setConfig(requestConfig);
CloseableHttpResponse response = httpclient.execute(httpGet);
System.out.println("Result:" + response.getStatusLine()); //得到请求结果
HttpEntity entity = response.getEntity(); //得到请求回来的数据

setConnectTimeout:设置连接超时时间,单位毫秒。
setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。
setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用

特殊问题解决

JS多次触发事件,在一定延迟内只执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
var div = document.querySelectorAll(".div")[0];
var num = 0; // 计数
var t = null;// 外部定义一个timeout对象
// 事件累加的功能
// JS 多次触发点击事件,在一定延迟内只执行一次
div.onclick = function(){
if(t != null) {
clearTimeout(t);// clearTimeout() 方法可取消由 setTimeout() 方法设置的 timeout。
}
t = setTimeout(function(){
num ++;
console.log(num);
}, 500)
}
</script>

还可以自己实现,通过时间控制。执行完操作,记录一个执行时间,下次执行的时候判断是否时间间隔是超过5秒,这样可以保证5秒不重复执行,还可以解决heartbeat重复调用脚本的问题。不过linux可以对文件加锁,避免多次访问。

应用

树型结构

实现方式:一次性全部价值,异步加载

ztree json设计:

标准:需要嵌套关系,返回json数据可以没有pId

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
id: "1",
pId: "-1",
treeType: "0",
name: "dept1",
open: false,
isParent: true,
children:[
{
id: "2",
pId: "1",
treeType: "1",
name: "user1",
open: false,
isParent: false
}
]
}
type 0:dept,1:person

非标准:不需要嵌套关系

1
2
3
4
5
[
{id:1, pId:0, name: "父节点1"},
{id:11, pId:1, name: "子节点1"},
{id:12, pId:1, name: "子节点2"}
]

如果异步加载每次都只返回单层的节点数据,那么可以不设置简单 JSON 数据模式
客户端自己特殊处理服务端给的数据比如 部门→设备、人员树:

1
2
3
4
5
[
{id:1, pId:0, name: "父节点部门1", memberList:[id:1, username: "用户1"], deviceList:[id:1, deviceName: "设备1"]},
{id:11, pId:1, name: "子节点部门1", memberList:[], deviceList:[]},
{id:12, pId:1, name: "子节点部门2", memberList:[], deviceList:[]}
]

模糊查询过滤,很多客户的树插件自带模糊搜索过滤功能根据情况选择后端或者前端实现,数据量不大可以考虑使用前端插件自带过滤功能

css

图片居中,在img标签父级标签增加text-align:center

安全

CSRF

CSRF跨站点请求伪造(Cross—Site Request Forgery),跟XSS攻击一样,存在巨大的危害性,你可以这样来理解。攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
CSRF攻击攻击原理及过程如下:

  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A
  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A
  3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B
  4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A
  5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行

解决方法就是验证请求头的来源Referer+请求内容url或者body加上随机码,后端进行验证。避免仅仅Cookies就通过验证

1
登录时后端加生成一个uuid返回给前端并且存储在session中,前端在ajax请求统一加一个uuid参数或者请求头加uuid参数,登录后的请求统一拦截判断是ajax请求进行校验。对于其他请求比如get对于旧框架目前还没有比较好的方法。对于refer还得改造保证客户端都带上。

XSS

跨站脚本攻击是指恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

参考