跳转至

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 演示概要

本文档使用 Air8101 开发板,就 Http 协议进行了较系统的演示,展示了 Air8101 在网络应用上的便捷性、严谨性以及简易性。使用者不需要有太多的 Http 协议相关的知识,也非常的容易上手,并完成特定的任务。本文档除了对 GET、POST 方法进行最基本的演示外,还在上传文件、下载文件以及 gzip 应用等方面作了一些扩展,具有一定的深度与广度,既可适于初学者,也可供有相当基础的开发人员参考。

2.2 Air8101 模块简介

Air8101 以 WiFi6 低功耗音视频 SoC 芯片为核心,针对物联网量身定制,具有功耗低、集成度高、安全性能高、接口与资源丰富等优势,同时集成音频、视频解码接口,是中控显示、智能门锁、远程监控等应用的不二选择。

2.3 网络环境要求

本演示是测试 “http” 协议,因而需要在演示时有网络,Ari8101 开发板是基于 wifi 的网络连接,因而外部环境要求有 wifi 网络,并在输入 SSID 及 密码后可直接连接上 “Internet”,不能再有须要转网页输密码等环节。可以使用手机热点进行连接并测试。

2.4 硬件环境及相关辅件

演示基于 Ari8101 开发板,串行口通讯模块一个以及用于运行软件 Luatools 的 PC 机及 USB 连接线一条。

三、平台搭建

3.1、硬件平台的搭建

[!TIP] 待模块开发板出来后更换图片 本演示使用 Air8101 开发板作为硬件平台,如图所示:

[!TIP] (相关内容有待正式开发板出来后,按正式板资源再组织语言)

通过开发板的通讯模块,接到 DL_UART0 的端口,如上图,将跳线子按图中所示配置好,接上 USB 口与 PC 机的 USB 口连接线,此时 PC 设备管理器将出新出现一个串行口设备,记住其端口号备用。

串行口的驱动程序请从此 合宙云盘目录 下载即可。

3.2、Luatools 软件准备

Luatools 最新版本可从网站 Luatools 下载和使用教程 - 合宙模组资料中心 下载,这里也有官方的软件使用说明书,请大家仔细研读,以熟练使用本工具。

Luatools 工具安装完成后,起动 Luatools 工具软件,如下图所示:

点击"下载固件"按钮,选择最新固件,本文档编写时的最新固件在下面给出了下载链接,大家可以直接取用。

如下图,选择好了固件,点击下载,将固件下载到开发板。

下载成功后,则本文档所要求的演示运行环境,即已经搭建完成。也就是说现在开发板具备了运行 lua 脚本的基本要求。特别注意的就是,固件脚本下载一次即可,不需要每次都下载。只有固件有更新或因需要替换成不同版本的固件时,才需要重新下载。

3.3、项目建立

现在我们就来建立一个项目,并通过项目来管理各 lua 脚本文件,脚本文件编写完成,即可加入到项目下载到开发板运行并查看运行结果。譬如下图中,就有多个项目,各项目完成不同的功能。本文档所建立的项目名称为 air8101,项目仅包含一个文件 main.lua。

在 Lautools 软件界面中,点击“项目管理测试”按钮,进入项目管理界面,如图:

在上图中,点击“创建”按钮并输入项目名称创建一个新项目,输入 “air8101”,名称可以任取,各位依兴趣或实际要求定义即可。

项目创建后,在界面的右边,点击“增加脚本或资源文件”,并选择 main.lua 文件,将测试文件加入到项目中。main.lua 文件列出于第一章 DEMO 开发中,各位可以复制粘贴创建一个 main.lua 文件,同时各位也可以下载本文档的演示例程 main.lua 直接使用。点击下面的 main.lua 字样即可直接下载。

四、功能演示

4.1 总体部署与规划

本文档完成 GET、POST 方法的演示,并进而对基于此方法的上传下载文件,gzip 传输数据进行了扩展。main.lua 文件的基本格式如下面代码所示,最开始部分是项目名称及版本号,接下来要将本文件所要用到的模块包含进来,如_G.sys = require("sys")。这两部分各项目的 main.lua 基本都类似。

