HTTP
一、HTTP 概述
此部分内容只是简单的对 HTTP 作一个介绍,更详细的说明或协议文档,请查阅相关网站或文档。
1.1 HTTP 请求方法
HTTP/1.1 协议中共定义了八种方法来以不同方式操作指定的资源。
a.GET
向指定的资源发出请求。使用 GET 方法应该只用在读取数据。
b.HEAD
与 GET 方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。
c.POST
向指定资源提交数据,请求服务器进行处理,例如上传文件。
d.PUT
向指定资源位置上传其最新内容。
e.DELETE
请求服务器删除 Request-URI 所标识的资源。
f.TRACE
回显服务器收到的请求,主要用于测试或诊断。
g.OPTIONS
这个方法可使服务器传回该资源所支持的所有 HTTP 请求方法。用’*'来代替资源名称,向 Web 服务器发送 OPTIONS 请求,可以测试服务器功能是否正常运作。
h.CONNECT
HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。通常用于 SSL 加密服务器的链接。
HTTP 服务器至少应该实现 GET 和 HEAD 方法,其他方法都是可选的。
1.2 HTTP 状态码
状态代码的第一个数字代表当前响应的类型:
1xx 消息——请求已被服务器接收,继续处理
2xx 成功——请求已成功被服务器接收、理解、并接受
3xx 重定向——需要后续操作才能完成这一请求
4xx 请求错误——请求含有词法错误或者无法被执行
5xx 服务器错误——服务器在处理某个正确请求时发生错误
RFC 2616 中已经推荐了描述状态的短语,例如"200 OK",“404 Not Found”。
1.3 URL 链接地址
超文本传输协议(HTTP)的地址包含五个基本元素,分别是:
a.传送协议, 层级 URL 标记符号(为[//],固定不变) 访问资源需要的凭证信息服务器,一般情况下为域名,也可以使用 IP 地址。
b.端口号,以数字方式表示,HTTP 默认为”:80“,默认值可不给出。
c.路径,字符“/”区别路径中的每一个目录名称。
d.查询,GET 模式的窗体参数,用“?”字符作为起点,每个参数用“&”隔开,再以“=”分开参数名称与数据。
e.片段,以“#”字符为起点。
由于超文本传输协议允许服务器将浏览器重定向到另一个网页地址,因此许多服务器允许用户省略网页地址中的部分内容,比如 www。
本文通过几个具体的例子,演示 http 与 https 协议的具体实现。
二、功能演示概述
2.1 演示内容
本文通过作者自身的使用经历与实践,给大家演示 http 的常见操作与使用,包括:
a.get 方法的实现;
b.post 方法的实现;
c.文件的上传操作;
d.文件的下载操作;
e.https 加密通讯;
f.处理 JSON 数据;
g.gzip 操作。
三、硬件环境准备
3.1 开发板准备及购买链接
本文所涉及到的所有演示,都使用开发板 EVB_Air724UG_A14 完成,大家可以通过以下的淘宝连接购买:
淘宝购买链接:Air724UG-NFM 开发板淘宝购买链接 ;
此开发板的详细使用说明参考:Air724UG 产品手册 中的 《EVB_Air724UG_AXX开发板使用说明》,写这篇文章时最新版本的使用说明为:《EVB_Air724UG_A14开发板使用说明》;开发板使用过程中遇到任何问题,可以直接参考这份使用说明文档。
中国大陆环境下,可以上网的 sim 卡,一般来说,使用移动,电信,联通的物联网卡或者手机卡都行;
3.2 开发板的接线方式
首先将开发板放置好,接上 USB 并连接到电脑,同时,记得将天线也连接好,保证信号环境比较良好,比如可以看看手机信号来判断一下所在环境的信号状况。USB 的连接如上图所示。
在上图中,连接 USB 的插口旁边有一个 USB 字样,在进行脚本下载时,须连接此端口。旁边另一个 USB 插口是 USB 转 UART 的接口,可以通过串行口工具查看调试 TRACE 信息。在本文的测试环境中,使用 USB 打印 trace,即在 Luatools工具中,将软件上的"usb 打印 trace"选中即可,不必再另外连接串行口监视打印 trace 的相关信息。
3.3 固件操作相关内容
a.Luatools,是下载固件与脚本必不可少的工具,并且使用其查看 TRACE 调试信息也非常方便。
https://docs.openluat.com/Luatools/
b.Luatools 工具的使用请参阅:
https://docs.openluat.com/blog/Luatools/
c.远程固件升级请参阅:
https://docs.openluat.com/blog/fota_lesson/
d.USB 驱动安装请参阅:
https://docs.openluat.com/blog/usb_drv/
四、软件环境准备
4.1 Lua 脚本语言
本文以 Lua 脚本语言为基础,因而需要有 Lua 脚本语言基础,可以通过下列文档了解:
Lua 脚本语言的语法,请参阅:
https://wiki.luatos.com/luaGuide/luaReference.html#lua-5-3
https://docs.openluat.com/blog/lua_lesson/
4.2 Lua 脚本语言的 http 接口 API
本文既然是说 http,除了在 http 概述内的简单说明,还有必要对 Lua 脚本语言的 API 有一个了解。http 的 API 接口定义如下面的代码。我之所以帖出下面这段定义代码,三个方面的原因。
a.首先当然是注释说明中有较明析的 http 协议结构的定义;
b.其次对参数的解释说明比演示文档 testHttp.lua 文件内容更全面,更有利于读者对各个参数形式有一个了解。比如,传递证书就有了较明晰的表达式形式,这样也方便大家组织参数时,能做到心里有数;
c.最后是给出了不少调用示例,而这些示例在 testHttp.lua 文档内没有或者不全面,而此处的示例能起到一个很好的补充。
--- 发送HTTP请求
-- @string method HTTP请求方法
-- 支持"GET","HEAD","POST","OPTIONS","PUT","DELETE","TRACE","CONNECT"
-- @string url HTTP请求url
-- url格式(除hostname外,其余字段可选;目前的实现不支持hash),url中如果包含UTF8编码中文,则需要调用string.rawurlEncode转换成RFC3986编码。
-- |------------------------------------------------------------------------------|
-- | protocol ||| auth | host | path | hash |
-- |----------|||-----------|-----------------|---------------------------|-------|
-- | ||| | hostname | port | pathname | search | |
-- | ||| |----------|------|----------|----------------| |
-- " http[s] :// user:pass @ host.com : 8080 /p/a/t/h ? query=string # hash "
-- | ||| | | | | | |
-- |------------------------------------------------------------------------------|
-- @table[opt=nil] cert,table或者nil类型,ssl证书,当url为https类型时,此参数才有意义。cert格式如下:
-- {
-- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
-- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
-- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
-- clientPassword = "123456", --客户端证书文件密码[可选]
-- }
-- @table[opt=nil] head,nil或者table类型,自定义请求头
-- http.lua会自动添加Host: XXX、Connection: short、Content-Length: XXX三个请求头
-- 如果这三个请求头满足不了需求,head参数传入自定义请求头,如果自定义请求头中存在Host、Connection、Content-Length三个请求头,将覆盖http.lua中自动添加的同名请求头
-- head格式如下:
-- 如果没有自定义请求头,传入nil或者{};否则传入{head1="value1", head2="value2", head3="value3"},value中不能有\r\n
-- @param[opt=nil] body,nil、string或者table类型,请求实体
-- 如果body仅仅是一串数据,可以直接传入一个string类型的body即可
--
-- 如果body的数据比较复杂,包括字符串数据和文件,则传入table类型的数据,格式如下:
-- {
-- [1] = "string1",
-- [2] = {file="/ldata/test.jpg"},
-- [3] = "string2"
-- }
-- 例如上面的这个body,索引必须为连续的数字(从1开始),实际传输时,先发送字符串"string1",再发送文件/ldata/test.jpg的内容,最后发送字符串"string2"
--
-- 如果传输的文件内容需要进行base64编码再上传,请把file改成file_base64,格式如下:
-- {
-- [1] = "string1",
-- [2] = {file_base64="/ldata/test.jpg"},
-- [3] = "string2"
-- }
-- 例如上面的这个body,索引必须为连续的数字(从1开始),实际传输时,先发送字符串"string1",再发送文件/ldata/test.jpg经过base64编码后的内容,最后发送字符串"string2"
-- @number[opt=30000] timeout,http请求应答整个过程中,每个子过程的超时时间,单位毫秒,默认为30秒,子过程包括如下两种:
-- 1、pdp数据网络激活的超时时间
-- 2、http请求发送成功后,分段接收服务器的应答数据,每段数据接收的超时时间
-- @function[opt=nil] cbFnc,执行HTTP请求的回调函数(请求发送结果以及应答数据接收结果都通过此函数通知用户),回调函数的调用形式为:
-- cbFnc(result,prompt,head,body)
-- result:true或者false,true表示成功收到了服务器的应答,false表示请求发送失败或者接收服务器应答失败
-- prompt:string类型,result为true时,表示服务器的应答码;result为false时,表示错误信息
-- head:table或者nil类型,表示服务器的应答头;result为true时,此参数为{head1="value1", head2="value2", head3="value3"},value中不包含\r\n;result为false时,此参数为nil
-- body:string类型,如果调用request接口时传入了rcvFileName,此参数表示下载文件的完整路径;否则表示接收到的应答实体数据
-- @string[opt=nil] rcvFileName,string类型时,保存“服务器应答实体数据”的文件名,可以传入完整的文件路径,也可以传入单独的文件名,如果是文件名,http.lua会自动生成一个完整路径,通过cbFnc的参数body传出
-- function类型时,rcvFileName(stepData,totalLen,statusCode)
-- stepData: 本次服务器应答实体数据
-- totalLen: 实体数据的总长度
-- statusCode:服务器的应答码
-- @table[opt=nil] tCoreExtPara,table类型{rcvBufferSize=0}修改缓冲空间大小,解决窗口满连接超时问题,单位:字节
-- @return string rcvFilePath,如果传入了rcvFileName,则返回对应的完整路径;其余情况都返回nil
-- @usage
-- http.request("GET","www.lua.org",nil,nil,nil,30000,cbFnc)
-- http.request("GET","http://www.lua.org",nil,nil,nil,30000,cbFnc)
-- http.request("GET","http://www.lua.org:80",nil,nil,nil,30000,cbFnc,"download.bin")
-- http.request("GET","www.lua.org/about.html",nil,nil,nil,30000,cbFnc)
-- http.request("GET","www.lua.org:80/about.html",nil,nil,nil,30000,cbFnc)
-- http.request("GET","http://wiki.openluat.com/search.html?q=123",nil,nil,nil,30000,cbFnc)
-- http.request("POST","www.test.com/report.html",nil,{Head1="ValueData1"},"BodyData",30000,cbFnc)
-- http.request("POST","www.test.com/report.html",nil,{Head1="ValueData1",Head2="ValueData2"},{[1]="string1",[2] ={file="/ldata/test.jpg"},[3]="string2"},30000,cbFnc)
-- http.request("GET","https://www.baidu.com",{caCert="ca.crt"})
-- http.request("GET","https://www.baidu.com",{caCert="ca.crt",clientCert = "client.crt",clientKey = "client.key"})
-- http.request("GET","https://www.baidu.com",{caCert="ca.crt",clientCert = "client.crt",clientKey = "client.key",clientPassword = "123456"})
function request(method,url,cert,head,body,timeout,cbFnc,rcvFileName,tCoreExtPara)
关于 http 接口的具体说明,读者还可以查询其它相关资料或参考其它文件档。
4.3 辅助工具
为了有效的分析开发过程中可能遇到的问题,并能查看数据,准备一些辅助工具很有必要,将大大缩短大家解决问题的时间。本文编写过程中,使用了几款工具介绍给大家,本文在后面将使用用或提到这些工具,请大家先了解。
a.Progress Telerik Fiddler Classic,抓包工具。
https://downloads.getfiddler.com/fiddler-classic/FiddlerSetup.5.0.20244.10953-latest.exe
b.Postman,API 调试平台工具,可以方便的验证本文所提到的各种操作。
https://dl.pstmn.io/download/latest/win64
c.httpbin.org,一个很不错的 API 调试网站,本文有关 http 的不少内容的演示,即使用本网站完成。
五、Luatools 的基本使用
5.1 准备演示代码
本文使用 air724 模块的演示代码 testHttp 作为蓝本,在此基础上进行修订删改,来完成本文第二章中所提到的内容。该演示蓝本有两个文件,分别为 main.lua、testHttp.lua 两个文件。大家如果了解可以跳过,如果不熟悉又不想去费劲下载资源,可以直接复制使用。testHttp.lua 随着本文的测试进行,会有所修订,本文最后会给出完整的包含所有修订的文件。
5.2 使用 Luatools 工具
按照本文的第三章第 2 节所描述的方式连接好开发板后,确认无误后,我们就可以启动 Luatools 工具,如下图所示。点击界面中“项目管理与测试”按钮,出现下图所示的窗口,点击“创建项目”,并输入项目名称,如本文建立的项目名称为 http_doc。
项目建立完成后,点添加脚本或资源文件,按上节所说将蓝本 main.lua 与 testHttp.lua 添加上去,如图所示。
将"usb 打印 trace”使能,“添加默认 lib”选中。
完成上面的操作后,还要选择底层 CORE。因为合宙模块有各种资源,不同的底层 CORE 支持的内容不同,这些底层内核一般安装在工具所在目录的 ressoure 子目录下。打开此目录,会有不同的选择,如图。因为 724ug 模块属于 8910 平台,所以我们要选择 8910 的某一个内核,本文所说都是指对脚本语言 Lua 进行开发,因而我们选择 8910_lua_lod 即可。点击进入,有多个内核,我们选择最基本的 LuatOS-Air_V4028_RDA8910.pac 即可,如果大家手上使用的内核进行了升级,会有一些区别,譬如版本号变化等,请留意即可。
如果电脑上没有上述固件,也可以通过下载的网址下载。
固件版本和上层脚本:https://docs.openluat.com/air724ug/luatos/firmware/
六、GET 请求演示
6.1 演示网站 httpbin.org
为了使本文的演示可直接使用并能得到结果,而不是随便给个示范文本,有必要找一个可以进行测试的网站,本文使用上面工具准备里提到过的 httpbin.org 给出的 API 来进行测试。
首先打开 httpbin.org 网站,并找到 Dynamic data 这一栏,然后点开下拉菜单,如图示。
找到 Dynamic data 这一栏,打开,如下图所示。
在上面看到第一项是 GET 方法,url 是/base64/{value},表示使用 GET 方法可以从此连接(url)得到经过 base64 编码的内容,也即将 value 还原为实际内容,即解码。我们来完成这个工作。
6.2 用 GET 方法实现 base64 解码 API
我们可以使用 crypto.base64_encode 函数来对字符完成 base64 的编码,在 testHttp.lua 中找到 http.request( "GET" , "www.lua.org",nil,nil,nil,nil,cbFnc)这一行语句,在语句前定义一个本地变量并赋值”用合宙 724ug 模块完成 http 的 GET 方法“,即 local base64_str = "用合宙 724ug 模块完成 http 的 GET 方法"。
按 API 的说明,将 url 路径也即将 "www.lua.org" 替换为 "httpbin.org/base64/"..crypto.base64_encode ( base64_str , base64_str:len() )。
修订完成后是如下这个样子,保存文件后我们使用 Luatools 将脚本下载到开发板中,看看结果如何。
local base64_str = "用合宙724ug模块完成http的GET方法"
http.request("GET","httpbin.org/base64/"..crypto.base64_encode(base64_str,base64_str:len()),nil,nil,nil,nil,cbFnc)
特别注意
如果是第一次下载,要选择下载底层与脚本,因为你到手的开发板,里面到底是什么内核还不清楚,如果内核不对,也就运行不起来,这一点切记。在完成了底层下载后,如果脚本再有修订,就不需要再下载底层了,只下载脚本即可。
下载完成后,会自动启动,并在 Luatools工具中显示 trace 信息。直接看结果吧,如图所示。
在上图中,可以看到结果 true 200,表示正确完成,http 状态码是 200。
同时返回了解码的字串”用合宙 724ug 模块完成 http 的 GET 方法“。
至此,GET 的演示工作顺利完成。
6.3 完整代码的约定
为方便大家取用,本文最后将列出完整的 testHttp.lua 代码。本文所作的全部修订,都将保留在 testHttp.lua 文件中,使用注释符如“--[[”与“]]”的来进行选取使用或不使用。
七、POST 请求演示
7.1 物联网卡信息 API 接口
对 get 的演练,感觉还是挺容易的,一路下来也没有什么障碍。不过 GET 我们使用的还不是真正的应用场合,因为服务器仍然还只是一个演示。我们在 POST 就使用一个真实的应用数据,算是一个真实的项目应用。
我们要演示,就得有服务器可以让我们来实操,上面说了,我们要完成一个真实的项目。使用 724ug 模块,就得有上网卡,物联网卡是最优的选择。这里有一个物联网卡服务商提供的物联网 SIM 卡信息的 API 接口,这个网站是
http://sim.taoyuan-tech.com。这是一个真实的网站,而且给客户提供一个测试帐号,那么我们就使用这个测试账号来进行 POST 方法的操作,以读取 SIM 卡的日志
这里测试账号的用户名:18168967871,密码:18168967871。登录进去后,是下图中所示的样子。
在上图所示界面中,点击获取”APIkey 各 Secret“字样的按钮,可得到测试用户的 API key 与 API Secret,注意保存这两个数据备用。点击”API 文档“字样的按钮,可进入详细的接口 API 说明文档页面,如图。
看文档目录,约定等前面几项内容是要关注的,好在简短,切一张图也就可以完全看到,列出于下图。
我们在上图中看到几项注意事项。
a.所有请求参数,使用 json 形式发送。这表示只接收 json 形式的数据与内容;
b.需要返回状态码 200 才正确;
c.约定了所使用的单位,这样可以方便的计算时间与费用;
d.对认证进行了说明,这很重要,要仔细阅读并理解 。认证方式为 HTTP Basic Authorization,认证信息的形式是 appkey:appsecret。就是前面提到要注意保存的那两个数据;
e.对可能的错误码进行了说明;
f.给出了主机地址为 http://api.taoyuan-tech.com/api/open。
所有这些约定及注意事项都很重要,不要忽略,可能后面我们都会要用到。
这个 API 有好几个接口,我们就选第一个来实现即可。就是下面图中所列出的日志查询。图中表格所给出的字段,是我们需要提供的访问参数及返回结果。
7.2 使用 postman 工具验证
为了方便,我们可以先使用 postman 组织包内容,然后再移植到 testHttp.lua 文件内进行测试。查看“物联网卡指定日期用量日志查询 ”API 的说明,我们知道此接口的访问的 url 为“/iotcard/usagelog”,所以完整的访问路径为 http://api.taoyuan-tech.com/api/open/iotcard/usagelog,将此路径填入到 postman 中,选择 POST 方法,选择头部信息 Auth Type 类型为 Basic Auth。这些内容都是 API 文档约定与注意事项所确定的内容。然后点击 Auth,将前文提到过的 Apikey 与 Apisecret 分别填入到用户名与密码输入框内。如下图所示。
接着再点击 Body,输入以下内容:
其中字段的含义在前图所示的表格中都有说明,请大家注意查阅。而 89860403102080512138 为 SIM 卡号的 iccids 号,此 SIM 卡可在测试用户账号中的卡片信息中查到。如果卡片信息没有此号码,可以查阅卡片信息以获取用户数据库中真实存在的卡片 iccids 号。从图中的说明中我们还知道,传入的参数可以是其它的三种识别号码之一,如 imei 号。在四个 SIM 卡的关联识别号中四选一,本文我们选择 iccids,想测试其它参数形式的,可以自行更改修订。比如修订为 imsis,则输入内容就变为:
如下图所示。
输入完成,点击 Send 发送。即返回如下图所示的响应。
依据前图所示的表格内容可知,2024-10-10 这一天所消耗的流量为 0M,即"data_usage":0。同时,查看状态码为 200,结果 OK。这些完成后,我们就可以根据组包的相关信息,对 testHttp.lua 进行修订。
7.3 修订文件内容
打开 testHttp.lua 文件,我们在 77 行与 84 行之间,发现了一段代码,使用了 POST 方法,可以在此基础上进行修订,如下图。首先将注释去掉,再查看具体内容,我们发现有几点存在疑问。
a.从上文可知,API 使用的是 Auth Basic 的方式,而且上面组包时,使用的是 Apikey 与 Apisecret 两个内容,这个与演示代码的 ["Authorization]"="Basic jffdsfdsfdsfdsfjakljfdoiuweonlkdsjdsjapodaskdsf",好像相符,但又好像不相符,这里怎么去得到这个串呢;
b.前文提到,使用的 Content-type 是 application/json,与代码中所使用的方式有所不同,因而此处需要修订;
c.同时请求参数的内容肯定也需要修订,只是如何组织这个参数呢。从 table 类型的各种组织方式看,也没有 name:value 这样的形式。
有问题就好,一个个解决也就好了。首先我们从第一个问题开始,这就要用到本文开头所给出抓包工具了,我们看看实际的数据是什么样子,不就知道了吗?直接上图,如下面图中所示,可以看到实际的数据包都有哪些内容,当然,此抓包工具也可以查看 BODY,auth 等各项数据,可以切换各页面看看都有哪些数据项,以加深理解。
在上图中,我们看到了一行文字内容:Authorization: Basic NkZhbXFsRmZTVmQ4OHNHejpLemt0SW8y ......这个正是我们所要的内容,因而将此项复制,填入代替。
第二个问题简单,直接修订为['Content-Type']="application/json"即可。
第三个问题,需要调用 json 的一个函数来解决,就是下面这个函数,即 json 编码。
json.encode(
{
["query_date"] = "20241010",
["iccids"] = "89860403102080512138"
}),
其它的,['Connection']="keep-alive"要不要加上,看示例代码中有些加了,有些没有加。Http 是短连接,但又希望环境能得到保存,使下次再次发起 http 请求时,能快速得到反应,因而才有了这个['Connection']="keep-alive"的配置项,但在 Http 1.1 之后,这个就是默认的值,因而可以加上,也可以不加上,效果一样。
经过上面的修订后,代码成为了下面这样子。
http.request("POST","http://api.taoyuan-tech.com/api/open/iotcard/usagelog",nil,
{
['Authorization']="Basic NkZhbXFsRmZTVmQ4OHNHejpLemt0SW8yUTNXcFhmbXRJTEtqME1mc3dsbHF0cTV0aldQM1BPUFU2d1M2M0E5VVlYOHJ1SFZCSVRaejlBak5w",
['Content-Type']="application/json"
--['Connection']="keep-alive"
},
json.encode(
{
["query_date"] = "20241010",
["iccids"] = "89860403102080512138"
}),
30000,cbFnc)
好了,现在我们将脚本下载到开发板中,看看是什么结果。
不出意外,如上图中所示,可以看到返回的状态码是 200,结果为 true,代码执行正确无误,同时,可以看到服务器返回的数据,与从 postman 得到的数据一致。
到这里,POST 的演示算是完成了,另外要补充说明的是,其实头部结构里的 Auth Basic 有另一种取得方式,可以直接调用 crypto 模块内的 base64 函数来完成。 如下代码所示。
local username = "6FamqlFfSVd88sGz"
local password = "KzktIo2Q3WpXfmtILKj0Mfswllqtq5tjWP3POPU6wS63A9UYX8ruHVBITZz9AjNp"
local basic_str = username..':'..password
http.request("POST","http://api.taoyuan-tech.com/api/open/iotcard/usagelog",nil,
{
["Authorization"] = "Basic "..crypto.base64_encode(basic_str,basic_str:len()),
['Content-Type']="application/json"},
--['Connection']="keep-alive"},
json.encode(
{
["query_date"] = "20241010",
["iccids"] = "89860403102080512138"
}),
30000,cbFnc)
具体要如何使用,看实际情况,因为有些网站的 API 提供的直接就是 BASE64 码,此时用第一种显然省事。本文最后完整源代码使用生成方式。
八、文件上传操作
8.1 准备工作
对于文件上传,我们仍使用 GET 方法时所使用的测试服务器,即 httpbin.org,因为我们使用”post"方法上传文件,所以 url 为 httpbin.org/post。需要说明一点的是,httpbin.org/post 是一个回环服务器,即当向其传输文件时,服务器会将传输内容作为响应返回,因而当我们看到响应并返回的内容后,就可以判断操作是否正确。其实我有考虑使用更直接的方式,但几天下来找不到合用的服务器来进行操作,只好以此作为本文的上传文件演示了。
还是前面的方法,我们先使用 Postman 工具来组织数据包。
a.启动 Postman 并将方法选择为 POST;
b.在主机栏内输入“httpbin.org/post”;
c.然后选择 body,选择 binary;
d.选择文件,本处为 test.txt,内容为“uses post method to upload a file.”;如果没有此文件,新建一个即可;
e.点击发送;
f.稍后返回数据。
所有操作及数据如下图所示。
在图中可以清楚地看到 200 OK 字样,表示操作正确。同时,我们在图中 6 号箭头所指的文本中,可以发现一些有用的字串,分别是:
"Content-Type":"text/plain",表示传输的内容类型,这个很重要,后面要用,需要留意。
"data":"uses post method to upload a file.",这是实际传输的文件的内容。
同时,我们也发现了:
"files":{}
"form":{}
为空,这个我们可以比对后面的多文件多参数上传时的内容来进行理解。
8.2 文件修订
如前面的方法,我们来修订 testHttp.lua 文件的内容。
在 testHttp.lua 的 56 行开始,有一处注释掉了的代码,可以为我们所用。如下图。
首先,去掉注释括号,将 url 替换成“httpbin.org/post”;
其次,将['Content-Type']的值替换成前面所说的内容,即"text/plain";
最后,将{[1]={['file']="/RecDir/rec001"}}修订为{[1]={['file']="/luar/test.txt"}}并按保存。
修订后的文本如列出如下。
http.request("POST","httpbin.org/post",nil,
{['Content-Type']="text/plain",['Connection']="keep-alive"},
{[1]={['file']="/lua/test.txt"}
},
30000,cbFnc)
8.3 固件下载及运行结果
启动 Luatools 固件下载工具,并点击项目管理测试按钮,将文件 main.lua,testHttp.lua 分别添加进去,同时将文件 test.txt 也添加上去,如下图所示。需要注意,大家建立项目的位置不同,图中的文件路径与项目路径可能有些不同,与自己的实际内容相符即可。
在图中点下载脚本,然后切换到 Luatools 的 trace 页面,等待脚本代码运行结果。
由图中内容可见,脚本复现了 Postman 演示的内容,上文中我们所关注的内容也都从图中得到印证,见图中红框中的内容,可以与 postman 的内容进行比较。
到这里文件上传可以顺利完成。可能有人要问了,文件是上传了,可是我需要传二进制文件怎么办,或者我要传图片怎么处理。其实也很简单,我们还是用 postman 来进行组包测试,如下图。
图中我们将 test.txt 去掉,使用 test.bin 取代,将脚本的内容类型即 Content-Type 修订为"application/octet-stream"即可,其它不变,其中 application/octet-stream 就表示数据内容是二进制流。点击 SEND 后,可看到返回信息。修订后的内容如下所示。
--如下示例代码是利用文件流模式,上传录音文件的demo,使用的URL是随意编造的
http.request("POST","httpbin.org/post",nil,
{['Content-Type']="application/octet-stream",['Connection']="keep-alive"},
{[1]={['file']="/lua/test.bin"}
},
30000,cbFnc)
使用 Luatools 下载后等待运行结果如图所示。与 postman 的相关结果亦相符。当然,这里要特别提示几点。
a.文件请注意大小,太大的文件可能会导致内存不足,因而太大的文件需要经过其它处理才可以。
b.相关资源文件请一并加到项目中,与脚本一起下载到模块中。
c.对于二进制文件,对内容进行了 base64 编码,因而文件上传后,真正使用时,需要进行解码。
8.4 文件上传进阶篇
也许有些用途一方面要上传文件,一方面又要传参数,此时又要如何处理呢。我们查看 testHttp.lua 文件,有一段代码如下,也就是文件最后面约从 90 行开始的部分。我们可以利用此段代码进行比较复杂的文件上传操作,比如传多个文件,多个参数。下面这段代码即是 testHttp.lua 文件中相应的代码段去掉注释后的样子。
local function postMultipartFormData(url,cert,params,timeout,cbFnc,rcvFileName)
local boundary,body,k,v,kk,vv = "--------------------------"..os.time()..rtos.tick(),{}
for k,v in pairs(params) do
if k=="texts" then
local bodyText = ""
for kk,vv in pairs(v) do
bodyText = bodyText.."--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"\r\n\r\n"..vv.."\r\n"
end
body[#body+1] = bodyText
elseif k=="files" then
local contentType =
{
jpg = "image/jpeg",
jpeg = "image/jpeg",
png = "image/png",
}
for kk,vv in pairs(v) do
print(kk,vv)
body[#body+1] = "--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"; filename=\""..kk.."\"\r\nContent-Type: "..contentType[vv:match("%.(%w+)$")].."\r\n\r\n"
body[#body+1] = {file = vv}
body[#body+1] = "\r\n"
end
end
end
body[#body+1] = "--"..boundary.."--\r\n"
http.request(
"POST",
url,
cert,
{
["Content-Type"] = "multipart/form-data; boundary="..boundary,
["Connection"] = "keep-alive"
},
body,
timeout,
cbFnc,
rcvFileName
)
end
postMultipartFormData(
"1.202.80.121:4567/api/uploadimage",
nil,
{
texts =
{
["imei"] = "862991234567890",
["time"] = "20180802180345"
},
files =
{
["logo_color.jpg"] = "/ldata/logo_color.jpg"
}
},
60000,
cbFnc
)
在上面的代码中,传了两个参数,即 imei 卡号与时间,传了一个文件,即"logo_color.jpg"。我们需要修订为我们所用。首先我们将 url 修订为“httpbin.org/post”,同时我们将上传文件修订为 “/lua/text.txt”。除此外,我们还有一个地方要修订,即添加一个文件类型,即 txt = "text/plain"。表示这是一个文本文件,大抵如果是一个 BIN 文件,需要添加一个 BIN="application/octet-stream"吧,依此类推。
依上面修订后,testHttp.lua 成为下面这个样子。这是进阶篇完整的代码,大家可以直接复制取用,为了节省篇章,此代码为传输两个文件,两个参数的例子,不再一一讲解一个文件,一个参数等其它搭配组合情况,请知悉。
local function postMultipartFormData(url,cert,params,timeout,cbFnc,rcvFileName)
local boundary,body,k,v,kk,vv = "--------------------------"..os.time()..rtos.tick(),{}
for k,v in pairs(params) do
if k=="texts" then
local bodyText = ""
for kk,vv in pairs(v) do
bodyText = bodyText.."--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"\r\n\r\n"..vv.."\r\n"
end
body[#body+1] = bodyText
log.info("body text "..bodyText)
elseif k=="files" then
local bodystr = ""
local contentType =
{
bin = "application/octet-stream",
txt = "text/plain",
jpg = "image/jpeg",
jpeg = "image/jpeg",
png = "image/png",
}
for kk,vv in pairs(v) do
print(kk,vv)
bodystr = "--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"; filename=\""..vv.."\"\r\nContent-Type: "..contentType[vv:match("%.(%w+)$")].."\r\n\r\n"
log.info("body bin "..bodystr)
body[#body+1] = bodystr
body[#body+1] = {file = vv}
log.info("body file "..vv.."\r\n" )
body[#body+1] = "\r\n"
end
end
end
body[#body+1] = "--"..boundary.."--\r\n"
http.request(
"POST",
url,
cert,
{
["Content-Type"] = "multipart/form-data; boundary="..boundary,
["Connection"] = "keep-alive"
},
body,
timeout,
cbFnc,
rcvFileName
)
end
postMultipartFormData(
"httpbin.org/post",
nil,
{
texts =
{
["imei"] = "862991234567890",
["time"] = "20180802180345"
},
files =
{
["test.txt"] = "/lua/test.txt",
["test.bin"] = "/lua/test.bin"
}
},
60000,
cbFnc
)
将上面代码修订后,与 main.lua,text.txt,text.bin 一起添加的项目文件内,如图所示。没有文本文件可以新建,二进制文件可以在电脑上找一个较小的,比如我就找了一个单片机的二进制固件 test.bin。文件不要过大,容易导致内存问题,这也是我们实际要考虑的问题,即在实际应用中,文件的处理还是要小心对待。
下载完成,我们查看运行结果,如图。
我们在上图中找到了 true 200 表示正确返回,然后我们找到了第一个文件 test.bin 的相关数据,因为窗口大小原因,没有显示第二个文件的内容,我们可以翻看到第二个文件的内容,如图。
我们在图中找到了第二个文件的信息,文本文本并没有进行 base64 加密处理。同时,两个参数的内容也可以找到。
下面说说文件类型怎么定义的问题,因为我们看到文件类型的定义有点蒙。如下面的代码中,有 jpg="image/jpeg"这样的,也有 png ="image/png" 这样的,而我们自己添加的两个文件类型,却不知这个类型为什么要这么填。
{
bin = "application/octet-stream",
txt = "text/plain",
jpg = "image/jpeg",
jpeg = "image/jpeg",
png = "image/png",
}
这个还是得说回 postman,fiddler 两个工具,我们在 postman 中组织演示时,发送后,可以在 fiddler 中看到具体的数据形式,在 fiddler 中切换到 WebForms,如下图。
在图中,各文件的数据转换为了两个表格,将 Content-Type 的值,如图中也就是 application/octet-stream、image/png 分别填入到对应的文件类型后即可。参照如此处理,大抵不会错了。
九、文件下载
通过 http 文件下载,原则上相对简单,但是由于服务器难找,平时的网站的文件又比较大,费了不少时间,好在终于找到了一个自认为比较好用的网站豆子外链:http://zuoye.free.fr/index.php,目前网站可用,可以用来进行文件下载的测试。进入网站,便会出现一个上传文件界面,可以上传一个自己的文件,然后使用 http 下载此文件进行验证。当然,也可以使用文件广场内已经上传的文件。
我们将文件外链地址复制下来,然后修订 testHttp.lua 文件。将其它的测试演示语句全部注释掉,然后在文件 testHttp.lua 中添加一行代码,然后按保存,并通过 Luatools 下载到开发板。
http.request("GET","http://zuoye.free.fr/files/flag.png",nil,nil,nil,nil,cbFnc)
下面是运行结果。
我们可以看到 true 200 的字样,表示网站正确返回,然后我们看到了文件名称是 flag.png 及文件大小是 785,文件类型是图片,然后也显示了文件内容,即 body 部分,只是由于是图片文件,这里不能显示出来,但我们可以查看一下我刚上传的文件属性比较一下文件大小是否相符,如下图。当然,二进制数据不能显示,但我们可以修改回调函数并使用 base64 编码来显示数据,但此内容不在本文演示范围,不作扩展显示,有兴趣的朋友可以自行试试。
十、HTTPS 加密通信
加密通信我们使用服务器"https://airtest.openluat.com 来进行测试,使用 GET 方法,访问此服务器将会返回 hello 字样。打开链接 https://doc.openluat.com/share_article/KwExQpfcbL9Fs,是服务器的使用说明与各功能接口的入口,如图。
点击 https Server 进入相关说明,如图。
可以看到服务器的说明及启动方式等内容,最后有一个 cert.rar 的压缩文件,点击下载,里面有三个证书文件,解压保存,并将这三个文件加入到 Luatools 项目文件内,如图所示。
完成上面操作后,接下来修订文件 testHttp.lua,添加下面的内容后按保存。然后转到 Luatools 工具,点击下载脚本。
--https加密通讯
http.request("GET","https://airtest.openluat.com",{caCert="ca.crt",
clientCert = "client.crt",clientKey = "client.key"},nil,nil,nil,cbFnc)
等几秒后,开发板重启运行,结果如下。
十一、处理 JSON 数据
json 数据处理,主要就是两个函数,即 json.encode 与 json.decode。其实 json.encode 我们在 post 那一节中已经有使用,因而我们本节不再就 encode 函数的讲解进行说明,本节我们使用直接从网站读取的 json 数据,然后调用 json.decode 解码。
本次我们仍使用 httpbin.org 网站来做这个事情,此网站有一个接口可以用于测试此功能,位于 Dynamic data 的 get 方法内,有一个 GET/stream/{n}的接口,根据{n}的数值,返回 n 组 json 数据,如下图是 n=4 的情形。
我们将 body 部分 COPY 出来,列出如下:
{"url": "https://httpbin.org/stream/4", "args": {}, "headers": {"Host": "httpbin.org", "X-Amzn-Trace-Id": "Root=1-67172dc3-4e57a1a85ef23a8237b043f7", "User-Agent": "PostmanRuntime/7.42.0", "Accept": "*/*", "Postman-Token": "b1c8fa6d-12c9-420e-a065-4dc144bf49f2", "Accept-Encoding": "gzip, deflate, br"}, "origin": "175.13.96.1", "id": 0}
{"url": "https://httpbin.org/stream/4", "args": {}, "headers": {"Host": "httpbin.org", "X-Amzn-Trace-Id": "Root=1-67172dc3-4e57a1a85ef23a8237b043f7", "User-Agent": "PostmanRuntime/7.42.0", "Accept": "*/*", "Postman-Token": "b1c8fa6d-12c9-420e-a065-4dc144bf49f2", "Accept-Encoding": "gzip, deflate, br"}, "origin": "175.13.96.1", "id": 1}
{"url": "https://httpbin.org/stream/4", "args": {}, "headers": {"Host": "httpbin.org", "X-Amzn-Trace-Id": "Root=1-67172dc3-4e57a1a85ef23a8237b043f7", "User-Agent": "PostmanRuntime/7.42.0", "Accept": "*/*", "Postman-Token": "b1c8fa6d-12c9-420e-a065-4dc144bf49f2", "Accept-Encoding": "gzip, deflate, br"}, "origin": "175.13.96.1", "id": 2}
{"url": "https://httpbin.org/stream/4", "args": {}, "headers": {"Host": "httpbin.org", "X-Amzn-Trace-Id": "Root=1-67172dc3-4e57a1a85ef23a8237b043f7", "User-Agent": "PostmanRuntime/7.42.0", "Accept": "*/*", "Postman-Token": "b1c8fa6d-12c9-420e-a065-4dc144bf49f2", "Accept-Encoding": "gzip, deflate, br"}, "origin": "175.13.96.1", "id": 3}
上面字符串,直接使用 json.decode()解析不了,原因是里面包含多条 json 字符串,因而需要进行分解,为此编写了下面的代码,以分离字符串。由上面的代码,1 行的结尾与 2 行的开始是字串”}{“,而且作为 json 文本,这样的字串也只在两个 json 字串之间存在,因而我们使用这个作为子串查找,以准确找出各个 json 子串。同时我们将回调函数重新编写一下,为了与原回调函数区别,本回调函数使用 cbFncJson 作为函数名。同时为了程序清晰,同时编写了一个 json 字串解析输出函数 json_out。代码列出如下。注:下面的字符串处理函数仅适用于本文演示的具体数据,如使用到其它场合,请依据格式与内容进行适当修订或补全。
--将json字符串解析输出
local function json_out( js )
log.info(" json str = ".. js.." " )
if js ~= nil then
local js_table = json.decode(js)
for k,v in pairs( js_table ) do
if type(v) == "table" then
log.info( "testHttp.cbFncJson "," body_vv =" ..k )
for kk,vv in pairs(v) do
log.info("testHttp.cbFncJson","body_vv=".. kk.." : ".. vv )
end
else
log.info("testHttp.cbFncJson","body_v=".. k.." : ".. v )
end
end
end
end
--json字串分析处理专用回调函数。
local function cbFncJson(result,prompt,head,body)
log.info("testHttp.cbFncJson",result,prompt)
if result and body then
local str = body
while( true ) do
--实际上}{之间有一个换行符,因而实际的分隔串为 "}\x0a{"
local ps,pe = str:find("}\x0a{")
if ps ~= nil then
local js = str:sub(1,ps)
log.info(" json point = ".. ps )
if js ~= nil then
json_out(js)
str = str:sub(ps+1,-1);
end
else
json_out(str)
break
end
end
end
end
完成上面的代码编写后,在 testHttp.lua 中再添加一行代码,以使用 GET 方法将数据取回。
--json字串分析处理,注意回调函数重新进行了编写,请注意。
http.request("GET","https://httpbin.org/stream/4",nil,nil,nil,nil,cbFncJson)
保存文件,并将文件下载到开发板,我们来看看执行结果。为了方便,我们将解析字串复制过来,列出如下,删除了一些多余的输出,保留 json 的解析部分,以看得分明。
[2024-10-22 20:16:09.950] [I]-[testHttp.cbFncJson] true 200
[2024-10-22 20:16:09.950] [I]-[ json str = {"url": "https://httpbin.org/stream/4", "args": {}, "headers": {"Host": "httpbin.org", "X-Amzn-Trace-Id": "Ro
[2024-10-22 20:16:09.950] ot=1-6717978a-1a84a6f636ce460307a9a738"}, "origin": "117.61.125.114", "id": 0} ]
[2024-10-22 20:16:09.950] [I]-[testHttp.cbFncJson] body_vv =args
[2024-10-22 20:16:09.950] [I]-[testHttp.cbFncJson] body_v=id : 0
[2024-10-22 20:16:09.950] [I]-[testHttp.cbFncJson] body_v=url : https://httpbin.org/stream/4
[2024-10-22 20:16:09.950] [I]-[testHttp.cbFncJson] body_v=origin : 117.61.125.114
[2024-10-22 20:16:09.950] [I]-[testHttp.cbFncJson] body_vv =headers
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_vv=Host : httpbin.org
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_vv=X-Amzn-Trace-Id : Root=1-6717978a-1a84a6f636ce460307a9a738
[2024-10-22 20:16:10.003] [I]-[ json point = 188]
[2024-10-22 20:16:10.003] [I]-[ json str =
[2024-10-22 20:16:10.003] {"url": "https://httpbin.org/stream/4", "args": {}, "headers": {"Host": "httpbin.org", "X-Amzn-Trace-Id": "Root=1-6717978a-1a84
[2024-10-22 20:16:10.003] a6f636ce460307a9a738"}, "origin": "117.61.125.114", "id": 1} ]
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson ] body_vv =args
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_v=id : 1
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_v=url : https://httpbin.org/stream/4
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_v=origin : 117.61.125.114
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson ] body_vv =headers
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_vv=Host : httpbin.org
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_vv=X-Amzn-Trace-Id : Root=1-6717978a-1a84a6f636ce460307a9a738
[2024-10-22 20:16:10.003] [I]-[ json point = 188]
[2024-10-22 20:16:10.003] [I]-[ json str =
[2024-10-22 20:16:10.003] {"url": "https://httpbin.org/stream/4", "args": {}, "headers": {"Host": "httpbin.org", "X-Amzn-Trace-Id": "Root=1-6717978a-1a84
[2024-10-22 20:16:10.003] a6f636ce460307a9a738"}, "origin": "117.61.125.114", "id": 2} ]
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson ] body_vv =args
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_v=id : 2
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_v=url : https://httpbin.org/stream/4
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_v=origin : 117.61.125.114
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson ] body_vv =headers
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_vv=Host : httpbin.org
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_vv=X-Amzn-Trace-Id : Root=1-6717978a-1a84a6f636ce460307a9a738
[2024-10-22 20:16:10.003] [I]-[ json str =
[2024-10-22 20:16:10.003] {"url": "https://httpbin.org/stream/4", "args": {}, "headers": {"Host": "httpbin.org", "X-Amzn-Trace-Id": "Root=1-6717978a-1a84
[2024-10-22 20:16:10.003] a6f636ce460307a9a738"}, "origin": "117.61.125.114", "id": 3}
[2024-10-22 20:16:10.003] ]
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson ] body_vv =args
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_v=id : 3
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_v=url : https://httpbin.org/stream/4
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_v=origin : 117.61.125.114
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson ] body_vv =headers
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_vv=Host : httpbin.org
[2024-10-22 20:16:10.003] [I]-[testHttp.cbFncJson] body_vv=X-Amzn-Trace-Id : Root=1-6717978a-1a84a6f636ce460307a9a738
[2024-10-22 20:16:10.003] [I]-[socket:sock_close] 1
[
上面文本的输出方式,首先输出 json 字串,然后是 json 字串的解析,将各名称对按一定的顺序列出。
虽然上面的代码是 json 的显示,但其实综合使用了 table,json,string 等多种数据类型的使用,因而这是一个综合性比较强的一次演示,大家可以在此基础上进行更进一步的测试演示,完成更复杂的内容。
十二、gzip 操作
本操作使用下面的代码进行测试。因 724 尚没有合用的解压工具,因而也不作讲解,只列出测试内容与测试结果,也不作解压还原操作。
--gzip压缩功能测试,本文未作说明,但该URL实际可用,只是因为找不到合适的解压工具,没有在文档中说明。
--http.request("GET","https://httpbin.org/gzip",nil,nil,nil,30000,cbFnc)
十三、最终的测试文件
最终的 testHttp.lua 文件,此文件包含本文所有示例演示内容,使用时,将对应的注释去掉,不使用该功能时,将注释恢复即可。
--- 模块功能:HTTP功能测试.
-- @author openLuat
-- @module http.testHttp
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.23
module(...,package.seeall)
--local zlib = require("zlib")
require"http"
--通用操作回调函数
local function cbFnc(result,prompt,head,body)
log.info("testHttp.cbFnc",result,prompt)
if result and head then
for k,v in pairs(head) do
log.info("testHttp.cbFnc",k..": "..v)
end
end
if result and body then
log.info("testHttp.cbFnc","bodyLen="..body:len())
log.info("testHttp.cbFnc","body=".. body )
end
end
--将json字符串解析输出
local function json_out( js )
log.info(" json str = ".. js.." " )
if js ~= nil then
local js_table = json.decode(js)
for k,v in pairs( js_table ) do
if type(v) == "table" then
log.info( "testHttp.cbFncJson "," body_vv =" ..k )
for kk,vv in pairs(v) do
log.info("testHttp.cbFncJson","body_vv=".. kk.." : ".. vv )
end
else
log.info("testHttp.cbFncJson","body_v=".. k.." : ".. v )
end
end
end
end
--json字串分析处理专用回调函数。
local function cbFncJson(result,prompt,head,body)
log.info("testHttp.cbFncJson",result,prompt)
if result and body then
local str = body
while( true ) do
--实际上}{之间有一个换行符,因而实际的分隔串为 "}\x0a{"
local ps,pe = str:find("}\x0a{")
if ps ~= nil then
local js = str:sub(1,ps)
log.info(" json point = ".. ps )
if js ~= nil then
json_out(js)
str = str:sub(ps+1,-1);
end
else
json_out(str)
break
end
end
end
end
--上传文件时回调函数
local function cbFncFile(result,prompt,head,filePath)
log.info("testHttp.cbFncFile",result,prompt,filePath)
if result and head then
for k,v in pairs(head) do
log.info("testHttp.cbFncFile",k..": "..v)
end
end
if result and filePath then
local size = io.fileSize(filePath)
log.info("testHttp.cbFncFile","fileSize="..size)
--输出文件内容,如果文件太大,一次性读出文件内容可能会造成内存不足,分次读出可以避免此问题
if size<=4096 then
log.info("testHttp.cbFncFile",io.readFile(filePath))
else
--大文件处理不是本演示内容,不作处理。
log.info("testHttp.cbFncFile"," FileSize > 4096 ")
end
end
--文件使用完之后,如果以后不再用到,需要自行删除
if filePath then os.remove(filePath) end
end
--http请求的接口定义
--http.request(method, url, cert, head, body, timeout, cbFnc, rcvFileName, tCoreExtPara)
--使用GET方法
--local base64_str = "用合宙724ug模块完成http的GET方法"
--http.request("GET","httpbin.org/base64/"..crypto.base64_encode(base64_str,base64_str:len()),nil,nil,nil,nil,cbFnc)
--文件下载测试
--http.request("GET","http://zuoye.free.fr/files/flag.png",nil,nil,nil,nil,cbFnc)
--https加密通讯
--http.request("GET","https://airtest.openluat.com",{caCert="ca.crt",clientCert = "client.crt",clientKey = "client.key"},nil,nil,nil,cbFnc)
--json字串分析处理,注意回调函数重新进行了编写,请注意。
http.request("GET","https://httpbin.org/stream/4",nil,nil,nil,nil,cbFncJson)
--gzip压缩功能测试,本文未作说明,但该URL实际可用,只是因为找不到合适的解压工具,没有在文档中说明。
--http.request("GET","https://httpbin.org/gzip",nil,nil,nil,30000,cbFnc)
--简单的文件上传功能
--[[http.request("POST","httpbin.org/post",nil,
{['Content-Type']="application/octet-stream",['Connection']="keep-alive"},
{[1]={['file']="/lua/test.bin"}
},
30000,cbFnc)
]]
--POST方法实现SIM卡查询接口,本测试同时有json操作,请留意。
--[[
local username = "6FamqlFfSVd88sGz"
local password = "KzktIo2Q3WpXfmtILKj0Mfswllqtq5tjWP3POPU6wS63A9UYX8ruHVBITZz9AjNp"
local basic_str = username..':'..password
http.request("POST","http://api.taoyuan-tech.com/api/open/iotcard/usagelog",nil,
{
["Authorization"] = "Basic "..crypto.base64_encode(basic_str,basic_str:len()),
--['Authorization']="Basic NkZhbXFsRmZTVmQ4OHNHejpLemt0SW8yUTNXcFhmbXRJTEtqME1mc3dsbHF0cTV0aldQM1BPUFU2d1M2M0E5VVlYOHJ1SFZCSVRaejlBak5w",
['Content-Type']="application/json"},
--['Connection']="keep-alive"},
json.encode(
{
["query_date"] = "20241010",
["iccids"] = "89860403102080512138"
}),
30000,cbFnc)
]]
--短信未修订,也没有实际测试,为方便使用者,继续保留
--如下示例代码是利用x-www-form-urlencoded模式,上传3个参数,通知openluat的sms平台发送短信
--[[
function urlencodeTab(params)
local msg = {}
for k, v in pairs(params) do
table.insert(msg, string.urlEncode(k) .. '=' .. string.urlEncode(v))
table.insert(msg, '&')
end
table.remove(msg)
return table.concat(msg)
end
http.request("POST","http://api.openluat.com/sms/send",nil,
{
["Authorization]"="Basic jffdsfdsfdsfdsfjakljfdoiuweonlkdsjdsjapodaskdsf",
["Content-Type"]="application/x-www-form-urlencoded",
},
urlencodeTab({content="您的煤气检测处于报警状态,请及时通风处理!", phone="13512345678", sign="短信发送方"}),
30000,cbFnc)
]]
--文件上传进阶操作,传两个参数,传两个文件。
--[[
local function postMultipartFormData(url,cert,params,timeout,cbFnc,rcvFileName)
local boundary,body,k,v,kk,vv = "--------------------------"..os.time()..rtos.tick(),{}
for k,v in pairs(params) do
if k=="texts" then
local bodyText = ""
for kk,vv in pairs(v) do
bodyText = bodyText.."--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"\r\n\r\n"..vv.."\r\n"
end
body[#body+1] = bodyText
log.info("body text "..bodyText)
elseif k=="files" then
local bodystr = ""
local contentType =
{
bin = "",
txt = "",
jpg = "image/jpeg",
jpeg = "image/jpeg",
png = "image/png",
}
for kk,vv in pairs(v) do
print(kk,vv)
bodystr = "--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"; filename=\""..vv.."\"\r\nContent-Type: "..contentType[vv:match("%.(%w+)$")].."\r\n\r\n"
log.info("body bin "..bodystr)
body[#body+1] = bodystr
body[#body+1] = {file = vv}
log.info("body file "..vv.."\r\n" )
body[#body+1] = "\r\n"
end
end
end
body[#body+1] = "--"..boundary.."--\r\n"
http.request(
"POST",
url,
cert,
{
["Content-Type"] = "multipart/form-data; boundary="..boundary,
["Connection"] = "keep-alive"
},
body,
timeout,
cbFnc,
rcvFileName
)
end
postMultipartFormData(
"httpbin.org/post",
nil,
{
texts =
{
["imei"] = "862991234567890",
["time"] = "20180802180345"
},
files =
{
["test.txt"] = "/lua/test.txt",
["test.bin"] = "/lua/test.bin"
}
},
60000,
cbFnc
)
]]
十四、总结
本文力求从实际应用出发,对 Http 协议的大部分方法进行了演示测试,每一项操作都经过本人实际操作。本文中,涉及一些服务器的选用,也是经过多方的查找,各种比照之后,才决定选用的,具有一定的稳定性,就是说在一定的时间范围内,相应资源都可用。最后,希望本文对各位读者有用,并能解决大家一些实际问题。
十五、常见问题
15.1 脚本下载后没有反应。
检查有没有下载底层核心,因为开发板出厂时,一般情况下是 AT 版本,此时如果不下载底层核心,lua 脚本就运行不起来。
15.2 接上 USB 线后,不能下载,也不闪灯
检查 USB 线是否连接正确,724 开发板有两个 USB 口,旁边有 USB 字样的,才是下载用的 USB 通讯口,另一个 USB 口是 USB 转 TTL 的 UART 通讯端口,此端口不提供电源支持,因而当然不会有反应。
15.3 不小心变砖了怎么办?
在 Luatools 软件中,点击下载固件,选择底层核心文件,使能 USB BOOT 下载,按住开发板上的重启按钮,直到听到嘀的一声后开始下载,看到可松开按键提示后,松开按键。如下图。
15.4 一直连接不上网
检查天线是否连接完好,检查所在位置信号是否良好,检查 SIM 卡是否锁死,检查 SIM 卡是否欠费等。可使用自己的手机卡换上去进行比较检查。如果手机卡能上网,则与硬件无关,此时可检查 SIM 的相关内容。
给读者的话
本篇文章由 PenGH 开发; 本篇文章描述的内容,如果有错误、细节缺失、细节不清晰或者其他任何问题,总之就是无法解决您遇到的问题; 请登录合宙技术交流论坛;合宙技术交流 用截图标注 + 文字描述的方式跟帖回复,记录清楚您发现的问题; 我们会迅速核实并且修改文档; 同时也会为您累计找错积分,您还可能赢取月度找错奖金!