【计算机网络】一文带你了解HTTP协议——内含经典面试题


引言:

在之前的文章中,我们一起学习了TCP/IP协议
网络原理之TCP/IP协议——含大量图解及详细解释网络原理之TCP/IP协议(二)
接下来就让我们一起来继续学习HTTP协议中的相关知识吧😊😊😊

HTTP 协议

HTTP是什么

HTTP (全称为 “超文本传输协议”) 是一种应用非常广泛的应用层协议,HTTP 往往是基于传输层的 TCP 协议实现的。——HTTP1.0,HTTP1.1,HTTP2.0 均为TCP;HTTP3 基于 UDP 实现
目前我们主要使用的还是 HTTP1.1 和 HTTP 2.0。

具体应用场景:

具体应用场景其实大家天天都在用,只要你打开浏览器,随便打开一个网站,这个时候其实你就用到了HTTP;或者你打开一个手机APP,随便加载一些数据,这个时候其实你也大概率用到了HTTP

在这里插入图片描述
当我们在浏览器中输入一个百度搜索的 “网址” (URL) 时, 浏览器就给百度的服务器发送了一个 HTTP 请求,百度的服务器返回了一个 HTTP 响应。
这个响应结果被浏览器解析之后, 就展示成我们看到的页面内容。 (这个过程中浏览器可能会给服务器发送多个 HTTP 请求, 服务器会对应返回多个响应, 这些响应里就包含了页面 HTML, CSS,JavaScript, 图片, 字体等信息)。

所谓 “超文本” 的含义, 就是传输的内容不仅仅是文本(比如 html, css 这个就是文本), 还可以是一些其他的资源, 比如图片, 视频, 音频等二进制的数据。

理解"应用层协议"

我们在之前的文章中已经学过 TCP/IP , 已经知道目前数据能从客户端进程经过路径选择跨网络传送到服务器端进程[ IP+Port ]。可是,仅仅把数据从 A 点 传送到 B点就完了吗❓❓

这就好比,你在京东上买了一部手机,卖家[ 客户端 ]把手机通过顺丰[ 传送+路径选择 ] 送到你 [ 服务器 ] 手里就完了吗❓
当然不是,因为你还要使用这款产品,而且还要在使用之后,给卖家打分评论❗

所以,我们把数据从A端传送到B端, TCP/IP 解决的是顺丰的功能,而两端还要对数据进行加工处理或者使用,所以我们还需要一层协议,不关心通信细节,关心应用细节!
这层协议就叫做应用层协议。而应用是有不同的场景的,所以应用层协议是有不同种类的,其中经典协议之一的HTTP就是其中的佼佼者。

再回到我们刚刚说的买手机的例子:顺丰相当于 TCP/IP 的功能,那么买回来的手机都附带了说明书【产品介绍,使用介绍,注意事项等】,该说明书指导用户该如何使用手机,此时的说明书就可以理解为用户层协议。

理解HTTP协议的工作过程

当我们在浏览器中输入一个 “网址”,此时浏览器就会给对应的服务器发送一个 HTTP 请求, 对方服务器收到这个请求之后, 经过计算处理, 就会返回一个 HTTP 响应

在这里插入图片描述
事实上, 当我们访问一个网站的时候, 可能涉及不止一次的 HTTP 请求/响应 的交互过程
可以通过 chrome 的开发者工具观察到这个详细的过程:

通过F12 打开 chrome 的开发者工具,切换到 Network 标签页,然后刷新页面即可看到如下图效果,每一条记录都是一次 HTTP请求/响应。

在这里插入图片描述
注意: 当前百度主页是通过 https 来进行通信的,https 是在 http 基础之上做了一个加密解密的工作, 等到后面文章再给大家介绍。

HTTP协议格式

HTTP 是一个文本格式的协议。可以通过 Chrome 开发者工具或者 Fiddler 抓包, 分析 HTTP 请求/响应的细节。

