博客 / 詳情

返回

【倉頡開發HarmonyOS系列】倉頡網絡請求功能封裝

背景

在萬物互聯的智能時代,應用的核心價值往往依賴於與外部世界的實時交互:社交軟件需要通過網絡同步消息,電商應用依賴接口獲取商品與訂單數據,智能助手依靠API調用大模型能力,甚至系統級的設備協同也需通過網絡傳遞指令。可以説,​網絡接口是連接應用與外部服務的“數字神經”​,其穩定性、效率與易用性直接影響用户體驗與開發效率。從技術角度看,網絡接口是應用與遠程服務器(或本地網絡服務)交換數據的標準化通道,承載着數據請求(如GET/POST)、響應處理(如JSON/XML解析)、狀態管理(如超時/重試)等關鍵功能。無論是簡單的天氣查詢(調用氣象API)、複雜的分佈式任務調度(跨設備數據同步),還是用户身份認證(Token校驗)、文件上傳下載(如圖片/視頻傳輸),本質上都是通過網絡接口完成“請求-響應”的閉環。本文介紹在HarmonyOS 場景中倉頡網絡請求的使用和工具的封裝。
【倉頡開發HarmonyOS系列】倉頡網絡請求功能封裝.png

倉頡網絡請求API介紹

net.http.*模塊介紹

倉頡網絡編程提供了Socket、HTTP、WebSocket等通信方式,在net.http.*包下提供,不僅支持客户端請求,還支持創建HTTP服務。以下示例展示瞭如何使用倉頡進行客户端和服務端編程:

import net.http.*
import std.time.*
import std.sync.*
import std.log.LogLevel

// 1. 構建 Server 實例
let server = ServerBuilder()
    .addr("127.0.0.1")
    .port(8080)//監聽8080端口
    .build()

func startServer(): Unit {
    // 2. 註冊請求處理邏輯
    server.distributor.register("/test", {httpContext =>
        httpContext.responseBuilder.body("Cangjie Success!")
    })
    server.logger.level = OFF
    // 3. 啓動服務
    server.serve()
}

func startClient(): Unit {
    // 1. 構建 client 實例
    let client = ClientBuilder().build()
    // 2. 發送 request
    let response = client.get("http://127.0.0.1:8080/test")
    // 3. 讀取response body
    let buffer = Array<Byte>(32, item: 0)
    let length = response.body.read(buffer)
    println(String.fromUtf8(buffer[..length]))
    // 4. 關閉連接
    client.close()
}

main () {
    spawn {
        startServer()
    }
    sleep(Duration.second)
    startClient()
}

上面示例搭建了一個簡單的http服務,監聽本地地址和8080端口,接口路徑為test,客户端請求時返回“Cangjie Success!”。

ohos.net.http模塊介紹

HarmonyOS 場景下倉頡API提供了ohos.net.http模塊發起網絡請求,應用可以使用該模塊通過HTTP發起一個數據請求,支持常見的GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT方法。

在該模塊下,通過HttpRequest類發起網絡請求,支持普通請求和流式請求。通過HttpRequestOptions構造請求方式、請求Header等。接下來介紹使用步驟。

首先申請網絡請求權限,導入模塊:import ohos.net.http.*
接下來通過public func createHttp(): HttpRequest構造HttpRequest
接着構造HttpRequestOptions,然後通過public func request(url: String, callback: (?BusinessException, ?HttpResponse) -> Unit, options!: ?HttpRequestOptions = None): Unit發起網絡請求,在callback中處理服務端響應事件。
最後銷燬HttpRequest。

HttpRequestOptions類的構造函數如下:

public class HttpRequestOptions {
    public HttpRequestOptions(
        public let method!: RequestMethod = RequestMethod.GET,
        public let extraData!: ?HttpData = None,
        public let expectDataType!: ?HttpDataType = None,
        public let usingCache!: Bool = true,
        public let priority!: UInt32 = 1,
        public let header!: ?HashMap<String, String> = None,
        public let readTimeout!: UInt32 = 60000,
        public let connectTimeout!: UInt32 = 60000,
        public let usingProtocol!: ?HttpProtocol = None,
        public let usingProxy!: UsingProxy = USE_DEFAULT,
        public let caPath!: ?String = None,
        public let resumeFrom!: ?Int64 = None,
        public let resumeTo!: ?Int64 = None,
        public let clientCert!: ?ClientCert = None,
        public let dnsOverHttps!: ?String = None,
        public let dnsServers!: ?Array<String> = None,
        public let maxLimit!: UInt32 = 5 * 1024 * 1024,
        public let multiFormDataList!: ?Array<MultiFormData> = None
    ) {}
}   