然后就是具体的功能或者项目需求编写,本文档为了完成 Http 的各需求,规划了两个协程(或者称任务),即网络任务与具体的功能演示任务,如下面代码所示。我们的演示将以此代码为基础,每完成一项功能,就增加一个函数,并在功能演示任务中增加一行调用,为了简明起见,也为了观察演示结果,当演示一项功能时,可以将其它功能演示注释掉,即在功能任务中将该功能的函数调用注释掉即可。完整的源代码 main.lua 将在本文档的最后提供下载。

-- LuaTools需要PROJECT和VERSION这两个信息
--必须在这个位置定义PROJECT和VERSION变量
--PROJECT:ascii string类型,可以随便定义,只要不使用,就行
--VERSION:ascii string类型,如果使用Luat物联云平台固件升级的功能,必须按照"X.X.X"定义,
--X表示1位数字;否则可随便定义

PROJECT = "httpdemo"
VERSION = "1.0.0"

--[[
本demo需要http库, 大部分能联网的设备都具有这个库
http也是内置库, 无需require

如需上传大文件,请使用 httpplus 库, 对应demo/httpplus
]]

-- sys库是标配
_G.sys = require("sys")
--[[特别注意, 使用http库需要下列语句]]
_G.sysplus = require("sysplus")

-- 网络任务
sys.taskInit(function()
    local result,data
    -----------------------------
    -- 统一联网函数, 可自行删减
    ----------------------------
    if wlan and wlan.connect then
        -- wifi 联网, ssid表示wifi网络名称,password为网络密码
        local ssid = "ChinaNet-7jU2"
        local password = "xnceqvkr"
        --输出wifi的用户名称、密码及硬件库名称
        log.info("wifi", ssid, password, rtos.bsp() )

        -- LED = gpio.setup(12, 0, gpio.PULLUP)
        -- 网络初始化
        wlan.init()
        -- 运行模式为 工作站/客户端
        wlan.setMode(wlan.STATION)
    end

    while true do
        if wlan and wlan.connect then
            -- 连接网络
            wlan.connect(ssid, password, 1)
            -- 在网络连接成功时,会发布一个系统消息 IP_READY,而
            -- sys.waitUntil 订阅此消息,能在设置的时间内收到此消息
            -- 即表示网络连接成功。
            result, data = sys.waitUntil("IP_READY", 30000)
            log.info("wlan", "IP_READY", result, data)
            -- 取得网络Mac
            device_id = wlan.getMac()
        else
            while 1 do
                sys.wait(1000)
                log.info("http", "当前固件未包含http库")
            end
        end
        if result == true then
            log.info("已联网")
            sys.publish("net_ready")
        end

        while wlan and wlan.ready() do
            sys.wait(1000)
        end
    end
end)

