http协议及基于http协议的文件下载

24 篇文章 31 订阅
1 篇文章 1 订阅

目录

1. HTTP 协议概述

2. URL 与资源

3. HTTP报文

4. 使用Postman 获取数据

5. 基于HTTP协议的文件下载

5.1 文件整体下载

 5.2 文件分段(Range)下载

5.2.1 获取文件的大小

5.2.2 下载分段文件

5.3 文件分块(chunk)下载


1. HTTP 协议概述

日常我们使用网络用得最多的无疑是在Web 浏览器(下文统一使用浏览器)上查找资料、看视频、看书、看新闻等等,而在浏览器中只需要输入一些搜索就可以得到想要的信息,这归根于搜索引擎的好处,但是实际上每个网页其实由多个资源组成。我们的浏览器就是一个HTTP 客户端,通过HTTP 协议访问服务器,得到服务器中的HTML 页面、文本文件、图片、音频等资源,并且将这些资源搬运到浏览器(客户端)显示给我们。

HTTP 协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传输协议,它是基于TCP/IP 协议通信的,因此它也是基于<客户端-服务器>模型运作的,是一个应用层协议,可以用它来传输服务器的各种资源,如文本、图片、音频等。

HTTP 协议的特点:

1. 简单:当客户端向服务器请求服务时,只需传送请求方法和路径即可获取服务器的资源,请求方法常用的有GET、HEAD、POST 等,每种方法规定了客户端与服务器通信的类型不同。

2. 快捷:由于HTTP 协议简单,使得HTTP 服务器的程序规模小,因而通信速度很快。

3. 灵活:HTTP 允许传输任意类型的数据对象,传输的类型由Content-Type 加以标记。

4. 无连接:无连接的含义是限制每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接,简单来说就是每进行一次HTTP 通信,都要断开一次TCP 连接,可随着HTTP 的普及,文档中包含大量图片的情况多了起来,每次请求完都要断开TCP 连接,无疑增加通信量的开销,为了解决TCP的连接问题,HTTP1.1 提出了持久连接的方法,即任意一端只要没有明确提出断开连接,则保持TCP 连接状态,这样子就就减少了TCP 连接的重复建立和断开所造成的额外开销,减轻了服务端的负载。注意,在HTTP1.1 版本之后才出现持久连接的方法

5. 无状态:HTTP 协议是无状态协议,无状态是指协议对于事务处理没有记忆能力,即HTTP 协议无法根据之前的状态进行本次的请求处理,这就意味着如果后续处理需要前面的信息,它必须重传数据,这样的情况可能导致HTTP 协议传输的数据量增大,当然,凡事都有两面性,在另一方面,在服务器不需要先前信息时它的应答就较快,可以减少服务器的资源消耗,其实这种无状态对于用户来说也是不友好的,因此为了解决无状态的问题,引入了Cookie 技术,这是一种可以让服务器知道用户上一次做了什么操作,并且记录下来,它是存储在客户端之中的,比如我们在淘宝上买东西,我们选择了几个商品,但是到了结账会跳转到另一个页面,此时如果服务器不知道我们选择了哪些商品,那怎么能结账成功呢?所以Cookie 就是用来绕开HTTP 的无状态性的“手段”之一,服务器可以设置或读取Cookies 中包含信息,让服务器知道我们选择了什么商品,借此维护用户跟服务器会话中的状态,当然,Cookie 会被加密存储在客户端中,直到过期或者手动清除。

2. URL 与资源

我们可以把整个英特网看做是一个巨大的图书馆,里面的资源应有尽有,并且是对我们是开放的,我们想要找一本书,那么我们就需要直到他存放在哪里,然后去找到它。网络中的资源也是应有尽有,那么怎么样才能在网络的海洋中找到我们想要的资源呢?因此URI(Uniform Resource Identifiers)就被设计出来,用于统一管理资源,就像我们去图书馆找书一样,我们必须通过图书馆的系统,找到书所在的位置,而不是让我们自己一本一本书去找。

URL 全称是Uniform Resource Locator,中文叫统一资源定位符,是互联网上用来标识某一处资源的绝对地址,使用它我们就必然能找到资源,除非资源已经被转移了。URI 是一个通用的概念,由两个子集组成,分别是URL 和URN,URL 是通过资源的位置来标识资源,而URN 更高级一点,只需通过资源名字即可识别资源,与他们所处的位置是无关的,目前暂时还未推广URN。