抓包工具的使用

以 Fiddler 为例:下载地址传送门😊😊
在这里插入图片描述

  • 左侧窗口显示了所有的 HTTP 请求/响应,可以选中某个请求,然后双击查看详情
  • 右侧上方显示了 HTTP 请求的报文内容(切换到 Raw 标签页可以看到详细的数据格式)
  • 右侧下方显示了 HTTP 响应的报文内容(切换到 Raw 标签页可以看到详细的数据格式)
  • 请求和响应的详细数据,可以通过右下角的 view in Notepad 通过记事本打开

小tip:可以使用 ctrl + a 全选左侧的抓包结果, delete 键清除所有被选中的结果。

抓包工具的原理

Fiddler 相当于一个 “代理”

浏览器访问 baidu.com 时, 就会把 HTTP 请求先发给 Fiddler, Fiddler 再把请求转发给 baidu 的服务器;当 baidu 服务器返回数据时, Fiddler 拿到返回数据, 再把数据交给浏览器。 因此 Fiddler 对于浏览器和 baidu 服务器之间交互的数据细节, 都是非常清楚的。

在这里插入图片描述
简单来说,代理就可以理解为你的男朋友/女朋友,比如你想吃零食,但是又不想自己下楼去超市,那么你就可以把钱给你的男朋友/女朋友,然后他来到超市把钱给超市老板, 再把零食拿回来交到你手上。 这个过程中,你的男朋友/女朋友对于 “你” 和 “超市老板” 之间的交易细节, 是非常清楚的。

抓包结果

在这里插入图片描述
以下是一个 HTTP 请求/响应 的抓包结果
HTTP 请求:
在这里插入图片描述
HTTP 响应:
在这里插入图片描述

协议格式总结

在这里插入图片描述
从上面的HTTP 请求和响应中可知利用空行作为响应头的结束标记,那么这是为什么呢❓🤔

这是因为 HTTP 协议并没有规定报头部分的键值对有多少个,而空行就相当于是 “报头的结束标记”, 或者是
“报头和正文之间的分隔符”;HTTP 在传输层依赖 TCP 协议, TCP 是面向字节流的,如果没有这个空行, 就会出现 “粘包问题”。

HTTP 请求 (Request)

认识 URL

URL: 统一资源定位系统(uniform resource locator)是互联网上标准资源的地址。而互联网上的每个文件都有唯一的一个的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。

在这里插入图片描述

URL 基本格式

在这里插入图片描述
URL小结:
对于URL 来说,里面的结构虽然看起来复杂,但其实最重要的,和开发关系紧密的,就四个部分:

  1. ip 地址/域名
  2. 端口号(经常是小透明)
  3. 带层次结构的路径
  4. query string 查询字符串
关于 URL encode

当 query string 中如果包含了特殊字符,就需要对特殊字符进行转义(url encode)

这个转义过程就叫做 url encode,反之,把转义后的内容还原回来就叫做 url decode
url 里面是有很多特殊含义符号的:/ : & = … 这些符号都是在 URL 中具有特定含义的~

注意: 如果,query string 里也包含了这类特殊符号,就可能导致 URL 被解析失败

比如: 当我们在baidu中搜索 C++ 的时候,就可以看到URL中的 query string 里面有一个键值对,就表示了查询词内容
在这里插入图片描述
转义的规则如下: 将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式

认识"方法"(method)

HTTP协议的方法非常多!但是最常用的,也就是 GET 和 POST

在这里插入图片描述

HTTP 中引入这些方法的初衷是为了表示不同的"语义"(语义:是否有特定的含义)
比如在 HTML 里:h3,p,a,img… 语义化标签; div ,span 无语义标签
但是随着时间的推移,使用就走形了,现在大家写代码,基本都是GET/POST一把梭,基本就没咋考虑语义的事情了,正因为如此,也就导致了多种HTTP方法之间的界限,就变的模糊了
GET也可以给服务器送个东西;POST也可以从服务器拿个东西

