跳转至

HTTP

一、HTTP 概述

1.1 简介

HTTP 是 Hyper Text Transfer Protocol(超文本传输协议)的缩写.HTTP 是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型.HTTP 是一个无状态的协议.HTTP 协议通常承载于 TCP 协议之上,有时也承载于 TLS 或 SSL 协议层之上,这个时候,就成了我们常说的 HTTPS,所以 HTTPS 相关的指令只需要参考 SSL 部分配置连接,其他和 http 都是一样的.

HTTP 协议的_主要应用场景_有:基于浏览器的网页获取与表单提交、文件上传与下载、移动应用、物联网设备的数据上报等.

1.2 请求报文

1. method:请求方法,GET 和 POST 是最常见的 HTTP 方法. 2. URL:为请求对应的 URL 地址,它和报文头的 Host 属性组成完整的请求 URL. 3. Version:协议名称及版本号. 4. Header lines:HTTP 的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息. 5. Entity body:是报文体,它将一个页面表单中的组件值通过 param1=value1&m2=value2 的键值对形式编码成一个格式化串,它承载多个请求参数的数据. 6. 请求报文示例如下:

1.3 响应报文

1. version:报文协议及版本. 2. status code:状态码及状态描述. 3. phrase:原因短语. 4. Header lines:响应报文头. 5. Entity body:响应报文体,即我们真正要的内容. 6. 响应报文示例如下:

注意:sp 表示空格,cr lf 表示回车换行,报文头和报文体之间要有一行空格

1.4 HTTP 请求方法

HTTP 客户端发出请求,告知服务端需要执行不同类型的请求命令,这些命令被称为 HTTP 方法.

1. GET:获取资源方法 2. POST:传输实体数据方法 3. HEAD:获取头部报文方法 4. PUT:传输文件方法 5. DELETE:删除指定资源方法

1.5 HTTP 状态码

HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态 码的类型.响应分为五类:

1. 信息响应(100–199),信息响应中,服务器收到请求,需要请求者继续执行操作 2. 成功响应(200–299),信息响应成功,操作被成功接收并处理 3. 重定向,需要进一步操作(300–399),信息需要被重新定向,需要进一步的操作以完成请求 4. 客户端错误(400–499),客户端错误,请求包含语法错误或无法完成请求 5. 服务器错误(500–599),服务器错误,服务器在处理请求的过程中发生了错误

二、演示功能概述

本文教你合宙 4G 模组使用 LuatOS 开发 4G 通信中 http 网络协议的应用,实现模组和服务器之间数据的传输.

本教程实现的功能定义是:

使用 Air780EP 核心板下载 Air780 的 LuatOS 示例代码中 http 的例程进行验证,包含 get 请求,post 请求,文件上传,文件下载等功能.

三、准备硬件环境

参考:硬件环境清单第二章节内容,准备以及组装好硬件环境。

四、软件环境

“凡事预则立,不预则废。”在详细阐述本功能示例之前,我们需先精心筹备好以下软件环境。

1. Luatools工具

2. 内核固件文件(底层core固件文件):LuatOS-SoC_V2002_Air780EP;参考项目使用的内核固件

3. luatos需要的脚本和资源文件

脚本和资源文件点我,查看demo链接

lib脚本文件:使用Luatools烧录时,勾选 添加默认lib 选项,使用默认lib脚本文件;

准备好软件环境之后,接下来查看如何烧录项目文件到Air780EP核心板,将本篇文章中演示使用的项目文件烧录到Air780EP核心板中。

五、API 说明

http 客户端:

http.request(method,url,headers,body,opts,ca_file,client_ca, client_key, client_password)

参数

传入值类型
解释
string
请求方法, 支持 GET/POST 等合法的 HTTP 方法
string
url 地址, 支持 http 和 https, 支持域名, 支持自定义端口
table
请求头 可选 例如 {[“Content-Type”] = “application/x-www-form-urlencoded”}
string/zbuff
body 可选
table
额外配置 可选 包含 timeout:超时时间单位 ms 可选,默认 10 分钟,写 0 即永久等待 dst:下载路径,可选 adapter:选择使用网卡,可选 debug:是否打开 debug 信息,可选,ipv6:是否为 ipv6 默认不是,可选 callback:下载回调函数,参数 content_len:总长度 body_len:以下载长度 userdata 用户传参,可选 userdata:回调自定义传参
string
服务器 ca 证书数据, 可选, 一般不需要
string
客户端 ca 证书数据, 可选, 一般不需要, 双向 https 认证才需要
string
客户端私钥加密数据, 可选, 一般不需要, 双向 https 认证才需要
string
客户端私钥口令数据, 可选, 一般不需要, 双向 https 认证才需要