大部分URL 都会遵循URL 的语法,一个URL 的组成有多个不同的组件,一个URL的通用格式如下

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

当然,绝大部分的URL 是不会包含所有组件的内容的.

URL 组件
scheme(方案)指定访问服务器获取资源时使用哪种协议,有HTTP、HTTPS、FTP、SMTP 等协议。
user(用户)某些方案访问资源时候需要指定用户名,才有权限获取资源。
password(密码)用户名后面可能需要密码进行验证,用户名与密码直接使用 “:” 冒号分隔连接。
host(主机)资源宿主服务器的主机名或者IP 地址(点分十进制)。
port(端口)资源宿主服务器正在监听的端口号,很多方案都有默认的端口号,而无需我们自己填写,比如HTTP 默认使用80 端口,HTTPS 默认使用443 端口。端口不是一个URL 必须的部分,如果省略端口部分,将采用默认端口。
path(路径)服务器本地资源的路径。类似于电脑中的文件路径一样,使用“/”将路径与端口隔离。从域名后的第一个“/”开始到最后一个“/”为止,虚拟目录也不是一个URL 必须的部分,在路径之后是需要一个文件名,这就是URL 指定的资源。文件名部分也不是一个URL 必须的部分,如果省略该部分,则使用默认的文件名
params(参数)某些方案会使用这个组件来输入参数,可以拥有多个参数,使用“;”符号与路径分隔开。
query(查询)某些方案会使用这个组件传递参数以激活应用程序,查询组件的内容没有通用的格式,用“?”字符与其他组件分隔开
frag(片段)一小片或者一部分资源的名字,引用对象时,不会将片段组件内容传输给服务器,这个字段是在客户端内部使用的,通过“#”字符与其他组件分隔开。

比如:https://blog.csdn.net/XieWinter,它就是一个URL,这是最简单的一个URL 格式,通过其可以访问相应的资源。

3. HTTP报文

HTTP 报文是由3 个部分组成,分别是:对报文进行描述的“起始行”,包含属性的“首部”,以及可选的“数据主体”,对于请求报文与应答报文,只有“起始行”的格式是不一样的。

http 请求报文

<method> <request-url> <version>                    // 起始行
<headers>                                           // 首部

<entity-body>                                       // 数据主体

http 应答报文

<version> <status> <reason-phrase>                  // 起始行
<headers>                                           // 首部

<entity-body>                                       // 数据主体

起始行和首部就是由行分隔的 ASCII 文本组成,每行都以由两个字符组成的行终止序列作为结束,其中包括一个回车符(ASCII 码 13)和一个换行符(ASCII 码 10), 这个行终止序列可以写做 CRLF。

这两种HTTP 报文的各个部分简单描述:

  • 方法(method):HTTP 请求报文的起始行以方法作为开始,方法用来告知服务器要做些什么,常见的方法有GET、POST、HEAD 等,比如“GET /forum.php HTTP/1.1” 使用的就是GET 方法。
  • 请求URL(request-URL):指定了所请求的资源。
  • 版本(version):指定报文所使用的HTTP 协议版本,其中<major>指定了主要版本号, <minor>指定了次要版本号,它们都是整数,其格式如下:
HTTP/<major>.<minor>
  • 状态码(status):这是在HTTP 应答报文中使用的,状态码是在每条响应报文的起始行中返回的一个数字码,描述了请求过程中所发送的情况,比如成功、失败等,不同的状态码有不同的含义

200 常见的返回OK,404错误等,206 状态码将会文件分段下载中使用。

  • 原因短语(reason-phrase):这其实是给我们看的原因短语,因为数字是不够直观,它只是状态码的一个文本形式表达而已。 
  • 首部(header):HTTP 报文可以有0 个、1 个或者多个首部,HTTP 首部字段向请求和响应报文中添加了一些附加信息,从本质上来说,它们是一个<名字:值>对,每个首部都包含一个名字,紧跟着一个冒号“:”,然后是一个可选的空格,接着是一个值,最后以CRLF 结束,比如“Host: blog.csdn.net”就是一个首部。
  • 数据主体(entity-body):这部分包含一个由任意数据组成的数据块,其实这与我们前面所讲的报文数据区域是一样的,用于携带数据,HTTP 报文可以承载很多类型的数字数据:图片、视频、音频、HTML 文档、软件应用程 序等。