GET 方法

GET 是最常用的 HTTP 方法, 常用于获取服务器上的某个资源;在浏览器中直接输入 URL, 此时浏览器就会发送出一个 GET 请求。 另外, HTML 中的 link, img, script 等标签, 也会触发 GET 请求。
使用 Fiddler 观察 GET 请求
打开 Fiddler,访问百度主页,观察抓包结果:
在这里插入图片描述
从上面的结果中可以看到出:⬇️⬇️⬇️在这里插入图片描述
是通过浏览器地址栏发送的 GET 请求,选中它并双击观察请求的详细结果:
在这里插入图片描述
GET 请求的特点:

  • 首行的第一部分为 GET
  • URL 的 query string 可以为空,也可以不为空
  • header 部分有若干个键值对结构
  • body 部分为空
POST 方法

POST 方法也是一种常见的方法,多用于提交用户输入的数据给服务器(例如登陆页面); 通过 HTML 中的 form 标签可以构造 POST 请求。
使用 Fiddler 观察 POST 方法
在Gitee登陆页面, 输入用户名,密码,点击登陆, 就可以看到 POST 请求。
在这里插入图片描述
选中它并双击观察请求的详细结果:
在这里插入图片描述
POST 请求的特点:

  • 首行第一部分为 POST
  • URL 的 query string 一般为空(也可以不为空)
  • header 部分有若干个键值对结构
  • body 部分一般不为空,body 内的数据格式通过 header 中的 content -Type 指定 body 的长度由header 中的 content-Length 指定
其他方法:
  • PUT 与 POST 相似,只是具有幂等特性,一般用于更新
  • DELETE 删除服务器指定资源
  • OPTIONS 返回服务器所支持的请求方法
  • HEAD 类似于GET,只不过响应体不返回,只返回响应头
  • TRACE 回显服务器端收到的请求,测试的时候会用到这个
  • CONNECT 预留,暂无使用
经典面试题:谈谈 GET 和 POST 的区别⭐⭐⭐⭐⭐

第一句话,先盖棺定论❗GET 和 POST 没有本质区别❗❗(具体来说,相当于是 GET 能使用的场景,也能替换成POST;POST使用的场景,也能替换成 GET),但是在细节上还是有一些区别的。

  1. 语义上的区别: GET 通常用来读取数据 POST 通常用来上传数据,但是现状是 GET 也经常用来上传数据,POST也经常用来获取数据
  2. 通常情况下, GET 是有没有 body,GET 通过 query string 向服务器传递数据
    通常情况下, POST 是有 body,POST 通过 body 向服务器传递数据,但是 POST 没有 query string
    注意: 如果我就想让 GET 有 body(自己构造一个带 body的 GET请求)或者就想让 POST 带有 query sting 是完全可以的❗❗
  3. GET 请求一般是幂等的,POST 请求一般是不幂等的(也不是强制要求,而是建议)
    幂等: 每次你相同的输入,得到的输出结果是确定的;
    不幂等: 每次你相同的输入,得到的结果是不确定的
  4. GET 可以被缓存,POST 不能被缓存(提前把结果记住,如果是幂等的,记住结果是很有用的,节省了下次访问的开销;如果不是幂等的,就不应该去记)

认识请求"报头"(header)

header 的整体的格式也是 “键值对” 结构,每个键值对占一行,键和值之间使用分号分割

Host: 表示服务器主机的地址和端口
在这里插入图片描述

Content-Length: 表示body中的数据长度
Content-Type: 表示请求的body中的数据格式

注意: 这两个属性是在描述 body,如果你的请求里没有 body(GET),那么也就不需要这两个字段了

一般POST都是带body,一般登录系统都是基于 POST 来实现的。
我们抓个包具体来观察一下:
在这里插入图片描述
详细情况:
在这里插入图片描述
这里我们要注意有两个问题:

  1. 为什么登录系统是使用POST实现❓使用GET能不能实现登录呢🤔🤔

