HTTP 1
文章目录
- 1.2个简单的预备知识
- 域名
- 统一资源定位符 URL
- 完整的URL
- 2. http请求和响应 格式画出来,两个工具见一见http请求/响应的样子
- 3. 写一个最简单的httpserver,用浏览器直接测试
- recv
- send
- 4. 读http报文的细节
1.2个简单的预备知识
域名
网址域名会被转化成IP地址,我们真正用到的就是IP地址
那我好像并没有看到端口号这个东西,协议默认前缀是http、https他是默认给你自动拼接的,这种知名的服务,他的端口号是必须要固定下来,http默认80,https 默认443。
浏览器默认在请求里添加端口号
统一资源定位符 URL
这个网址就叫做URL
用域名标识互联网中唯一一台主机,协议默认端口号标识该主机唯一的服务,
资源路径标识能找到对应的网页资源,图片资源,音视频资源。
所以从此往后,我们在公网服务器上每一个资源都有唯一的一个字符串路径,这个路径是唯一的,区分和其他主机之间的区别是通过域名来区分的,哪些资源哪些进程应该访问呢由端口号帮我们指明,服务器上有不同路径,每个字段都具有唯一性。
所有网络上的资源,都可以用唯一的一个“字符串”标识,并且可以通过URL获取到。
完整的URL
包含协议的名称,登录信息不考虑,服务器地址就是域名,端口号冒号80所以可以省略因为协议已经默认了就像火警号一样,端口号80后面的/,当你看到/的时候linux中路径分隔符就是/,也能间接证明这个资源所在的位置有可能是在Linux服务器上的。第一个/叫做web根目录,web就是万维网,凡是通过URL能访问到的各种资源最终Linux一切皆文件,每个文件在单机上一定有所在的路径,这个根目录不一定是Linux的根目录/,他也可以选择一个相对路径作为他的根目录。
网络请求无非就有两种动作
网路行为:
1.把别人的东西拿下来
2.把自己的东西传上去
如果要把自己的东西上传,在URL可能把自己的信息挟带上,就是?uid=1是个kv键值对,这个请求的动态数据,这种方式把参数传上去,所以URL可以跟问号,问号后面可以带参,还有一个#就不说了,网页轮播图,标识第几张图片。
百度一下 helloworld 看看上传的参数
s可能是可执行程序,可能是一段c++代码,或者java入口代码
如果提交了多个参数,URL会用&符号作为分隔符。
总结
域名和URL
1.网络通信访问资源都是通过URL定位某一个资源
2.网络所有暴露出来的通过浏览器能访问到的,都叫他资源
URL本身具有全网唯一性,连接代表特定服务器某一个路径下的文件资源
URL本身有很多功能性的符号,我就要搜一个,提参一个?行不行,没有问题吗?
提交的关键字内容上一定出现特殊符号,那么服务端收到之后他就无法区分&到底是分割两个参数呢,还是就要搜一个&
在进行URL使用的时候,少量情况提交或者获取的数据本身可能包含和URL中特殊的字符冲突的字符,要求BS浏览器服务器要进行编码(encode)和解码(decode)
历史上写的代码没有这个过程,不需要,我们也没有URL,但是这里需要传参特殊字符被编程了2B%…之类。这个过程叫做编码,当服务器收到完整的字符串时,他要把编码转回特殊字符,这样做保证特殊字符不会在URL中出现。
URL本身已经出现特殊字符具有特殊功能,而你自己个人信息里面包含特殊字符是不会
影响到URL的特殊符号的。
编码和解码的工作由浏览器和服务器双方自由完成。
编码解码的意义
1.规避用户给服务器提交参数里有URL本身特殊符号,就和URL就冲突了,不能干扰URL,所以需要编码
2.编码解码不是为了保证加密,只为了用户提交数据和URL不冲突
3.编码和解码的工作一般不需要做,除非提交信息有特殊字符在你的字符串中出现了。
4.编码转义规则,把数据转成16进制
编码的工作是由浏览器自己做的。
那服务器收到这样的编码怎么转回去呢?
网上有现成的decode代码。
大部分情况下我们不需要。
或者在线平台做转化。
我在浏览器上输入域名回车拿到了网页,这工作就是相当于向目标服务器发起了一次http请求,那我大概是什么样子的请求呢?
|
v
2. http请求和响应 格式画出来,两个工具见一见http请求/响应的样子
http 请求的格式
无论是请求还是响应,都是以行为方式来进行陈列他的请求和响应格式的
http请求是由多行内容构成
请求行 是第一个
请求报头 第二个
他的内容也是由多行内容构成
http请求可能带参,也可能不带参
除了URL可以传参之外,还有请求正文,他不一定是以行为单位的,因为可能你传的是图片二进制的。
这是用户要上传的内容,可以没有!
发起http请求,不要请求正文,我没有东西上传,我想让他把东西给我。
这上面一行一行的,每一个分隔符都是\r\n作为结尾的。
我们该如何看待\r\n呢?
在我看来他也是字符,所以他就相当于是一个大的字符串只不过中间有特殊字符\r\n,TCP缓冲区存的时候其实还是按照字节一个一个存的,只不过打印出来变成了两行,所以存的结构和打印的结构是不一样的。
有了字符串之后你怎么区分第一行第二行,中间我们就用\r\n来分隔,和自定义协议\n是一样的。
请求行
- 内容中不能再出现\r\n符,因为这是行分隔符
- 所有报头字段一共分三部分,第一部分叫请求方法Method
95%方法只有两种,GET 、POST
第二个以空格作为分隔符 下一个是URL,再下来第三个区域用空格分隔符,
HTTP Version 最常见的1.0 1.1 2.0
最常见的 1.1 ,字符写进去是HTTP/1.1
URL一般不带域名,都是从第一个斜杠往后的内容
这就是他的第一行
请求报头
由多行内容构成,每一行叫做http请求的请求属性,大部分都是以Key: Value键值对
:空格
最终KV也是字符串,以\r\n作为结尾的。
怎么有效的区分哪部分是报头,哪部分是有效载荷部分呢?
今天请求报头最后一行也有\r\n,请求正文部分可能也有\r\n,所以你怎么将报头和有效载荷分离呢?
所以HTTP协议规定报头部分和正文部分中间新加一个空行
在进行HTTP请求还是报头读取时,我们都一直按行读取,直到完整的一行,这行就是个空行,此时前半部分就是HTTP的报头,后半部分是内容,前半部分的第一行就是他的请求行。
我们用空行的方式将报头和有效载荷作分离。
你这个HTTP协议你怎么保证你读到的是一个完整的报文呢?
上面的所有这一坨内容,在网络里就是下面的大字符串。
我今天保证不了,我虽然保证不了我读到一个完整的HTTP请求,但我一定能保证我能读到一个完整的HTTP报头,我读到空行就可以了。
那有没有正文呢?正文部分有多少呢?你怎么保证把正文正确的读上来呢?
他不是请求报头里面有属性吗,Content-Length:XX表明的是正文部分的长度
所以当我们提取HTTP时,
1.根据固定要求按空格把报头全读完
2.根据报头中的属性Content-Length里的数字,就一定能把该HTTP完整的读完一个请求。
HTTP请求不是结构化的数据吗,我们这个类应该怎么定?
我们之前不是int x , iny y char op 吗
请求类里面定义一个大的Vector,里面放各种string,不想这样那就,请求行直接是string,请求报头是Vector里面是String,空行就是空行,正文部分请求类里再定一个大的string。
把他序列化就把所有的string全部加起来中间用\r\n隔离开,不就构建了一个HTTP请求吗。
反序列化
一直读到空行,再根据content-length读取正文,就一定读到一个完整的报文,把空行前面的内容全部按\r\n打散,把字符串一变多PUSH进请求类内部包含的Vector里,此时完成了反序列化。
HTTP的response和request几乎是一模一样的。
第一行状态行,\r\n结尾
第二大块叫做响应报头
也是包含多行文本,大量的KV式的内容,
最后呢,你为什么向我发送http请求不就是想拿到网页吗
所以第三部分叫做响应正文
响应正文一般是html、css/js/图片各种各样的资源他就给你返回了。
他的所有报头都是按\r\n来分割的,当我作为一个客户端我怎么保证收到的response全都读完了呢?我保证不了,我们并不能确定响应正文里是否有\r\n,但是响应报头中可以全部读完,响应也包含第三部分
空行
所以有了空行之后对于响应来讲能不能把整个响应全都读完呢,答案是可以只要按行进行读取一直读到空行就能把状态行和响应报头全部读完,响应正文部分是多大也可以根据content-length读上来。
所以网络通信请求就是一来一回,完成一次Http的请求和响应
小工具抓一个响应看看什么样子。
telnet www.baidu.com 80
请求的 请求报头和请求正文可以没有,但是请求行和空行必须要有
Get / (获取首页资源) HTTP/1.1
回车再回车,带上空行才可以
就能得到一个错误但是完整的响应了,因为GET才对
下面一大坨就是传说中的网页了,他的网页放在服务器上被浏览器拿到的时候是以http响应的方式被拿到的。
响应
状态行,也是分为三部分的,空格作为分隔符。
第一个自动叫做HTTP Version 服务端version,也就是HTTP/1.1之类
这是什么意思?请求表示的是谁请求的http版本,服务器是什么版本的,双方要交换一下,就像微信是1.0,服务器可能是2.0了
,所以网络服务时,对客户端进行升级,客户端和服务器版本并不一致,但是为了保证兼容性,所以1.0发给对方就知道,只给你提供1.0的功能。
第二个叫做状态码,
有一种你肯定见过404,这次请求的状态,就像网络版本计算器时,计算结果是否可信的code
404标识资源并不存在
200表示可信
第三个叫做状态码描述
他就是字符串版本的状态码描述,描述状态码而已
所以http里面,哪怕请求的资源是失败的,他也要有响应。
所有的请求必有响应。
我们的网络版本计算器也是有响应。
那看到请求呢?
基于Http抓包
fiddier
但现在都是https加了密,所以不明显。
或者postman
他自己就是浏览器,帮我们发送请求
fiddler是作为代理。
直接看课件上明显的请求吧
3. 写一个最简单的httpserver,用浏览器直接测试
version 1. hello world
我们之前是read来网络读取,今天再认识一个
recv
也可以TCP网络读取,参数返回值和read一模一样,唯一不同的是读取方式flags,以非阻塞方式读,读的时候不把队列数据清空。
我们设为0,和read一模一样。
我们服务器其实不用客户端,因为http本身是基于TCP的,所以写的TCP服务器本身可以被浏览器直接访问。
服务器创建线程提供服务,你请求什么,我直接原封不动打印出来,缓冲区Buffer大一点儿。
服务器启动后用 IP地址:端口号浏览器直接访问发起请求就能在服务端看到发来的请求。
基于TCP收到这个请求,接下来的工作是什么?
好比网络计算器,我们要对报文做解析,然后构建响应返回去,我们不做解析直接返回符号标准的报文,在你的浏览器上就能显示出来。
user-Agent属性表明当前客户端是否是一个合法的客户端,如果是伪造的,服务器做了反爬虫机制,就不爬不下来。
百度服务器会根据我们的user-Agent标识我们是什么平台来匹配对应的响应,比如下载,给你推送手机下载或者Windows下载安装包。
现在已然能读到请求了,但是recv是有BUG的,你怎么保证你就能把一个完整的HTTP请求读完了呢
答案是不一定,有可能buffer里面包含半个,一个半,多个请求都有可能,这样搞你怎么保证把一个Http请求读完了呢?
所以不能保证,我们这里还是要做把报文和报文之间解构出来,之后还要对它反序列化。
所以今天响应最简单的,直接响应报头状态行和空行,正文是字符串,不带响应报头
手动构建一个响应,硬编码。
就是构建字符串响应的每一行,就可以把手写的响应返回去让你看到,把消息发回去,我们也是忽略网络通信细节,我们不一定能通过write直接把信息全发过去,现在先不管
今天再换一个发送接口
send
无论是从返回值,前三个参数和write的含义一模一样,无非多了个参数flags,发送策略,阻塞或非阻塞,我们直接设置为0就行。
在浏览器看到的内容底层是服务器按照字符串协议给我们返回的,最终浏览器就能去解释他了。
那我怎么没看到content-length?
你的浏览器其实显示的是正文部分,完全由浏览器做解释。
我们的http响应正文包含的网页或者视频图片,我们今天只有字符串,我们在正文部分其实可以简单的写一个网页让浏览器显示出来的。
推荐线上教程
w3cschool html教程
说到底网页就是文本文件,只不过这个文本文件以文件的形式呈现出来的,如果在网络发送时把文件里的内容读出来,把文件的内容充当http响应的正文部分,也就是如果有一个网页,把这个网页打开,打开之后把内容拼到http响应的后面,此时就能构建出一个挟带网页的http响应了。
我们从w3c拿一个用
< html> < /html> 表示这是一个 完整的网页文档
范围性标签
一个是< head> < /head> 表明当前网页的头部信息,一般网页里面的样式,javascrip的连接一般都是在这里引入。
第二部分是< body> < /body>挟带的是网页的主体,剩下的里面都是标签。
浏览器发来的请求,一般是请求/ 也就是Web根目录
如果没有带路径直接IP+端口浏览器自动拼接/ /叫做web根目录
那我要把Web根目录所有资源给你返回吗,答案是不是的,一般
在所有网站里都会在自己项目路径下存在Web根目录的概念。
那我们可以在IP+端口后面/继续请求路径
这就是URL 可能会带域名,这个URL一般都是去掉ip+端口号剩下的这部分,对应的URL部分。
URL可以带参,我们可以再带上?
浏览器用URL方式向服务器发起请求,IP+PORT找到我这个机器,最本质是想找到我机器上的特定路径上的文件,给我返回过来,可以携带参数。
这里的/.ico请求的是网页的小图标,服务器把他放到web根目录就可以请求到了
所以两组知识
- 我们将来收到请求时,给别人响应要添加默认的一些报头,我们的网页会被拼接到响应正文部分
- 别人请求时,会通过请求里包含请求什么样的资源,路径URL呈现出来,所以就可以动态在服务器上根据路径挑选他想要的文件
这里就有个鸿沟了
之前讲的 / 你说叫做web根目录,不一定是Linux根目录,这玩意究竟怎么理解?
其二
我们的网页每次都要通过静态编码写到服务器里面吗,我们的网页应该是单独的网页文件,你需要访问哪个文件就通过Http去访问就行了。
其三
网页文件不止一个,包括未来还有图片,JS文件,各种文件在Linux上存在的话一定存在目录结构,这个目录结构整体就应该可以被外部整体去访问。
接下来先做第一件事
一个http协议一定要有自己的Web根目录
他可以是Linux根目录,也可以由自己制定。
我在我的当前路径下存在一个wwwroot文件夹,将来所有网页图片视频全放在这个目录里面让别人去访问,我们把这个目录叫做web根目录。
我们把这种网页版本改成文件版本
当前目录./默认就有,所以可以省略直接wwwroot
也可以把这个目录写到配置文件里,动态的去指明
网页首页就在wwwroot目录下去建立,比如固定写法,index.html
这就是要写的一个网页。
http响应的正文网页不应该静态的编码到代码里,而应该写到网页里。
今天也没有对请求做任何分析,还是返回网页,今天网页内容不要静态编码,而是直接把网页动态让服务器响应给浏览器,所以我们需要一个ReadHtmlContent();
我把路径先写死,就是把指定路径下的文件内容返回成string字符串
相当于http请求时,把指定路径下的文件打开然后拼到响应的后面,最终就能拿到文件版的http响应了
我们这里还是跟之前网络版本计算器一样,按行读取,getline每次都会从line的0位置写入,追加到content中。
最终服务器不用关了,我们把网页文件一变,浏览器刷新立马就能看到
version 2 http done
version 3 TODO