鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例

1. HttpRequest文件上传简介

在本系列的第21篇文章《鸿蒙网络编程系列21-使用HttpRequest上传任意文件到服务端示例》中,使用ArkTS语言基于API 9环境演示了文件上传功能的实现,本节将使用仓颉语言基于API 12环境实现类似的功能。

2. HttpRequest文件上传演示

本示例运行后的页面如图所示:

单击"选择"按钮,会弹出文件选择窗口,可以选择需要上传的文件,如图所示:

选择文件后,单击"完成"按钮返回主界面,输入上传地址后,单击"上传"按钮,即可上传文件到服务端,如图所示:

日志显示,文件上传成功,此时如果打开服务端,就可以看到已经上传的文件。

3. HttpRequest文件上传示例编写

下面详细介绍创建该示例的步骤(确保DevEco Studio已安装仓颉插件)。

步骤1:创建[Cangjie]Empty Ability项目。

步骤2:在module.json5配置文件加上对权限的声明:

json 复制代码
"requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]

这里添加了访问互联网的权限。

步骤3:在build-profile.json5配置文件加上仓颉编译架构:

json 复制代码
"cangjieOptions": {
      "path": "./src/main/cangjie/cjpm.toml",
      "abiFilters": ["arm64-v8a", "x86_64"]
    }

步骤4:在main_ability.cj文件里添加如下的引用:

javascript 复制代码
import ohos.ability.*

然后定义变量globalContext:

javascript 复制代码
var globalContext: Option<AbilityContext> = Option<AbilityContext>.None

在onCreate函数里添加如下的代码:

javascript 复制代码
globalContext = this.context

步骤5:在index.cj文件里添加如下的代码:

javascript 复制代码
package ohos_app_cangjie_entry

import ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*
import std.collection.HashMap
import ohos.net.http.*
import ohos.file_picker.*
import ohos.ability.getStageContext
import ohos.ability.*
import ohos.file_fs.*
import std.time.DateTime

@Entry
@Component
class EntryView {
    @State
    var title: String = '仓颉版文件上传示例';
    //连接、通讯历史记录
    @State
    var msgHistory: String = ''
    //首页地址
    @State
    var uploadUrl: String = "http://*.*.*.*:8081/upload"

    //是否允许上传
    @State
    var canUpload: Bool = false

    @State
    //要上传的文件
    var uploadFilePath: String = ""

    let scroller: Scroller = Scroller()

    func build() {
        Row {
            Column {
                Text(title).fontSize(14).fontWeight(FontWeight.Bold).width(100.percent).textAlign(TextAlign.Center).
                    padding(10)

                Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {
                    Text("文件:").fontSize(14).width(80)

                    TextInput(text: uploadFilePath).onChange({
                        value => uploadFilePath = value
                    }).width(100).fontSize(11).flexGrow(1)

                    Button("选择").onClick {
                        evt => selectUploadFile()
                    }.width(70).fontSize(14).flexGrow(0)
                }.width(100.percent).padding(10)

                Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {
                    Text("上传地址:").fontSize(14)

                    TextInput(text: uploadUrl).onChange({
                        value => uploadUrl = value
                    }).width(100).fontSize(11).flexGrow(1)

                    Button("上传").onClick {
                        evt => uploadFile()
                    }.width(70).fontSize(14)
                }.width(100.percent).padding(10)

                Scroll(scroller) {
                    Text(msgHistory).textAlign(TextAlign.Start).padding(10).width(100.percent).backgroundColor(0xeeeeee)
                }.align(Alignment.Top).backgroundColor(0xeeeeee).height(300).flexGrow(1).scrollable(
                    ScrollDirection.Vertical).scrollBar(BarState.On).scrollBarWidth(20)
            }.width(100.percent).height(100.percent)
        }.height(100.percent)
    }