-- 功能实现区
-- GET 请求
function demo_http_get()
    -- 最普通的Http GET请求
    local code, headers, body = http.request("GET", "https://www.air32.cn/").wait()
    log.info("http.get", code, headers, body)

    sys.wait(100)

    local code, headers, body = http.request("GET", "https://www.luatos.com/").wait()
    log.info("http.get", code, headers, body)

    -- 按需打印
    -- code 响应值, 若大于等于 100 为服务器响应, 小于的均为错误代码
    -- headers是个table, 一般作为调试数据存在
    -- body是字符串. 注意lua的字符串是带长度的byte[]/char*, 是可以包含不可见字符的
    -- log.info("http", code, json.encode(headers or {}), #body > 512 and #body or body)
end

-- 功能演示任务
sys.taskInit(function()
    sys.wait(100)
    -- 打印一下支持的加密套件, 通常来说, 固件已包含常见的99%的加密套件
    -- if crypto.cipher_suites then
    --     log.info("cipher", "suites", json.encode(crypto.cipher_suites()))
    -- end

    -------------------------------------
    -------- HTTP 演示代码 --------------
    -------------------------------------
    sys.waitUntil("net_ready") -- 等联网

    while 1 do
        -- 大家可以在下面的演示函数中,选择需要进行操作的功能,去掉注释符,
        -- 保存后下载到开发板进行测试演示

        -- 演示GET请求
        demo_http_get()
        sys.wait(1000)
        -- 打印一下内存状态
        log.info("sys", rtos.meminfo("sys"))
        log.info("lua", rtos.meminfo("lua"))
        --sys.wait(600000)
        sys.wait(10000)
        while wlan and wlan.ready() == false do
            log.info("http demo","wifi_disconnected")
            sys.waitUntil("net_ready",10000) -- 等联网  
        end
    end
end)

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

4.2 GET 方法测试

我们以上一节所提供的基本框架文件为基础来进行各项测试,因上节所提供的基本框架已经包含了 GET 方法的功能函数 demo_http_get(),因而我们直接将上面代码下载到开发板即可。将上面代码保存为 main.lua,将此文件加入项目,即可点击“下载脚本”按键,将脚本下载到开发板中,下载完成后开发板将自动重启,并显示连网相关内容,如图:

在上图中,我们看到了网络连接的相关内容,如图中较小红框中所示内容,如 IP 地址是:192.168.1.4,使用网关作为 DNS 服务器等。

在较大的红框中,我所看到了最简单的 http 请求操作,即:

local code, headers, body = http.request("GET", "https://www.air32.cn/").wait()
    log.info("http.get", code, headers, body)

这段代码使用 “GET” 方法,访问网站:“https://www.air32.cn”,而较大的红框中的内容正是访问时的响应。我们可以看到网站的应答代码 “200” 表示响应正确,同时我们也看到了由 “body” 到 “/body” 之间的网站响应内容。

这是一个最基本的测试,也是对硬件或者开发板的一个初步检验,经过这个测试,一方面表示硬件与软件工作都非常正常。

4.3 POST 方法测试

4.3.1 POST 方法中的数据类型

Ⅰ. form-data:

通常用于文件上传和表单提交。数据以键值对的形式发送,每个键值对可以包含文本(text)或文件(file)。适合上传文件时使用。

Ⅱ. x-www-form-urlencoded:

通常用于普通的表单提交(例如 HTML 表单,登录表单、搜索表单)。数据以 URL 编码的键值对形式发送。所有特殊字符都会被 URL 编码。

Ⅲ. raw:

发送纯文本数据。可以是 JSON、XML、HTML、文本等。用户可以完全控制数据的格式和内容。

Ⅳ. binary:

发送二进制数据,例如图片、文件等。直接发送文件的二进制内容,不进行任何编码或处理。

Ⅴ. GraphQL:

发送 GraphQL 查询。允许用户使用 GraphQL 查询语言发送请求。请求体通常包含一个查询字符串和可选的变量。

当然还有一些数据类型,不再一一列举,有兴趣的同仁可以查阅有关资料获取相关内容。本演示不涉及过多的内容,仅演示 raw 的 Json 文本数据类型, x-www-form-urlencoded 表单数据类型,至于 form-data 数据类型在文件上传这部分的内容将会涉及,因而这里也不再另开小节专门讲述。

4.3.2 POST 方法 Json 数据内容的演示

本节内容使用 HTTP - luatos@air724ug - 合宙文档中心 文档同样的测试源,完成与该文档 POST 方法同样的演示与测试。大家可以阅读该文档的第七章《POST 请求演示》来了解该接口的有关事项,本文不再赘述,本文直接给出演示代码并展示演示结果。

演示代码如下:

function demo_http_post_json()
    -- POST request 演示

    local req_headers = {}

    req_headers={
        ["Authorization"] = "Basic NkZhbXFsRmZTVmQ4OHNHejpLemt0SW8yUTNXcFhmbXRJTEtqME1mc3dsbHF0cTV0aldQM1BPUFU2d1M2M0E5VVlYOHJ1SFZCSVRaejlBak5w",
        ["Content-Type"] = "application/json",
        ["Connection"] = "keep-alive"
    }

    local body = json.encode( 
        {
            ["query_date"] = "20241212",
            ["iccids"] = "89860403102080512138"
        }
    )

    local code, headers, body = http.request("POST","http://api.taoyuan-tech.com/api/open/iotcard/usagelog", 
            req_headers,
            body -- POST请求所需要的body, string, zbuff, file均可
    ).wait()
    log.info("http.post", code, headers, body)
end

上面代码为一个函数,具体就是完成 Json 的数据内容的演示。可将此函数加入到前面的 DEMO 代码 main.lua 中,然后在 taskInit 所创建的任务中运行,具体如下:

sys.taskInit(function()
    sys.wait(100)
    -------------------------------------
    -------- HTTP 演示代码 --------------
    -------------------------------------
    sys.waitUntil("net_ready") -- 等联网

    while 1 do
        -- 演示GET请求
        --demo_http_get()
        -- POST一个json字符串
         demo_http_post_json()

        sys.wait(1000)
        -- 打印一下内存状态
        log.info("sys", rtos.meminfo("sys"))
        log.info("lua", rtos.meminfo("lua"))
        sys.wait(600000)
    end
end)

完成 main.lua 的修订并保存后,在 Luatools 下载脚本到开发板,然后等待开发板重启,即可查看到代码运行结果,如下图所示:

在上图中红色框中的内容即是查询内容,返回了所查询卡片的当前状态。

4.3.3 POST 方法 x-www-form-urlencoded 数据内容的演示

与前一节类似,使用一函数来完成此演示要求(本文如无特别说明,都使用此种方式,一个函数完成一个演示,后面不再作说明),函数代码如下:

function demo_http_post_form()
    -- POST request 演示
    local req_headers = {}
    req_headers["Content-Type"] = "application/x-www-form-urlencoded"
    local params = {
        ABC = "123",
        DEF = "345"
    }
    local body = ""
    for k, v in pairs(params) do
        body = body .. tostring(k) .. "=" .. tostring(v):urlEncode() .. "&"
    end
    local code, headers, body = http.request("POST","http://httpbin.air32.cn/post", 
            req_headers,
            body -- POST请求所需要的body, string, zbuff, file均可
    ).wait()
    log.info("http.post.form", code, headers, body)
end

这是一个回环服务器,即请求什么返回什么,因而参数可以随意编写,符合格式要求即可。将上面代码修订完成并保存后,通过 Luatools 下载到开发板,等待开发板重启并运行,如果如下图。

上图中,红色框中为运行结果,可以看到返回码为 200,即表示正确返回,返回数据在 form 表格中,即:

form{
  "ABD":"123"
  "DEF":"345"
}

有关的头部信息牌 headers 表格中,不再列举。由上在返回数据可知,返回内容与发送内容相符。

4.4 Http 文件下载

文件下载可以通过 GET 方法实现,也可以通过 POST 方法实现,大家可以参考 HTTP - luatos@air724ug - 合宙文档中心 第九章的内容,本文使用 GET 方法与 POST 方法分别演示文件下载。

4.4.1 GET 方法文件下载

也没有什么好说的,直接上代码吧。

function demo_http_download()

    --http.request("GET","http://zuoye.free.fr/files/flag.png",nil,nil,nil,nil,cbFnc)
    --关于http文件下载的演示,由于服务器比较难找,因而使用了http://zuoye.free.fr网站,如果下面的文件
    --演示失败,请试着打开上面的链接,查看文件是否存在,或者更换文件测试。如果该网站不存在了,那还请大
    --家谅解一二。或者大家也可以使用自己知道的网站资源来对http的下载功能进行测试。

    local code, headers, body = http.request("GET","http://zuoye.free.fr/files/flag.png",
            {}, -- 请求所添加的 headers, 可以是nil
            "", 
            nil
    ).wait()
    log.info("http.get", code, headers, string.toHex(body)) -- 只返回code和headers
end

修订完成后保存,并下载到开发板中,等待开发板重启运行,运行结果如下:

上图是演示结果返回,由于 png 文件为二进制数据形式,因而通过 toHex 函数转化化十六进制文本格式显示,如图中红色框中内容,演示返回码为 200 表示演示正确返回。

4.4.2 POST 方法文件下载

为了与前面 GET 方法文件下载区别,此函数命名为 demo_http_download_post,以示区别。

function demo_http_download_post()

    -- POST and download, task内的同步操作
    local opts = {}                 -- 额外的配置项
    opts["dst"] = "/data.bin"       -- 下载路径,可选
    opts["timeout"] = 30000         -- 超时时长,单位ms,可选
    -- opts["adapter"] = socket.ETH0  -- 使用哪个网卡,可选
    -- opts["callback"] = http_download_callback
    -- opts["userdata"] = http_userdata

    for k, v in pairs(opts) do
        print("opts",k,v)
    end

    local code, headers, body = http.request("POST","http://site0.cn/api/httptest/simple/date",
            {}, -- 请求所添加的 headers, 可以是nil
            "", 
            opts
    ).wait()
    log.info("http.post", "code ="..code.." headers = ",headers, " body= "..body) -- 只返回code和headers

    --大家也可以依具体情况对文件进行操作处理
    local f = io.open("/data.bin", "rb")
    if f then
        local data = f:read("*a")
        log.info("fs", "data", data, data:toHex())
    end

    -- GET request, 开个task让它自行执行去吧, 不管执行结果了
    sys.taskInit(http.request("GET","http://site0.cn/api/httptest/simple/time").wait)
end

上面这段代码中,除了通过 http 协议将文件下载的演示,还演示了如何进行文件操作,具体操作结果如下图所示:

由上图中的反馈结果可以看到返回码 200,表示操作成功。

我们也可以看到文件操作的结果,打开文件读取的数据是“Thu Dec 26 14:13:01 2024”,其后就是转化成为 HEX 格式的字符“546875204465632032362031343A31323A353020323032340”。

4.5 Http 文件上传

上文说到有关数据类型时,说到 form-data 的演示在文件上传中有演示,因而本节内容即是文件上传的演示,也是 form-data 的一个演示。

4.5.1 通用文件上传

阅读本文档时,可以参考 HTTP - luatos@air724ug - 合宙文档中心 第八章的相关内容。

---- MultipartForm上传文件
-- url string 请求URL地址
-- req_headers table 请求头
-- params table 需要传输的数据参数
function postMultipartFormData(url, params)
    local boundary = "----WebKitFormBoundary"..os.time()
    local req_headers = {
        ["Content-Type"] = "multipart/form-data; boundary="..boundary,
    }
    local body = {}

    --log.info("postMultipart_params=",url.."----"..params.."------")
    -- 解析拼接 body
    for k,v in pairs(params) do
        if k=="texts" then
            local bodyText = ""
            for kk,vv in pairs(v) do
                print(kk,vv)
                bodyText = bodyText.."--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"\r\n\r\n"..vv.."\r\n"
            end
            table.insert(body, bodyText)
        elseif k=="files" then
            local contentType =
            {
                txt = "text/plain",             -- 文本
                jpg = "image/jpeg",             -- JPG 格式图片
                jpeg = "image/jpeg",            -- JPEG 格式图片
                png = "image/png",              -- PNG 格式图片   
                gif = "image/gif",              -- GIF 格式图片
                html = "image/html",            -- HTML
                json = "application/json"       -- JSON
            }

            for kk,vv in pairs(v) do
                if type(vv) == "table" then
                    for i=1, #vv do
                        print(kk,vv[i])
                        table.insert(body, "--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"; filename=\""..vv[i]:match("[^%/]+%w$").."\"\r\nContent-Type: "..contentType[vv[i]:match("%.(%w+)$")].."\r\n\r\n")
                        table.insert(body, io.readFile(vv[i]))
                        table.insert(body, "\r\n")
                    end
                else
                    print(kk,vv)
                    table.insert(body, "--"..boundary.."\r\nContent-Disposition: form-data; name=\""..kk.."\"; filename=\""..vv:match("[^%/]+%w$").."\"\r\nContent-Type: "..contentType[vv:match("%.(%w+)$")].."\r\n\r\n")
                    table.insert(body, io.readFile(vv))
                    table.insert(body, "\r\n")
                end
            end
        end
    end 
    table.insert(body, "--"..boundary.."--\r\n")
    body = table.concat(body)
    log.info("headers: ", "\r\n" .. json.encode(req_headers), type(body))
    log.info("body: " .. body:len() .. "\r\n" .. body)
    local code, headers, body = http.request("POST",url,
            req_headers,
            body
    ).wait()   
    log.info("http.post", code, headers, body)
end

上面代码根据传入的参数 params 组织链接参数,并最终形成 http.request 函数的 body 参数,从而完成文件的上传操作。

分析上面的代码,我们发现上传文件的 body 参数的一般格式(传数据时类似,请大家自己理解),即由 boundary 作为边界所包括的内容,其中包括文件名、文件格式等内容,如下示例文本所示:

local body = "--"..boundary.."\r\n"..
                     "Content-Disposition: form-data; name=\"uploadFile\"; filename=\"luatos_uploadFile_TEST01.txt\""..
                     "\r\nContent-Type: text/plain\r\n\r\n"..
                     "1111http_测试一二三四654zacc\r\n"..
                     "--"..boundary

上面文档,boundary 就是边界,头尾都有,这个可以依个人喜好自行定义。后面 Content-Disposition 指明回复的数据类型,参数名称即 name 是 uploadFile,文件名 filename 为 luatos_uploadFile_TEST01.txt,文件内容的数据类型为 text/plain,其它内容就是文件的实际数据,即字符 “http_测试一二三四 654zacc\r\n”。

通过上面的分析,我们对上传文件的具体格式有了一个较清晰的了解,接下来我们在任务中调用此函数,如下面代码所示,在任务中添加调用代码:

sys.taskInit(function()
    -------------------------------------
    -------- HTTP 演示代码 --------------
    -------------------------------------
    sys.waitUntil("net_ready") -- 等联网

    while 1 do
        -- 演示GET请求
        --demo_http_get()
        -- 表单提交
        -- demo_http_post_form()
        -- POST一个json字符串
       --  demo_http_post_json()
        -- 上传文件, mulitform形式
         --demo_http_post_file()

         postMultipartFormData(
            "http://airtest.openluat.com:2900/uploadFileToStatic",
            {
                -- texts = 
                -- {
                --     ["imei"] = "862991234567890",
                --     ["time"] = "20180802180345"
                -- },

                files =
                {
                    ["uploadFile"] = "/luadb/luatos_uploadFile.txt",
                }
            }
        )
        -- 文件下载
        -- demo_http_download_old()
         --demo_http_download()
        -- gzip压缩的响应, 以和风天气为例
        -- demo_http_get_gzip()

        sys.wait(1000)
        -- 打印一下内存状态
        log.info("sys", rtos.meminfo("sys"))
        log.info("lua", rtos.meminfo("lua"))
        sys.wait(600000)
    end
end)

保存上面代码,并依据上面的代码,我们新建一个文本文件,命名为 luatos_uploadFile.txt, 放到目录 luadb 下。编辑此文本文本,录入以下内容:

1 208
2 416
3 624
4 832
5 1040

6.84S + 5ms 开始

11100000 6.87S  00000111
00111010 6.9S   01011100
6.91S
11111000 6.93   00011111
6.94
01101100 6.96   00110110


6*2
*255

上面内容为随机输入,内容没有实际含义,请大家知悉。然后在 Luatools 工具内添加此文件,如下图所示:

完成这些操作下,点击“下载脚本”按钮将脚本及文件下载到开发板中,我们便可以等待开发重启并运行演示代码:果:

上图中,可以看到文件上传成功,大家可以仔细查看较大红框中的内容,是不是与上面所分析的格式相符。至于下面的小红框,则是表示成功的代码 200。

4.5.2 虚拟内存文件上传

通过上面的文件上传演示,有人就要问了,这个都需要事先准备文件,但有些文件可能是依据程序的运行动态产生的,比如日志,那这样的文件又要如何上传呢?

我们上面分析了上传文件的具体格式,那我们就可以通过拼接的方式,按格式组织 http.request 函数的 body 参数,从而达到上面类似日志文件的上传目的。

为此我们编写了下面的代码,请大家参考:

function demo_http_post_file()
        -- -- POST multipart/form-data模式 上传文件---手动拼接虚拟内存文件
        local boundary = "----WebKitFormBoundary"..os.time()
        local req_headers = {
            ["Content-Type"] = "multipart/form-data; boundary="..boundary,
        }
        local body = "--"..boundary.."\r\n"..
                     "Content-Disposition: form-data; name=\"uploadFile\"; filename=\"luatos_uploadFile_TEST01.txt\""..
                     "\r\nContent-Type: text/plain\r\n\r\n"..
                     "1111http_测试一二三四654zacc\r\n"..
                     "--"..boundary

        log.info("headers: ", "\r\n"..json.encode(req_headers))
        log.info("body: ", "\r\n"..body)
        local code, headers, body = http.request("POST","http://airtest.openluat.com:2900/uploadFileToStatic",
                req_headers,
                body -- POST请求所需要的body, string, zbuff, file均可
        ).wait()
        log.info("http.post", code, headers, body)
end

这段代码我们就不作分析了,大家可以参考 3.6.1 节关于上传文件格式的有关内容理解上面代码。保存修改并将 main.lua 文件下载到开发板运行:

上图中,我们看到文件的具体内容,并看到了请求的返回代码 200,因而内存文件的上传亦圆满完成。

4.6 压缩 gzip 演示

Http 传输内容大多都经过了 gzip 压缩处理,因而解压缩是一个常规的操作,LuatOS 包含了一个简单的压缩库,即 miniz,大家可以参考 miniz - 简易 zlib 压缩 - LuatOS 文档 作更进一步的了解。我们以天气预报的数据包作为演示例子。首先我们可以直接通过浏览器看看得到的数据包会有哪些数据,然后我们再看具体代码可能就更容易理解。我们将链接 https://devapi.qweather.com/v7/weather/now?location=101010100&key=0e8c72015e2b4a1dbff1688ad54053de 输入到浏览器的地址栏内回车,得到如下所示的 Json 代码:

"code": "200",
    "updateTime": "2024-12-20T22:17+08:00",
    "fxLink": "https://www.qweather.com/weather/beijing-101010100.html",
    "now": {
        "obsTime": "2024-12-20T22:09+08:00",
        "temp": "-2",
        "feelsLike": "-11",
        "icon": "150",
        "text": "晴",
        "wind360": "0",
        "windDir": "北风",
        "windScale": "5",
        "windSpeed": "37",
        "humidity": "28",
        "precip": "0.0",
        "pressure": "1026",
        "vis": "25",
        "cloud": "91",
        "dew": "-17"
    },
    "refer": {
        "sources": [
            "QWeather"
        ],
        "license": [
            "CC BY-SA 4.0"
        ]
    }
}