答案就是使用 GET 是完全可以实现登录的

  1. 那为什么还主要使用 POST 呢❓🤔

那是因为登录肯定就会给服务器传递用户名和密码,如果是 使用GET的话,用户名密码习惯上就会放到 URL 的 query string 中来传递(此时浏览器的地址栏里的路径,就可能变的很长一串)这个时候用户体验可能就不太好。😊😊

网上有种说法是:使用 POST 实现登录,是因为 POST 比 GET 更安全

对于这个说法是大错特错的,安不安全取决于你的数据是否明文传输,是否是加密过的。如果你的数据是明文传输而且没有加密过,那么就算你使用POST也是不安全的❗❗❗

第三种最常见请求中的 body 的格式:json
body格式如下:
在这里插入图片描述
关于 Content-Length 的补充:
HTTP 也是基于 TCP 的协议,而TCP 是一个面向字节流的协议,那么也就存在粘包问题,因此要合理设计应用层协议,来明确包和包之间的边界:

  1. 使用分隔符
  2. 使用长度

如果当前有若干个GET请求,到了TCP接收缓冲区中,应用程序读取请求的时候,就以空行为分隔符
如果当前是有若干个POST请求,到了TCP缓冲区了,这个时候,空行后面还有body,当应用程序读到空行之后,就需要按照Content-Length表明的长度,继续读若干长度的数据

User-Agent(简称 UA)

表示的是,当前用户是在拿一个什么样的东西来上网❗❗

在这里插入图片描述
Referer

表示了当前的页面,是从哪个页面跳转过来的

在这里插入图片描述
注意: Referer 不是一定有的,如果你是通过浏览器地址栏直接输入地址,或者直接点收藏夹,这个时候是没 referer 的❗❗
Cookie

Cookie 就是浏览器给页面提供的一种 能够持久化存储数据的机制
持久化指的是: 数据不会因为程序重启或者主机重启而丢失(写在磁盘里)

Cookie 具体的组织形式:

  1. 先按照域名来组织,针对每个域名,分别分配一个小房间;就好比如:我访问百度,浏览器就会给 baidu这个域名记录一组cookie,我访问码云,浏览器也就会给码云一组 cookie
  2. 一个小房间里面,又会按照 键值对 的方式来组织数据
    在这里插入图片描述
    Cookie 数据从哪里来的呢❓🤔

其实是服务器返回给客户端的

下图是gitee的抓包实例:
在这里插入图片描述
下面再举个通俗易懂的例子方便我们对 Cookie 的理解:
Cookie里面保存的身份信息,这件事,就类似于我们去医院看病
在这里插入图片描述
由于cookie也有可能会丢(可以手动删除),所以这些关键信息,还是存储在服务器上比较靠谱。这些关键信息,存储在服务器上,我们管这个东西称为 “session”,也就是会话

