背景
在萬物互聯的智能時代,應用的核心價值往往依賴於與外部世界的實時交互:社交軟件需要通過網絡同步消息,電商應用依賴接口獲取商品與訂單數據,智能助手依靠API調用大模型能力,甚至系統級的設備協同也需通過網絡傳遞指令。可以説,網絡接口是連接應用與外部服務的“數字神經”,其穩定性、效率與易用性直接影響用户體驗與開發效率。從技術角度看,網絡接口是應用與遠程服務器(或本地網絡服務)交換數據的標準化通道,承載着數據請求(如GET/POST)、響應處理(如JSON/XML解析)、狀態管理(如超時/重試)等關鍵功能。無論是簡單的天氣查詢(調用氣象API)、複雜的分佈式任務調度(跨設備數據同步),還是用户身份認證(Token校驗)、文件上傳下載(如圖片/視頻傳輸),本質上都是通過網絡接口完成“請求-響應”的閉環。本文介紹在HarmonyOS 場景中倉頡網絡請求的使用和工具的封裝。
倉頡網絡請求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中,倉頡通過**net.http.***與ohos.net.http提供從Socket/HTTP/WebSocket到HttpRequest的完整能力,支持GET/POST/PUT/DELETE等常用方法、請求頭與超時配置、JSON解析及token鑑權;同時可通過onHeadersReceive訂閲響應頭、在回調中切換主線程更新UI。為降低樣板代碼,文中封裝了HttpService與NetApi,統一header、__timeout__、JSON轉換與錯誤處理,使接口調用更簡潔、可維護性更高。