因为浏览器会自动进行解压缩,因而我们可以直接看到具体的数据,如上面文本所示。有了上面的数据,我们分析下面的代码就方便多了:

local function demo_http_get_gzip()
    -- 这里用 和风天气 的API做演示
    -- 这个API的响应, 总会gzip压缩过, 需要配合miniz库进行解压
    local code, headers, body = http.request("GET", "https://devapi.qweather.com/v7/weather/now?location=101010100&key=0e8c72015e2b4a1dbff1688ad54053de").wait()
    log.info("http.gzip", code)
    if code == 200 then
        local re = miniz.uncompress(body:sub(11), 0)
        log.info("和风天气", re)
        if re then
            local jdata = json.decode(re)
            log.info("jdata", jdata)
            if jdata then
                log.info("和风天气", jdata.code)
                if jdata.now then
                    log.info("和风天气", "天气", jdata.now.text)
                    log.info("和风天气", "温度", jdata.now.temp)
                end
            end
        end
    end
end

这是一个很简单 GET 方法演示,代码也很简单,即取得返回的数据 body,然后取得 body 的从 11 位置开始的数据进行解压即可,然后就是依需要提取数据并输出。至于为何要提取从 11 位置开始的字符,我们从上面的结果代码可知,这个是返回的结果代码 “ code: 200 ”所占的空间,因而剔除在解压缩之外。

具体代码很简单就不作分析了,直接看看演示结果吧:

上面图中的红框分别是返回码 200、解压后的 Json 代码以及依需要选择的输出内容。

五、总结

本次拿到 Ari8101 开发板,感觉接口丰富,各模块都比较实用。用了几天时间进行了解,然后就使用这款开发板进行 http 的相关测试,感觉还是非常容易上手。结合以前关于 http 的文档 HTTP - luatos@air724ug - 合宙文档中心 顺利地完成了 GET、POST、GZIP 以及文件上传下载等操作,个人觉得收获很多。同时,也希望本文能给广大读者以帮助,作为参考解决一些实际的问题,使大家在使用 Air8101 时少走弯路,少花时间与精力。

六、 完全代码

脚本文件下载 main.lua

最新固件下载

最新固件:

固件如更新,一般也会同步更新 Luatools 工具包,因而具体使用时,当有 Luatools 更新时,及时更新即可。