包含了請求方式,header,超時時間配置,ca證書路徑等,都有默認的值。

下面介紹post請求發送Json數據示例。

var authorization: String = ""
func getHeaderMethod():HashMap<String, String>{  
     if(CollectionUtils.isEmpty<String>(this.authorization)){  
         LogUtil.d(TAG, "header===: Empty-cookieValue=${authorization}")  
         return HashMap<String, String>([("content-type", "application/json")])  
     } else {  
         //var cookie:String = AppStorage.get<String>("Cookie").getOrThrow()  
         LogUtil.d(TAG, "header===: Value-cookieValue=${authorization}")  
         return  HashMap<String, String>([("content-type", "application/json"),("authorization",authorization)])  
     }  
}

//post請求  
public func httpRequestPost<E>(callback: (data:BaseResponse<E>)->Unit) where E <: Serializable<E> {  
    let option = HttpRequestOptions(  
        method: RequestMethod.POST, // 可選,默認為http.RequestMethod.GET  
        usingCache: true, // 可選,默認為true  
        extraData: HttpData.STRING_DATA("{\"email\":\"${phoneNum}\"}"),  
        expectDataType: HttpDataType.STRING, // 可選,指定返回數據的類型  
        // 開發者根據自身業務需要添加header字段  
        header:getHeaderMethod(),  
        readTimeout: 60000, // 可選,默認為60000ms  
        connectTimeout: 60000, // 可選,默認為60000ms  
        usingProxy: UsingProxy.NOT_USE, //可選,默認不使用網絡代理,自API 10開始支持該屬性  
    )  
    let httpRequest = createHttp();  
    // 用於訂閲HTTP響應頭,此接口會比request請求先返回。可以根據業務需要訂閲此消息  
    httpRequest.onHeadersReceive({header: HashMap<String, String> =>  
        LogUtil.d(TAG, "resp===: header: ${header}")  
    })  
    try {  
        httpRequest.request(http://qignkouwei.com/api/auth/send-code,{ err, resp =>  
                var responseResult = Option<BaseResponse<E>>.None  
                if (let Some(e) <- err) {  
                    LogUtil.d(TAG, "exception: ${e.message}")  
                    var jo = JsonUtil.String2JsonObject(NetUtil.getResult(400, e.message, ""))  
                    responseResult = JsonUtil.JsonObject2ResponseResult<E>(jo)  
                }  
                if (let Some(r) <- resp) {  
                    LogUtil.d(TAG, "resp===: data:${r.result}")  
                    //數據類解析  
                    var jo = JsonUtil.String2JsonObject(r.result.toString())  
                    responseResult = JsonUtil.JsonObject2ResponseResult<E>(jo)  
                } else {  
                    LogUtil.d(TAG, "response is none")  
                    var jo = JsonUtil.String2JsonObject(NetUtil.getResult(404, "response is none", ""))  
                    responseResult = JsonUtil.JsonObject2ResponseResult<E>(jo)  
                }  
                callback(responseResult.getOrThrow())  
                httpRequest.destroy()  
            },  
            options: option  
        )  
    } catch (exception: Exception) {  
        var jo = JsonUtil.String2JsonObject(NetUtil.getResult(500, "${exception.message}", "出錯了"))  
        var responseResult = JsonUtil.JsonObject2ResponseResult<E>(jo)  
        callback(responseResult)  
    } finally {  
    }  
    }
}

示例中通過header增加了token身份校驗。

網絡請求封裝

上面看起來每次操作都有很多代碼,非常繁瑣,一般的做法是把網絡請求封裝成工具,在使用的地方直接調用。

public class HttpService{  
    let TAG:String = "HttpService"  
    var authorization: String = ""  
  