服务器这里管理着很多很多的session,每个session里面都存储了用户的关键信息(基本信息要做的检查,以往病例…每个session也有一个 sessionld (会话的标识)就诊卡上其实存储的是这个会话的id😊😊

关于 Session 会话的理解:
在这里插入图片描述
总结:

  1. Cookie 是浏览器提供的一个持久化存储数据的机制
  2. Cookie 的最重要应用场景,就是存储会话 id,进一步的让访问服务器的后续页面的时候,能够带上这个id从而让服务器能够知道当前用户信息(服务器上保存用户信息这样的机制就称为 Session 会话)
  3. Cookie 能不能用来存储别的信息❓🤔当然是可以的,具体想存啥就存啥,都是程序猿自定义的

HTTP 响应详解

认识"状态码"(status code)

状态码表示访问一个页面的结果(是访问成功,还是失败,还是其他的一些情况)

  • 200 OK: 这是一个最常见的状态码,表示访问成功
  • 404 Not Found: 要访问的资源不存在
  • 403 Forbidden: 表示访问被拒绝, 有的页面通常需要用户具有一定的权限才能访问(登陆后才能访问), 如果用户没有登陆直接访问, 就容易见到 403
  • 405 Method Not Allowed: 前面我们已经学习了 HTTP 中所支持的方法, 有 GET,POST等, 但是对方的服务器不一定都支持所有的方法(或者不允许用户使用一些其他的方法)。
  • 500 Internal Server Error: 服务器自己出问题了,意味着出现 bug,外面的服务器上看到这种情况的概率也是比较低的;但是我们后面自己写代码,可能容易出现这种情况😂😂
  • 504 Gateway Timeout: 当服务器负载比较大的时候, 服务器处理单条请求的时候消耗的时间就会很长, 就可能会导致出现超时的情况。
  • 302 Move temporarily: 在登录过程中,非常典型的情况,重定向:这个词在计算机的很多常见中都会涉及到,不仅仅是 HTTP,但是虽然在不同场景中细节有差异,但是表示的核心含义都是呼叫转移

呼叫转移–>中国移动运营商这里可以办理的一个业务——比如说:我想换一个新的手机号码,但是我的旧的电话号码已经被很多人都知道了,那么我需要一个一个给他们说让他们都保存一下我这个新号码嘛🤔不用那么麻烦,我可以办理一个呼叫转移,只要有人拨打我的旧号码,就会由运营商自动的转接到我的新号码上

在重定向响应中,一般都是需要 Location 属性的
在这里插入图片描述
总结:
上面就介绍了最常见的一些状态码,但是实际上HTTP 状态码,种类非常多❗❗
在这里插入图片描述

通过 form 表单构造 HTTP 请求

form (表单) 是 HTML 中的一个常用标签, 可以用于给服务器发送 GET 或者 POST 请求。

注意: 不要把 form 拼写成 from❗❗❗

form发送GET请求

form 的重要参数:

  • action: 构造的 HTTP 请求的 URL 是什么
  • method: 构造的 HTTP 请求的 方法 是 GET 还是 POST (form 只支持 GET 和 POST)

input 的重要参数:

  • type: 表示输入框的类型: text 表示文本, password 表示密码, submit 表示提交按钮。
  • name: 表示构造出的 HTTP 请求的 query string 的 key, query string 的 value 就是输入框的用户输入的内容。
  • value: input 标签的值, 对于 type 为 submit 类型来说, value 就对应了按钮上显示的文本。

示例代码如下:

<body>
    <form action="https://www.baidu.com" method="get">
        <input type="text" name="username"><br>
        <input type="password" name="password"><br>
        <input type="submit" value="提交">
    </form>
</body>

图解如下:
在这里插入图片描述
当我们在输入框输入内容点击提交的时候页面会跳转到我们之前 action 指定的 URL
在这里插入图片描述
在这里插入图片描述

form发送POST请求

示例代码如下:

<body>
    <form action="https://www.sogo.com/index.html" method="post">
        <input type="text" name="username"><br>
        <input type="password" name="password"><br>
        <input type="submit" value="提交">
    </form>
</body>

图解如下:
在这里插入图片描述
在这里插入图片描述

通过 ajax构造HTTP请求

ajax 全称为 Asynchronous(异步) Javascript And XML,是 2005 年提出的一种 JavaScript给服务器发送HTTP 请求的方式。

那么为什么要使用它呢❓❓🤔
那是因为form表单这种方式,是一个更加原始的构造方法,而使用 form 一定会涉及到"页面跳转",浏览器需要加载出一个全新页面,但是随着前端页面越来越复杂,所以就希望能够让页面不去整个全部加载,而是只加载其中需要变化的某个小部分,这个情况就可以使用 ajax了。——可以不需要刷新页面/页面跳转就能进行数据传输

ajax是属于基于异步等待的方式来进行的,下面先让我们弄清楚这几个概念:

在这里插入图片描述

发送 GET 请求

在发送 GET 请求之前 先引入 jquery

  1. 现在搜索引擎中搜索 jquery cdn 查询词
  2. 在结果中,找到一个 合适的 cdn 的 url
  3. 打开对应的 url,加载出 jquery 本体
  4. 复制粘贴内容到本地文件

示例代码如下:

<script src="jquery.js"></script>
    <script>
        $.ajax({
           type:'get',
           url:'http://www.sogou.com/index.html',
           success:function(body){
               //sucess对应一个回调函数
               //这个函数就会在正确获取到HTTP响应之后,来调用
               //异步
               //回调函数的参数,就是HTTP响应的body部分
               console.log("获取到响应数据!"+body);
           },
           error:function(){
               //error也对应一个回调函数
               //这个函数会在请求失败之后触发
               //异步
               console.log("获取响应失败!");
           }
        });
    </script>

图解如下:
在这里插入图片描述
当我们运行程序后,会发现页面上什么也没有,但是切到控制台会发现有报错:
在这里插入图片描述
通过刚才 ajax 的请求,我们通过抓包可以看到,响应里面 是200OK,并且 body也是 html数据
在这里插入图片描述
但是浏览器仍然认为这是一个"出错"的请求,那么这是为什么呢❓❓🤔

出现这个报错的原因,是因为浏览器禁止 ajax进行跨域访问(跨越多个域名/多个服务器)
由于当前 页面所处在的服务器是本地文件,而页面中 ajax 请求的URL 域名是 www.sougou.com
,这样就触发了 跨域操作。

那么什么样才不算是跨域呢❓❓

当前页面所在的服务器,就是在 www.sogou.com中 ,然后页面中再通过ajax,请求 URL,域名为 www.sogou.com 这种就不算跨域

上述行为是浏览器给出的限制,当然我们也是有办法绕过这个限制的,那就是如果对方服务器返回的响应中带有相关的响应头,允许跨域操作,就是可以正常被浏览器显示的

发送 POST 请求

发送 POST 请求方法和代码同上,只是将 type:‘get’ 改为 **‘post’**即可,这里就不再具体赘述了。

<script src="jquery.js"></script>
    <script>
        $.ajax({
           type:'post',
           url:'http://www.sogou.com/index.html',
           success:function(body){
               //sucess对应一个回调函数
               //这个函数就会在正确获取到HTTP响应之后,来调用
               //异步
               //回调函数的参数,就是HTTP响应的body部分
               console.log("获取到响应数据!"+body);
           },
           error:function(){
               //error也对应一个回调函数
               //这个函数会在请求失败之后触发
               //异步
               console.log("获取响应失败!");
           }
        });
    </script>

通过 Java socket 构造 HTTP 请求

java 构造一个 HTTP 请求,主要就是基于 TCP socket,按照 HTTP 请求的报文格式,构造出一个匹配的字符串,再写入 socket 即可。
在实际研发过程中,确实也会有一些基于 java构造 http请求的情况,可以直接使用第三方库来实现,不一定非得要直接使用scoket

示例代码如下:

public class HttpClient {
    private Socket socket;
    private String ip;
    private int port;
    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.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 String post(String url, String body) throws IOException {
        StringBuilder request = new StringBuilder();
        // 构造首行
        request.append("POST " + url + " HTTP/1.1\n");
        // 构造 header
        request.append("Host: " + ip + ":" + port + "\n");
        request.append("Content-Length: " + body.getBytes().length + "\n");
        request.append("Content-Type: text/plain\n");
        // 构造 空行
        request.append("\n");
        // 构造 body
        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", 8080);
        String getResp = httpClient.get("/AjaxMockServer/info");
        System.out.println(getResp);
        String postResp = httpClient.post("/AjaxMockServer/info", "this is 
                body");
                System.out.println(postResp);
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值