    //选择需要要上传的文件
    func selectUploadFile() {
        let option = DocumentSelectOptions(maxSelectNumber: 1, selectMode: DocumentSelectMode.MIXED)
        let documentPicker = DocumentViewPicker(globalContext.getOrThrow())

        let documentSelectCallback = {
            errorCode: Option<AsyncError>, data: Option<Array<String>> => match (errorCode) {
                case Some(e) => msgHistory += "选择失败,错误码:${e.code}\r\n"
                case _ => match (data) {
                    case Some(value) => uploadFilePath = value[0]
                    case _ => ()
                }
            }
        }
        documentPicker.select(documentSelectCallback, option: option)
    }

    //构造上传文件的body内容
    func buildBodyContent(boundary: String, fileName: String, content: Array<UInt8>,
        contentType!: String = "application/octet-stream") {

        //构造文件内容前的部分
        var preFileContent = "--${boundary}\r\n"
        preFileContent = preFileContent + "Content-Disposition: form-data; name=\"file\"; filename=\"${fileName}\"\r\n"
        preFileContent = preFileContent + "Content-Type: ${contentType}\r\n"
        preFileContent = preFileContent + "\r\n"
        var preArray = preFileContent.toArray()

        //构造文件内容后的部分
        var aftFileContent = '\r\n'
        aftFileContent = aftFileContent + "--${boundary}"
        aftFileContent = aftFileContent + "--\r\n"
        let aftArray = aftFileContent.toArray()

        //文件前后内容和文件内容组合
        return preArray.concat(content).concat(aftArray)
    }

    //上传文件
    func uploadFile() {
        //上传文件使用的分隔符
        let boundary: String = '----HarmonyOSNextBoundary' + DateTime.now().toUnixTimeStamp().toString()
        let fileName = getFileNameFromPath(this.uploadFilePath)
        //选择要上传的文件的内容
        let fileContent = readArrayBufferContentFromFile(this.uploadFilePath)
        //上传请求的body内容
        let bodyContent = buildBodyContent(boundary, fileName, fileContent)


        //http请求对象
        let httpRequest = createHttp();
        let header = HashMap<String, String>(("Content-Type", "multipart/form-data; boundary=${boundary}"),
            ("Content-Length", bodyContent.size.toString()));
        let opt = HttpRequestOptions(
            method: RequestMethod.POST,
            header: header,
            extraData: HttpData.ARRAY_DATA(bodyContent)
        )

        httpRequest.request(this.uploadUrl, uploadFileResponse, options: opt)
    }

    //上传文件请求的响应函数
    func uploadFileResponse(err: ?BusinessException, response: ?HttpResponse) {
        if (let Some(e) <- err) {
            msgHistory += "请求失败:" + e.message + "\r\n"
        }

        if (let Some(resp) <- response) {
            msgHistory += "响应码:${resp.responseCode.getValue()}\r\n"
            msgHistory += "上传成功\r\n"
        }
    }
}

步骤6:编译运行,可以使用模拟器或者真机,在当前版本下,最好使用真机,笔者使用模拟器的文件选择窗口时有些显示问题。

步骤7:按照本节第2部分"HttpRequest文件上传演示"操作即可。

4. 代码分析

在文件上传时,服务端一般对上传的信息有严格的要求,比如文件名、文件大小、文件类型等,在客户端上传时,需要将这些信息一起上传,服务端才能正确接收到文件。

本示例上传时Content-Type指定为multipart/form-data;,这是一种常用的方式;除此之外,也有服务端要求Content-Type是application/octet-stream,这是一种标准的格式,在实际中使用的相对少一些,如果是这种方式,上传内容的格式和本文不同,读者在实际使用中需要注意。

(本文作者原创,除非明确授权禁止转载)

本文源码地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/http/FileUpload4Cj

本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples

相关推荐
庸俗今天不摸鱼10 分钟前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
马剑威(威哥爱编程)11 分钟前
在HarmonyOS NEXT 开发中,如何指定一个号码,拉起系统拨号页面
华为·harmonyos·arkts
黄毛火烧雪下17 分钟前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox28 分钟前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞31 分钟前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行31 分钟前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_5937581032 分钟前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
叫醒你笛莎33 分钟前
IGMP(Internet Group Management Protocol)与组播技术深度解析
网络
掘金一周35 分钟前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
lulinhao39 分钟前
HCIA/HCIP基础知识笔记汇总
网络·笔记