4. 使用Postman 获取数据

既然了解了HTTP 协议与HTTP 报文的相关知识,我们就来使用Postman 软件了解一下HTTP 协议的传输过程。

官网:https://www.getpostman.com/

安装后打开软件,就可以使用软件进行测试HTTP 协议,可以很直观看到发送的HTTP 报文是什么,也能很直观看到响应的数据。

可以简单注册一下,就可以记录发送的命令,相当不错。

或者

查看发送HTTP 请求报文

  Postman 功能很强大,只是用到其最简单的功能。

5. 基于HTTP协议的文件下载

5.1 文件整体下载

文件整体下载比较简单,只需要知道资源的位置下载就可以。

 5.2 文件分段(Range)下载

在嵌入式设备或者资源有限的设备中,可能需要下载的文件的大小,自身资源不能一次性的满足,需要分段下载才行。文件分段下载的前提条件是,支持文件分段下载,否则,即便采用分段获取的,下载的也是整个文件大小,比如GitHub上面的就会下载整个文件的大小。

文件下载的流程如下:

  • 获取文件的大小
  • 下载各分段文件
  • 文件拼接
  • 文件完整性检查

5.2.1 获取文件的大小

因为需要分段下载,因此,首先需要知道文件的大小,才方便计算分段,当然,不分段也不是不可以,但处理起来,没有先获取文件大小规范。

可以直接获取资源链接的网站,检查响应的消息头字段中是否存在 如下字段,存在则支持,否则,不支持

Accept-Ranges: bytes

这就需要用到消息头字段添加 Range 字段


Range: bytes=0-1024 获取最前面1025个字节
Range: bytes=-500   获取最后500个字节
Range: bytes=1025-  获取从1025开始到文件末尾所有的字节
Range: 0-0          获取第一个字节
Range: -1           获取最后一个字节

请求成功后服务器会返回状态码206, 并返回如下字段指示返回结果, 0-1024指示返回分段范围, 7877指示文件总大小
Content-Range: bytes 0-1024/7877

因此采用如下,获取文件大小:

Range: 0-0          获取第一个字节

示例:

5.2.2 下载分段文件

分段下载分为两种,一种就是一次请求一个分段,一种就是一次请求多个分段。

下面两种都会有介绍,但是,实际上,除非为了多线程多段下载(PC下载软件 Internet Download Manager ),否则,在嵌入式开发中一般还是采用一次请求一个分段。

根5.2.1 可知文件大小,如示例文件大小为 3700512 字节。

具体怎么分段,根据设备资源来分配,原理是一样都。

假如分三段 0-999999,1000000-1999999,2000000-3700511

一次请求一个分段

  • 第一段消息头字段添加
Range: bytes=0-999999 获取最前面1000000个字节

 

 

  • 第二段消息头字段添加

Range: bytes=1000000-1999999 获取第二个1000000个字节

 

 

  • 末段消息头字段添加

Range: bytes=2000000-3700511 获取最后一段
或另一种方式

Range: bytes=2000000- 获取最后一段             // 这种更好些

 

 一次请求多个分段

 至此,文件下载完成,文件的拼接,如果是 类似都 xxx.bin文件,那么保存的时候,同一块地址连续保存就完成来拼接来。因为这里主要也是想表达通过http 服务分段下载文件,来升级程序,实现 FOTA 功能 ,其它方法暂不多说;

文件下载完成后,需要校验文件都完整性,否则不完整都程序,一旦升级将造成死机的情况。

5.3 文件分块(chunk)下载

Transfer-Encoding: chunked 表示输出的内容长度不能确定,普通的静态页面、图片之类的基本上都用不到这个。但动态页面就有可能会用到,但也注意到大部分asp,php,asp.net动态页面输出的时候大部分还是使用Content-Length,没有使用Transfer-Encoding: chunked。

Transfer-Encoding: chunked使用,就不必申请一个很大的字节数组了,可以一块一块的输出,更科学,占用资源更少。