返回值

返回值类型
解释
int
code , 服务器反馈的值 >=100, 最常见的是 200.如果是底层错误,例如连接失败, 返回值小于 0
table
headers 当 code>100 时, 代表服务器返回的头部数据
string/int
body 服务器响应的内容字符串,如果是下载模式, 则返回文件大小

创建 HTTP 客户端

-- 使用http库,需要引入sysplus库, 且需要在task内使用
require "sys"
require "sysplus"
sys.taskInit(function()
    sys.wait(1000)
    local code,headers,body = http.request("GET","http://www.example.com/abc").wait()
    log.info("http",code,body)
end)
--[[
返回码code报错信息列表:
-1 HTTP_ERROR_STATE 错误的状态, 一般是底层异常,请报issue
-2 HTTP_ERROR_HEADER 错误的响应头部, 通常是服务器问题
-3 HTTP_ERROR_BODY 错误的响应体,通常是服务器问题
-4 HTTP_ERROR_CONNECT 连接服务器失败, 未联网,地址错误,域名错误
-5 HTTP_ERROR_CLOSE 提前断开了连接, 网络或服务器问题
-6 HTTP_ERROR_RX 接收数据报错, 网络问题
-7 HTTP_ERROR_DOWNLOAD 下载文件过程报错, 网络问题或下载路径问题
-8 HTTP_ERROR_TIMEOUT 超时, 包括连接超时,读取数据超时
-9 HTTP_ERROR_FOTA fota功能报错,通常是更新包不合法
]]

六、功能验证

6.1 GET 请求

HTTP GET 请求是一种用于从指定资源 URI(统一资源标识符)请求数据的 HTTP 方法.它通常用于请求服务器发送资源(如 HTML 页面、图片等)给客户端,且请求信息包含在 URL 中.

下面根据 demo 演示 HTTP 的 GET 请求用法,示例代码如下 (具体 demo 可以点此链接跳转)

示例如下:

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)
    end
end)

对应 log:

6.2 POST 请求

HTTP POST 请求是一种 HTTP 方法,用于向指定的资源提交数据.与 GET 请求不同,POST 请求的数据包含在请求体中,可以提交大量数据且数据不会显示在 URL 中,常用于提交表单数据或上传文件等操作.

下面根据 demo 演示 HTTP 的 POST 请求方法提交一个表单,示例代码如下 (具体 demo 可以点此链接跳转)

示例:

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
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
        -- post表单提交
        demo_http_post_form()
        sys.wait(1000)
        -- 打印一下内存状态
        log.info("sys", rtos.meminfo("sys"))
        log.info("lua", rtos.meminfo("lua"))
        sys.wait(600000)
    end
end)

对应 log:

6.3 文件上传

HTTP POST 请求在文件上传场景中发挥着关键作用.用户通过 POST 请求可以将文件数据包含在请求体中发送给服务器,而不是像 GET 请求那样通过 URL 传递.这种方式允许上传大量数据,包括各种类型的文件,如图片、视频、文档等.服务器接收到请求后,会解析请求体中的文件数据,并存储到服务器上相应的位置.文件上传是 HTTP 应用中常见的功能.

下面根据 demo 演示 HTTP 文件上传的功能,示例代码如下 (具体 demo 可以点此链接跳转)

示例:

---- 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 = {}
    -- 解析拼接 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
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)
    -- 也可用postMultipartFormData(url, params) 上传文件
    postMultipartFormData(
        "http://airtest.openluat.com:2900/uploadFileToStatic",
        {
            -- texts =
            -- {
            --     ["imei"] = "862991234567890",
            --     ["time"] = "20180802180345"
            -- },
            files =
            {
                ["uploadFile"] = "/luadb/luatos_uploadFile.txt",
            }
        }
    )
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
        demo_http_post_file()
        sys.wait(1000)
        -- 打印一下内存状态
        log.info("sys", rtos.meminfo("sys"))
        log.info("lua", rtos.meminfo("lua"))
        sys.wait(600000)
    end
end)

对应 log:

考虑例程的服务器不方面查看上传结果,所以自己找了一个回环服务器(httpbin.org 的测试服务器)来验证。httpbin.org/post 是一个回环服务器,即当向其传输文件时,服务器会将传输内容作为响应返回,因而当我们看到响应并返回的内容后,就可以判断操作是否正确。 因为我们使用”post"方法上传文件,所以 url 为 httpbin.org/post。只需要例程把网址改成 http://httpbin.org/post 即可。

  -- local code, headers, body = http.request("POST","http://airtest.openluat.com:2900/uploadFileToStatic",
  local code, headers, body = http.request("POST", "http://httpbin.org/post",
        req_headers,
        body         -- POST请求所需要的body, string, zbuff, file均可
    ).wait()
出现以下log,说明上传成功:
[2024-11-11 16:23:16.614][000000006.286] D/DNS httpbin.org state 0 id 2 ipv6 0 use dns server2, try 0
[2024-11-11 16:23:16.693][000000006.377] I/DNS dns all done ,now stop
[2024-11-11 16:23:17.746][000000007.425] I/user.http.post   200 table: 00471318
[2024-11-11 16:23:17.752][000000007.426] {
  "args": {},
  "data": "",
  "files": {
    "uploadFile": "\u6d4b\u8bd5\u6587\u672c   adcc1234x\u8863\u4e8c\u4e09\n\t84as1c188\n-*accxxx6-4"
  },
  "form": {},
  "headers": {
    "Content-Length": "245",
    "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundary1731313396",
    "Host": "httpbin.org",
    "X-Amzn-Trace-Id": "Root=1-6731bef5-67791b71404cfedf68d953f2"
  },
  "json": null,
  "origin": "223.104.84.124",
  "url": "http://httpbin.org/post"
}

6.4 文件下载

HTTP 文件下载的功能可以参考示例代码,示例代码如下 (具体 demo 可以点此链接跳转)

示例:

对应 log:

示例代码中的服务器已经不能用了,所以自己又找了一个外链服务器:http://zuoye.free.fr。 首先进入外链服务器网页,首页有一个"选择文件",点击添加自己的文件 添加文件后得到外链地址: 加入自己的程序进行验证:

    --文件整体下载比较简单,只需要知道资源的位置下载就可以。
   local code, headers, body = http.request("GET","http://zuoye.free.fr/files/Air780EP_LuatOS.png").wait()

   log.info("http.post", code, headers, body) -- 只返回code和headers
对应 log:
[2024-11-09 12:21:05.261][000000002.086] I/user.已联网
[2024-11-09 12:21:05.264][000000002.089] D/DNS zuoye.free.fr state 0 id 1 ipv6 0 use dns server2, try 0
[2024-11-09 12:21:05.319][000000002.170] I/DNS dns all done ,now stop
[2024-11-09 12:21:05.398][000000002.245] D/mobile TIME_SYNC 0
[2024-11-09 12:21:06.538][000000003.398] I/user.http.post   200 table: 00472820
[2024-11-09 12:21:06.540][000000003.398] 塒NG


6.5 处理 JSON 数据

处理 json 数据主要有两个函数,json.encode(t)和 json.decode(str),参考示例代码:https://gitee.com/openLuat/LuatOS-Air780EP/tree/master/demo/json

6.6 压缩和解压

这个例程用 和风天气 的 api 做演示,请求到的数据配合 miniz 库进行解压,示例代码如下 (具体 demo 可以点此链接跳转)

示例:

对应 log:

也可以参考例程:https://gitee.com/openLuat/LuatOS-Air780EP/tree/master/demo/miniz

七、总结

本文档主要介绍 4G 通信中 http 网络协议的应用.讲解了 HTTP 基本原理,GET 和 POST 请求,以及文件上传下载、JSON 数据处理和数据压缩等高级功能,直接烧录例程即可测试,旨在实现高效、安全的数据传输.

八、常见问题

8.1 HTTP 应用一直连接失败

HTTP 一直请求应答失败,则尝试使用如下手段恢复:

1、使用 RESET 引脚复位模块

2、极端情况下,直接给模块断电,再上电,POWER KEY 引脚拉低开机

8.2 为什么我只发了 10 字节消息,100 次却消耗了那么多流量?

因为还有 HTTP 自带的请求头.如何统计流量

8.3 为什么频繁请求会失败?

建议一个 http 连接返回请求结果之后,再去请求下一个连接;不要使用循环定时器方式不断的发起新的 http 请求.

8.4 专网卡访问白名单

用定向 Ip 的物联网卡,需要把域名或 IP 加入白名单才能使用.如果不加入白名单会出现无法访问服务器的情况.

给读者的话

本篇文章由公帅开发;

本篇文章描述的内容,如果有错误、细节缺失、细节不清晰或者其他任何问题,总之就是无法解决您遇到的问题;

请登录合宙技术交流论坛,点击文档找错赢奖金-Air780EP-LuatOS-软件指南-网络驱动-HTTP通信

用截图标注+文字描述的方式跟帖回复,记录清楚您发现的问题;

我们会迅速核实并且修改文档;

同时也会为您累计找错积分,您还可能赢取月度找错奖金!