    //單例  
    private HttpService() {}  
    private static var instance: HttpService = HttpService()  
    public static func getInstance(): HttpService {  
        return instance  
    }  
    public func setAuthorization(token:String){  
        LogUtil.d(TAG, "setAuthorization:${token}")  
        this.authorization = "Bearer ${token}";  
    }  
   func getHeaderMethod():HashMap<String, String>{  
        if(CollectionUtils.isEmpty<String>(this.authorization)){  
            LogUtil.d(TAG, "header===: Empty-cookieValue=${authorization}")  
            return HashMap<String, String>([("content-type", "application/json")])  
        } else {  
            //var cookie:String = AppStorage.get<String>("Cookie").getOrThrow()  
            LogUtil.d(TAG, "header===: Value-cookieValue=${authorization}")  
            return  HashMap<String, String>([("content-type", "application/json"),("authorization",authorization)])  
        }  
   }  
    //get請求  
    func httpRequestGet<E>(url: String, callback: (data:BaseResponse<E>)->Unit) where E <: Serializable<E> {  
  
        let option = HttpRequestOptions(  
            method: RequestMethod.GET, // 可選,默認為http.RequestMethod.GET  
            expectDataType: HttpDataType.STRING, // 可選,指定返回數據的類型  
            usingCache: true, // 可選,默認為true  
            priority: 1, // 可選,默認為1  
            // 開發者根據自身業務需要添加header字段  
            header:getHeaderMethod(),  
            readTimeout: 60000, // 可選,默認為60000ms  
            connectTimeout: 60000, // 可選,默認為60000ms  
            usingProtocol: HttpProtocol.HTTP1_1, // 可選,協議類型默認值由系統自動指定  
            usingProxy: UsingProxy.NOT_USE, //可選,默認不使用網絡代理,自API 10開始支持該屬性  
        )  
        return httpRequest(url, option, callback)  
    }  
    //post請求  
    public func httpRequestPost<E>(url: String, params: String, callback: (data:BaseResponse<E>)->Unit) where E <: Serializable<E> {  
        let option = HttpRequestOptions(  
            method: RequestMethod.POST, // 可選,默認為http.RequestMethod.GET  
            usingCache: true, // 可選,默認為true  
            extraData: HttpData.STRING_DATA(params),  
            expectDataType: HttpDataType.STRING, // 可選,指定返回數據的類型  
            // 開發者根據自身業務需要添加header字段  
            header:getHeaderMethod(),  
            readTimeout: 60000, // 可選,默認為60000ms  
            connectTimeout: 60000, // 可選,默認為60000ms  
            usingProxy: UsingProxy.NOT_USE, //可選,默認不使用網絡代理,自API 10開始支持該屬性  
        )  
        return httpRequest(url, option, callback)  
    }  
    //put請求  
    public func httpRequestPut<E>(url: String, params: String, callback: (data:BaseResponse<E>)->Unit) where E <: Serializable<E> {  
        let option = HttpRequestOptions(  
            method: RequestMethod.PUT, // 可選,默認為http.RequestMethod.GET  
            usingCache: true, // 可選,默認為true  
            extraData: HttpData.STRING_DATA(params),  
            expectDataType: HttpDataType.STRING, // 可選,指定返回數據的類型  
            // 開發者根據自身業務需要添加header字段  
            header:getHeaderMethod(),  
            readTimeout: 60000, // 可選,默認為60000ms  
            connectTimeout: 60000, // 可選,默認為60000ms  
            usingProxy: UsingProxy.NOT_USE, //可選,默認不使用網絡代理,自API 10開始支持該屬性  
        )  
        return httpRequest(url, option, callback)  
    }  
    public func httpRequestDelete<E>(url: String, params: String, callback: (data:BaseResponse<E>)->Unit) where E <: Serializable<E> {  
        let option = HttpRequestOptions(  
            method: RequestMethod.DELETE, // 可選,默認為http.RequestMethod.GET  
            usingCache: true, // 可選,默認為true  
            extraData: HttpData.STRING_DATA(params),  
            expectDataType: HttpDataType.STRING, // 可選,指定返回數據的類型  
            // 開發者根據自身業務需要添加header字段  
            header:getHeaderMethod(),  
            readTimeout: 60000, // 可選,默認為60000ms  
            connectTimeout: 60000, // 可選,默認為60000ms  
            usingProxy: UsingProxy.NOT_USE, //可選,默認不使用網絡代理,自API 10開始支持該屬性  
        )  
        return httpRequest(url, option, callback)  
    }  
    func httpRequest<E>(url: String, option: HttpRequestOptions, callback: (data:BaseResponse<E>)->Unit) where E <: Serializable<E>{  
        let httpRequest = createHttp();  
        // 用於訂閲HTTP響應頭,此接口會比request請求先返回。可以根據業務需要訂閲此消息  
        httpRequest.onHeadersReceive({header: HashMap<String, String> =>  
            LogUtil.d(TAG, "resp===: header: ${header}")  
        })  
        try {  
            httpRequest.request(url,{ err, resp =>  
                    var responseResult = Option<BaseResponse<E>>.None  
                    if (let Some(e) <- err) {  
                        LogUtil.d(TAG, "exception: ${e.message}")  
                        var jo = JsonUtil.String2JsonObject(NetUtil.getResult(400, e.message, ""))  
                        responseResult = JsonUtil.JsonObject2ResponseResult<E>(jo)  
                    }  
                    if (let Some(r) <- resp) {  
                        LogUtil.d(TAG, "resp===: data:${r.result}")  
                        //數據類解析  
                        var jo = JsonUtil.String2JsonObject(r.result.toString())  
                        responseResult = JsonUtil.JsonObject2ResponseResult<E>(jo)  
                    } else {  
                        LogUtil.d(TAG, "response is none")  
                        var jo = JsonUtil.String2JsonObject(NetUtil.getResult(404, "response is none", ""))  
                        responseResult = JsonUtil.JsonObject2ResponseResult<E>(jo)  
                    }  
                    callback(responseResult.getOrThrow())  
                    httpRequest.destroy()  
                },  
                options: option  
            )  
        } catch (exception: Exception) {  
            var jo = JsonUtil.String2JsonObject(NetUtil.getResult(500, "${exception.message}", "出錯了"))  
            var responseResult = JsonUtil.JsonObject2ResponseResult<E>(jo)  
            callback(responseResult)  
        } finally {  
        }  
    }  
}