这在http协议中也是个常见的字段,用于http传送过程的分块技术,原因是http服务器响应的报文长度经常是不可预测的,使用Content-length的实体搜捕并不是总是管用。分块技术的意思是说,实体被分成许多的块,也就是应用层的数据,TCP在传送的过程中,不对它们做任何的解释,而是把应用层产生数据全部理解成二进制流,然后按照MSS(Maxitum Segment Size 最大分段大小,一般客户端资源有限,大小受其限制)的长度切成一分一分的,一股脑塞到tcp协议栈里面去,而具体这些二进制的数据如何做解释,需要应用层来完成,所以在这之前,一块整体应用层的数据需要等它分成的所有TCP  segment到达对方,重新组装后,应用程序才使用自己的解码方法还原它们。

HTTP1.1采用了持久的连接,也就是一次TCP的连接不马上释放,允许许多的请求跟响应在一个TCP的连接上发送,所以客户机与服务器需要某种方式来标示一个报文在哪里结束和在下一个报文在哪里开始。简单的方法是使用呢content-length,但这只有当报文长度可以预先判断的时候才起作用,而对于动态的内容或者在发送数据前不能判定长度的情况下,可以使用分块的方法来传送编码。

Web服务器有时生成HTTPResponse无法在Header就确定消息大小的,这时一般来说服务器将不会提供Content-Length的头信息,而采用Chunked编码动态的提供body内容的长度。进行Chunked编码传输的HTTP Response会在消息头部设置:Transfer-Encoding: chunked表示Content Body将用Chunked编码传输内容。

 

 

 Chunked编码使用若干个Chunk串连而成,由一个标明长度为0的chunk标示结束。每个Chunk分为头部和正文两部分,头部内容指定下一段正文的字符总数(十六进制的数字)和数量单位(一般不写),正文部分就是指定长度的实际内容,两部分之间用回车换行(CRLF)隔开。在最后一个长度为0的Chunk中的内容是称为footer的内容,是一些附加的Header信息(通常可以直接忽略)。

 

chunk编码其实是一种动态数据传输协议,针对大数据可以动态传输,网页可以动态显示。

chunk编码格式如下:

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

chunk size是以十六进制的ASCII码表示,比如33 37 35,计算成长度应该是:0x375(885),表示从回车之后有连续的885字节的数据。

chunk数据以0长度的chunk块结束。

分块传输的前提条件是,服务器支持该方式!!!且为http1.1协议。

 

注:本节没有详细介绍HTTP及其消息头字段,只是从FOTA都角度,来说明相关都东西,深入都需要查询相关协议。

  • 12
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是基于Spring Boot框架设计并实现HTTP协议接入的一般步骤: 1. 创建Spring Boot工程:在IDE中创建一个新的Spring Boot工程,或者使用Spring官方提供的Initializr工具创建,并添加Web依赖。 2. 配置HTTP接入:在application.properties或application.yml文件中配置HTTP接入的相关属性,例如端口号、请求路径等等。 3. 定义Controller:创建一个Controller类,使用@RestController注解标记,定义HTTP请求的处理方法。例如,可以使用@GetMapping注解标记一个GET请求的处理方法,@PostMapping注解标记一个POST请求的处理方法,等等。 4. 处理HTTP请求:在Controller中的处理方法中,使用Spring提供的注解和类来处理HTTP请求和响应。例如,可以使用@PathVariable注解将URL中的参数映射到Controller方法的参数中,使用@RequestParam注解将HTTP请求的查询参数映射到方法参数中,使用@ResponseBody注解将方法返回值转换为HTTP响应数据等等。 5. 测试接口:使用HTTP客户端工具(例如Postman或curl)测试HTTP接口的功能和正确性。 下面是一个简单的示例代码,演示了如何使用Spring Boot实现一个简单的HTTP接口: ```java @RestController @RequestMapping("/api") public class ApiController { @GetMapping("/hello") public String hello(@RequestParam(value = "name", defaultValue = "World") String name) { return "Hello, " + name + "!"; } } ``` 在上述示例中,我们定义了一个名为“hello”的接口,该接口接受一个名为“name”的查询参数,并返回一个字符串形式的问候语。例如,访问“/api/hello?name=Tom”将返回“Hello, Tom!”的响应。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值