鸿蒙网络编程系列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

相关推荐
喜欢吃豆16 分钟前
使用 OpenAI Responses API 构建生产级应用的终极指南—— 状态、流式、异步与文件处理
网络·人工智能·自然语言处理·大模型
zengyuhan50316 分钟前
Windows BLE 开发指南(Rust windows-rs)
前端·rust
用户4639897543217 分钟前
Harmony os——AbilityStage 组件管理器:我理解的 Module 级「总控台」
harmonyos
醉方休20 分钟前
Webpack loader 的执行机制
前端·webpack·rust
前端老宋Running28 分钟前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔28 分钟前
如何自己构建一个Markdown增量渲染器
前端·javascript
用户44455436542631 分钟前
Android的自定义View
前端
WILLF31 分钟前
HTML iframe 标签
前端·javascript
用户4639897543237 分钟前
Harmony os——UIAbility 组件基本用法:启动页、Context、终止与拉起方信息全流程
harmonyos
xixixi7777740 分钟前
解析一下存储安全——“它是什么”,更是关于“它为何存在”、“如何实现”以及“面临何种挑战与未来”
网络·安全·通信