一般一個APP的和服務端通信,有一些公共的協議字段放在請求header中,比如標識用户身份的token,UA等,這裏面封裝了獲取通用header的方法getHeaderMethod。
接着把不同請求方式的請求封裝成GET、POST、DELETE方法,最後在httpRequest發起真正的請求。返回的數據類型是Json,這裏做了統一的String到對象的轉換。

有了底層請求工具,可以封裝不同的請求方法了:

public interface  NetApi {  
     //發送驗證碼  
      static func sendLoginCode(params:String, callback:(data:BaseResponse<String>)->Unit){  
        return HttpService.getInstance().httpRequestPost<String>('http://qingkouwei.com/api/auth/send-code', params, callback)  
      }
       //驗證碼登錄  
    static func doCodeLogin(params:String, callback:(data:BaseResponse<String>)->Unit){  
      return HttpService.getInstance().httpRequestPost<String>('http://qingkouwei.com/api/auth/login-code', params, callback)  
    }
  }

使用注意

在實際使用時,HttpRequest請求時啓動了子線程,callback回調也在子線程,所以要更新UI需要切換到主線程。

let todosListCallback = {data: BaseResponse<Array<TodoGroupBean>> =>  
    LogUtil.d(TAG, "getTodosList response")  
    launch{  
        if(data.errno == 0){  
            try{  
                let todoGroupResponse = data.data.getOrThrow()  
            }catch (e: NoneValueException) {  
                PromptAction.showToast(message: "獲取列表失敗,請稍後重試")  
            }  
        }else{  
            PromptAction.showToast(message: "獲取列表失敗,請稍後重試")  
        }  
  
        isShowLoading = false  
    }  
}  
NetApi.getTodosList(todosListCallback)

在callback中通過launch切換到主線程更新狀態變量更新UI。整體使用流程圖如下:

【倉頡開發HarmonyOS系列】倉頡網絡請求功能封裝-2.png

總結

網絡接口是應用與外部服務的“數字神經”,決定着穩定性效率易用性。在HarmonyOS中,​倉頡通過**net.http.​**​*ohos.net.http提供從Socket/HTTP/WebSocket到HttpRequest的完整能力,支持GET/POST/PUT/DELETE等常用方法、請求頭與超時配置、JSON解析及token鑑權;同時可通過onHeadersReceive訂閲響應頭、在回調中切換主線程更新UI。為降低樣板代碼,文中封裝了HttpService與NetApi,統一header、__timeout__、JSON轉換與錯誤處理,使接口調用更簡潔、可維護性更高。
【倉頡開發HarmonyOS系列】倉頡網絡請求功能封裝-2.png

user avatar pannideshoutao 頭像 jiegeng_23 頭像 wei-boke 頭像 guangmingleiluodetouyingyi_bccdlf 頭像 alixitongruanjianjishu 頭像 lanyiyun666 頭像 u_16099253 頭像 u_17213027 頭像
8 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.