HTTP协议 超文本传输协议 由万维网制定(w3c) 是浏览器与服务器通讯的应用层协议,规定了浏览器与服务器之间的交互规则以及交互数据的 格式信息等。 HTTP协议对于客户端与服务端之间的交互规则有以下定义: 要求浏览器与服务端之间必须遵循一问一答的规则,即:浏览器与服务端建立TCP连接后需要 先发送一个请求(问)然后服务端接收到请求并予以处理后再发送响应(答)。注意,服务端永远 不会主动给浏览器发送信息。 HTTP要求浏览器与服务端的传输层协议必须是可靠的传输,因此是使用TCP协议作为传输层 协议的。 HTTP协议对于浏览器与服务端之间交互的数据格式,内容也有一定的要求。 浏览器给服务端发送的内容称为请求Request 服务端给浏览器发送的内容称为响应Response 请求和响应中大部分内容都是文本信息(字符串),并且这些文本数据使用的字符集为: ISO8859-1.这是一个欧洲的字符集,里面是不支持中文的!!!。而实际上请求和响应出现 的字符也就是英文,数字,符号。 请求Request 请求是浏览器发送给服务端的内容,HTTP协议中一个请求由三部分构成: 分别是:请求行,消息头,消息正文。消息正文部分可以没有。 1:请求行 请求行是一行字符串,以连续的两个字符(回车符和换行符)作为结束这一行的标志。 回车符:在ASC编码中2进制内容对应的整数是13.回车符通常用cr表示。 换行符:在ASC编码中2进制内容对应的整数是10.换行符通常用lf表示。 回车符和换行符实际上都是不可见字符。 请求行分为三部分: 请求方式(SP)抽象路径(SP)协议版本(CRLF) 注:SP是空格 GET /myweb/index.html HTTP/1.1 GET / HTTP/1.1 URL地址格式: 协议://主机地址信息/抽象路径 http://localhost:8088/TeduStore/index GET /TeduStore/index.html HTTP/1.1 2:消息头 消息头是浏览器可以给服务端发送的一些附加信息,有的用来说明浏览器自身内容,有的 用来告知服务端交互细节,有的告知服务端消息正文详情等。 消息头由若干行组成,每行结束也是以CRLF标志。 每个消息头的格式为:消息头的名字(:SP)消息的值(CRLF) 消息头部分结束是以单独的(CRLF)标志。 例如: Host: localhost:8088(CRLF) Connection: keep-alive(CRLF) Upgrade-Insecure-Requests: 1(CRLF) User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36(CRLF) Sec-Fetch-User: ?1(CRLF) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9(CRLF) Sec-Fetch-Site: none(CRLF) Sec-Fetch-Mode: navigate(CRLF) Accept-Encoding: gzip, deflate, br(CRLF) Accept-Language: zh-CN,zh;q=0.9(CRLF)(CRLF) 3:消息正文 消息正文是2进制数据,通常是用户上传的信息,比如:在页面输入的注册信息,上传的 附件等内容。 GET /myweb/index.html HTTP/1.1 Host: localhost:8088 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Sec-Fetch-User: ?1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 1010101101001..... HTTP响应Response 响应是服务端发送给客户端的内容。一个响应包含三部分:状态行,响应头,响应正文 1:状态行 状态行是一行字符串(CRLF结尾),并且状态行由三部分组成,格式为: protocol(SP)statusCode(SP)statusReason(CRLF) 协议版本(SP)状态代码(SP)状态描述(CRLF) 例如: HTTP/1.1 200 OK 状态代码是一个3位数字,分为5类: 1xx:保留 2xx:成功,表示处理成功,并正常响应 3xx:重定向,表示处理成功,但是需要浏览器进一步请求 4xx:客户端错误,表示客户端请求错误导致服务端无法处理 5xx:服务端错误,表示服务端处理请求过程出现了错误 具体的数字在HTTP协议手册中有相关的定义,可参阅。 状态描述手册中根据不同的状态代码有参考值,也可以自行定义。通常使用参考值即可。 响应头: 响应头与请求中的消息头格式一致,表示的是服务端发送给客户端的附加信息。 响应正文: 2进制数据部分,包含的通常是客户端实际请求的资源内容。 响应的大致内容: HTTP/1.1 404 NotFound(CRLF) Content-Type: text/html(CRLF) Content-Length: 2546(CRLF)(CRLF) 1011101010101010101...... 这里的两个响应头: Content-Type是用来告知浏览器响应正文中的内容是什么类型的数据(图片,页面等等) 不同的类型对应的值是不同的,比如: 文件类型 Content-Type对应的值 html text/html css text/css js application/javascript png image/png gif image/gif jpg image/jpeg Content-Length是用来告知浏览器响应正文的长度,单位是字节。 浏览器接收正文前会根据上述两个响应头来得知长度和类型从而读取出来做对应的处理以 显示给用户看。
定位要发送的文件(将src/main/resources/static/myweb/index.html)
定义为resource目录(maven项目中src/main/java和src/main/resources实际上是一个目录)
只不过java目录中存放的都是.java的源代码文件
而resources目录下存放的就是非.java的其他程序中需要用到的资源文件
实际开发中,我们在定位目录时,常使用相对路径,而实际应用的相对路径都是类加载路径
类加载路径可以用:
类名.class.getClassLoader().getResource(".")就是类加载路径
这里可以理解为时src/main/java目录或src/main/resources
实际表达的是编译后这两个目录最终合并的target/classes目录。
<!--
<h1>--<h6>:标题,标题独立占一行,分别表示1级-6级标题
<center>:居中显示,该标签在html5中已经不再建议使用了。
<input>:输入组件,该组件用来在页面上获取用户的输入。
该组件有一个重要的属性:type,用来指定组件的样子。
常见的值有:
type="text":文本框
type="password":密码框
type="radio":单选框
type="checkbox":复选框
type="button":按钮
<br>:换行
<a>:超链接,标签中间的文本就是超链接上显示的内容,href属性用于指定点击后
跳转的路径。
<table>标签:表格。属性border用于指定边框。
<table>标签中包含<tr>标签用于表示行
<tr>标签中包含<td>标签用于表示列
<td>标签中常见属性:
align:对其方式。left左对齐,right右对齐,center剧中对其
colspan:跨列合并列,合并是从左向右合并列
rowspan:跨行合并列,合并是从上向下合并列
<img>:图片,属性src用于指定图片的位置。
-->
<!-- <h1>百度</h1> -->
<!--
<img>标签:图片,其中src用于指定图片的位置
在html页面上我们可以指定一个资源的路径以便加载或使用。例如超链接,图片
等标签上常常会指定一个路径。
路径有两种:
1:相对路径
页面上使用"./"表示当前目录时,由于html是被浏览器解释的,因此该路径
不能与在java源代码中使用"./"混淆(实际表示的路径不相同!)
浏览器在解释页面上的"./"时,是根据请求当前页面时URL中该页面所在的
目录:
例如:
请求当前首页。我们在浏览器输入的地址为:
http://localhost:8088/myweb/index.html
那么在index.html页面中若使用了"./"路径,则浏览器理解的实际位置:
http://localhost:8088/myweb/
因此若页面上<img src="./logo.png">
浏览器实际理解的该图片路径为:
http://localhost:8088/myweb/logo.png
相对路径在进行内部转发操作时可能导致定位失效。
http://localhost:8088/myweb/index123.html
404页面上
<img src="./404.png">
http://localhost:8088/myweb/404.png
"./"是URL中抽象路径中最后一个"/"的位置
"/"是URL中抽象路径中第一个"/"的位置
2:绝对路径
"/":根。指的位置是URL当中抽象路径开始的"/"
举例: v
http://localhost:8088/myweb/index.html
|----抽象路径-----|
在index.html页面上:
<img src="/myweb/logo.png">
^就是绝对路径中"/",对应URL中抽象路径部分起始的"/":
http://localhost:8088/myweb/index.html
^
因此,浏览器理解该图片的实际位置:
http://localhost:8088/myweb/logo.png
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
</head>
<body>
<center>
<h1>用户注册</h1>
<!--
<form>:表单,用于将用户在页面上输入的信息提交给服务端使用
注意:只有被包含在form里面的输入框才会被提交!
form上有两个重要的属性:
action:用于指定表单提交的位置,该位置需要服务端配合
method:提交方式,有两个值:get,post
get:地址栏形式提交,数据会被包含在URL的抽象路径部分
post:打包提交,数据会被包含在请求的消息正文中
当表单含有用户隐私信息或附件上传是应当使用post提交。
<input type="submit">:提交按钮,这个按钮的作用是将包含它的form表单
进行提交。
注意:form表单中所有输入框都要使用name属性指定名字,否则提交表单时会被
表单忽略。
-->
<!--
提交表单后,浏览器地址栏内容大致如下:
http://localhost:8088/myweb/reg?username=fancq&password=123456&nickname=chuanqi&age=22
其中抽象路径部分为:
/myweb/reg?username=fancq&password=123456&nickname=chuanqi&age=22
“?”是一个分隔符,左侧内容是form表单action指定的提交位置。右侧是表单数据
表单数据的格式:
输入框1的名字=输入框1的值&输入框2的名字=输入框2的值&.....
-->
<form action="/myweb/reg" method="post">
<table border="1">
<tr>
<td> 用户名</td>
<td><input name="username" type="text" > </td>
</tr>
<tr>
<td> 密码</td>
<td><input name="password" type="password" > </td>
</tr>
<tr>
<td> 昵称</td>
<td><input name="nickname" type="text" > </td>
</tr>
<tr>
<td> 年龄</td>
<td> <input name="age" type="text" > </td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="注册"></td>
</tr>
</table>
</form>
</center>
</body>
</html>
浏览器向服务端发送中文汉字问题:
解决传递中文问题
以GET形式提交表单数据时,数据会被包含在URL的抽象路径中"?"右侧.而抽象路径部分会随着请求的
请求行传递给服务端(请求行的第二部分)。
HTTP协议要求请求的请求行和消息头是文本部分,且符合的字符集必须是ISO8859,这是一个欧洲编码
里面不支持中文!
解决办法:
用ISO8859支持的字符来表示其不支持的字符
字符'0'和字符'1'是ISO8859支持的字符。我们用这两个字符的组合表示2进制中的1010的字节组合。
例如:
希望传递"范传奇"时。
浏览器会进行如下操作:
1:浏览器会利用支持中文的字符集(通常是UTF-8。准确的说是用页面指定的字符集,<meta charset="">)
将中文转换为对应的一组字节:"范传奇:----UTF-8---->11101000.....(9个字节,72个2进制)
2:将2进制中的1和0用字符'1'和字符'0'表示,既可以传递了
原本的:username=范传奇&password=123456
变为:username=111010001......(72个字符)&password=123456
虽然解决了问题,但是副作用:数据太长,影响传输速度
进而优化长度问题
解决办法:
将2进制的1和0的组合用16进制表示。因为16进制使用的字符可以用(0-9A-F表示,它们仍然是ISO8859
支持的字符)
换算关系
2进制 10进制 16进制
0000 0 0
0001 1 1
0010 2 2
0011 3 3
0100 4 4
0101 5 5
0110 6 6
0111 7 7
1000 8 8
1001 9 9
1010 10 A
1011 11 B
1100 12 C
1101 13 D
1110 14 E
1111 15 F
1个字节用2进制需要8位,而用16进制则仅需要2位
例如:
11101000
E8
将2进制部分换位16进制部分后,传递中文时可以变为:
原本:username=111010001......(72个字符)&password=123456
变为:username=E88C83...(18个字符)&password=123456
虽然解决了问题,但是副作用:服务端如何与实际的英文数字内容区分?
假如该用户输入的用户名就叫:E88C83
username=E88C83&password=123456
服务端读取到E88C83如何理解?
1:该人名字就叫这个?
2:6个16进制,表示3个字节,还原为中文"范"?
为了解决这个问题,URL规定,若使用英文和数字表示的是16进制,那必须在每两位16进制前加上一个%
因此:
username=%E8%8C%83&password=123456
服务端则认为用户名是3个字节
如果
username=E88C83&password=123456
服务端则认为用户名就是E88C83
范---->UTF-8---->2进制:11101000 10001100 10000011----换算为16进制---->E8 8C 83
传递是每两个16进制前加上%就形成了:username=%E8%8C%83&password=123456
服务端需要反向还原,但是此工作java已经提供了API:URLDecoder.decode()
实现:
修改HttpServletRequest的parseParameter方法,将参数部分转化后再进行解析
//用来保存客户端传递过来的每一组参数
private Map<String,String> parameters = new HashMap<>();
/**
* 解析参数。参数格式应当是:name1=value1&name2=value2.....
* 将每一组参数的参数名作为key,参数值作为value存入parameters
*
* /myweb/login?username=%E4%BB%98%E4%B9%89&password=fuyi1234
* @param line
*/
private void parseParameter(String line) {
try {
line = URLDecoder.decode(line,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String[] data = line.split("&");
for (String para : data) {
String[] paras = para.split("=");
parameters.put(paras[0], paras.length > 1 ? paras[1] : null);
}
}
Spring 获取request和response:
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();