From efd21a0a639a909d2e50d943498bf7fe9c1d5c20 Mon Sep 17 00:00:00 2001 From: leo9001 Date: Tue, 2 Sep 2025 14:35:06 +0800 Subject: [PATCH] =?UTF-8?q?http=20=E6=A8=A1=E5=9D=97=E5=AF=B9=E6=8E=A5=20a?= =?UTF-8?q?ni=5Frs=20=E6=8E=A5=E5=8F=A3=E8=83=BD=E5=8A=9B=E8=A1=A5?= =?UTF-8?q?=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: leo9001 --- .../ets/ani/http/ets/@ohos.net.http.ets | 112 ++- frameworks/ets/ani/http/src/bridge.rs | 141 +++- frameworks/ets/ani/http/src/callback.rs | 190 +++-- frameworks/ets/ani/http/src/http.rs | 396 ++++++++-- frameworks/ets/ani/http/src/lib.rs | 3 +- .../http/http_client/http_client_constant.cpp | 4 + .../http/http_client/http_client_request.cpp | 148 +++- .../http/http_client/http_client_response.cpp | 15 + .../http_client/http_client_secure_data.cpp | 30 + .../http/http_client/http_client_task.cpp | 674 +++++++++++++++++- .../http_client/http_client_tls_config.cpp | 167 +++++ interfaces/innerkits/http_client/BUILD.gn | 16 + .../cache/base64/include/base64_utils.h | 26 + .../cache/base64/src/base64_utils.cpp | 188 +++++ .../cache_constant/include/casche_constant.h | 47 ++ .../cache/cache_proxy/include/cache_proxy.h | 48 ++ .../cache/cache_proxy/src/cache_proxy.cpp | 166 +++++ .../include/http_cache_request.h | 72 ++ .../include/http_cache_response.h | 105 +++ .../include/http_cache_strategy.h | 69 ++ .../cache_strategy/src/http_cache_request.cpp | 146 ++++ .../src/http_cache_response.cpp | 236 ++++++ .../src/http_cache_strategy.cpp | 297 ++++++++ .../cache/lru_cache/include/disk_handler.h | 41 ++ .../cache/lru_cache/include/lru_cache.h | 69 ++ .../include/lru_cache_disk_handler.h | 56 ++ .../cache/lru_cache/src/disk_handler.cpp | 57 ++ .../cache/lru_cache/src/lru_cache.cpp | 205 ++++++ .../lru_cache/src/lru_cache_disk_handler.cpp | 104 +++ .../innerkits/http_client/include/common.h | 32 + .../include/http_client_constant.h | 4 + .../http_client/include/http_client_request.h | 198 +++++ .../include/http_client_response.h | 20 + .../include/http_client_secure_data.h | 30 + .../http_client/include/http_client_task.h | 76 +- .../include/http_client_tls_config.h | 70 ++ .../innerkits/http_client/libhttp_client.map | 1 + .../innerkits/rust/netstack_rs/BUILD.gn | 7 +- .../netstack_rs/include/cache_proxy_ani.h | 34 + .../rust/netstack_rs/include/wrapper.h | 24 + .../netstack_rs/src/cxx/cache_proxy_ani.cpp | 43 ++ .../rust/netstack_rs/src/cxx/wrapper.cpp | 180 ++++- .../innerkits/rust/netstack_rs/src/request.rs | 223 +++++- .../rust/netstack_rs/src/response.rs | 27 + .../innerkits/rust/netstack_rs/src/task.rs | 45 ++ .../innerkits/rust/netstack_rs/src/wrapper.rs | 263 ++++++- 46 files changed, 4889 insertions(+), 216 deletions(-) create mode 100644 frameworks/native/http/http_client/http_client_secure_data.cpp create mode 100644 frameworks/native/http/http_client/http_client_tls_config.cpp create mode 100644 interfaces/innerkits/http_client/cache/base64/include/base64_utils.h create mode 100644 interfaces/innerkits/http_client/cache/base64/src/base64_utils.cpp create mode 100644 interfaces/innerkits/http_client/cache/cache_constant/include/casche_constant.h create mode 100644 interfaces/innerkits/http_client/cache/cache_proxy/include/cache_proxy.h create mode 100644 interfaces/innerkits/http_client/cache/cache_proxy/src/cache_proxy.cpp create mode 100644 interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_request.h create mode 100644 interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_response.h create mode 100644 interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_strategy.h create mode 100644 interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_request.cpp create mode 100644 interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_response.cpp create mode 100644 interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_strategy.cpp create mode 100644 interfaces/innerkits/http_client/cache/lru_cache/include/disk_handler.h create mode 100644 interfaces/innerkits/http_client/cache/lru_cache/include/lru_cache.h create mode 100644 interfaces/innerkits/http_client/cache/lru_cache/include/lru_cache_disk_handler.h create mode 100644 interfaces/innerkits/http_client/cache/lru_cache/src/disk_handler.cpp create mode 100644 interfaces/innerkits/http_client/cache/lru_cache/src/lru_cache.cpp create mode 100644 interfaces/innerkits/http_client/cache/lru_cache/src/lru_cache_disk_handler.cpp create mode 100644 interfaces/innerkits/http_client/include/common.h create mode 100644 interfaces/innerkits/http_client/include/http_client_secure_data.h create mode 100644 interfaces/innerkits/http_client/include/http_client_tls_config.h create mode 100644 interfaces/innerkits/rust/netstack_rs/include/cache_proxy_ani.h create mode 100644 interfaces/innerkits/rust/netstack_rs/src/cxx/cache_proxy_ani.cpp diff --git a/frameworks/ets/ani/http/ets/@ohos.net.http.ets b/frameworks/ets/ani/http/ets/@ohos.net.http.ets index c3efff79b..a0693a252 100644 --- a/frameworks/ets/ani/http/ets/@ohos.net.http.ets +++ b/frameworks/ets/ani/http/ets/@ohos.net.http.ets @@ -46,6 +46,8 @@ export default namespace http { ONLY_V6 = 'CURL_IPRESOLVE_V6' } + export type RecordData = undefined | null | Object | Record | Array; + export interface HttpRequestOptions { method?: RequestMethod; @@ -85,42 +87,66 @@ export default namespace http { certificatePinning?: CertificatePinning | CertificatePinning[]; - remoteValidation?: RemoteValidation; + remoteValidation?: RemoteValidation; - tlsOptions?: TlsOptions; + tlsOptions?: TlsOptions; - serverAuthentication?: ServerAuthentication; + serverAuthentication?: ServerAuthentication; addressFamily?: AddressFamily; } - export interface ServerAuthentication { - credential: Credential; - authenticationType?: AuthenticationType; - } + export interface ServerAuthentication { + credential: Credential; + + authenticationType?: AuthenticationType; + } - export type TlsOptions = 'system' | TlsConfig; + class ServerAuthenticationInner implements ServerAuthentication { + credential: Credential; - export type RemoteValidation = 'system' | 'skip'; + authenticationType?: AuthenticationType; + } - export type AuthenticationType = 'basic' | 'ntlm' | 'digest'; + export type TlsOptions = 'system' | TlsConfig; - export interface Credential { - username: string; - password: string; - } + export type RemoteValidation = 'system' | 'skip'; - export interface TlsConfig { - tlsVersionMin: TlsVersion; - tlsVersionMax: TlsVersion; - cipherSuites?: CipherSuite[]; - } + export type AuthenticationType = 'basic' | 'ntlm' | 'digest'; + + export interface Credential { + username: string; + + password: string; + } - export type TlsV13SpecificCipherSuite = 'TLS_AES_128_GCM_SHA256' | 'TLS_AES_256_GCM_SHA384' | 'TLS_CHACHA20_POLY1305_SHA256'; + class CredentialInner implements Credential { + username: string; + + password: string; + } + + export interface TlsConfig { + tlsVersionMin: TlsVersion; + + tlsVersionMax: TlsVersion; + + cipherSuites?: CipherSuite[]; + } - export type TlsV12SpecificCipherSuite = 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256' | 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' | 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384' | 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' | 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256' | 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256' | 'TLS_RSA_WITH_AES_128_GCM_SHA256' | 'TLS_RSA_WITH_AES_256_GCM_SHA384'; + export class TlsConfigInner implements TlsConfig { + tlsVersionMin: TlsVersion; - export type TlsV10SpecificCipherSuite = 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA' | 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA' | 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA' | 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA' | 'TLS_RSA_WITH_AES_128_CBC_SHA' | 'TLS_RSA_WITH_AES_256_CBC_SHA' | 'TLS_RSA_WITH_3DES_EDE_CBC_SHA'; + tlsVersionMax: TlsVersion; + + cipherSuites?: CipherSuite[]; + } + + export type TlsV13SpecificCipherSuite = 'TLS_AES_128_GCM_SHA256' | 'TLS_AES_256_GCM_SHA384' | 'TLS_CHACHA20_POLY1305_SHA256'; + + export type TlsV12SpecificCipherSuite = 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256' | 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' | 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384' | 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' | 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256' | 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256' | 'TLS_RSA_WITH_AES_128_GCM_SHA256' | 'TLS_RSA_WITH_AES_256_GCM_SHA384'; + + export type TlsV10SpecificCipherSuite = 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA' | 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA' | 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA' | 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA' | 'TLS_RSA_WITH_AES_128_CBC_SHA' | 'TLS_RSA_WITH_AES_256_CBC_SHA' | 'TLS_RSA_WITH_3DES_EDE_CBC_SHA'; export type CipherSuite = TlsV13CipherSuite; @@ -154,6 +180,18 @@ export default namespace http { filePath?: string; } + class MultiFormDataInner implements MultiFormData { + name: string; + + contentType: string; + + remoteFileName?: string; + + data?: string | RecordData | ArrayBuffer; + + filePath?: string; + } + export enum CertType { PEM = 'PEM', @@ -172,6 +210,16 @@ export default namespace http { keyPassword?: string; } + class ClientCertInner implements ClientCert { + certPath: string; + + certType?: CertType; + + keyPath: string; + + keyPassword?: string; + } + interface CertificatePinning { publicKeyHash: string; hashAlgorithm: 'SHA-256'; @@ -264,7 +312,7 @@ export default namespace http { native destroy(): void; - native onHeaderReceive(callback: AsyncCallback>): void; + native onHeaderReceive(callback: AsyncCallback): void; native onHeadersReceive(callback: Callback>): void; native onDataReceive(callback: Callback): void; native onDataEnd(callback: Callback): void; @@ -280,7 +328,7 @@ export default namespace http { on(type: 'headerReceive' | 'headersReceive' | 'dataReceive' | 'dataEnd' | 'dataReceiveProgress' | 'dataSendProgress', callback: Object): void { if (type == 'headerReceive') { - this.onHeaderReceive(callback as AsyncCallback>) + this.onHeaderReceive(callback as AsyncCallback) } else if (type == 'headersReceive') { this.onHeadersReceive(callback as Callback>) } else if (type == 'dataReceive') { @@ -520,15 +568,29 @@ export default namespace http { export interface DataReceiveProgressInfo { receiveSize: int; + + totalSize: int; + } + + class DataReceiveProgressInfoInner implements DataReceiveProgressInfo { + receiveSize: int; + totalSize: int; } export interface DataSendProgressInfo { sendSize: int; + + totalSize: int; + } + + class DataSendProgressInfoInner implements DataSendProgressInfo { + sendSize: int; + totalSize: int; } - native function createHttpResponseCache(cacheSize?: int): HttpResponseCache; + export native function createHttpResponseCache(cacheSize?: int): HttpResponseCache; export interface HttpResponseCache { flush(callback: AsyncCallback): void; diff --git a/frameworks/ets/ani/http/src/bridge.rs b/frameworks/ets/ani/http/src/bridge.rs index ea65dfe11..81b2d7457 100644 --- a/frameworks/ets/ani/http/src/bridge.rs +++ b/frameworks/ets/ani/http/src/bridge.rs @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ani_rs::business_error::BusinessError; +use ani_rs::{business_error::BusinessError, global::GlobalRef, objects::{AniObject, AniRef}}; use netstack_rs::error::HttpClientError; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -26,7 +26,8 @@ pub struct HttpRequest { pub native_ptr: i64, } -#[ani_rs::ani] +#[ani_rs::ani(path = "L@ohos/net/http/http/AddressFamily")] +#[repr(i32)] pub enum AddressFamily { Default, @@ -35,24 +36,43 @@ pub enum AddressFamily { OnlyV6, } -#[derive(Serialize, Deserialize)] -pub enum Data<'a> { - S(String), - Record(HashMap), - ArrayBuffer(&'a [u8]), +impl AddressFamily { + pub fn to_i32(&self) -> i32 { + match self { + AddressFamily::Default => 0, + AddressFamily::OnlyV4 => 1, + AddressFamily::OnlyV6 => 2, + } + } +} + +#[derive(Serialize, Deserialize, Clone)] +#[repr(C)] +pub enum Data { + S(String) } #[derive(Serialize, Deserialize)] +#[repr(i32)] pub enum UsingProxy { Boolean(bool), // HttpProxy } +impl UsingProxy { + pub fn to_i32(&self) -> i32 { + match self { + UsingProxy::Boolean(false) => 0, + UsingProxy::Boolean(true) => 1, + } + } +} + #[ani_rs::ani] -pub struct HttpRequestOptions<'a> { +pub struct HttpRequestOptions<'local> { pub method: Option, - pub extra_data: Option>, + pub extra_data: Option>, pub expect_data_type: Option, @@ -84,31 +104,34 @@ pub struct HttpRequestOptions<'a> { pub max_limit: Option, - #[serde(borrow)] - pub multi_form_data_list: Option>>, + pub multi_form_data_list: Option>>, // certificate_pinning:Option< CertificatePinning | CertificatePinning[]>, pub remote_validation: Option, - pub tls_options: Option, + pub tls_options: Option>, pub server_authentication: Option, pub address_family: Option, } -#[ani_rs::ani] +#[ani_rs::ani(path = "L@ohos/net/http/http/ServerAuthenticationInner")] +#[repr(C)] pub struct ServerAuthentication { pub credential: Credential, pub authentication_type: Option, } -#[ani_rs::ani] +#[ani_rs::ani(path = "L@ohos/net/http/http/CredentialInner")] +#[repr(C)] pub struct Credential { pub username: String, pub password: String, } +#[ani_rs::ani(path = "L@ohos/net/http/http/TlsConfigInner")] +#[repr(C)] pub struct TlsConfig { pub tls_version_min: TlsVersion, pub tls_version_max: TlsVersion, @@ -116,7 +139,8 @@ pub struct TlsConfig { } #[allow(non_camel_case_types)] -#[ani_rs::ani] +#[ani_rs::ani(path = "L@ohos/net/http/http/TlsVersion")] +#[repr(C)] pub enum TlsVersion { TlsV_1_0 = 4, @@ -127,21 +151,22 @@ pub enum TlsVersion { TlsV_1_3 = 7, } -#[ani_rs::ani] -pub struct MultiFormData<'a> { +#[ani_rs::ani(path = "L@ohos/net/http/http/MultiFormDataInner")] +#[repr(C)] +pub struct MultiFormData<'local> { pub name: String, pub content_type: String, pub remote_file_name: Option, - #[serde(borrow)] - pub data: Option>, + pub data: Option>, pub file_path: Option, } #[ani_rs::ani(path = "L@ohos/net/http/http/CertType")] +#[repr(i32)] pub enum CertType { Pem, @@ -150,7 +175,8 @@ pub enum CertType { P12, } -#[ani_rs::ani] +#[ani_rs::ani(path = "L@ohos/net/http/http/ClientCertInner")] +#[repr(C)] pub struct ClientCert { pub cert_path: String, @@ -201,6 +227,7 @@ impl RequestMethod { } #[ani_rs::ani(path = "L@ohos/net/http/http/ResponseCode")] +#[derive(Clone)] pub enum ResponseCode { Ok = 200, @@ -297,6 +324,8 @@ impl HttpProtocol { } #[ani_rs::ani(path = "L@ohos/net/http/http/HttpDataType")] +#[derive(Clone)] +#[repr(i32)] pub enum HttpDataType { String, @@ -305,7 +334,17 @@ pub enum HttpDataType { ArrayBuffer = 2, } -#[derive(Serialize)] +impl HttpDataType { + pub fn to_i32(&self) -> i32 { + match self { + HttpDataType::String => 0, + HttpDataType::Object => 1, + HttpDataType::ArrayBuffer => 2, + } + } +} + +#[derive(Serialize, Clone)] pub enum ResponseCodeOutput { #[serde(rename = "L@ohos/net/http/http/ResponseCode;")] Code(ResponseCode), @@ -314,7 +353,7 @@ pub enum ResponseCodeOutput { #[ani_rs::ani(path = "L@ohos/net/http/http/HttpResponseInner", output = "only")] pub struct HttpResponse { - pub result: String, + pub result: GlobalRef>, pub result_type: HttpDataType, pub response_code: ResponseCodeOutput, pub header: HashMap, @@ -322,7 +361,27 @@ pub struct HttpResponse { pub performance_timing: PerformanceTiming, } +impl HttpResponse { + pub fn new( + result: GlobalRef>, + result_type: HttpDataType, + code: i32, + header: HashMap, + cookies: String, + performance_timing: PerformanceTiming) -> Self { + Self { + result, + result_type, + response_code: ResponseCodeOutput::I32(code), + header, + cookies, + performance_timing, + } + } +} + #[ani_rs::ani(path = "L@ohos/net/http/http/PerformanceTimingInner")] +#[derive(Clone)] pub struct PerformanceTiming { pub dns_timing: f64, pub tcp_timing: f64, @@ -370,19 +429,21 @@ impl From for PerformanceTiming { } } -#[ani_rs::ani(path = "L@ohos/net/http/http/HttpResponse")] +#[ani_rs::ani(path = "L@ohos/net/http/http/DataReceiveProgressInfoInner")] +#[derive(Clone)] pub struct DataReceiveProgressInfo { pub receive_size: i32, pub total_size: i32, } -#[ani_rs::ani(path = "L@ohos/net/http/http/HttpResponse")] +#[ani_rs::ani(path = "L@ohos/net/http/http/DataSendProgressInfoInner")] +#[derive(Clone)] pub struct DataSendProgressInfo { pub send_size: i32, pub total_size: i32, } -#[ani_rs::ani] +#[ani_rs::ani(path = "L@ohos/net/http/http/HttpResponseCacheInner")] pub struct HttpResponseCache { pub native_ptr: i64, } @@ -392,3 +453,33 @@ pub fn convert_to_business_error(client_error: &HttpClientError) -> BusinessErro let msg = client_error.msg().to_string(); BusinessError::new(error_code, msg) } + +impl From for netstack_rs::request::ClientCert { + fn from(cert: ClientCert) -> Self { + unsafe { std::mem::transmute(cert) } + } +} + +impl From for netstack_rs::request::ServerAuthentication { + fn from(auth: ServerAuthentication) -> Self { + unsafe { std::mem::transmute(auth) } + } +} + +impl From for netstack_rs::request::CertType { + fn from(ct: CertType) -> Self { + unsafe { std::mem::transmute(ct) } + } +} + +impl From for netstack_rs::request::TlsVersion { + fn from(version: TlsVersion) -> Self { + unsafe { std::mem::transmute(version) } + } +} + +impl From for netstack_rs::request::TlsConfig { + fn from(config: TlsConfig) -> Self { + unsafe { std::mem::transmute(config) } + } +} diff --git a/frameworks/ets/ani/http/src/callback.rs b/frameworks/ets/ani/http/src/callback.rs index 20a512bac..c934c2b15 100644 --- a/frameworks/ets/ani/http/src/callback.rs +++ b/frameworks/ets/ani/http/src/callback.rs @@ -13,29 +13,38 @@ use std::collections::HashMap; -use ani_rs::{business_error::BusinessError, objects::GlobalRefAsyncCallback}; +use ani_rs::{business_error::BusinessError, objects::{ + GlobalRefAsyncCallback, + GlobalRefCallback, + AniObject, + JsonValue, + AniRef + }, + typed_array::ArrayBuffer, AniVm, AniEnv, global::GlobalRef}; use netstack_rs::{error::HttpErrorCode, request::RequestCallback}; use crate::bridge::{ convert_to_business_error, DataReceiveProgressInfo, DataSendProgressInfo, HttpDataType, - HttpResponse, PerformanceTiming, ResponseCodeOutput, + HttpResponse, PerformanceTiming, ResponseCodeOutput, Data }; pub struct TaskCallback { pub on_response: Option>, - pub on_header_receive: Option,)>>, - pub on_headers_receive: Option,)>>, - pub on_data_receive: Option,)>>, + pub on_response_in_stream: Option>, + pub on_header_receive: Option>, + pub on_headers_receive: Option,)>>, + pub on_data_receive: Option,)>>, - pub on_data_end: Option>, - pub on_data_receive_progress: Option>, - pub on_data_send_progress: Option>, + pub on_data_end: Option>, + pub on_data_receive_progress: Option>, + pub on_data_send_progress: Option>, } impl TaskCallback { pub fn new() -> Self { Self { on_response: None, + on_response_in_stream: None, on_header_receive: None, on_headers_receive: None, on_data_receive: None, @@ -48,22 +57,55 @@ impl TaskCallback { } impl RequestCallback for TaskCallback { - fn on_success(&mut self, response: netstack_rs::response::Response) { - if let Some(callback) = self.on_response.take() { - let code = response.status() as i32; - let response = HttpResponse { - result_type: HttpDataType::String, - response_code: ResponseCodeOutput::I32(code), - header: response.headers(), - cookies: response.cookies(), - performance_timing: PerformanceTiming::from(response.performance_timing()), - result: response.get_result(), - }; - callback.execute(None, (response,)); + fn on_success(&mut self, response: netstack_rs::response::Response, is_request_in_stream: bool) { + let code = response.status() as i32; + let string_data = response.get_result(); + let data_type = response.get_expect_data_type(); + let header = response.headers(); + let cookies = response.cookies(); + let performance_timing = PerformanceTiming::from(response.performance_timing()); + + if (is_request_in_stream) { + if let Some(callback) = self.on_response_in_stream.take() { + ani_rs::send_event_from_closure(move || { + let _ = callback.execute(None, (code,)); + }, "http_response_instream_success_callback").unwrap(); + } + } else { + if let Some(global_callback) = self.on_response.take() { + ani_rs::send_event_from_closure(move || { + let env = AniVm::get_instance().get_env().unwrap(); + let ret = match data_type { + netstack_rs::response::HttpDataType::StringType => { + let s_ref = env.serialize(&string_data).unwrap().into_global(&env).unwrap(); + let ets_response = HttpResponse::new(s_ref, HttpDataType::String, + code, header, cookies, performance_timing); + global_callback.execute(None, (ets_response,)); + }, + netstack_rs::response::HttpDataType::ObjectType => { + let json_value = JsonValue::parse(&env, &string_data).unwrap(); + let json_global = AniRef::from(json_value).into_global(&env).unwrap(); + let ets_response = HttpResponse::new(json_global, HttpDataType::Object, + code, header, cookies, performance_timing); + global_callback.execute(None, (ets_response,)); + }, + netstack_rs::response::HttpDataType::ArrayBuffer => { + let array_buffer = ArrayBuffer::new_with_vec(string_data.as_bytes().to_vec()); + let buffer_global = env.serialize(&array_buffer).unwrap().into_global(&env).unwrap(); + let ets_response = HttpResponse::new(buffer_global, HttpDataType::ArrayBuffer, + code, header, cookies, performance_timing); + global_callback.execute(None, (ets_response,)); + }, + _ => { + info!("send_event_from_closure httpDataType is None"); + } + }; + }, "http_response_success_callback").unwrap(); + } } if let Some(callback) = self.on_data_end.take() { - callback.execute(None, ()); + callback.execute(()); } } @@ -71,50 +113,98 @@ impl RequestCallback for TaskCallback { &mut self, response: netstack_rs::response::Response, error: netstack_rs::error::HttpClientError, + is_request_in_stream: bool ) { let code = response.status() as i32; + let ets_response = HttpResponse { + result: GlobalRef(AniRef::<'static>::null()), + result_type: HttpDataType::String, + response_code: ResponseCodeOutput::I32(code), + header: HashMap::new(), + cookies: String::new(), + performance_timing: PerformanceTiming::new(), + }; + let business_error = convert_to_business_error(&error); error!("OnFiled. response_code = {}, error = {:?}", code, error); - if let Some(callback) = self.on_response.take() { - let business_error = convert_to_business_error(&error); - let response = HttpResponse { - result_type: HttpDataType::String, - response_code: ResponseCodeOutput::I32(code), - header: HashMap::new(), - cookies: String::new(), - performance_timing: PerformanceTiming::new(), - result: String::new(), - }; - callback.execute(Some(business_error), (response,)); + if (is_request_in_stream) { + if let Some(callback) = self.on_response_in_stream.take() { + callback.execute(Some(business_error), (code,)); + } + } else { + if let Some(callback) = self.on_response.take() { + callback.execute(Some(business_error), (ets_response,)); + } } if let Some(callback) = self.on_data_end.take() { - callback.execute(None, ()); + callback.execute(()); } } - fn on_cancel(&mut self, response: netstack_rs::response::Response) { + fn on_cancel(&mut self, response: netstack_rs::response::Response, is_request_in_stream: bool) { let code = response.status() as i32; - if let Some(callback) = self.on_response.take() { - let business_error = BusinessError::new( - HttpErrorCode::HttpWriteError as i32, - "request canceled".to_string(), - ); - let response = HttpResponse { - result_type: HttpDataType::String, - response_code: ResponseCodeOutput::I32(code), - header: HashMap::new(), - cookies: String::new(), - performance_timing: PerformanceTiming::new(), - result: String::new(), - }; - callback.execute(Some(business_error), (response,)); + let ets_response = HttpResponse { + result: GlobalRef(AniRef::<'static>::null()), + result_type: HttpDataType::String, + response_code: ResponseCodeOutput::I32(code), + header: HashMap::new(), + cookies: String::new(), + performance_timing: PerformanceTiming::new(), + }; + let business_error = BusinessError::new( + HttpErrorCode::HttpWriteError as i32, + "request canceled".to_string(), + ); + if (is_request_in_stream) { + if let Some(callback) = self.on_response_in_stream.take() { + callback.execute(Some(business_error), (code,)); + } + } else { + if let Some(callback) = self.on_response.take() { + callback.execute(Some(business_error), (ets_response,)); + } } } fn on_data_receive(&mut self, data: &[u8], mut task: netstack_rs::task::RequestTask) { - let headers = task.headers(); + if let Some(callback) = self.on_data_receive.as_ref() { + info!("on_data_receive callback set"); + let data_bytes: Vec = data.to_vec(); + callback.execute((data_bytes,)); + } + } + + fn on_progress(&mut self, dl_total: u64, dl_now: u64, ul_total: u64, ul_now: u64) { + if let Some(callback) = self.on_data_send_progress.as_ref() { + if (ul_total != 0 && ul_total >= ul_now) { + let send_info = DataSendProgressInfo { + send_size: ul_now as i32, + total_size: ul_total as i32, + }; + callback.execute((send_info,)); + } + } + if let Some(callback) = self.on_data_receive_progress.as_ref() { + if (dl_total != 0 && dl_total >= dl_now) { + let receive_info = DataReceiveProgressInfo { + receive_size: dl_now as i32, + total_size: dl_total as i32, + }; + callback.execute((receive_info,)); + } + } + } + + fn on_header_receive(&mut self, header: String) { if let Some(callback) = self.on_header_receive.as_ref() { info!("on_header_receive callback set"); - callback.execute(None, (headers.clone(),)); + callback.execute(None, (header.clone(),)); + } + } + + fn on_headers_receive(&mut self, headers: HashMap) { + if let Some(callback) = self.on_headers_receive.as_ref() { + info!("on_headers_receive callback set"); + callback.execute((headers.clone(),)); } } } diff --git a/frameworks/ets/ani/http/src/http.rs b/frameworks/ets/ani/http/src/http.rs index 17b050f28..e481c60f6 100644 --- a/frameworks/ets/ani/http/src/http.rs +++ b/frameworks/ets/ani/http/src/http.rs @@ -18,14 +18,25 @@ use std::{ use ani_rs::{ business_error::BusinessError, - objects::{AniAsyncCallback, AniRef}, - AniEnv, + typed_array::ArrayBuffer, + objects::{AniAsyncCallback, AniFnObject, AniRef, AniObject, JsonValue}, + AniEnv, signature +}; +use netstack_rs::{ + error::HttpErrorCode, + request::{ + Request, + EscapedData, + run_cache, + flush_cache, + delete_cache + }, + task::RequestTask }; -use netstack_rs::{error::HttpErrorCode, request::Request, task::RequestTask}; use crate::{ bridge::{ - convert_to_business_error, Cleaner, HttpRequest, HttpRequestOptions, HttpResponseCache, + convert_to_business_error, Cleaner, HttpRequest, HttpRequestOptions, HttpResponseCache, TlsConfig }, callback::TaskCallback, }; @@ -62,7 +73,32 @@ pub fn create_http<'local>(env: &AniEnv<'local>) -> Result, Busin Ok(obj.into()) } -pub fn http_set_options(request: &mut Request, options: HttpRequestOptions) { +pub fn parse_escaped_data_from_original_data<'local>(env: &AniEnv, obj_data: AniObject<'local>) -> EscapedData { + let string_class = env.find_class(signature::STRING).unwrap(); + let array_buffer_class = env.find_class(signature::ARRAY_BUFFER).unwrap(); + let mut res = EscapedData { + data_type: 0, + data: String::new(), + }; + if env.instance_of(&obj_data, &string_class).unwrap() { + res.data_type = 0; + res.data = env.deserialize::(obj_data).unwrap(); + } else if env.instance_of(&obj_data, &array_buffer_class).unwrap() { + res.data_type = 2; + let buffer = env.deserialize::(obj_data).unwrap(); + res.data = String::from_utf8_lossy(buffer.as_ref()).to_string(); + } else { + let json_value = env.deserialize::(obj_data).unwrap(); + res.data_type = 1; + res.data = json_value.stringify(env).unwrap(); + }; + res +} + +pub fn http_set_options( + env: &AniEnv, + request: &mut Request, + options: HttpRequestOptions) { if let Some(method) = options.method { request.method(method.to_str()); } @@ -83,6 +119,89 @@ pub fn http_set_options(request: &mut Request, options: HttpReques if let Some(protocol) = options.using_protocol { request.protocol(protocol.to_i32()); } + if let Some(proxy_type) = options.using_proxy { + request.using_proxy(proxy_type.to_i32()); + } + if let Some(&max_limit) = options.max_limit.as_ref() { + request.max_limit(max_limit as u32); + } + if let Some(ca_path) = options.ca_path { + request.ca_path(&ca_path); + } + if let Some(resume_from) = options.resume_from { + request.resume_from(resume_from); + } + if let Some(resume_to) = options.resume_to { + request.resume_to(resume_to); + } + if let Some(address_family) = options.address_family { + request.address_family(address_family.to_i32()); + } + if let Some(extra_data) = options.extra_data { + request.extra_data(parse_escaped_data_from_original_data(env, extra_data)); + } + if let Some(expect_data_type) = options.expect_data_type { + request.expect_data_type(expect_data_type.to_i32()); + } + if let Some(using_cache) = options.using_cache { + request.using_cache(using_cache); + } + if let Some(client_cert) = options.client_cert { + request.client_cert(client_cert.into()); + } + if let Some(dns_over_https) = options.dns_over_https { + request.dns_over_https(&dns_over_https); + } + if let Some(dns_servers) = options.dns_servers { + request.dns_servers(dns_servers); + } + if let Some(multi_form_data_list) = options.multi_form_data_list { + for item in multi_form_data_list { + let mut escaped_data: Option = None; + if let Some(original_data) = item.data { + escaped_data = Some(parse_escaped_data_from_original_data(env, original_data).data); + } + let mut multi_form_data = netstack_rs::request::MultiFormData { + name: item.name, + content_type: item.content_type, + remote_file_name: item.remote_file_name.unwrap_or(String::new()), + data: escaped_data.unwrap_or(String::new()), + file_path: item.file_path.unwrap_or(String::new()), + }; + request.add_multi_form_data(multi_form_data); + } + } + if let Some(remote_validation) = options.remote_validation { + request.remote_validation(&remote_validation); + } + if let Some(tls_options) = options.tls_options { + let obj_data = tls_options; + let string_class = env.find_class(signature::STRING).unwrap(); + if env.instance_of(&obj_data, &string_class).unwrap() { + // noting todo + } else { + let opt = env.deserialize::(obj_data).unwrap(); + request.tls_options(opt.into()); + } + } + if let Some(server_authentication) = options.server_authentication { + request.server_authentication(server_authentication.into()); + } +} + +#[ani_rs::native] +pub fn create_http_response_cache<'local>( + env: &AniEnv<'local>, cacheSize : Option) -> Result, BusinessError> { + static HTTP_RESPONSE_CACHE_CLASS: &CStr = + unsafe { CStr::from_bytes_with_nul_unchecked(b"L@ohos/net/http/http/HttpResponseCacheInner;\0") }; + static CTOR_SIGNATURE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"J:V\0") }; + + let class = env.find_class(HTTP_RESPONSE_CACHE_CLASS).unwrap(); + let obj = env + .new_object_with_signature(&class, CTOR_SIGNATURE, (0,)) + .unwrap(); + run_cache(cacheSize); + Ok(obj.into()) } #[ani_rs::native] @@ -110,7 +229,7 @@ pub(crate) fn request( request.url(url.as_str()); if let Some(opts) = options { - http_set_options(&mut request, opts); + http_set_options(env, &mut request, opts); } let mut cb = task.callback.take().unwrap_or_else(TaskCallback::new); @@ -133,12 +252,49 @@ pub(crate) fn request( #[ani_rs::native] pub(crate) fn request_in_stream( + env: &AniEnv, this: HttpRequest, url: String, async_callback: AniAsyncCallback, options: Option, -) -> Result { - todo!() +) -> Result<(), BusinessError> { + let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; + if task.is_destroy.load(Ordering::Relaxed) { + error!("Request is already destroyed"); + let business_error = BusinessError::new( + HttpErrorCode::HttpUnknownOtherError as i32, + "Request is already destroyed".to_string(), + ); + let undefined: Option = None; //None will serialize arkts's undefined + async_callback + .execute_local(env, Some(business_error), (undefined,)) + .unwrap(); + return Ok(()); + } + let mut request = Request::::new(); + + request.url(url.as_str()); + if let Some(opts) = options { + http_set_options(env, &mut request, opts); + } + + let mut cb = task.callback.take().unwrap_or_else(TaskCallback::new); + cb.on_response_in_stream = Some(async_callback.clone().into_global_callback(env).unwrap()); + request.callback(cb); + let mut request_task = request.build(); + request_task.set_is_request_in_stream(true); + if !request_task.start() { + let error = request_task.get_error(); + error!("request_task.start error = {:?}", error); + let business_error = convert_to_business_error(&error); + let undefined: Option = None; //None will serialize arkts's undefined + async_callback + .execute_local(env, Some(business_error), (undefined,)) + .unwrap(); + return Ok(()); + } + task.request_task = Some(request_task); + Ok(()) } #[ani_rs::native] @@ -161,7 +317,7 @@ pub(crate) fn clean_http_request(this: Cleaner) -> Result<(), BusinessError> { #[ani_rs::native] pub(crate) fn clean_http_cache(this: Cleaner) -> Result<(), BusinessError> { - todo!() + Ok(()) } #[ani_rs::native] @@ -191,26 +347,43 @@ pub(crate) fn off_header_receive( this: HttpRequest, async_callback: AniAsyncCallback, ) -> Result<(), BusinessError> { - todo!() + let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; + match task.request_task { + Some(ref mut request_task) => { + request_task.off_header_receive(); + } + None => { + // noting todo + } + } + match task.callback { + Some(ref mut callback) => { + callback.on_header_receive = None; + } + None => { + // noting todo + } + } + Ok(()) } #[ani_rs::native] pub(crate) fn on_headers_receive( env: &AniEnv, this: HttpRequest, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; match task.callback { - Some(ref mut callback) => { + Some(ref mut task_callback) => { // Convert the async callback to a global reference - callback.on_headers_receive = Some(async_callback.into_global_callback(env).unwrap()); + task_callback.on_headers_receive = Some(callback.into_global_callback(env).unwrap()); } None => { - let mut task_callback = TaskCallback::new(); - task_callback.on_headers_receive = - Some(async_callback.into_global_callback(env).unwrap()); - task.callback = Some(task_callback); + let mut new_task_callback = TaskCallback::new(); + new_task_callback.on_headers_receive = + Some(callback.into_global_callback(env).unwrap()); + task.callback = Some(new_task_callback); } } Ok(()) @@ -219,27 +392,44 @@ pub(crate) fn on_headers_receive( #[ani_rs::native] pub(crate) fn off_headers_receive( this: HttpRequest, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { - todo!() + let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; + match task.request_task { + Some(ref mut request_task) => { + request_task.off_headers_receive(); + } + None => { + // noting todo + } + } + match task.callback { + Some(ref mut task_callback) => { + task_callback.on_headers_receive = None; + } + None => { + // noting todo + } + } + Ok(()) } #[ani_rs::native] pub(crate) fn on_data_receive( env: &AniEnv, this: HttpRequest, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; match task.callback { - Some(ref mut callback) => { + Some(ref mut task_callback) => { // Convert the async callback to a global reference - callback.on_data_receive = Some(async_callback.into_global_callback(env).unwrap()); + task_callback.on_data_receive = Some(callback.into_global_callback(env).unwrap()); } None => { - let mut task_callback = TaskCallback::new(); - task_callback.on_data_receive = Some(async_callback.into_global_callback(env).unwrap()); - task.callback = Some(task_callback); + let mut new_task_callback = TaskCallback::new(); + new_task_callback.on_data_receive = Some(callback.into_global_callback(env).unwrap()); + task.callback = Some(new_task_callback); } } Ok(()) @@ -248,27 +438,44 @@ pub(crate) fn on_data_receive( #[ani_rs::native] pub(crate) fn off_data_receive( this: HttpRequest, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { - todo!() + let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; + match task.request_task { + Some(ref mut request_task) => { + request_task.off_data_receive(); + } + None => { + // noting todo + } + } + match task.callback { + Some(ref mut task_callback) => { + task_callback.on_data_receive = None; + } + None => { + // noting todo + } + } + Ok(()) } #[ani_rs::native] pub(crate) fn on_data_end( env: &AniEnv, this: HttpRequest, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; match task.callback { - Some(ref mut callback) => { + Some(ref mut task_callback) => { // Convert the async callback to a global reference - callback.on_data_end = Some(async_callback.into_global_callback(env).unwrap()); + task_callback.on_data_end = Some(callback.into_global_callback(env).unwrap()); } None => { - let mut task_callback = TaskCallback::new(); - task_callback.on_data_end = Some(async_callback.into_global_callback(env).unwrap()); - task.callback = Some(task_callback); + let mut new_task_callback = TaskCallback::new(); + new_task_callback.on_data_end = Some(callback.into_global_callback(env).unwrap()); + task.callback = Some(new_task_callback); } } Ok(()) @@ -278,29 +485,38 @@ pub(crate) fn on_data_end( pub(crate) fn off_data_end( env: &AniEnv, this: HttpRequest, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { - todo!() + let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; + match task.callback { + Some(ref mut task_callback) => { + task_callback.on_data_end = None; + } + None => { + // noting todo + } + } + Ok(()) } #[ani_rs::native] pub(crate) fn on_data_receive_progress( env: &AniEnv, this: HttpRequest, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; match task.callback { - Some(ref mut callback) => { + Some(ref mut task_callback) => { // Convert the async callback to a global reference - callback.on_data_receive_progress = - Some(async_callback.into_global_callback(env).unwrap()); + task_callback.on_data_receive_progress = + Some(callback.into_global_callback(env).unwrap()); } None => { - let mut task_callback = TaskCallback::new(); - task_callback.on_data_receive_progress = - Some(async_callback.into_global_callback(env).unwrap()); - task.callback = Some(task_callback); + let mut new_task_callback = TaskCallback::new(); + new_task_callback.on_data_receive_progress = + Some(callback.into_global_callback(env).unwrap()); + task.callback = Some(new_task_callback); } } Ok(()) @@ -309,29 +525,46 @@ pub(crate) fn on_data_receive_progress( #[ani_rs::native] pub(crate) fn off_data_receive_progress( this: HttpRequest, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { - todo!() + let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; + match task.request_task { + Some(ref mut request_task) => { + request_task.off_progress(); + } + None => { + // noting todo + } + } + match task.callback { + Some(ref mut task_callback) => { + task_callback.on_data_receive_progress = None; + } + None => { + // noting todo + } + } + Ok(()) } #[ani_rs::native] pub(crate) fn on_data_send_progress( env: &AniEnv, this: HttpRequest, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; match task.callback { - Some(ref mut callback) => { + Some(ref mut task_callback) => { // Convert the async callback to a global reference - callback.on_data_send_progress = - Some(async_callback.into_global_callback(env).unwrap()); + task_callback.on_data_send_progress = + Some(callback.into_global_callback(env).unwrap()); } None => { - let mut task_callback = TaskCallback::new(); - task_callback.on_data_send_progress = - Some(async_callback.into_global_callback(env).unwrap()); - task.callback = Some(task_callback); + let mut new_task_callback = TaskCallback::new(); + new_task_callback.on_data_send_progress = + Some(callback.into_global_callback(env).unwrap()); + task.callback = Some(new_task_callback); } } Ok(()) @@ -340,26 +573,71 @@ pub(crate) fn on_data_send_progress( #[ani_rs::native] pub(crate) fn off_data_send_progress( this: HttpRequest, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { - todo!() + let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; + match task.request_task { + Some(ref mut request_task) => { + request_task.off_progress(); + } + None => { + // noting todo + } + } + match task.callback { + Some(ref mut task_callback) => { + task_callback.on_data_send_progress = None; + } + None => { + // noting todo + } + } + Ok(()) } #[ani_rs::native] pub(crate) fn once( + env: &AniEnv, this: HttpRequest, ty: String, - async_callback: AniAsyncCallback, + callback: AniFnObject, ) -> Result<(), BusinessError> { - todo!() + let task = unsafe { &mut (*(this.native_ptr as *mut Task)) }; + match task.callback { + Some(ref mut task_callback) => { + // Convert the async callback to a global reference + task_callback.on_headers_receive = Some(callback.into_global_callback(env).unwrap()); + } + None => { + let mut new_task_callback = TaskCallback::new(); + new_task_callback.on_headers_receive = + Some(callback.into_global_callback(env).unwrap()); + task.callback = Some(new_task_callback); + } + } + match task.request_task { + Some(ref mut request_task) => { + if (ty == "headersReceive") { + request_task.set_is_headers_once(true); + } else if (ty == "headerReceive") { + request_task.set_is_header_once(true); + } + } + None => { + // noting todo + } + } + Ok(()) } #[ani_rs::native] pub(crate) fn flush(this: HttpResponseCache) -> Result<(), BusinessError> { - todo!() + flush_cache(); + Ok(()) } #[ani_rs::native] pub(crate) fn delete(this: HttpResponseCache) -> Result<(), BusinessError> { - todo!() + delete_cache(); + Ok(()) } diff --git a/frameworks/ets/ani/http/src/lib.rs b/frameworks/ets/ani/http/src/lib.rs index d8fdbd3bb..e0be6b34a 100644 --- a/frameworks/ets/ani/http/src/lib.rs +++ b/frameworks/ets/ani/http/src/lib.rs @@ -27,7 +27,8 @@ mod http; ani_rs::ani_constructor! { namespace "L@ohos/net/http/http" [ - "createHttp" : http::create_http + "createHttp" : http::create_http, + "createHttpResponseCache" : http::create_http_response_cache ] class "L@ohos/net/http/http/HttpRequestInner" [ diff --git a/frameworks/native/http/http_client/http_client_constant.cpp b/frameworks/native/http/http_client/http_client_constant.cpp index bba35dc84..843a197f9 100644 --- a/frameworks/native/http/http_client/http_client_constant.cpp +++ b/frameworks/native/http/http_client/http_client_constant.cpp @@ -31,6 +31,8 @@ const char *const HttpConstant::HTTP_METHOD_CONNECT = "CONNECT"; const uint32_t HttpConstant::DEFAULT_READ_TIMEOUT = 60000; const uint32_t HttpConstant::DEFAULT_CONNECT_TIMEOUT = 60000; +const uint32_t HttpConstant::DEFAULT_MAX_LIMIT = 5 * 1024 * 1024; +const uint32_t HttpConstant::MAX_LIMIT = 100 * 1024 * 1024; const size_t HttpConstant::MAX_JSON_PARSE_SIZE = 65536; const size_t HttpConstant::MAX_DATA_LIMIT = 100 * 1024 * 1024; @@ -65,6 +67,7 @@ const char *const HttpConstant::HTTP_URL_PARAM_START = "?"; const char *const HttpConstant::HTTP_URL_PARAM_SEPARATOR = "&"; const char *const HttpConstant::HTTP_URL_NAME_VALUE_SEPARATOR = "="; const char *const HttpConstant::HTTP_HEADER_SEPARATOR = ":"; +const char *const HttpConstant::HTTP_HEADER_BLANK_SEPARATOR = ";"; const char *const HttpConstant::HTTP_LINE_SEPARATOR = "\r\n"; const char *const HttpConstant::HTTP_RESPONSE_HEADER_SEPARATOR = "\r\n\r\n"; @@ -81,6 +84,7 @@ const char *const HttpConstant::HTTP_CONTENT_TYPE_URL_ENCODE = "application/x-ww const char *const HttpConstant::HTTP_CONTENT_TYPE_JSON = "application/json"; const char *const HttpConstant::HTTP_CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; const char *const HttpConstant::HTTP_CONTENT_TYPE_IMAGE = "image"; +const char *const HttpConstant::HTTP_CONTENT_TYPE_MULTIPART = "multipart/form-data"; const char *const HttpConstant::HTTP_PREPARE_CA_PATH = "/etc/security/certificates"; const char *const HttpConstant::HTTP_CONTENT_ENCODING_GZIP = "gzip"; diff --git a/frameworks/native/http/http_client/http_client_request.cpp b/frameworks/native/http/http_client/http_client_request.cpp index 158b0118b..2a34dfd58 100644 --- a/frameworks/native/http/http_client/http_client_request.cpp +++ b/frameworks/native/http/http_client/http_client_request.cpp @@ -22,6 +22,7 @@ #include "http_client_constant.h" #include "netstack_common_utils.h" #include "netstack_log.h" +#include "curl/curl.h" namespace OHOS { namespace NetStack { @@ -39,8 +40,12 @@ HttpClientRequest::HttpClientRequest() proxyType_(HttpProxyType::NOT_USE), priority_(HTTP_DEFAULT_PRIORITY), resumeFrom_(HTTP_DEFAULT_RANGE), - resumeTo_(HTTP_DEFAULT_RANGE) + resumeTo_(HTTP_DEFAULT_RANGE), + maxLimit_(HttpConstant::DEFAULT_MAX_LIMIT), + usingCache_(false), + dataType_(HttpDataType::STRING) { + extraData_.dataType = HttpDataType::NO_DATA_TYPE; } void HttpClientRequest::SetURL(const std::string &url) @@ -102,6 +107,11 @@ void HttpClientRequest::SetHttpProxyType(HttpProxyType type) void HttpClientRequest::SetMaxLimit(uint32_t maxLimit) { + if (maxLimit > HttpConstant::MAX_LIMIT) { + NETSTACK_LOGD("maxLimit setting exceeds the maximum limit, use max limit"); + maxLimit_ = HttpConstant::MAX_LIMIT; + return; + } maxLimit_ = maxLimit; } @@ -114,6 +124,12 @@ void HttpClientRequest::SetCaPath(const std::string &path) caPath_ = path; } +void HttpClientRequest::SetCertsPath(std::vector &&certPathList, const std::string &certFile) +{ + certsPath_.certPathList = std::move(certPathList); + certsPath_.certFile = certFile; +} + void HttpClientRequest::SetPriority(unsigned int priority) { if (priority < HTTP_MIN_PRIORITY || priority > HTTP_MAX_PRIORITY) { @@ -168,6 +184,11 @@ const std::string &HttpClientRequest::GetCaPath() return caPath_; } +const CertsPath &HttpClientRequest::GetCertsPath() +{ + return certsPath_; +} + uint32_t HttpClientRequest::GetPriority() const { return priority_; @@ -241,6 +262,131 @@ const std::string &HttpClientRequest::GetAddressFamily() const { return addressFamily_; } + +void HttpClientRequest::SetUsingCache(bool usingCache) +{ + usingCache_ = usingCache; +} + +void HttpClientRequest::SetDNSOverHttps(const std::string &dnsOverHttps) +{ + dnsOverHttps_ = dnsOverHttps; +} + +void HttpClientRequest::SetRemoteValidation(const std::string &remoteValidation) +{ + remoteValidation_ = remoteValidation; + + if (remoteValidation == "skip") { + NETSTACK_LOGI("set remoteValidation skip"); + SetCanSkipCertVerifyFlag(true); + } else if (remoteValidation != "system") { + remoteValidation_ = ""; + NETSTACK_LOGE("remoteValidation config error"); + } +} + +void HttpClientRequest::SetCanSkipCertVerifyFlag(bool canCertVerify) +{ + canSkipCertVerify_ = canCertVerify; +} + +void HttpClientRequest::SetTLSOptions(const TlsOption &tlsOptions) +{ + tlsOptions_ = tlsOptions; +} + +void HttpClientRequest::SetExtraData(const EscapedData& extraData) +{ + extraData_ = extraData; +} + +void HttpClientRequest::SetExpectDataType(HttpDataType dataType) +{ + dataType_ = dataType; +} + +void HttpClientRequest::SetDNSServers(const std::vector& dnsServers) +{ + dnsServers_ = dnsServers; +} + +void HttpClientRequest::AddMultiFormData(const HttpMultiFormData& data) +{ + multiFormDataList_.emplace_back(data); +} + +void HttpClientRequest::SetServerAuthentication(const HttpServerAuthentication& server_auth) +{ + serverAuth_ = server_auth; +} + +bool HttpClientRequest::GetUsingCache() const +{ + return usingCache_; +} + +const std::string& HttpClientRequest::GetDNSOverHttps() const +{ + return dnsOverHttps_; +} + +const std::string& HttpClientRequest::GetRemoteValidation() const +{ + return remoteValidation_; +} + +bool HttpClientRequest::GetCanSkipCertVerifyFlag() const +{ + return canSkipCertVerify_; +} + +const TlsOption& HttpClientRequest::GetTLSOptions() const +{ + return tlsOptions_; +} + +const EscapedData& HttpClientRequest::GetExtraData() const +{ + return extraData_; +} + +HttpDataType HttpClientRequest::GetExpectDataType() const +{ + return dataType_; +} + +const std::vector& HttpClientRequest::GetDNSServers() const +{ + return dnsServers_; +} + +const std::vector& HttpClientRequest::GetMultiFormDataList() const +{ + return multiFormDataList_; +} + +const HttpServerAuthentication& HttpClientRequest::GetServerAuthentication() const +{ + return serverAuth_; +} + +uint32_t HttpClientRequest::GetHttpVersion() +{ + if (protocol_ == HttpProtocol::HTTP3) { + NETSTACK_LOGD("CURL_HTTP_VERSION_3"); + return CURL_HTTP_VERSION_3; + } + if (protocol_ == HttpProtocol::HTTP2) { + NETSTACK_LOGD("CURL_HTTP_VERSION_2_0"); + return CURL_HTTP_VERSION_2_0; + } + if (protocol_ == HttpProtocol::HTTP1_1) { + NETSTACK_LOGD("CURL_HTTP_VERSION_1_1"); + return CURL_HTTP_VERSION_1_1; + } + return CURL_HTTP_VERSION_NONE; +} } // namespace HttpClient } // namespace NetStack } // namespace OHOS diff --git a/frameworks/native/http/http_client/http_client_response.cpp b/frameworks/native/http/http_client/http_client_response.cpp index 113185130..1983c0837 100644 --- a/frameworks/native/http/http_client/http_client_response.cpp +++ b/frameworks/native/http/http_client/http_client_response.cpp @@ -119,6 +119,11 @@ void HttpClientResponse::SetRawHeader(const std::string &header) rawHeader_ = header; } +const std::string &HttpClientResponse::GetRawHeader() const +{ + return rawHeader_; +} + void HttpClientResponse::SetCookies(const std::string &cookies) { cookies_ = cookies; @@ -143,6 +148,16 @@ PerformanceInfo HttpClientResponse::GetPerformanceTiming() const { return performanceInfo_; } + +void HttpClientResponse::SetExpectDataType(const HttpDataType &type) +{ + dataType_ = type; +} + +HttpDataType HttpClientResponse::GetExpectDataType() const +{ + return dataType_; +} } // namespace HttpClient } // namespace NetStack } // namespace OHOS \ No newline at end of file diff --git a/frameworks/native/http/http_client/http_client_secure_data.cpp b/frameworks/native/http/http_client/http_client_secure_data.cpp new file mode 100644 index 000000000..5e6724453 --- /dev/null +++ b/frameworks/native/http/http_client/http_client_secure_data.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "http_client_secure_data.h" + +#include "securec.h" + +namespace OHOS { +namespace NetStack { +namespace HttpClient { +SecureData::~SecureData() +{ + // Clear Data, to keep the memory safe + (void)memset_s(data(), size(), 0, size()); +} +} // namespace HttpClient +} // namespace NetStack +} // namespace OHOS \ No newline at end of file diff --git a/frameworks/native/http/http_client/http_client_task.cpp b/frameworks/native/http/http_client/http_client_task.cpp index faf1a8683..bf446ac46 100644 --- a/frameworks/native/http/http_client/http_client_task.cpp +++ b/frameworks/native/http/http_client/http_client_task.cpp @@ -21,6 +21,7 @@ #include #endif #include +#include #include #include "http_client.h" #include "http_client_constant.h" @@ -35,6 +36,8 @@ #include "netsys_client.h" #endif #include "netstack_hisysevent.h" +#include "cache_proxy.h" +#include "cJSON.h" #define NETSTACK_CURL_EASY_SET_OPTION(handle, opt, data) \ do { \ @@ -54,6 +57,7 @@ namespace HttpClient { static const size_t MAX_LIMIT = HttpConstant::MAX_DATA_LIMIT; static constexpr const char *HTTP_AF_ONLYV4 = "ONLY_V4"; static constexpr const char *HTTP_AF_ONLYV6 = "ONLY_V6"; +static constexpr const char *TLS12_SECURITY_CIPHER_SUITE = R"(DEFAULT:!eNULL:!EXPORT)"; std::atomic HttpClientTask::nextTaskId_(0); @@ -75,11 +79,15 @@ HttpClientTask::HttpClientTask(const HttpClientRequest &request) } HttpClientTask::HttpClientTask(const HttpClientRequest &request, TaskType type, const std::string &filePath) - : request_(request), + : isHeaderOnce_(false), + isHeadersOnce_(false), + isRequestInStream_(false), + request_(request), type_(type), status_(IDLE), taskId_(nextTaskId_++), curlHeaderList_(nullptr), + curMultiPart_(nullptr), canceled_(false), filePath_(filePath), file_(nullptr), @@ -104,6 +112,10 @@ HttpClientTask::~HttpClientTask() curl_slist_free_all(curlHeaderList_); curlHeaderList_ = nullptr; } + if (curMultiPart_ != nullptr) { + curl_mime_free(curMultiPart_); + curMultiPart_ = nullptr; + } if (curlHandle_) { curl_easy_cleanup(curlHandle_); @@ -169,11 +181,22 @@ CURLcode HttpClientTask::SslCtxFunction(CURL *curl, void *sslCtx) NETSTACK_LOGE("sslCtx is null"); return CURLE_SSL_CERTPROBLEM; } + auto hostname = CommonUtils::GetHostnameFromURL(request_.GetURL()); std::vector certs; - TrustUser0AndUserCa(certs); - certs.emplace_back(HttpConstant::HTTP_PREPARE_CA_PATH); + // add app cert path + auto ret = NetManagerStandard::NetworkSecurityConfig::GetInstance().GetTrustAnchorsForHostName(hostname, certs); + if (ret != 0) { + NETSTACK_LOGE("GetTrustAnchorsForHostName error. ret [%{public}d]", ret); + } + if (!request_.GetCanSkipCertVerifyFlag()) { + TrustUser0AndUserCa(certs); + // add system cert path + certs.emplace_back(HttpConstant::HTTP_PREPARE_CA_PATH); + request_.SetCertsPath(std::move(certs), request_.GetCaPath()); + } - for (const auto &path : certs) { + auto certsPath = request_.GetCertsPath(); + for (const auto &path : certsPath.certPathList) { if (path.empty() || access(path.c_str(), F_OK) != 0) { NETSTACK_LOGD("certificate directory path is not exist"); continue; @@ -184,9 +207,9 @@ CURLcode HttpClientTask::SslCtxFunction(CURL *curl, void *sslCtx) } } - if (access(request_.GetCaPath().c_str(), F_OK) != 0) { + if (access(certsPath.certFile.c_str(), F_OK) != 0) { NETSTACK_LOGD("certificate directory path is not exist"); - } else if (!SSL_CTX_load_verify_locations(static_cast(sslCtx), request_.GetCaPath().c_str(), nullptr)) { + } else if (!SSL_CTX_load_verify_locations(static_cast(sslCtx), certsPath.certFile.c_str(), nullptr)) { NETSTACK_LOGE("loading certificates from context cert error."); } #endif // HTTP_MULTIPATH_CERT_ENABLE @@ -208,8 +231,13 @@ bool HttpClientTask::SetSSLCertOption(CURL *handle) NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_CTX_FUNCTION, sslCtxFunc); NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_CTX_DATA, this); NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_CAINFO, nullptr); - NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_VERIFYPEER, 1L); - NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_VERIFYHOST, 2L); + if (request_.GetCanSkipCertVerifyFlag()) { + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_VERIFYPEER, 0L); + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_VERIFYHOST, 0L); + } else { + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_VERIFYPEER, 1L); + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_VERIFYHOST, 2L); + } #else NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_CAINFO, nullptr); NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_VERIFYPEER, 0L); @@ -255,12 +283,49 @@ bool HttpClientTask::SetRequestOption(CURL *handle) // Some servers don't like requests that are made without a user-agent field, so we provide one NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_USERAGENT, HttpConstant::HTTP_DEFAULT_USER_AGENT); } + if (!request_.GetDNSOverHttps().empty()) { + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_DOH_URL, request_.GetDNSOverHttps().c_str()); + } + + SetDnsOption(handle); SetSSLCertOption(handle); + SetMultiPartOption(handle); SetDnsCacheOption(handle); SetIpResolve(handle); return true; } +bool HttpClientTask::SetAuthOptions(CURL *handle) +{ + long authType = CURLAUTH_ANY; + auto authentication = request_.GetServerAuthentication(); + switch (authentication.authenticationType) { + case HttpAuthenticationType::BASIC: + authType = CURLAUTH_BASIC; + break; + case HttpAuthenticationType::NTLM: + authType = CURLAUTH_NTLM; + break; + case HttpAuthenticationType::DIGEST: + authType = CURLAUTH_DIGEST; + break; + case HttpAuthenticationType::AUTO: + default: + break; + } + auto username = authentication.credential.username; + auto password = authentication.credential.password; + if (!username.empty()) { + NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_HTTPAUTH, authType); + NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_USERNAME, username.c_str()); + } + if (!password.empty()) { + NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_PASSWORD, password.c_str()); + } + + return true; +} + bool HttpClientTask::SetOtherCurlOption(CURL *handle) { // set proxy @@ -279,6 +344,8 @@ bool HttpClientTask::SetOtherCurlOption(CURL *handle) NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_PROXYTYPE, proxyType); } + SetTlsOption(handle); + const std::string range = GetRangeString(); if (!range.empty()) { if (request_.GetMethod() == HttpConstant::HTTP_METHOD_PUT) { @@ -327,6 +394,77 @@ bool HttpClientTask::SetIpResolve(CURL *handle) return true; } +bool HttpClientTask::IsBuiltWithOpenSSL() +{ + const auto data = curl_version_info(CURLVERSION_NOW); + if (data == nullptr || data->ssl_version == nullptr) { + return false; + } + + const auto sslVersion = CommonUtils::ToLower(data->ssl_version); + return sslVersion.find("openssl") != std::string::npos; +} + +unsigned long GetTlsVersion(TlsVersion tlsVersionMin, TlsVersion tlsVersionMax) +{ + unsigned long tlsVersion = CURL_SSLVERSION_DEFAULT; + if (tlsVersionMin == TlsVersion::DEFAULT || tlsVersionMax == TlsVersion::DEFAULT) { + return tlsVersion; + } + if (tlsVersionMin > tlsVersionMax) { + return tlsVersion; + } + if (tlsVersionMin == TlsVersion::TLSv1_0) { + tlsVersion |= static_cast(CURL_SSLVERSION_TLSv1_0); + } else if (tlsVersionMin == TlsVersion::TLSv1_1) { + tlsVersion |= static_cast(CURL_SSLVERSION_TLSv1_1); + } else if (tlsVersionMin == TlsVersion::TLSv1_2) { + tlsVersion |= static_cast(CURL_SSLVERSION_TLSv1_2); + } else if (tlsVersionMin == TlsVersion::TLSv1_3) { + tlsVersion |= static_cast(CURL_SSLVERSION_TLSv1_3); + } + + if (tlsVersionMax == TlsVersion::TLSv1_0) { + tlsVersion |= static_cast(CURL_SSLVERSION_MAX_TLSv1_0); + } else if (tlsVersionMax == TlsVersion::TLSv1_1) { + tlsVersion |= static_cast(CURL_SSLVERSION_MAX_TLSv1_1); + } else if (tlsVersionMax == TlsVersion::TLSv1_2) { + tlsVersion |= static_cast(CURL_SSLVERSION_MAX_TLSv1_2); + } else if (tlsVersionMax == TlsVersion::TLSv1_3) { + tlsVersion |= static_cast(CURL_SSLVERSION_MAX_TLSv1_3); + } + + return tlsVersion; +} + +bool HttpClientTask::SetTlsOption(CURL *handle) +{ + const auto &tlsOption = request_.GetTLSOptions(); + unsigned long tlsVersion = GetTlsVersion(tlsOption.tlsVersionMin, tlsOption.tlsVersionMax); + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSLVERSION, static_cast(tlsVersion)); + const auto &cipherSuite = tlsOption.cipherSuite; + const auto &cipherSuiteString = ConvertCipherSuiteToCipherString(cipherSuite); + const auto &normalString = cipherSuiteString.ciperSuiteString; + const auto &tlsV13String = cipherSuiteString.tlsV13CiperSuiteString; + if (tlsVersion == CURL_SSLVERSION_DEFAULT) { + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_CIPHER_LIST, TLS12_SECURITY_CIPHER_SUITE); + } else if (normalString.empty() && tlsV13String.empty()) { + NETSTACK_LOGD("no cipherSuite config"); + } else if (!normalString.empty()) { + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_CIPHER_LIST, normalString.c_str()); + if (!tlsV13String.empty() && IsBuiltWithOpenSSL()) { + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_TLS13_CIPHERS, tlsV13String.c_str()); + } + } else if (!tlsV13String.empty() && IsBuiltWithOpenSSL()) { + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_TLS13_CIPHERS, tlsV13String.c_str()); + } else { + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_SSL_CIPHER_LIST, TLS12_SECURITY_CIPHER_SUITE); + } + return true; +} + std::string HttpClientTask::GetRangeString() const { bool isSetFrom = request_.GetResumeFrom() >= MIN_RESUM_NUMBER; @@ -344,6 +482,96 @@ std::string HttpClientTask::GetRangeString() const } } +bool HttpClientTask::MethodForGet(const std::string &method) +{ + return (method == HttpConstant::HTTP_METHOD_HEAD || method == HttpConstant::HTTP_METHOD_OPTIONS || + method == HttpConstant::HTTP_METHOD_TRACE || method == HttpConstant::HTTP_METHOD_GET || + method == HttpConstant::HTTP_METHOD_CONNECT); +} + +bool HttpClientTask::MethodForPost(const std::string &method) +{ + return (method == HttpConstant::HTTP_METHOD_POST || method == HttpConstant::HTTP_METHOD_PUT || + method == HttpConstant::HTTP_METHOD_DELETE || method.empty()); +} + +static void SetFormDataOption(HttpMultiFormData &multiFormData, curl_mimepart *part, CURL *curl) +{ + CURLcode result = curl_mime_name(part, multiFormData.name.c_str()); + if (result != CURLE_OK) { + NETSTACK_LOGE("Failed to set name error: %{public}s", curl_easy_strerror(result)); + return; + } + if (!multiFormData.contentType.empty()) { + result = curl_mime_type(part, multiFormData.contentType.c_str()); + if (result != CURLE_OK) { + NETSTACK_LOGE("Failed to set contentType error: %{public}s", curl_easy_strerror(result)); + } + } + if (!multiFormData.remoteFileName.empty()) { + result = curl_mime_filename(part, multiFormData.remoteFileName.c_str()); + if (result != CURLE_OK) { + NETSTACK_LOGE("Failed to set remoteFileName error: %{public}s", curl_easy_strerror(result)); + } + } + if (!multiFormData.data.empty()) { + result = curl_mime_data(part, multiFormData.data.c_str(), multiFormData.data.length()); + if (result != CURLE_OK) { + NETSTACK_LOGE("Failed to set data error: %{public}s", curl_easy_strerror(result)); + } + } else { + if (!multiFormData.remoteFileName.empty()) { + std::string fileData; + bool isReadFile = CommonUtils::GetFileDataFromFilePath(multiFormData.filePath.c_str(), fileData); + if (isReadFile) { + result = curl_mime_data(part, fileData.c_str(), fileData.size()); + } else { + result = curl_mime_filedata(part, multiFormData.filePath.c_str()); + } + } else { + result = curl_mime_filedata(part, multiFormData.filePath.c_str()); + } + if (result != CURLE_OK) { + NETSTACK_LOGE("Failed to set file data error: %{public}s", curl_easy_strerror(result)); + } + } +} + +bool HttpClientTask::SetMultiPartOption(CURL *handle) +{ + auto header = request_.GetHeaders(); + auto type = CommonUtils::ToLower(header[HttpConstant::HTTP_CONTENT_TYPE]); + if (type != HttpConstant::HTTP_CONTENT_TYPE_MULTIPART) { + return true; + } + auto multiPartDataList = request_.GetMultiFormDataList(); + if (multiPartDataList.empty()) { + return true; + } + curMultiPart_ = curl_mime_init(handle); + if (curMultiPart_ == nullptr) { + return false; + } + curl_mimepart *part = nullptr; + bool hasData = false; + for (auto &multiFormData : multiPartDataList) { + if (multiFormData.name.empty()) { + continue; + } + if (multiFormData.data.empty() && multiFormData.filePath.empty()) { + NETSTACK_LOGE("Failed to set multiFormData error no data and filepath at the same time"); + continue; + } + part = curl_mime_addpart(curMultiPart_); + SetFormDataOption(multiFormData, part, handle); + hasData = true; + } + if (hasData) { + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_MIMEPOST, curMultiPart_); + } + return true; +} + bool HttpClientTask::SetServerSSLCertOption(CURL *curl) { auto hostname = CommonUtils::GetHostnameFromURL(request_.GetURL()); @@ -449,11 +677,62 @@ bool HttpClientTask::SetTraceOptions(CURL *curl) } bool HttpClientTask::SetCurlOptions() +{ + if (!SetCurlMethod()) { + return false; + } + + if (request_.GetUsingCache() || !SetCallbackFunctions()) { + return false; + } + + if (!SetHttpHeaders()) { + return false; + } + + NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_FOLLOWLOCATION, 1L); + + /* first #undef CURL_DISABLE_COOKIES in curl config */ + NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_COOKIEFILE, ""); + + NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_NOSIGNAL, 1L); + + NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_HTTP_VERSION, GetHttpVersion(request_.GetHttpProtocol())); + + if (!SetRequestOption(curlHandle_)) { + return false; + } + + if (!SetOtherCurlOption(curlHandle_)) { + return false; + } + + if (!SetAuthOptions(curlHandle_)) { + return false; + } + return true; +} + +bool HttpClientTask::SetCurlMethod() { auto method = request_.GetMethod(); + if (!MethodForGet(method) && !MethodForPost(method)) { + NETSTACK_LOGE("method %{public}s not supported", method.c_str()); + return false; + } + if (method == HttpConstant::HTTP_METHOD_HEAD) { NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_NOBODY, 1L); } + auto extraData = request_.GetExtraData(); + if (extraData.dataType != HttpDataType::NO_DATA_TYPE && !extraData.data.empty()) { + if (request_.MethodForGet(method)) { + HandleMethodForGet(); + } else if (request_.MethodForPost(method)) { + GetRequestBody(); + } + } + SetTraceOptions(curlHandle_); NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_URL, request_.GetURL().c_str()); @@ -474,7 +753,11 @@ bool HttpClientTask::SetCurlOptions() NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_POSTFIELDSIZE, request_.GetBody().size()); } } + return true; +} +bool HttpClientTask::SetCallbackFunctions() +{ NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_XFERINFOFUNCTION, ProgressCallback); NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_XFERINFODATA, this); NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_NOPROGRESS, 0L); @@ -485,33 +768,30 @@ bool HttpClientTask::SetCurlOptions() NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_HEADERFUNCTION, HeaderReceiveCallback); NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_HEADERDATA, this); + return true; +} + +bool HttpClientTask::SetHttpHeaders() +{ if (curlHeaderList_ != nullptr) { curl_slist_free_all(curlHeaderList_); curlHeaderList_ = nullptr; } + if (curMultiPart_ != nullptr) { + curl_mime_free(curMultiPart_); + curMultiPart_ = nullptr; + } + for (const auto &header : request_.GetHeaders()) { - std::string headerStr = header.first + HttpConstant::HTTP_HEADER_SEPARATOR + header.second; + std::string headerStr; + if (!header.second.empty()) { + headerStr = header.first + HttpConstant::HTTP_HEADER_SEPARATOR + header.second; + } else { + headerStr = header.first + HttpConstant::HTTP_HEADER_BLANK_SEPARATOR; + } curlHeaderList_ = curl_slist_append(curlHeaderList_, headerStr.c_str()); } NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_HTTPHEADER, curlHeaderList_); - - NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_FOLLOWLOCATION, 1L); - - /* first #undef CURL_DISABLE_COOKIES in curl config */ - NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_COOKIEFILE, ""); - - NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_NOSIGNAL, 1L); - - NETSTACK_CURL_EASY_SET_OPTION(curlHandle_, CURLOPT_HTTP_VERSION, GetHttpVersion(request_.GetHttpProtocol())); - - if (!SetRequestOption(curlHandle_)) { - return false; - } - - if (!SetOtherCurlOption(curlHandle_)) { - return false; - } - return true; } @@ -619,6 +899,80 @@ void HttpClientTask::OnHeadersReceive(const std::function &onHeaderReceive) +{ + onHeaderReceive_ = onHeaderReceive; +} + +bool HttpClientTask::OffDataReceive() +{ + if (onDataReceive_ == nullptr) { + return false; + } + onDataReceive_ = nullptr; + return true; +} + +bool HttpClientTask::OffProgress() +{ + if (onProgress_ == nullptr) { + return false; + } + onProgress_ = nullptr; + return true; +} + +bool HttpClientTask::OffHeaderReceive() +{ + if (onHeaderReceive_ == nullptr) { + return false; + } + onHeaderReceive_ = nullptr; + isHeaderOnce_ = false; + return true; +} + +bool HttpClientTask::OffHeadersReceive() +{ + if (onHeadersReceive_ == nullptr) { + return false; + } + onHeadersReceive_ = nullptr; + isHeadersOnce_ = false; + return true; +} + +void HttpClientTask::SetIsHeaderOnce(bool isOnce) +{ + isHeaderOnce_ = isOnce; +} + +bool HttpClientTask::IsHeaderOnce() const +{ + return isHeaderOnce_; +} + +void HttpClientTask::SetIsHeadersOnce(bool isOnce) +{ + isHeadersOnce_ = isOnce; +} + +bool HttpClientTask::IsHeadersOnce() const +{ + return isHeadersOnce_; +} + +void HttpClientTask::SetIsRequestInStream(bool isRequestInStream) +{ + isRequestInStream_ = isRequestInStream; +} + +bool HttpClientTask::IsRequestInStream() +{ + return isRequestInStream_; +} + size_t HttpClientTask::DataReceiveCallback(const void *data, size_t size, size_t memBytes, void *userData) { auto task = static_cast(userData); @@ -628,15 +982,22 @@ size_t HttpClientTask::DataReceiveCallback(const void *data, size_t size, size_t NETSTACK_LOGD("canceled"); return 0; } + if (task->onDataReceive_) { HttpClientRequest request = task->request_; task->onDataReceive_(request, static_cast(data), size * memBytes); } + if (task->IsRequestInStream()) { + return size * memBytes; + } - if (task->response_.GetResult().size() < MAX_LIMIT) { - task->response_.AppendResult(data, size * memBytes); + if (task->response_.GetResult().size() > task->request_.GetMaxLimit() || + size * memBytes > task->request_.GetMaxLimit()) { + NETSTACK_LOGE("response data exceeds the maximum limit"); + return 0; } + task->response_.AppendResult(data, size * memBytes); return size * memBytes; } @@ -663,7 +1024,15 @@ int HttpClientTask::ProgressCallback(void *userData, curl_off_t dltotal, curl_of size_t HttpClientTask::HeaderReceiveCallback(const void *data, size_t size, size_t memBytes, void *userData) { auto task = static_cast(userData); + if (task == nullptr) { + return 0; + } task->GetTrace().Tracepoint(TraceEvents::RECEIVING); + if (task->canceled_) { + NETSTACK_LOGD("canceled"); + return 0; + } + NETSTACK_LOGD("taskId=%{public}d size=%{public}zu memBytes=%{public}zu", task->taskId_, size, memBytes); if (size * memBytes > MAX_LIMIT) { @@ -672,6 +1041,12 @@ size_t HttpClientTask::HeaderReceiveCallback(const void *data, size_t size, size } task->response_.AppendHeader(static_cast(data), size * memBytes); + if (!task->canceled_ && task->onHeaderReceive_ && !task->response_.GetRawHeader().empty()) { + task->onHeaderReceive_(task->request_, task->response_.GetRawHeader()); + if (task->IsHeaderOnce()) { + task->OffHeaderReceive(); + } + } if (!task->canceled_ && task->onHeadersReceive_ && CommonUtils::EndsWith(task->response_.GetHeader(), HttpConstant::HTTP_RESPONSE_HEADER_SEPARATOR)) { task->response_.ParseHeaders(); @@ -687,6 +1062,9 @@ size_t HttpClientTask::HeaderReceiveCallback(const void *data, size_t size, size } headerWithSetCookie[HttpConstant::RESPONSE_KEY_SET_COOKIE] = setCookies; task->onHeadersReceive_(task->request_, headerWithSetCookie); + if (task->IsHeadersOnce()) { + task->OffHeadersReceive(); + } } return size * memBytes; @@ -845,6 +1223,9 @@ void HttpClientTask::ProcessResponse(CURLMsg *msg) response_.SetResponseTime(HttpTime::GetNowTimeGMT()); DumpHttpPerformance(); + if (ProcessUsingCache()) { + return; + } if (CURLE_ABORTED_BY_CALLBACK == code) { (void)ProcessResponseCode(); if (onCanceled_) { @@ -862,7 +1243,8 @@ void HttpClientTask::ProcessResponse(CURLMsg *msg) ProcessCookie(curlHandle_); response_.ParseHeaders(); - + response_.SetExpectDataType(request_.GetExpectDataType()); + WriteResopnseToCache(response_); if (ProcessResponseCode()) { if (onSucceeded_) { onSucceeded_(request_, response_); @@ -886,6 +1268,21 @@ RequestTracer::Trace &HttpClientTask::GetTrace() return *trace_; } +bool HttpClientTask::SetDnsOption(CURL *handle) +{ + auto dnsServers = request_.GetDNSServers(); + if (dnsServers.empty()) { + return false; + } + std::string serverList; + for (auto &server : dnsServers) { + serverList += server + ","; + NETSTACK_LOGD("SetDns server: %{public}s", CommonUtils::AnonymizeIp(server).c_str()); + } + serverList.pop_back(); + NETSTACK_CURL_EASY_SET_OPTION(handle, CURLOPT_DNS_SERVERS, serverList.c_str()); + return true; +} bool HttpClientTask::SetDnsCacheOption(CURL *handle) { @@ -894,6 +1291,223 @@ bool HttpClientTask::SetDnsCacheOption(CURL *handle) #endif return true; } + +bool HttpClientTask::ReadResopnseFromCache() +{ + CacheProxy proxy(request_); + auto response = proxy.ReadResponseFromCache(); + if (response == nullptr) { + return false; + } + auto status = proxy.RunStrategy(response); + if (status == CacheStatus::FRESH) { + SetResponse(*response); + return true; + } + if (status == CacheStatus::STALE) { + SetResponse(*response); + return false; + } + NETSTACK_LOGD("cache should not be used"); + return false; +} + +void HttpClientTask::WriteResopnseToCache(const HttpClientResponse &response) +{ + CacheProxy proxy(request_); + proxy.WriteResponseToCache(response); +} + +bool HttpClientTask::ProcessUsingCache() +{ + if (!request_.GetUsingCache()) { + return false; + } + if (!ReadResopnseFromCache()) { + return false; + } + if (onSucceeded_) { + onSucceeded_(request_, response_); + } else if (onFailed_) { + onFailed_(request_, response_, error_); + } + return true; +} + +bool HttpClientTask::IsUnReserved(unsigned char in) +{ + if ((in >= '0' && in <= '9') || (in >= 'a' && in <= 'z') || (in >= 'A' && in <= 'Z')) { + return true; + } + switch (in) { + case '-': + case '.': + case '_': + case '~': + return true; + default: + break; + } + return false; +} + +bool HttpClientTask::EncodeUrlParam(std::string &str) +{ + char encoded[4]; + std::string encodeOut; + size_t length = strlen(str.c_str()); + for (size_t i = 0; i < length; ++i) { + auto c = static_cast(str.c_str()[i]); + if (IsUnReserved(c)) { + encodeOut += static_cast(c); + } else { + if (sprintf_s(encoded, sizeof(encoded), "%%%02X", c) < 0) { + return false; + } + encodeOut += encoded; + } + } + + if (str == encodeOut) { + return false; + } + str = encodeOut; + return true; +} + +std::string HttpClientTask::MakeUrl(const std::string &url, std::string param, const std::string &extraParam) +{ + if (param.empty()) { + param += extraParam; + } else { + param += HttpConstant::HTTP_URL_PARAM_SEPARATOR; + param += extraParam; + } + + if (param.empty()) { + return url; + } + + return url + HttpConstant::HTTP_URL_PARAM_START + param; +} + +std::string HttpClientTask::GetJsonFieldValue(const cJSON* item) +{ + std::string result; + if (item == nullptr) { + return result; + } + std::stringstream ss; + switch (item->type) { + case cJSON_String: + ss << item->valuestring; + break; + case cJSON_Number: + ss << item->valuedouble; + break; + case cJSON_True: + ss << "true"; + break; + case cJSON_False: + ss << "false"; + break; + case cJSON_NULL: + ss << "null"; + break; + default: + NETSTACK_LOGE("unknown type"); + } + result = ss.str(); + return result; +} + +void HttpClientTask::TraverseJson(const cJSON* item, std::string &output) +{ + if (item == nullptr) { + return; + } + if (item->type == cJSON_Object) { + cJSON* child = item->child; + while (child != nullptr) { + if (child->type == cJSON_Object || child->type == cJSON_Array) { + TraverseJson(child, output); + } + std::string key(child->string); + std::string value = GetJsonFieldValue(child); + if (key.empty() || value.empty()) { + child = child->next; + continue; + } + bool encodeName = EncodeUrlParam(key); + bool encodeValue = EncodeUrlParam(value); + if (encodeName || encodeValue) { + request_.SetHeader(CommonUtils::ToLower(HttpConstant::HTTP_CONTENT_TYPE), + HttpConstant::HTTP_CONTENT_TYPE_URL_ENCODE); + } + output += + key + HttpConstant::HTTP_URL_NAME_VALUE_SEPARATOR + value + HttpConstant::HTTP_URL_PARAM_SEPARATOR; + child = child->next; + } + } else if (item->type == cJSON_Array) { + auto size = cJSON_GetArraySize(item); + for (int i = 0; i < size; ++i) { + cJSON* arrayItem = cJSON_GetArrayItem(item, i); + TraverseJson(arrayItem, output); + } + } +} + +std::string HttpClientTask::ParseJsonValueToExtraParam(const std::string &jsonStr) +{ + std::string extraParam; + cJSON* root = cJSON_Parse(jsonStr.c_str()); + if (root == nullptr) { + NETSTACK_LOGE("json parse failed"); + return extraParam; + } + TraverseJson(root, extraParam); + cJSON_Delete(root); + return extraParam; +} + +void HttpClientTask::HandleMethodForGet() +{ + std::string url = request_.GetURL(); + std::string param; + auto index = url.find(HttpConstant::HTTP_URL_PARAM_START); + if (index != std::string::npos) { + param = url.substr(index + 1); + url.resize(index); + } + + auto extraData = request_.GetExtraData(); + switch (extraData.dataType) { + case HttpDataType::STRING: + request_.SetURL(MakeUrl(url, param, extraData.data)); + break; + case HttpDataType::ARRAY_BUFFER: + case HttpDataType::OBJECT: { + auto extraParam = ParseJsonValueToExtraParam(extraData.data); + if (!extraParam.empty()) { + extraParam.pop_back(); + } + request_.SetURL(MakeUrl(url, param, extraParam)); + break; + } + default: + break; + } +} + +bool HttpClientTask::GetRequestBody() +{ + auto extraDataStr = request_.GetExtraData().data; + if (extraDataStr.empty()) { + return false; + } + request_.SetBody(extraDataStr.c_str(), extraDataStr.size()); + return true; +} } // namespace HttpClient } // namespace NetStack } // namespace OHOS diff --git a/frameworks/native/http/http_client/http_client_tls_config.cpp b/frameworks/native/http/http_client/http_client_tls_config.cpp new file mode 100644 index 000000000..2c030eae7 --- /dev/null +++ b/frameworks/native/http/http_client/http_client_tls_config.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "http_client_tls_config.h" + +namespace OHOS::NetStack::HttpClient { +struct CipherSuiteConvertor { + CipherSuite cipherSuite = CipherSuite::INVALID; + const char *innerName = nullptr; + const char *standardName = nullptr; +}; + +static constexpr const CipherSuiteConvertor CIPHER_SUITE_CONVERTOR[] = { + { + .cipherSuite = CipherSuite::TLS_AES_128_GCM_SHA256, + .innerName = "TLS_AES_128_GCM_SHA256", + .standardName = "TLS_AES_128_GCM_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_AES_256_GCM_SHA384, + .innerName = "TLS_AES_256_GCM_SHA384", + .standardName = "TLS_AES_256_GCM_SHA384", + }, + { + .cipherSuite = CipherSuite::TLS_CHACHA20_POLY1305_SHA256, + .innerName = "TLS_CHACHA20_POLY1305_SHA256", + .standardName = "TLS_CHACHA20_POLY1305_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + .innerName = "ECDHE-ECDSA-AES128-GCM-SHA256", + .standardName = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + .innerName = "ECDHE-RSA-AES128-GCM-SHA256", + .standardName = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + .innerName = "ECDHE-ECDSA-AES256-GCM-SHA384", + .standardName = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + .innerName = "ECDHE-RSA-AES256-GCM-SHA384", + .standardName = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + .innerName = "ECDHE-ECDSA-CHACHA20-POLY1305", + .standardName = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + .innerName = "ECDHE-RSA-CHACHA20-POLY1305", + .standardName = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256, + .innerName = "AES128-GCM-SHA256", + .standardName = "TLS_RSA_WITH_AES_128_GCM_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384, + .innerName = "AES256-GCM-SHA384", + .standardName = "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + .innerName = "ECDHE-ECDSA-AES128-SHA", + .standardName = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + .innerName = "ECDHE-RSA-AES128-SHA", + .standardName = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + .innerName = "ECDHE-ECDSA-AES256-SHA", + .standardName = "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + .innerName = "ECDHE-RSA-AES256-SHA", + .standardName = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA, + .innerName = "AES128-SHA", + .standardName = "TLS_RSA_WITH_AES_128_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA, + .innerName = "AES256-SHA", + .standardName = "TLS_RSA_WITH_AES_256_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA, + .innerName = "DES-CBC3-SHA", + .standardName = "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + }, +}; + +CipherSuite GetTlsCipherSuiteFromStandardName(const std::string &standardName) +{ + for (const auto &suite : CIPHER_SUITE_CONVERTOR) { + if (suite.standardName == standardName) { + return suite.cipherSuite; + } + } + return CipherSuite::INVALID; +} + +std::string GetInnerNameFromCipherSuite(CipherSuite cipherSuite) +{ + for (const auto &suite : CIPHER_SUITE_CONVERTOR) { + if (suite.cipherSuite == cipherSuite) { + return suite.innerName; + } + } + return {}; +} + +static bool IsTlsV13Cipher(const std::string &innerName) +{ + return innerName == "TLS_AES_128_GCM_SHA256" || innerName == "TLS_AES_256_GCM_SHA384" || + innerName == "TLS_CHACHA20_POLY1305_SHA256"; +} + +TlsCipherString ConvertCipherSuiteToCipherString(const std::unordered_set &cipherSuite) +{ + TlsCipherString cipherString; + for (const auto &cipher : cipherSuite) { + auto innerName = GetInnerNameFromCipherSuite(cipher); + if (innerName.empty()) { + continue; + } + if (IsTlsV13Cipher(innerName)) { + cipherString.tlsV13CiperSuiteString.append(innerName).append(":"); + } else { + cipherString.ciperSuiteString.append(innerName).append(":"); + } + } + if (!cipherString.tlsV13CiperSuiteString.empty()) { + cipherString.tlsV13CiperSuiteString.pop_back(); + } + if (!cipherString.ciperSuiteString.empty()) { + cipherString.ciperSuiteString.pop_back(); + } + return cipherString; +} + +} // namespace OHOS::NetStack::HttpClient \ No newline at end of file diff --git a/interfaces/innerkits/http_client/BUILD.gn b/interfaces/innerkits/http_client/BUILD.gn index cc2d3ffc4..3fc09b413 100644 --- a/interfaces/innerkits/http_client/BUILD.gn +++ b/interfaces/innerkits/http_client/BUILD.gn @@ -21,6 +21,11 @@ config("http_client_config") { "$NETSTACK_DIR/utils/profiler_utils/include", "$NETSTACK_DIR/utils/tlv_utils/include", "$NETSTACK_DIR/utils/netstack_chr_client/include", + "$NETSTACK_INNERKITS_DIR/http_client/cache/base64/include", + "$NETSTACK_INNERKITS_DIR/http_client/cache/cache_constant/include", + "$NETSTACK_INNERKITS_DIR/http_client/cache/cache_proxy/include", + "$NETSTACK_INNERKITS_DIR/http_client/cache/cache_strategy/include", + "$NETSTACK_INNERKITS_DIR/http_client/cache/lru_cache/include", ] cflags = [] @@ -80,9 +85,19 @@ ohos_shared_library("http_client") { "$NETSTACK_NATIVE_ROOT/http/http_client/http_client_constant.cpp", "$NETSTACK_NATIVE_ROOT/http/http_client/http_client_error.cpp", "$NETSTACK_NATIVE_ROOT/http/http_client/http_client_request.cpp", + "$NETSTACK_NATIVE_ROOT/http/http_client/http_client_secure_data.cpp", "$NETSTACK_NATIVE_ROOT/http/http_client/http_client_response.cpp", "$NETSTACK_NATIVE_ROOT/http/http_client/http_client_task.cpp", "$NETSTACK_NATIVE_ROOT/http/http_client/http_client_time.cpp", + "$NETSTACK_NATIVE_ROOT/http/http_client/http_client_tls_config.cpp", + "$NETSTACK_INNERKITS_DIR/http_client/cache/base64/src/base64_utils.cpp", + "$NETSTACK_INNERKITS_DIR/http_client/cache/cache_proxy/src/cache_proxy.cpp", + "$NETSTACK_INNERKITS_DIR/http_client/cache/cache_strategy/src/http_cache_request.cpp", + "$NETSTACK_INNERKITS_DIR/http_client/cache/cache_strategy/src/http_cache_response.cpp", + "$NETSTACK_INNERKITS_DIR/http_client/cache/cache_strategy/src/http_cache_strategy.cpp", + "$NETSTACK_INNERKITS_DIR/http_client/cache/lru_cache/src/disk_handler.cpp", + "$NETSTACK_INNERKITS_DIR/http_client/cache/lru_cache/src/lru_cache_disk_handler.cpp", + "$NETSTACK_INNERKITS_DIR/http_client/cache/lru_cache/src/lru_cache.cpp", ] include_dirs = [ @@ -144,6 +159,7 @@ ohos_shared_library("http_client") { "curl:curl_shared", "openssl:libcrypto_shared", "openssl:libssl_shared", + "cJSON:cjson", ] if (defined(global_parts_info) && diff --git a/interfaces/innerkits/http_client/cache/base64/include/base64_utils.h b/interfaces/innerkits/http_client/cache/base64/include/base64_utils.h new file mode 100644 index 000000000..6245e7da2 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/base64/include/base64_utils.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMUNICATIONNETSTACK_CLIENT_BASE64_H +#define COMMUNICATIONNETSTACK_CLIENT_BASE64_H + +#include + +namespace OHOS::NetStack::Base64 { +std::string Encode(const std::string &source); +std::string Decode(const std::string &encoded); +} // namespace OHOS::NetStack::Base64 + +#endif /* COMMUNICATIONNETSTACK_CLIENT_BASE64_H */ diff --git a/interfaces/innerkits/http_client/cache/base64/src/base64_utils.cpp b/interfaces/innerkits/http_client/cache/base64/src/base64_utils.cpp new file mode 100644 index 000000000..2260e20e1 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/base64/src/base64_utils.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base64_utils.h" + +#include + +namespace OHOS::NetStack::Base64 { +#ifdef __linux__ +static std::string BASE64_CHARS = /* NOLINT */ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +static constexpr const uint32_t CHAR_ARRAY_LENGTH_THREE = 3; +static constexpr const uint32_t CHAR_ARRAY_LENGTH_FOUR = 4; + +enum BASE64_ENCODE_CONSTANT : uint8_t { + BASE64_ENCODE_MASK1 = 0xfc, + BASE64_ENCODE_MASK2 = 0x03, + BASE64_ENCODE_MASK3 = 0x0f, + BASE64_ENCODE_MASK4 = 0x3f, + BASE64_ENCODE_MASK5 = 0xf0, + BASE64_ENCODE_MASK6 = 0xc0, + BASE64_ENCODE_OFFSET2 = 2, + BASE64_ENCODE_OFFSET4 = 4, + BASE64_ENCODE_OFFSET6 = 6, + BASE64_ENCODE_INDEX0 = 0, + BASE64_ENCODE_INDEX1 = 1, + BASE64_ENCODE_INDEX2 = 2, +}; + +enum BASE64_DECODE_CONSTANT : uint8_t { + BASE64_DECODE_MASK1 = 0x30, + BASE64_DECODE_MASK2 = 0xf, + BASE64_DECODE_MASK3 = 0x3c, + BASE64_DECODE_MASK4 = 0x3, + BASE64_DECODE_OFFSET2 = 2, + BASE64_DECODE_OFFSET4 = 4, + BASE64_DECODE_OFFSET6 = 6, + BASE64_DECODE_INDEX0 = 0, + BASE64_DECODE_INDEX1 = 1, + BASE64_DECODE_INDEX2 = 2, + BASE64_DECODE_INDEX3 = 3, +}; + +static inline bool IsBase64Char(const char c) +{ + return (isalnum(c) || (c == '+') || (c == '/')); +} + +static inline void MakeCharFour(const std::array &charArrayThree, + std::array &charArrayFour) +{ + const uint8_t table[CHAR_ARRAY_LENGTH_FOUR] = { + static_cast((charArrayThree[BASE64_ENCODE_INDEX0] & BASE64_ENCODE_MASK1) >> BASE64_ENCODE_OFFSET2), + static_cast(((charArrayThree[BASE64_ENCODE_INDEX0] & BASE64_ENCODE_MASK2) << BASE64_ENCODE_OFFSET4) + + ((charArrayThree[BASE64_ENCODE_INDEX1] & BASE64_ENCODE_MASK5) >> BASE64_ENCODE_OFFSET4)), + static_cast(((charArrayThree[BASE64_ENCODE_INDEX1] & BASE64_ENCODE_MASK3) << BASE64_ENCODE_OFFSET2) + + ((charArrayThree[BASE64_ENCODE_INDEX2] & BASE64_ENCODE_MASK6) >> BASE64_ENCODE_OFFSET6)), + static_cast(charArrayThree[BASE64_ENCODE_INDEX2] & BASE64_ENCODE_MASK4), + }; + for (size_t index = 0; index < CHAR_ARRAY_LENGTH_FOUR; ++index) { + charArrayFour[index] = table[index]; + } +} + +static inline void MakeCharTree(const std::array &charArrayFour, + std::array &charArrayThree) +{ + const uint8_t table[CHAR_ARRAY_LENGTH_THREE] = { + static_cast((charArrayFour[BASE64_DECODE_INDEX0] << BASE64_DECODE_OFFSET2) + + ((charArrayFour[BASE64_DECODE_INDEX1] & BASE64_DECODE_MASK1) >> BASE64_DECODE_OFFSET4)), + static_cast(((charArrayFour[BASE64_DECODE_INDEX1] & BASE64_DECODE_MASK2) << BASE64_DECODE_OFFSET4) + + ((charArrayFour[BASE64_DECODE_INDEX2] & BASE64_DECODE_MASK3) >> BASE64_DECODE_OFFSET2)), + static_cast(((charArrayFour[BASE64_DECODE_INDEX2] & BASE64_DECODE_MASK4) << BASE64_DECODE_OFFSET6) + + charArrayFour[BASE64_DECODE_INDEX3]), + }; + for (size_t index = 0; index < CHAR_ARRAY_LENGTH_THREE; ++index) { + charArrayThree[index] = table[index]; + } +} + +#endif + +std::string Encode(const std::string &source) +{ +#ifdef __linux__ + auto it = source.begin(); + std::string ret; + size_t index = 0; + std::array charArrayThree = {0}; + std::array charArrayFour = {0}; + + while (it != source.end()) { + charArrayThree[index] = *it; + ++index; + ++it; + if (index != CHAR_ARRAY_LENGTH_THREE) { + continue; + } + MakeCharFour(charArrayThree, charArrayFour); + for (auto idx : charArrayFour) { + ret += BASE64_CHARS[idx]; + } + index = 0; + } + if (index == 0) { + return ret; + } + + for (auto i = index; i < CHAR_ARRAY_LENGTH_THREE; ++i) { + charArrayThree[i] = 0; + } + MakeCharFour(charArrayThree, charArrayFour); + + for (size_t i = 0; i < index + 1; ++i) { + ret += BASE64_CHARS[charArrayFour[i]]; + } + + while (index < CHAR_ARRAY_LENGTH_THREE) { + ret += '='; + ++index; + } + return ret; +#else + return {}; +#endif +} + +std::string Decode(const std::string &encoded) +{ +#ifdef __linux__ + auto it = encoded.begin(); + size_t index = 0; + std::array charArrayThree = {0}; + std::array charArrayFour = {0}; + std::string ret; + + while (it != encoded.end() && IsBase64Char(*it)) { + charArrayFour[index] = *it; + ++index; + ++it; + if (index != CHAR_ARRAY_LENGTH_FOUR) { + continue; + } + for (index = 0; index < CHAR_ARRAY_LENGTH_FOUR; ++index) { + charArrayFour[index] = BASE64_CHARS.find(static_cast(charArrayFour[index])); + } + MakeCharTree(charArrayFour, charArrayThree); + for (auto idx : charArrayThree) { + ret += static_cast(idx); + } + index = 0; + } + if (index == 0) { + return ret; + } + + for (auto i = index; i < CHAR_ARRAY_LENGTH_FOUR; ++i) { + charArrayFour[i] = 0; + } + for (unsigned char &i : charArrayFour) { + i = BASE64_CHARS.find(static_cast(i)); + } + MakeCharTree(charArrayFour, charArrayThree); + + for (size_t i = 0; i < index - 1; i++) { + ret += static_cast(charArrayThree[i]); + } + return ret; +#else + return {}; +#endif +} +} // namespace OHOS::NetStack::Base64 \ No newline at end of file diff --git a/interfaces/innerkits/http_client/cache/cache_constant/include/casche_constant.h b/interfaces/innerkits/http_client/cache/cache_constant/include/casche_constant.h new file mode 100644 index 000000000..5ccc94064 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/cache_constant/include/casche_constant.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HTTP_CLIENT_CACHE_CONSTANT_H +#define HTTP_CLIENT_CACHE_CONSTANT_H + +static constexpr const int DECIMAL = 10; +static constexpr const char *SPLIT = ", "; +static constexpr const char EQUAL = '='; + +static constexpr const char *NO_CACHE = "no-cache"; +static constexpr const char *NO_STORE = "no-store"; +static constexpr const char *NO_TRANSFORM = "no-transform"; +static constexpr const char *ONLY_IF_CACHED = "only-if-cached"; +static constexpr const char *MAX_AGE = "max-age"; +static constexpr const char *MAX_STALE = "max-stale"; +static constexpr const char *MIN_FRESH = "min-fresh"; +static constexpr const char *CACHE_CONTROL = "cache-control"; +static constexpr const char *IF_MODIFIED_SINCE = "if-modified-since"; +static constexpr const char *IF_NONE_MATCH = "if-none-match"; + +static constexpr const char *MUST_REVALIDATE = "must-revalidate"; +static constexpr const char *PUBLIC = "public"; +static constexpr const char *PRIVATE = "private"; +static constexpr const char *PROXY_REVALIDATE = "proxy-revalidate"; +static constexpr const char *S_MAXAGE = "s-maxage"; +static constexpr const char *EXPIRES = "expires"; +static constexpr const char *LAST_MODIFIED = "last-modified"; +static constexpr const char *ETAG = "etag"; +static constexpr const char *AGE = "age"; +static constexpr const char *DATE = "date"; + +static constexpr const int INVALID_TIME = -1; + +#endif /* HTTP_CLIENT_CACHE_CONSTANT_H */ diff --git a/interfaces/innerkits/http_client/cache/cache_proxy/include/cache_proxy.h b/interfaces/innerkits/http_client/cache/cache_proxy/include/cache_proxy.h new file mode 100644 index 000000000..89a702b9b --- /dev/null +++ b/interfaces/innerkits/http_client/cache/cache_proxy/include/cache_proxy.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMUNICATIONNETSTACK_HTTP_CLIENT_CACHE_PROXY_H +#define COMMUNICATIONNETSTACK_HTTP_CLIENT_CACHE_PROXY_H + +#include "http_cache_strategy.h" +#include "http_client_response.h" + +namespace OHOS::NetStack::HttpClient { +class CacheProxy final { +public: + CacheProxy() = delete; + + explicit CacheProxy(HttpClientRequest &requestOptions); + + std::shared_ptr ReadResponseFromCache(); + + CacheStatus RunStrategy(const std::shared_ptr &response); + + void WriteResponseToCache(const HttpClientResponse &response); + + static void RunCacheWithSize(size_t capacity); + + static void RunCache(); + + static void FlushCache(); + + static void StopCacheAndDelete(); + +private: + std::string key_; + HttpCacheStrategy strategy_; +}; +} // namespace OHOS::NetStack::HttpClient +#endif // COMMUNICATIONNETSTACK_HTTP_CLIENT_CACHE_PROXY_H diff --git a/interfaces/innerkits/http_client/cache/cache_proxy/src/cache_proxy.cpp b/interfaces/innerkits/http_client/cache/cache_proxy/src/cache_proxy.cpp new file mode 100644 index 000000000..2a604e9d8 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/cache_proxy/src/cache_proxy.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cache_proxy.h" + +#include +#include +#include +#include + +#include "base64_utils.h" +#include "casche_constant.h" +#include "http_client_constant.h" +#include "lru_cache_disk_handler.h" +#include "netstack_common_utils.h" +#include "netstack_log.h" + +#ifdef HTTP_CACHE_FILE_PATH_USE_BASE +static constexpr const char *CACHE_FILE = "/data/storage/el2/base/cache/cache.json"; +#else +static constexpr const char *CACHE_FILE = "/data/storage/el2/database/cache.json"; +#endif +static constexpr int32_t WRITE_INTERVAL = 60; + +namespace OHOS::NetStack::HttpClient { +std::mutex g_diskCacheMutex; +std::mutex g_cacheNeedRunMutex; +std::atomic_bool g_cacheNeedRun(false); +std::atomic_bool g_cacheIsRunning(false); +std::condition_variable g_cacheThreadCondition; +std::condition_variable g_cacheNeedRunCondition; +static LRUCacheDiskHandler DISK_LRU_CACHE(CACHE_FILE, 0); // NOLINT(cert-err58-cpp) + +CacheProxy::CacheProxy(HttpClientRequest &requestOptions) : strategy_(requestOptions) +{ + std::string str = requestOptions.GetURL() + HttpConstant::HTTP_LINE_SEPARATOR + + CommonUtils::ToLower(requestOptions.GetMethod()) + HttpConstant::HTTP_LINE_SEPARATOR; + for (const auto &p : requestOptions.GetHeaders()) { + if (p.first == IF_NONE_MATCH || p.first == IF_MODIFIED_SINCE) { + continue; + } + str += p.first + HttpConstant::HTTP_HEADER_SEPARATOR + p.second + HttpConstant::HTTP_LINE_SEPARATOR; + } + str += std::to_string(requestOptions.GetHttpVersion()); + key_ = Base64::Encode(str); +} + +std::shared_ptr CacheProxy::ReadResponseFromCache() +{ + if (!g_cacheIsRunning.load()) { + return nullptr; + } + + if (!strategy_.CouldUseCache()) { + NETSTACK_LOGI("only GET/HEAD method or header has [Range] can use cache"); + return nullptr; + } + + auto responseFromCache = DISK_LRU_CACHE.Get(key_); + if (responseFromCache.empty()) { + NETSTACK_LOGI("no cache with this request"); + return nullptr; + } + auto cachedResponse = std::make_shared(); + cachedResponse->SetRawHeader(Base64::Decode(responseFromCache[HttpConstant::RESPONSE_KEY_HEADER])); + cachedResponse->SetResult(Base64::Decode(responseFromCache[HttpConstant::RESPONSE_KEY_RESULT])); + cachedResponse->SetCookies(Base64::Decode(responseFromCache[HttpConstant::RESPONSE_KEY_COOKIES])); + cachedResponse->SetResponseTime(Base64::Decode(responseFromCache[HttpConstant::RESPONSE_TIME])); + cachedResponse->SetRequestTime(Base64::Decode(responseFromCache[HttpConstant::REQUEST_TIME])); + cachedResponse->SetResponseCode(ResponseCode::OK); + cachedResponse->ParseHeaders(); + return cachedResponse; +} + +CacheStatus CacheProxy::RunStrategy(const std::shared_ptr &cachedResponse) +{ + if (cachedResponse == nullptr) { + return CacheStatus::DENY; + } + return strategy_.RunStrategy(*cachedResponse); +} + +void CacheProxy::WriteResponseToCache(const HttpClientResponse &response) +{ + if (!g_cacheIsRunning.load()) { + return; + } + + if (!strategy_.IsCacheable(response)) { + NETSTACK_LOGE("do not cache this response"); + return; + } + std::unordered_map cacheResponse; + cacheResponse[HttpConstant::RESPONSE_KEY_HEADER] = Base64::Encode(response.GetRawHeader()); + cacheResponse[HttpConstant::RESPONSE_KEY_RESULT] = Base64::Encode(response.GetResult()); + cacheResponse[HttpConstant::RESPONSE_KEY_COOKIES] = Base64::Encode(response.GetCookies()); + cacheResponse[HttpConstant::RESPONSE_TIME] = Base64::Encode(response.GetResponseTime()); + cacheResponse[HttpConstant::REQUEST_TIME] = Base64::Encode(response.GetRequestTime()); + + DISK_LRU_CACHE.Put(key_, cacheResponse); +} + +void CacheProxy::RunCache() +{ + RunCacheWithSize(MAX_DISK_CACHE_SIZE); +} + +void CacheProxy::RunCacheWithSize(size_t capacity) +{ + if (g_cacheIsRunning.load()) { + return; + } + DISK_LRU_CACHE.SetCapacity(capacity); + + g_cacheNeedRun.store(true); + + DISK_LRU_CACHE.ReadCacheFromJsonFile(); + + std::thread([]() { + g_cacheIsRunning.store(true); + while (g_cacheNeedRun.load()) { + std::unique_lock lock(g_cacheNeedRunMutex); + g_cacheNeedRunCondition.wait_for(lock, std::chrono::seconds(WRITE_INTERVAL), + [] { return !g_cacheNeedRun.load(); }); + + DISK_LRU_CACHE.WriteCacheToJsonFile(); + } + + g_cacheIsRunning.store(false); + g_cacheThreadCondition.notify_all(); + }).detach(); +} + +void CacheProxy::FlushCache() +{ + if (!g_cacheIsRunning.load()) { + return; + } + DISK_LRU_CACHE.WriteCacheToJsonFile(); +} + +void CacheProxy::StopCacheAndDelete() +{ + if (!g_cacheIsRunning.load()) { + return; + } + g_cacheNeedRun.store(false); + g_cacheNeedRunCondition.notify_all(); + + std::unique_lock lock(g_diskCacheMutex); + g_cacheThreadCondition.wait(lock, [] { return !g_cacheIsRunning.load(); }); + DISK_LRU_CACHE.Delete(); +} +} // namespace OHOS::NetStack::HttpClient diff --git a/interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_request.h b/interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_request.h new file mode 100644 index 000000000..267fb480c --- /dev/null +++ b/interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_request.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HTTP_CLIENT_CACHE_REQUEST_H +#define HTTP_CLIENT_CACHE_REQUEST_H + +#include +#include +#include + +namespace OHOS::NetStack::HttpClient { +class HttpCacheRequest { +public: + HttpCacheRequest(); + + ~HttpCacheRequest(); + + void ParseRequestHeader(const std::map &requestHeader); + + void SetRequestTime(const std::string &requestTime); + + [[nodiscard]] time_t GetIfModifiedSince() const; + + [[nodiscard]] time_t GetRequestTime() const; + + [[nodiscard]] time_t GetMaxAgeSeconds() const; + + [[nodiscard]] time_t GetMaxStaleSeconds() const; + + [[nodiscard]] time_t GetMinFreshSeconds() const; + + [[nodiscard]] bool IsNoCache() const; + + [[nodiscard]] bool IsNoStore() const; + + [[nodiscard]] bool IsNoTransform() const; + + [[nodiscard]] bool IsOnlyIfCached() const; + + [[nodiscard]] std::string GetIfNoneMatch() const; + +private: + void ParseCacheControl(const std::string &cacheControl); + +private: + std::string requestTime_; + std::string ifModifiedSince_; + std::string ifNoneMatch_; + + std::string maxAge_; + std::string maxStale_; + std::string minFresh_; + + bool noCache_ = false; + bool noStore_ = false; + bool noTransform_ = false; + bool onlyIfCached_ = false; +}; +} // namespace OHOS::NetStack::HttpClient +#endif // HTTP_CLIENT_CACHE_REQUEST_H diff --git a/interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_response.h b/interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_response.h new file mode 100644 index 000000000..bcf2cc879 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_response.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HTTP_CLIENT_CACHE_RESPONSE_H +#define HTTP_CLIENT_CACHE_RESPONSE_H + +#include +#include +#include + +#include "http_client_response.h" + +namespace OHOS::NetStack::HttpClient { +class HttpCacheResponse { +public: + HttpCacheResponse(); + + ~HttpCacheResponse(); + + void ParseCacheResponseHeader(const std::map &cacheResponseHeader); + + [[nodiscard]] time_t GetDate() const; + + [[nodiscard]] time_t GetExpires() const; + + [[nodiscard]] time_t GetLastModified() const; + + [[nodiscard]] std::string GetLastModifiedStr() const; + + [[nodiscard]] std::string GetDateStr() const; + + [[nodiscard]] std::string GetEtag() const; + + [[nodiscard]] std::string GetAge() const; + + [[nodiscard]] time_t GetAgeSeconds() const; + + [[nodiscard]] time_t GetResponseTime() const; + + [[nodiscard]] bool IsMustRevalidate() const; + + [[nodiscard]] bool IsNoCache() const; + + [[nodiscard]] bool IsNoStore() const; + + [[nodiscard]] bool IsPublicCache() const; + + [[nodiscard]] bool IsPrivateCache() const; + + [[nodiscard]] bool IsProxyRevalidate() const; + + [[nodiscard]] bool IsNoTransform() const; + + [[nodiscard]] time_t GetMaxAgeSeconds() const; + + [[nodiscard]] time_t GetSMaxAgeSeconds() const; + + [[nodiscard]] ResponseCode GetRespCode() const; + + void SetRespCode(ResponseCode respCode); + + void SetResponseTime(const std::string &responseTime); + + void SetRequestTime(const std::string &requestTime); + + [[nodiscard]] time_t GetRequestTime() const; + +private: + void ParseCacheControl(const std::string &cacheControl); + + std::string date_; + std::string expires_; + std::string lastModified_; + std::string etag_; + std::string age_; + std::string responseTime_; + std::string requestTime_; + + bool mustRevalidate_; + bool noCache_; + bool noStore_; + bool publicCache_; + bool privateCache_; + bool proxyRevalidate_; + bool noTransform_; + + std::string maxAge_; + std::string sMaxAge_; + + ResponseCode respCode_; +}; +} // namespace OHOS::NetStack::HttpClient +#endif /* HTTP_CLIENT_CACHE_RESPONSE_H */ diff --git a/interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_strategy.h b/interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_strategy.h new file mode 100644 index 000000000..5f0832fc4 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/cache_strategy/include/http_cache_strategy.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HTTP_CLIENT_CACHE_STRATEGY_H +#define HTTP_CLIENT_CACHE_STRATEGY_H + +#include +#include +#include + +#include "http_cache_request.h" +#include "http_cache_response.h" +#include "http_client_request.h" +#include "http_client_response.h" + +namespace OHOS::NetStack::HttpClient { +enum CacheStatus { + FRESH, + STALE, + DENY +}; + +class HttpCacheStrategy { +public: + HttpCacheStrategy() = delete; + + explicit HttpCacheStrategy(HttpClientRequest &requestOptions); + + CacheStatus RunStrategy(HttpClientResponse &response); + + bool IsCacheable(const HttpClientResponse &response); + + bool CouldUseCache(); + +private: + CacheStatus RunStrategyInternal(HttpClientResponse &response); + + bool IsCacheable(const HttpCacheResponse &cacheResponse); + + int64_t CacheResponseAgeMillis(); + + std::tuple GetFreshness(); + + int64_t ComputeFreshnessLifetimeMillis(); + + int64_t ComputeFreshnessLifetimeSecondsInternal(); + + void UpdateRequestHeader(const std::string &etag, const std::string &lastModified, const std::string &date); + + HttpClientRequest &requestOptions_; + + HttpCacheResponse cacheResponse_; + + HttpCacheRequest cacheRequest_; +}; +} // namespace OHOS::NetStack::HttpClient +#endif /* HTTP_CLIENT_CACHE_STRATEGY_H */ diff --git a/interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_request.cpp b/interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_request.cpp new file mode 100644 index 000000000..693fb59b3 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_request.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "http_cache_request.h" + +#include "casche_constant.h" +#include "netstack_common_utils.h" +#include "http_client_time.h" + +namespace OHOS::NetStack::HttpClient { +void HttpCacheRequest::ParseCacheControl(const std::string &cacheControl) +{ + auto vec = CommonUtils::Split(cacheControl, SPLIT); + for (const auto &str : vec) { + if (str == NO_CACHE) { + noCache_ = true; + } else if (str == NO_STORE) { + noStore_ = true; + } else if (str == NO_TRANSFORM) { + noTransform_ = true; + } else if (str == ONLY_IF_CACHED) { + onlyIfCached_ = true; + } + auto pos = str.find(EQUAL); + if (pos != std::string::npos) { + std::string key = str.substr(0, pos); + std::string value = str.substr(pos + 1); + if (key == MAX_AGE) { + maxAge_ = value; + } else if (key == MAX_STALE) { + maxStale_ = value; + } else if (key == MIN_FRESH) { + minFresh_ = value; + } + } + } +} + +void HttpCacheRequest::ParseRequestHeader(const std::map &requestHeader) +{ + if (requestHeader.empty()) { + return; + } + + for (const auto &iterRequest : requestHeader) { + std::string key = CommonUtils::ToLower(iterRequest.first); + std::string value = iterRequest.second; + + if (key == CACHE_CONTROL) { + ParseCacheControl(value); + } else if (key == IF_MODIFIED_SINCE) { + ifModifiedSince_ = value; + } else if (key == IF_NONE_MATCH) { + ifNoneMatch_ = value; + } + } +} + +void HttpCacheRequest::SetRequestTime(const std::string &requestTime) +{ + requestTime_ = requestTime; +} + +time_t HttpCacheRequest::GetIfModifiedSince() const +{ + if (ifModifiedSince_.empty()) { + return INVALID_TIME; + } + return HttpTime::StrTimeToTimestamp(ifModifiedSince_); +} + +time_t HttpCacheRequest::GetRequestTime() const +{ + if (requestTime_.empty()) { + return INVALID_TIME; + } + return HttpTime::StrTimeToTimestamp(requestTime_); +} + +time_t HttpCacheRequest::GetMaxAgeSeconds() const +{ + if (maxAge_.empty()) { + return INVALID_TIME; + } + + return std::strtol(maxAge_.c_str(), nullptr, DECIMAL); +} + +time_t HttpCacheRequest::GetMaxStaleSeconds() const +{ + if (maxStale_.empty()) { + return INVALID_TIME; + } + + return std::strtol(maxStale_.c_str(), nullptr, DECIMAL); +} + +time_t HttpCacheRequest::GetMinFreshSeconds() const +{ + if (minFresh_.empty()) { + return INVALID_TIME; + } + return std::strtol(minFresh_.c_str(), nullptr, DECIMAL); +} + +bool HttpCacheRequest::IsNoCache() const +{ + return noCache_; +} + +bool HttpCacheRequest::IsNoStore() const +{ + return noStore_; +} + +bool HttpCacheRequest::IsNoTransform() const +{ + return noTransform_; +} + +bool HttpCacheRequest::IsOnlyIfCached() const +{ + return onlyIfCached_; +} + +std::string HttpCacheRequest::GetIfNoneMatch() const +{ + return ifNoneMatch_; +} + +HttpCacheRequest::HttpCacheRequest() : noCache_(false), noStore_(false), noTransform_(false), onlyIfCached_(false) {} + +HttpCacheRequest::~HttpCacheRequest() = default; +} // namespace OHOS::NetStack::HttpClient diff --git a/interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_response.cpp b/interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_response.cpp new file mode 100644 index 000000000..ec7de9e4d --- /dev/null +++ b/interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_response.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "http_cache_response.h" + +#include "casche_constant.h" +#include "netstack_common_utils.h" +#include "http_client_time.h" + +namespace OHOS::NetStack::HttpClient { +void HttpCacheResponse::ParseCacheControl(const std::string &cacheControl) +{ + auto vec = CommonUtils::Split(cacheControl, SPLIT); + + for (const auto &str : vec) { + if (str == NO_CACHE) { + noCache_ = true; + } else if (str == NO_STORE) { + noStore_ = true; + } else if (str == NO_TRANSFORM) { + noTransform_ = true; + } else if (str == MUST_REVALIDATE) { + mustRevalidate_ = true; + } else if (str == PUBLIC) { + publicCache_ = true; + } else if (str == PRIVATE) { + privateCache_ = true; + } else if (str == PROXY_REVALIDATE) { + proxyRevalidate_ = true; + } + auto pos = str.find('='); + if (pos != std::string::npos) { + std::string key = str.substr(0, pos); + std::string value = str.substr(pos + 1); + if (key == MAX_AGE) { + maxAge_ = value; + } else if (key == S_MAXAGE) { + sMaxAge_ = value; + } + } + } +} + +void HttpCacheResponse::ParseCacheResponseHeader(const std::map &cacheResponseHeader) +{ + if (cacheResponseHeader.empty()) { + return; + } + for (const auto &iterCacheResponse : cacheResponseHeader) { + std::string key = CommonUtils::ToLower(iterCacheResponse.first); + std::string value = iterCacheResponse.second; + + if (key == CACHE_CONTROL) { + ParseCacheControl(value); + } else if (key == EXPIRES) { + expires_ = value; + } else if (key == LAST_MODIFIED) { + lastModified_ = value; + } else if (key == ETAG) { + etag_ = value; + } else if (key == AGE) { + age_ = value; + } else if (key == DATE) { + date_ = value; + } + } +} + +time_t HttpCacheResponse::GetDate() const +{ + if (date_.empty()) { + return INVALID_TIME; + } + + return HttpTime::StrTimeToTimestamp(date_); +} + +time_t HttpCacheResponse::GetExpires() const +{ + if (expires_.empty()) { + return INVALID_TIME; + } + + return HttpTime::StrTimeToTimestamp(expires_); +} + +time_t HttpCacheResponse::GetLastModified() const +{ + if (lastModified_.empty()) { + return INVALID_TIME; + } + + return HttpTime::StrTimeToTimestamp(lastModified_); +} + +std::string HttpCacheResponse::GetLastModifiedStr() const +{ + return lastModified_; +} + +std::string HttpCacheResponse::GetDateStr() const +{ + return date_; +} + +std::string HttpCacheResponse::GetEtag() const +{ + return etag_; +} + +std::string HttpCacheResponse::GetAge() const +{ + return age_; +} + +time_t HttpCacheResponse::GetAgeSeconds() const +{ + if (age_.empty()) { + return INVALID_TIME; + } + return std::strtol(age_.c_str(), nullptr, DECIMAL); +} + +time_t HttpCacheResponse::GetMaxAgeSeconds() const +{ + if (maxAge_.empty()) { + return INVALID_TIME; + } + return std::strtol(maxAge_.c_str(), nullptr, DECIMAL); +} + +time_t HttpCacheResponse::GetSMaxAgeSeconds() const +{ + if (sMaxAge_.empty()) { + return INVALID_TIME; + } + return std::strtol(sMaxAge_.c_str(), nullptr, DECIMAL); +} + +time_t HttpCacheResponse::GetResponseTime() const +{ + if (responseTime_.empty()) { + return INVALID_TIME; + } + return HttpTime::StrTimeToTimestamp(responseTime_); +} + +bool HttpCacheResponse::IsMustRevalidate() const +{ + return mustRevalidate_; +} + +bool HttpCacheResponse::IsNoCache() const +{ + return noCache_; +} + +bool HttpCacheResponse::IsNoStore() const +{ + return noStore_; +} + +bool HttpCacheResponse::IsPublicCache() const +{ + return publicCache_; +} + +bool HttpCacheResponse::IsPrivateCache() const +{ + return privateCache_; +} + +bool HttpCacheResponse::IsProxyRevalidate() const +{ + return proxyRevalidate_; +} + +bool HttpCacheResponse::IsNoTransform() const +{ + return noTransform_; +} + +ResponseCode HttpCacheResponse::GetRespCode() const +{ + return respCode_; +} + +void HttpCacheResponse::SetResponseTime(const std::string &responseTime) +{ + responseTime_ = responseTime; +} + +void HttpCacheResponse::SetRespCode(ResponseCode respCode) +{ + respCode_ = respCode; +} + +void HttpCacheResponse::SetRequestTime(const std::string &requestTime) +{ + requestTime_ = requestTime; +} + +time_t HttpCacheResponse::GetRequestTime() const +{ + if (requestTime_.empty()) { + return INVALID_TIME; + } + return HttpTime::StrTimeToTimestamp(requestTime_); +} + +HttpCacheResponse::HttpCacheResponse() + : mustRevalidate_(false), + noCache_(false), + noStore_(false), + publicCache_(false), + privateCache_(false), + proxyRevalidate_(false), + noTransform_(false), + respCode_(static_cast(0)) +{ +} + +HttpCacheResponse::~HttpCacheResponse() = default; +} // namespace OHOS::NetStack::HttpClient diff --git a/interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_strategy.cpp b/interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_strategy.cpp new file mode 100644 index 000000000..9df07d765 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/cache_strategy/src/http_cache_strategy.cpp @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "http_cache_strategy.h" + +#include +#include + +#include "casche_constant.h" +#include "http_cache_request.h" +#include "http_cache_response.h" +#include "http_client_constant.h" +#include "http_client_time.h" +#include "netstack_log.h" + +static constexpr int64_t ONE_DAY_MILLISECONDS = 24 * 60 * 60 * 1000L; +static constexpr int64_t CONVERT_TO_MILLISECONDS = 1000; +static constexpr const char *KEY_RANGE = "range"; + +// RFC 7234 + +namespace OHOS::NetStack::HttpClient { +HttpCacheStrategy::HttpCacheStrategy(HttpClientRequest &requestOptions) : requestOptions_(requestOptions) +{ + cacheRequest_.ParseRequestHeader(requestOptions_.GetHeaders()); + cacheRequest_.SetRequestTime(requestOptions_.GetRequestTime()); +} + +bool HttpCacheStrategy::CouldUseCache() +{ + return requestOptions_.GetMethod() == HttpConstant::HTTP_METHOD_GET || + requestOptions_.GetMethod() == HttpConstant::HTTP_METHOD_HEAD || + requestOptions_.GetHeaders().find(KEY_RANGE) != requestOptions_.GetHeaders().end(); +} + +CacheStatus HttpCacheStrategy::RunStrategy(HttpClientResponse &response) +{ + cacheResponse_.ParseCacheResponseHeader(response.GetHeaders()); + cacheResponse_.SetRespCode(static_cast(response.GetResponseCode())); + cacheResponse_.SetResponseTime(response.GetResponseTime()); + cacheResponse_.SetRequestTime(response.GetRequestTime()); + return RunStrategyInternal(response); +} + +bool HttpCacheStrategy::IsCacheable(const HttpClientResponse &response) +{ + if (!CouldUseCache()) { + return false; + } + HttpCacheResponse tempCacheResponse; + tempCacheResponse.ParseCacheResponseHeader(response.GetHeaders()); + tempCacheResponse.SetRespCode(static_cast(response.GetResponseCode())); + return IsCacheable(tempCacheResponse); +} + +int64_t HttpCacheStrategy::CacheResponseAgeMillis() +{ + // 4.2.3. Calculating Age + + /* The following data is used for the age calculation: + + age_value + The term "age_value" denotes the value of the Age header field (Section 5.1), in a form appropriate for arithmetic + operation; or 0, if not available. + + date_value + The term "date_value" denotes the value of the Date header field, in a form appropriate for arithmetic operations. + See Section 7.1.1.2 of [RFC7231] for the definition of the Date header field, and for requirements regarding + responses without it. + + now + The term "now" means "the current value of the clock at the host performing the calculation". A host ought to use + NTP ([RFC5905]) or some similar protocol to synchronize its clocks to Coordinated Universal Time. + + request_time + The current value of the clock at the host at the time the request resulting in the stored response was made. + + response_time + The current value of the clock at the host at the time the response was received. + A response's age can be calculated in two entirely independent ways: + + the "apparent_age": response_time minus date_value, if the local clock is reasonably well synchronized to the origin + server's clock. If the result is negative, the result is replaced by zero. the "corrected_age_value", if all of the + caches along the response path implement HTTP/1.1. A cache MUST interpret this value relative to the time the + request was initiated, not the time that the response was received. apparent_age = max(0, response_time - + date_value); + + response_delay = response_time - request_time; + corrected_age_value = age_value + response_delay; + These are combined as + + corrected_initial_age = max(apparent_age, corrected_age_value); + unless the cache is confident in the value of the Age header field (e.g., because there are no HTTP/1.0 hops in the + Via header field), in which case the corrected_age_value MAY be used as the corrected_initial_age. + + The current_age of a stored response can then be calculated by adding the amount of time (in seconds) since the + stored response was last validated by the origin server to the corrected_initial_age. + + resident_time = now - response_time; + current_age = corrected_initial_age + resident_time; */ + + int64_t age = std::max(0, cacheResponse_.GetAgeSeconds()); + int64_t dateTime = std::max(0, cacheResponse_.GetDate()); + int64_t nowTime = std::max(0, HttpTime::GetNowTimeSeconds()); + int64_t requestTime = std::max(0, cacheResponse_.GetRequestTime()); + int64_t responseTime = std::max(0, cacheResponse_.GetResponseTime()); + + NETSTACK_LOGI("CacheResponseAgeMillis: %{public}lld, %{public}lld, %{public}lld, %{public}lld, %{public}lld", + static_cast(age), static_cast(dateTime), static_cast(nowTime), + static_cast(requestTime), static_cast(responseTime)); + + int64_t apparentAge = std::max(0, responseTime - dateTime); + int64_t responseDelay = std::max(0, responseTime - requestTime); + int64_t correctedAgeValue = age + responseDelay; + int64_t correctedInitialAge = std::max(apparentAge, correctedAgeValue); + + int64_t residentTime = std::max(0, nowTime - responseTime); + + return (correctedInitialAge + residentTime) * CONVERT_TO_MILLISECONDS; +} + +int64_t HttpCacheStrategy::ComputeFreshnessLifetimeSecondsInternal() +{ + int64_t sMaxAge = cacheResponse_.GetSMaxAgeSeconds(); + if (sMaxAge != INVALID_TIME) { + // If the cache is shared and the s-maxage response directive (Section 5.2.2.9) is present, use its value + return sMaxAge; + } + + int64_t maxAge = cacheResponse_.GetMaxAgeSeconds(); + if (maxAge != INVALID_TIME) { + // If the max-age response directive (Section 5.2.2.8) is present, use its value + return maxAge; + } + + if (cacheResponse_.GetExpires() != INVALID_TIME) { + // If the Expires response header field (Section 5.3) is present, use its value minus the value of the Date + // response header field + int64_t responseTime = cacheResponse_.GetResponseTime(); + if (cacheResponse_.GetDate() != INVALID_TIME) { + responseTime = cacheResponse_.GetDate(); + } + int64_t delta = cacheResponse_.GetExpires() - responseTime; + return std::max(delta, 0); + } + + if (cacheResponse_.GetLastModified() != INVALID_TIME) { + // 4.2.2. Calculating Heuristic Freshness + int64_t requestTime = cacheRequest_.GetRequestTime(); + if (cacheResponse_.GetDate() != INVALID_TIME) { + requestTime = cacheResponse_.GetDate(); + } + int64_t delta = requestTime - cacheResponse_.GetLastModified(); + return std::max(delta / DECIMAL, 0); + } + + return 0; +} + +int64_t HttpCacheStrategy::ComputeFreshnessLifetimeMillis() +{ + int64_t lifeTime = ComputeFreshnessLifetimeSecondsInternal(); + + int64_t reqMaxAge = cacheRequest_.GetMaxAgeSeconds(); + if (reqMaxAge != INVALID_TIME) { + lifeTime = std::min(lifeTime, reqMaxAge); + } + + NETSTACK_LOGI("lifeTime=%{public}lld", static_cast(lifeTime)); + return lifeTime * CONVERT_TO_MILLISECONDS; +} + +void HttpCacheStrategy::UpdateRequestHeader(const std::string &etag, + const std::string &lastModified, + const std::string &date) +{ + if (!etag.empty()) { + requestOptions_.SetHeader(IF_NONE_MATCH, etag); + } else if (!lastModified.empty()) { + requestOptions_.SetHeader(IF_MODIFIED_SINCE, lastModified); + } else if (!date.empty()) { + requestOptions_.SetHeader(IF_MODIFIED_SINCE, date); + } +} + +bool HttpCacheStrategy::IsCacheable(const HttpCacheResponse &cacheResponse) +{ + switch (cacheResponse.GetRespCode()) { + case ResponseCode::OK: + case ResponseCode::NOT_AUTHORITATIVE: + case ResponseCode::NO_CONTENT: + case ResponseCode::MULT_CHOICE: + case ResponseCode::MOVED_PERM: + case ResponseCode::NOT_FOUND: + case ResponseCode::BAD_METHOD: + case ResponseCode::GONE: + case ResponseCode::REQ_TOO_LONG: + case ResponseCode::NOT_IMPLEMENTED: + // These codes can be cached unless headers forbid it. + break; + + case ResponseCode::MOVED_TEMP: + if (cacheResponse.GetExpires() != INVALID_TIME || cacheResponse.GetMaxAgeSeconds() != INVALID_TIME || + cacheResponse.IsPublicCache() || cacheResponse.IsPrivateCache()) { + break; + } + return false; + + default: + return false; + } + + return !cacheResponse.IsNoStore() && !cacheRequest_.IsNoStore(); +} + +std::tuple HttpCacheStrategy::GetFreshness() +{ + int64_t ageMillis = CacheResponseAgeMillis(); + + int64_t lifeTime = ComputeFreshnessLifetimeMillis(); + + int64_t minFreshMillis = std::max(0, cacheRequest_.GetMinFreshSeconds() * CONVERT_TO_MILLISECONDS); + + int64_t maxStaleMillis = 0; + if (!cacheResponse_.IsMustRevalidate()) { + maxStaleMillis = std::max(0, cacheRequest_.GetMaxStaleSeconds() * CONVERT_TO_MILLISECONDS); + } + + NETSTACK_LOGI("GetFreshness: %{public}lld, %{public}lld, %{public}lld, %{public}lld", + static_cast(ageMillis), static_cast(minFreshMillis), + static_cast(lifeTime), static_cast(maxStaleMillis)); + return {ageMillis, minFreshMillis, lifeTime, maxStaleMillis}; +} + +CacheStatus HttpCacheStrategy::RunStrategyInternal(HttpClientResponse &response) +{ + if (cacheRequest_.IsNoCache()) { + NETSTACK_LOGI("return DENY"); + return DENY; + } + + if (cacheResponse_.IsNoCache()) { + NETSTACK_LOGI("return STALE"); + return STALE; + } + + if (cacheRequest_.IsOnlyIfCached()) { + NETSTACK_LOGI("return FRESH"); + return FRESH; + } + + // T.B.D https and TLS handshake + if (!IsCacheable(cacheResponse_)) { + NETSTACK_LOGI("return DENY"); + return DENY; + } + + if (cacheRequest_.GetIfModifiedSince() != INVALID_TIME || !cacheRequest_.GetIfNoneMatch().empty()) { + NETSTACK_LOGI("return DENY"); + return DENY; + } + + auto [ageMillis, minFreshMillis, lifeTime, maxStaleMillis] = GetFreshness(); + + if (ageMillis + minFreshMillis < lifeTime + maxStaleMillis) { + if (ageMillis + minFreshMillis >= lifeTime) { + response.SetWarning("110 \"Response is STALE\""); + } + + if (ageMillis > ONE_DAY_MILLISECONDS && cacheRequest_.GetMaxAgeSeconds() == INVALID_TIME && + cacheResponse_.GetExpires() == INVALID_TIME) { + response.SetWarning("113 \"Heuristic expiration\""); + } + + NETSTACK_LOGI("return FRESH"); + return FRESH; + } + + // The cache has expired and the request needs to be re-initialized + UpdateRequestHeader(cacheResponse_.GetEtag(), cacheResponse_.GetLastModifiedStr(), cacheResponse_.GetDateStr()); + + NETSTACK_LOGI("return STALE"); + return STALE; +} +} // namespace OHOS::NetStack::HttpClient diff --git a/interfaces/innerkits/http_client/cache/lru_cache/include/disk_handler.h b/interfaces/innerkits/http_client/cache/lru_cache/include/disk_handler.h new file mode 100644 index 000000000..3e9cfb969 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/lru_cache/include/disk_handler.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMUNICATIONNETSTACK_HTTP_CLIENT_DISK_HANDLER_H +#define COMMUNICATIONNETSTACK_HTTP_CLIENT_DISK_HANDLER_H + +#include +#include + +namespace OHOS::NetStack::HttpClient { +class DiskHandler final { +public: + DiskHandler() = delete; + + explicit DiskHandler(std::string fileName); + + void Write(const std::string &str); + + void Delete(); + + [[nodiscard]] std::string Read(); + +private: + std::mutex mutex_; + + std::string fileName_; +}; +} // namespace OHOS::NetStack::HttpClient +#endif /* COMMUNICATIONNETSTACK_HTTP_CLIENT_DISK_HANDLER_H */ diff --git a/interfaces/innerkits/http_client/cache/lru_cache/include/lru_cache.h b/interfaces/innerkits/http_client/cache/lru_cache/include/lru_cache.h new file mode 100644 index 000000000..2e790ed1c --- /dev/null +++ b/interfaces/innerkits/http_client/cache/lru_cache/include/lru_cache.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMUNICATIONNETSTACK_HTTP_CLIENT_LRU_CACHE_H +#define COMMUNICATIONNETSTACK_HTTP_CLIENT_LRU_CACHE_H + +#include +#include +#include +#include +#include + +#include "cJSON.h" + +namespace OHOS::NetStack::HttpClient { +class LRUCache { +public: + LRUCache(); + + explicit LRUCache(size_t capacity); + + std::unordered_map Get(const std::string &key); + + void Put(const std::string &key, const std::unordered_map &value); + + void MergeOtherCache(const LRUCache &other); + + cJSON* WriteCacheToJsonValue(); + + void ReadCacheFromJsonValue(const cJSON* root); + + void Clear(); + +private: + struct Node { + std::string key; + std::unordered_map value; + + Node() = delete; + + Node(std::string key, std::unordered_map value); + }; + + void AddNode(const Node &node); + + void MoveNodeToHead(const std::list::iterator &it); + + void EraseTailNode(); + + std::mutex mutex_; + std::unordered_map::iterator> cache_; + std::list nodeList_; + size_t capacity_; + size_t size_; +}; +} // namespace OHOS::NetStack::HttpClient +#endif /* COMMUNICATIONNETSTACK_HTTP_CLIENT_LRU_CACHE_H */ diff --git a/interfaces/innerkits/http_client/cache/lru_cache/include/lru_cache_disk_handler.h b/interfaces/innerkits/http_client/cache/lru_cache/include/lru_cache_disk_handler.h new file mode 100644 index 000000000..a1ed5fbc6 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/lru_cache/include/lru_cache_disk_handler.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMUNICATIONNETSTACK_HTTP_CLIENT_LRU_CACHE_DISK_HANDLER_H +#define COMMUNICATIONNETSTACK_HTTP_CLIENT_LRU_CACHE_DISK_HANDLER_H + +#include + +#include "disk_handler.h" +#include "lru_cache.h" + +static constexpr const int MAX_DISK_CACHE_SIZE = 1024 * 1024 * 10; +static constexpr const int MIN_DISK_CACHE_SIZE = 1024 * 1024; + +namespace OHOS::NetStack::HttpClient { +class LRUCacheDiskHandler { +public: + LRUCacheDiskHandler() = delete; + + LRUCacheDiskHandler(std::string fileName, size_t capacity); + + void WriteCacheToJsonFile(); + + void ReadCacheFromJsonFile(); + + void Delete(); + + void SetCapacity(size_t capacity); + + std::unordered_map Get(const std::string &key); + + void Put(const std::string &key, const std::unordered_map &value); + +private: + LRUCache cache_; + DiskHandler diskHandler_; + std::atomic capacity_; + + cJSON* ReadJsonValueFromFile(); + + void WriteJsonValueToFile(const cJSON *root); +}; +} // namespace OHOS::NetStack::HttpClient +#endif /* COMMUNICATIONNETSTACK_HTTP_CLIENT_LRU_CACHE_DISK_HANDLER_H */ diff --git a/interfaces/innerkits/http_client/cache/lru_cache/src/disk_handler.cpp b/interfaces/innerkits/http_client/cache/lru_cache/src/disk_handler.cpp new file mode 100644 index 000000000..7f0fca939 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/lru_cache/src/disk_handler.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "disk_handler.h" + +#include +#include + +#include "netstack_log.h" + +namespace OHOS::NetStack::HttpClient { +DiskHandler::DiskHandler(std::string fileName) : fileName_(std::move(fileName)) {} + +void DiskHandler::Write(const std::string &str) +{ + std::lock_guard guard(mutex_); + std::ofstream w(fileName_); + if (!w.is_open()) { + return; + } + w << str; + w.close(); +} + +std::string DiskHandler::Read() +{ + std::lock_guard guard(mutex_); + std::ifstream r(fileName_); + if (!r.is_open()) { + return {}; + } + std::stringstream b; + b << r.rdbuf(); + r.close(); + return b.str(); +} + +void DiskHandler::Delete() +{ + std::lock_guard guard(mutex_); + if (remove(fileName_.c_str()) < 0) { + NETSTACK_LOGI("remove file error %{public}d", errno); + } +} +} // namespace OHOS::NetStack::HttpClient diff --git a/interfaces/innerkits/http_client/cache/lru_cache/src/lru_cache.cpp b/interfaces/innerkits/http_client/cache/lru_cache/src/lru_cache.cpp new file mode 100644 index 000000000..06b32f047 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/lru_cache/src/lru_cache.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lru_cache.h" + +#include +#include + +#include "netstack_log.h" + +static constexpr const char *LRU_INDEX = "LRUIndex"; +static constexpr const int DECIMAL_BASE = 10; +static constexpr const int MAX_SIZE = 1024 * 1024; +static constexpr const size_t INVALID_SIZE = SIZE_MAX; + +namespace OHOS::NetStack::HttpClient { +static size_t GetMapValueSize(const std::unordered_map &m) +{ + size_t size = 0; + for (const auto &p : m) { + if (p.second.size() > MAX_SIZE) { + return INVALID_SIZE; + } + if (size + p.second.size() > MAX_SIZE) { + return INVALID_SIZE; + } + size += p.second.size(); + } + if (size > MAX_SIZE || size == 0) { + return INVALID_SIZE; + } + return size; +} + +LRUCache::Node::Node(std::string key, std::unordered_map value) + : key(std::move(key)), value(std::move(value)) +{ +} + +LRUCache::LRUCache() : capacity_(MAX_SIZE), size_(0) {} + +LRUCache::LRUCache(size_t capacity) : capacity_(std::min(MAX_SIZE, capacity)), size_(0) {} + +void LRUCache::AddNode(const Node &node) +{ + nodeList_.emplace_front(node); + cache_[node.key] = nodeList_.begin(); + size_ += GetMapValueSize(node.value); +} + +void LRUCache::MoveNodeToHead(const std::list::iterator &it) +{ + std::string key = it->key; + std::unordered_map value = it->value; + nodeList_.erase(it); + nodeList_.emplace_front(key, value); + cache_[key] = nodeList_.begin(); +} + +void LRUCache::EraseTailNode() +{ + if (nodeList_.empty()) { + return; + } + Node node = nodeList_.back(); + nodeList_.pop_back(); + cache_.erase(node.key); + size_ -= GetMapValueSize(node.value); +} + +std::unordered_map LRUCache::Get(const std::string &key) +{ + std::lock_guard guard(mutex_); + + if (cache_.find(key) == cache_.end()) { + return {}; + } + auto it = cache_[key]; + auto value = it->value; + MoveNodeToHead(it); + return value; +} + +void LRUCache::Put(const std::string &key, const std::unordered_map &value) +{ + std::lock_guard guard(mutex_); + + if (GetMapValueSize(value) == INVALID_SIZE) { + NETSTACK_LOGE("value is invalid(0 or too long) can not insert to cache"); + return; + } + + if (cache_.find(key) == cache_.end()) { + AddNode(Node(key, value)); + while (size_ > capacity_) { + EraseTailNode(); + } + return; + } + + auto it = cache_[key]; + + size_ -= GetMapValueSize(it->value); + it->value = value; + size_ += GetMapValueSize(it->value); + + MoveNodeToHead(it); + while (size_ > capacity_) { + EraseTailNode(); + } +} + +void LRUCache::MergeOtherCache(const LRUCache &other) +{ + std::list reverseList; + { + // set mutex in min scope + std::lock_guard guard(mutex_); + if (other.nodeList_.empty()) { + return; + } + reverseList = other.nodeList_; + } + reverseList.reverse(); + for (const auto &node : reverseList) { + Put(node.key, node.value); + } +} + +cJSON* LRUCache::WriteCacheToJsonValue() +{ + cJSON* root = cJSON_CreateObject(); + + int index = 0; + { + // set mutex in min scope + std::lock_guard guard(mutex_); + for (const auto &node : nodeList_) { + cJSON *nodeKey = cJSON_CreateObject(); + for (const auto &p : node.value) { + cJSON_AddItemToObject(nodeKey, p.first.c_str(), cJSON_CreateString(p.second.c_str())); + } + cJSON_AddItemToObject(nodeKey, LRU_INDEX, cJSON_CreateString(std::to_string(index).c_str())); + ++index; + cJSON_AddItemToObject(root, node.key.c_str(), nodeKey); + } + } + return root; +} + +void LRUCache::ReadCacheFromJsonValue(const cJSON* root) +{ + std::vector nodeVec; + for (int32_t i = 0; i < cJSON_GetArraySize(root); i++) { + cJSON *keyItem = cJSON_GetArrayItem(root, i); + if (keyItem == nullptr || !cJSON_IsObject(keyItem)) { + continue; + } + std::string key = keyItem->string; + std::unordered_map m; + for (int32_t j = 0; j < cJSON_GetArraySize(keyItem); j++) { + cJSON *valueItem = cJSON_GetArrayItem(keyItem, j); + if (valueItem == nullptr) { + NETSTACK_LOGD("valueItem is null"); + continue; + } + std::string valueKey = valueItem->string; + m[valueKey] = cJSON_GetStringValue(valueItem); + } + + if (m.find(LRU_INDEX) != m.end()) { + nodeVec.emplace_back(key, m); + } + } + std::sort(nodeVec.begin(), nodeVec.end(), [](Node &a, Node &b) { + return std::strtol(a.value[LRU_INDEX].c_str(), nullptr, DECIMAL_BASE) > + std::strtol(b.value[LRU_INDEX].c_str(), nullptr, DECIMAL_BASE); + }); + for (auto &node : nodeVec) { + node.value.erase(LRU_INDEX); + if (!node.value.empty()) { + Put(node.key, node.value); + } + } +} + +void LRUCache::Clear() +{ + std::lock_guard guard(mutex_); + cache_.clear(); + nodeList_.clear(); +} +} // namespace OHOS::NetStack::HttpClient diff --git a/interfaces/innerkits/http_client/cache/lru_cache/src/lru_cache_disk_handler.cpp b/interfaces/innerkits/http_client/cache/lru_cache/src/lru_cache_disk_handler.cpp new file mode 100644 index 000000000..e66449cf6 --- /dev/null +++ b/interfaces/innerkits/http_client/cache/lru_cache/src/lru_cache_disk_handler.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lru_cache_disk_handler.h" + +#include + +#include "netstack_log.h" + +namespace OHOS::NetStack::HttpClient { +LRUCacheDiskHandler::LRUCacheDiskHandler(std::string fileName, size_t capacity) + : diskHandler_(std::move(fileName)), + capacity_(std::max(std::min(MAX_DISK_CACHE_SIZE, capacity), MIN_DISK_CACHE_SIZE)) +{ +} + +void LRUCacheDiskHandler::SetCapacity(size_t capacity) +{ + capacity_ = std::max(std::min(MAX_DISK_CACHE_SIZE, capacity), MIN_DISK_CACHE_SIZE); + WriteCacheToJsonFile(); +} + +void LRUCacheDiskHandler::Delete() +{ + cache_.Clear(); + diskHandler_.Delete(); +} + +cJSON* LRUCacheDiskHandler::ReadJsonValueFromFile() +{ + std::string jsonStr = diskHandler_.Read(); + cJSON *root = cJSON_Parse(jsonStr.c_str()); + if (root == nullptr) { + NETSTACK_LOGE("parse json not success, maybe file is broken"); + return nullptr; + } + return root; +} + +void LRUCacheDiskHandler::WriteJsonValueToFile(const cJSON *root) +{ + char *jsonStr = cJSON_Print(root); + if (jsonStr == nullptr) { + NETSTACK_LOGE("write json failed"); + return; + } + std::string s = jsonStr; + diskHandler_.Write(s); + free(jsonStr); +} + +void LRUCacheDiskHandler::WriteCacheToJsonFile() +{ + LRUCache oldCache(capacity_); + cJSON *readRoot = ReadJsonValueFromFile(); + oldCache.ReadCacheFromJsonValue(readRoot); + cJSON_Delete(readRoot); + oldCache.MergeOtherCache(cache_); + cJSON *writeRoot = oldCache.WriteCacheToJsonValue(); + WriteJsonValueToFile(writeRoot); + cJSON_Delete(writeRoot); + cache_.Clear(); +} + +void LRUCacheDiskHandler::ReadCacheFromJsonFile() +{ + cJSON *root = ReadJsonValueFromFile(); + cache_.ReadCacheFromJsonValue(root); + cJSON_Delete(root); +} + +std::unordered_map LRUCacheDiskHandler::Get(const std::string &key) +{ + auto valueFromMemory = cache_.Get(key); + if (!valueFromMemory.empty()) { + return valueFromMemory; + } + + LRUCache diskCache(capacity_); + cJSON *root = ReadJsonValueFromFile(); + diskCache.ReadCacheFromJsonValue(root); + cJSON_Delete(root); + auto valueFromDisk = diskCache.Get(key); + cache_.Put(key, valueFromDisk); + return valueFromDisk; +} + +void LRUCacheDiskHandler::Put(const std::string &key, const std::unordered_map &value) +{ + cache_.Put(key, value); +} +} // namespace OHOS::NetStack::HttpClient diff --git a/interfaces/innerkits/http_client/include/common.h b/interfaces/innerkits/http_client/include/common.h new file mode 100644 index 000000000..7be71f7eb --- /dev/null +++ b/interfaces/innerkits/http_client/include/common.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMUNICATIONNETSTACK_COMMON_H +#define COMMUNICATIONNETSTACK_COMMON_H + +namespace OHOS { +namespace NetStack { +namespace HttpClient { +enum class HttpDataType { + STRING = 0, + OBJECT, + ARRAY_BUFFER, + NO_DATA_TYPE, +}; +} // namespace HttpClient +} // namespace NetStack +} // namespace OHOS + +#endif /* COMMUNICATIONNETSTACK_COMMON_H */ diff --git a/interfaces/innerkits/http_client/include/http_client_constant.h b/interfaces/innerkits/http_client/include/http_client_constant.h index 43d4cad4a..5cc6e3719 100644 --- a/interfaces/innerkits/http_client/include/http_client_constant.h +++ b/interfaces/innerkits/http_client/include/http_client_constant.h @@ -39,6 +39,8 @@ public: /* default options */ static const uint32_t DEFAULT_READ_TIMEOUT; static const uint32_t DEFAULT_CONNECT_TIMEOUT; + static const uint32_t DEFAULT_MAX_LIMIT; + static const uint32_t MAX_LIMIT; static const size_t MAX_JSON_PARSE_SIZE; static const size_t MAX_DATA_LIMIT; @@ -75,6 +77,7 @@ public: static const char *const HTTP_URL_PARAM_SEPARATOR; static const char *const HTTP_URL_NAME_VALUE_SEPARATOR; static const char *const HTTP_HEADER_SEPARATOR; + static const char *const HTTP_HEADER_BLANK_SEPARATOR; static const char *const HTTP_LINE_SEPARATOR; static const char *const HTTP_RESPONSE_HEADER_SEPARATOR; @@ -91,6 +94,7 @@ public: static const char *const HTTP_CONTENT_TYPE_JSON; static const char *const HTTP_CONTENT_TYPE_OCTET_STREAM; static const char *const HTTP_CONTENT_TYPE_IMAGE; + static const char *const HTTP_CONTENT_TYPE_MULTIPART; static const char *const HTTP_CONTENT_ENCODING_GZIP; diff --git a/interfaces/innerkits/http_client/include/http_client_request.h b/interfaces/innerkits/http_client/include/http_client_request.h index b8f8599c6..dce13f2cc 100644 --- a/interfaces/innerkits/http_client/include/http_client_request.h +++ b/interfaces/innerkits/http_client/include/http_client_request.h @@ -19,6 +19,9 @@ #include #include #include +#include "http_client_secure_data.h" +#include "http_client_tls_config.h" +#include "common.h" namespace OHOS { namespace NetStack { @@ -55,6 +58,52 @@ struct HttpClientCert { std::string keyPassword; }; +struct EscapedData { + HttpDataType dataType; + // If the type is object or arrayBuffer, the data format is required to be JSON. + std::string data; +}; + +struct HttpMultiFormData { + HttpMultiFormData() = default; + ~HttpMultiFormData() = default; + std::string name; + std::string contentType; + std::string remoteFileName; + std::string data; + std::string filePath; +}; + +enum class HttpAuthenticationType { + AUTO, + BASIC, + NTLM, + DIGEST, +}; + +struct HttpCredential { + HttpClient::SecureData username; + HttpClient::SecureData password; +}; + +struct HttpServerAuthentication { + HttpCredential credential; + HttpAuthenticationType authenticationType = HttpAuthenticationType::AUTO; +}; + +struct TlsOption { + std::unordered_set cipherSuite; + TlsVersion tlsVersionMin = TlsVersion::DEFAULT; + TlsVersion tlsVersionMax = TlsVersion::DEFAULT; +}; + +struct CertsPath { + CertsPath() = default; + ~CertsPath() = default; + std::vector certPathList; + std::string certFile; +}; + class HttpClientRequest { public: /** @@ -160,6 +209,72 @@ public: */ void SetAddressFamily(const std::string &addressFamily); + /** + * Set the UsingCache for the HTTP request. + * @param UsingCache The UsingCache value to be set. + */ + void SetUsingCache(bool usingCache); + + /** + * Set the DNSOverHttps for the HTTP request. + * @param DNSOverHttps The DNSOverHttps value to be set. + */ + void SetDNSOverHttps(const std::string &dnsOverHttps); + + /** + * Set the canCertVerify for the HTTP request. + * @param CanCertVerify The CanCertVerify value to be set. + */ + void SetCanSkipCertVerifyFlag(bool canCertVerify); + + /** + * Set the RemoteValidation for the HTTP request. + * @param RemoteValidation The RemoteValidation value to be set. + */ + void SetRemoteValidation(const std::string &remoteValidation); + + /** + * Set the TLSOptions for the HTTP request. + * @param TLSOptions The TLSOptions value to be set. + */ + void SetTLSOptions(const TlsOption &tlsOptions); + + /** + * Set the CertsPath for the HTTP request. + * @param certPathList, certFile The cert info to be set. + */ + void SetCertsPath(std::vector &&certPathList, const std::string &certFile); + + /** + * Set the ExtraData for the HTTP request. + * @param ExtraData The ExtraData value to be set. + */ + void SetExtraData(const EscapedData& extraData); + + /** + * Set the ExpectDataType for the HTTP request. + * @param ExpectDataType The ExpectDataType value to be set. + */ + void SetExpectDataType(HttpDataType dataType); + + /** + * Set the DNSServers for the HTTP request. + * @param DNSServers The DNSServers value to be set. + */ + void SetDNSServers(const std::vector& dnsServers); + + /** + * Add a HttpMultiFormData to the list for the HTTP request. + * @param HttpMultiFormData The HttpMultiFormData value to be set. + */ + void AddMultiFormData(const HttpMultiFormData& data); + + /** + * Set the HttpServerAuthentication for the HTTP request. + * @param HttpServerAuthentication The HttpServerAuthentication value to be set. + */ + void SetServerAuthentication(const HttpServerAuthentication& server_auth); + /** * Get the URL of the HTTP request. * @return The URL of the request. @@ -256,6 +371,72 @@ public: */ [[nodiscard]] const std::string &GetAddressFamily() const; + /** + * Get the UsingCache of the HTTP request. + * @return The UsingCache of the request. + */ + [[nodiscard]] bool GetUsingCache() const; + + /** + * Get the DNSOverHttps of the HTTP request. + * @return The DNSOverHttps of the request. + */ + [[nodiscard]] const std::string& GetDNSOverHttps() const; + + /** + * Get the CanSkipCertVerifyFlag of the HTTP request. + * @return The CanSkipCertVerifyFlag of the request. + */ + [[nodiscard]] bool GetCanSkipCertVerifyFlag() const; + + /** + * Get the RemoteValidation of the HTTP request. + * @return The RemoteValidation of the request. + */ + [[nodiscard]] const std::string& GetRemoteValidation() const; + + /** + * Get the TLSOptions of the HTTP request. + * @return The TLSOptions of the request. + */ + [[nodiscard]] const TlsOption& GetTLSOptions() const; + + /** + * Get the CertsPath of the HTTP request. + * @return The CertsPath of the request. + */ + [[nodiscard]] const CertsPath &GetCertsPath(); + + /** + * Get the ExtraData of the HTTP request. + * @return The ExtraData of the request. + */ + [[nodiscard]] const EscapedData& GetExtraData() const; + + /** + * Get the ExpectDataType of the HTTP request. + * @return The ExpectDataType of the request. + */ + [[nodiscard]] HttpDataType GetExpectDataType() const; + + /** + * Get the DNSServers of the HTTP request. + * @return The DNSServers of the request. + */ + [[nodiscard]] const std::vector& GetDNSServers() const; + + /** + * Get the MultiFormDataList of the HTTP request. + * @return The MultiFormDataList of the request. + */ + [[nodiscard]] const std::vector& GetMultiFormDataList() const; + + /** + * Get the ServerAuthentication of the HTTP request. + * @return The ServerAuthentication of the request. + */ + [[nodiscard]] const HttpServerAuthentication& GetServerAuthentication() const; + /** * Check if the specified method is suitable for a GET request. * @param method The method to check. @@ -282,6 +463,12 @@ public: */ const std::string &GetRequestTime() const; + /** + * Retrieves the request time from the object. + * @return The request time. + */ + uint32_t GetHttpVersion(); + private: std::string url_; std::string method_; @@ -293,6 +480,7 @@ private: HttpProxy proxy_; HttpProxyType proxyType_; std::string caPath_; + CertsPath certsPath_; unsigned int priority_; std::string requestTime_; int64_t resumeFrom_; @@ -300,6 +488,16 @@ private: HttpClientCert clientCert_; std::string addressFamily_; uint32_t maxLimit_; + bool usingCache_; + std::string dnsOverHttps_; + std::string remoteValidation_; + bool canSkipCertVerify_ = false; + TlsOption tlsOptions_; + EscapedData extraData_; + HttpDataType dataType_; + std::vector dnsServers_; + std::vector multiFormDataList_; + HttpServerAuthentication serverAuth_; }; } // namespace HttpClient } // namespace NetStack diff --git a/interfaces/innerkits/http_client/include/http_client_response.h b/interfaces/innerkits/http_client/include/http_client_response.h index 8ef07c039..6de755e38 100644 --- a/interfaces/innerkits/http_client/include/http_client_response.h +++ b/interfaces/innerkits/http_client/include/http_client_response.h @@ -18,6 +18,7 @@ #include #include +#include "common.h" namespace OHOS { namespace NetStack { @@ -167,6 +168,12 @@ public: */ void SetRawHeader(const std::string &header); + /** + * Get the raw header of the HTTP response. + * @return The raw header of the response. + */ + const std::string &GetRawHeader() const; + /** * Sets the cookies for the HTTP response. * @param cookies The cookie string. @@ -191,6 +198,18 @@ public: */ [[nodiscard]] PerformanceInfo GetPerformanceTiming() const; + /** + * Sets the expect type of the HTTP response. + * @param type The expect type. + */ + void SetExpectDataType(const HttpDataType &type); + + /** + * Get the time taken of various stages of HTTP request. + * @return Expected types of the response result. + */ + [[nodiscard]] HttpDataType GetExpectDataType() const; + private: friend class HttpClientTask; @@ -218,6 +237,7 @@ private: std::string result_; PerformanceInfo performanceInfo_; std::vector setCookie_; + HttpDataType dataType_ = HttpDataType::NO_DATA_TYPE; }; } // namespace HttpClient } // namespace NetStack diff --git a/interfaces/innerkits/http_client/include/http_client_secure_data.h b/interfaces/innerkits/http_client/include/http_client_secure_data.h new file mode 100644 index 000000000..75a55820b --- /dev/null +++ b/interfaces/innerkits/http_client/include/http_client_secure_data.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMUNICATIONNETSTACK_HTTP_CLIENT_SECURE_DATA_H +#define COMMUNICATIONNETSTACK_HTTP_CLIENT_SECURE_DATA_H + +#include + +namespace OHOS { +namespace NetStack { +namespace HttpClient { +struct SecureData : public std::string { + ~SecureData(); +}; +} // namespace HttpClient +} // namespace NetStack +} // namespace OHOS +#endif // COMMUNICATIONNETSTACK_HTTP_CLIENT_SECURE_DATA_H diff --git a/interfaces/innerkits/http_client/include/http_client_task.h b/interfaces/innerkits/http_client/include/http_client_task.h index cc5725dc8..5ffc54b33 100644 --- a/interfaces/innerkits/http_client/include/http_client_task.h +++ b/interfaces/innerkits/http_client/include/http_client_task.h @@ -31,6 +31,7 @@ #include "netstack_network_profiler.h" #endif +struct cJSON; namespace OHOS { namespace NetStack { namespace RequestTracer { @@ -196,7 +197,21 @@ public: */ void SetResponse(const HttpClientResponse &response); + void OnHeaderReceive( + const std::function &onHeaderReceive); + bool OffDataReceive(); + bool OffProgress(); + bool OffHeaderReceive(); + bool OffHeadersReceive(); + void SetIsHeaderOnce(bool isOnce); + bool IsHeaderOnce() const; + void SetIsHeadersOnce(bool isOnce); + bool IsHeadersOnce() const; + void SetIsRequestInStream(bool isRequestInStream); + bool IsRequestInStream(); + bool ProcessUsingCache(); RequestTracer::Trace &GetTrace(); + static bool IsBuiltWithOpenSSL(); private: friend class HttpSession; @@ -225,6 +240,13 @@ private: */ bool SetOtherCurlOption(CURL *handle); + /** + * Sets the authentication options for the HTTP request. + * @param handle The Curl handle. + * @return Returns true if the authentication options are set successfully, false otherwise. + */ + bool SetAuthOptions(CURL *handle); + /** * Sets the range options for the HTTP request. * @param handle The Curl handle. @@ -345,6 +367,12 @@ private: */ void DumpHttpPerformance(); + /** + * Sets the DNS servers options for the HTTP request. + * @return Returns true if the Curl options are set successfully, false otherwise. + */ + bool SetDnsOption(CURL *handle); + /** * Sets the DNS cache options for the HTTP request. * @return Returns true if the Curl options are set successfully, false otherwise. @@ -357,12 +385,52 @@ private: */ bool SetIpResolve(CURL *handle); + /** + * Sets the tls option for the HTTP request. + * @return Returns true if the tls option are set successfully, false otherwise. + */ + bool SetTlsOption(CURL *handle); + /** * Gets the start and end download position for the HTTP request. * @return Returns the string of range. If the position is invallid, the string is empty. */ std::string GetRangeString() const; + /** + * Determine whether the given HTTP method belongs to the GET class method. + * @return Returns true if it is a GET class method. + */ + bool MethodForGet(const std::string &method); + + /** + * Determine whether the given HTTP method belongs to the POST class method. + * @return Returns true if it is a POST class method. + */ + bool MethodForPost(const std::string &method); + + /** + * Sets the multipart/form-data for the HTTP request. + * @return Returns true if the set options are set successfully, false otherwise. + */ + bool SetMultiPartOption(CURL *handle); + + bool ReadResopnseFromCache(); + void WriteResopnseToCache(const HttpClientResponse &response); + + bool IsUnReserved(unsigned char in); + bool EncodeUrlParam(std::string &str); + std::string MakeUrl(const std::string &url, std::string param, const std::string &extraParam); + std::string GetJsonFieldValue(const cJSON* item); + void TraverseJson(const cJSON* item, std::string &output); + std::string ParseJsonValueToExtraParam(const std::string &jsonStr); + void HandleMethodForGet(); + bool GetRequestBody(); + + bool SetCallbackFunctions(); + bool SetHttpHeaders(); + bool SetCurlMethod(); + std::function onSucceeded_; std::function onCanceled_; std::function headerWithSetCookie)> onHeadersReceive_; - + std::function onHeaderReceive_; + bool isHeaderOnce_; + bool isHeadersOnce_; + bool isRequestInStream_; HttpClientRequest request_; HttpClientResponse response_; HttpClientError error_; @@ -381,7 +452,8 @@ private: TaskType type_; TaskStatus status_; unsigned int taskId_; - struct curl_slist *curlHeaderList_; + curl_slist *curlHeaderList_; + curl_mime *curMultiPart_; bool canceled_; std::mutex mutex_; diff --git a/interfaces/innerkits/http_client/include/http_client_tls_config.h b/interfaces/innerkits/http_client/include/http_client_tls_config.h new file mode 100644 index 000000000..533ec4f1a --- /dev/null +++ b/interfaces/innerkits/http_client/include/http_client_tls_config.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NETSTACK_HTTP_CLIENT_TLS_CONFIG_H +#define NETSTACK_HTTP_CLIENT_TLS_CONFIG_H + +#include +#include +#include +#include +#include +#include +#include + +#include "securec.h" + +namespace OHOS::NetStack::HttpClient { +enum class CipherSuite { + INVALID = -1, + TLS_AES_128_GCM_SHA256 = 0x1301, + TLS_AES_256_GCM_SHA384 = 0x1302, + TLS_CHACHA20_POLY1305_SHA256 = 0x1303, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xc02b, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xc02f, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xc02c, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xc030, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca9, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca8, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0x009c, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0x009d, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xc009, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xc013, + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0xc00a, + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0xc014, + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002f, + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035, + TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000a, +}; + +enum class TlsVersion { + DEFAULT = 0, + TLSv1_0 = 4, + TLSv1_1 = 5, + TLSv1_2 = 6, + TLSv1_3 = 7, +}; + +struct TlsCipherString { + std::string ciperSuiteString; + std::string tlsV13CiperSuiteString; +}; + +[[nodiscard]] CipherSuite GetTlsCipherSuiteFromStandardName(const std::string &standardName); +[[nodiscard]] std::string GetInnerNameFromCipherSuite(CipherSuite cipherSuite); +[[nodiscard]] TlsCipherString ConvertCipherSuiteToCipherString(const std::unordered_set &cipherSuite); + +} // namespace OHOS::NetStack::HttpClient +#endif // NETSTACK_HTTP_CLIENT_TLS_CONFIG_H diff --git a/interfaces/innerkits/http_client/libhttp_client.map b/interfaces/innerkits/http_client/libhttp_client.map index 5e7508d97..f83b0cbd8 100644 --- a/interfaces/innerkits/http_client/libhttp_client.map +++ b/interfaces/innerkits/http_client/libhttp_client.map @@ -22,6 +22,7 @@ *NetworkProfilerUtils*; *NetworkMessage*; *TlvUtils*; + *CacheProxy*; local: *; }; \ No newline at end of file diff --git a/interfaces/innerkits/rust/netstack_rs/BUILD.gn b/interfaces/innerkits/rust/netstack_rs/BUILD.gn index 2a345ce34..4deaf2a61 100644 --- a/interfaces/innerkits/rust/netstack_rs/BUILD.gn +++ b/interfaces/innerkits/rust/netstack_rs/BUILD.gn @@ -33,7 +33,10 @@ ohos_static_library("netstack_rs_cxx") { "include", "${target_gen_dir}/src", ] - sources = [ "src/cxx/wrapper.cpp" ] + sources = [ + "src/cxx/wrapper.cpp", + "src/cxx/cache_proxy_ani.cpp", + ] sources += get_target_outputs(":netstack_rs_cxx_gen") @@ -69,6 +72,8 @@ ohos_rust_shared_library("netstack_rs") { external_deps = [ "hilog:hilog_rust", + "netmanager_base:ani_rs", + "bounds_checking_function:libsec_shared", ] part_name = "netstack" diff --git a/interfaces/innerkits/rust/netstack_rs/include/cache_proxy_ani.h b/interfaces/innerkits/rust/netstack_rs/include/cache_proxy_ani.h new file mode 100644 index 000000000..75106e552 --- /dev/null +++ b/interfaces/innerkits/rust/netstack_rs/include/cache_proxy_ani.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMUNICATIONNETSTACK_CACHE_PROXY_ANI_H +#define COMMUNICATIONNETSTACK_CACHE_PROXY_ANI_H + +#include "cxx.h" +#include "cache_proxy.h" + +namespace OHOS::Request { + +void RunCacheWithSize(size_t capacity); + +void RunCache(); + +void FlushCache(); + +void StopCacheAndDelete(); + +} // namespace OHOS::Request + +#endif // COMMUNICATIONNETSTACK_CACHE_PROXY_ANI_H diff --git a/interfaces/innerkits/rust/netstack_rs/include/wrapper.h b/interfaces/innerkits/rust/netstack_rs/include/wrapper.h index cd2015f6e..4b1ecd85a 100644 --- a/interfaces/innerkits/rust/netstack_rs/include/wrapper.h +++ b/interfaces/innerkits/rust/netstack_rs/include/wrapper.h @@ -27,6 +27,11 @@ namespace OHOS::Request { using namespace OHOS::NetStack::HttpClient; struct CallbackWrapper; struct PerformanceInfoRust; +struct ClientCert; +struct EscapedDataRust; +struct MultiFormDataRust; +struct ServerAuthentication; +struct TlsConfigRust; void OnCallback(const std::shared_ptr &task, rust::Box callback); @@ -45,6 +50,25 @@ inline void SetHttpProtocol(HttpClientRequest &request, int32_t protocol) request.SetHttpProtocol(static_cast(protocol)); } +inline void SetHttpUsingProxy(HttpClientRequest &request, int32_t proxy) +{ + request.SetHttpProxyType(static_cast(proxy)); +} + +void SetAddressFamily(HttpClientRequest &request, int32_t address_family); +void SetExtraData(HttpClientRequest &request, const EscapedDataRust& extraData); +void SetExpectDataType(HttpClientRequest &request, int32_t expect_data_type); + +void SetClientCert(HttpClientRequest &request, const ClientCert& cert); + +void SetDNSServers(HttpClientRequest &request, const rust::vec& cert); + +void AddMultiFormData(HttpClientRequest &request, const MultiFormDataRust &data); + +void SetServerAuthentication(HttpClientRequest &request, const ServerAuthentication& server_auth); + +void SetTLSOptions(HttpClientRequest &request, const TlsConfigRust &tls_options); + inline std::shared_ptr NewHttpClientTask(const HttpClientRequest &request) { auto &session = NetStack::HttpClient::HttpSession::GetInstance(); diff --git a/interfaces/innerkits/rust/netstack_rs/src/cxx/cache_proxy_ani.cpp b/interfaces/innerkits/rust/netstack_rs/src/cxx/cache_proxy_ani.cpp new file mode 100644 index 000000000..44fc1471f --- /dev/null +++ b/interfaces/innerkits/rust/netstack_rs/src/cxx/cache_proxy_ani.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cache_proxy_ani.h" +#include "wrapper.rs.h" + +namespace OHOS::Request { + +using namespace NetStack::HttpClient; + +void RunCacheWithSize(size_t capacity) +{ + CacheProxy::RunCacheWithSize(capacity); +} + +void RunCache() +{ + CacheProxy::RunCache(); +} + +void FlushCache() +{ + CacheProxy::FlushCache(); +} + +void StopCacheAndDelete() +{ + CacheProxy::StopCacheAndDelete(); +} + +} // namespace OHOS::Request diff --git a/interfaces/innerkits/rust/netstack_rs/src/cxx/wrapper.cpp b/interfaces/innerkits/rust/netstack_rs/src/cxx/wrapper.cpp index 3b1e003fb..11579a268 100644 --- a/interfaces/innerkits/rust/netstack_rs/src/cxx/wrapper.cpp +++ b/interfaces/innerkits/rust/netstack_rs/src/cxx/wrapper.cpp @@ -19,23 +19,56 @@ #include "http_client_error.h" #include "wrapper.rs.h" +#include "http_client_request.h" namespace OHOS::Request { using namespace OHOS::NetStack::HttpClient; +static const int32_t ADDRESS_FAMILY_DEFAULT = 0; +static const int32_t ADDRESS_FAMILY_ONLY_V4 = 1; +static const int32_t ADDRESS_FAMILY_ONLY_V6 = 2; + +void OnRequestStyleCallback(const std::shared_ptr &task, const std::shared_ptr &shared) +{ + if (task == nullptr || shared == nullptr) { + return; + } + auto weak = task->weak_from_this(); + task->OnSuccess([shared, weak](const HttpClientRequest &request, const HttpClientResponse &response) { + bool isRequestInStream = false; + auto httpTask = weak.lock(); + if (httpTask != nullptr) { + isRequestInStream = httpTask->IsRequestInStream(); + } + shared->on_success(request, response, isRequestInStream); + }); + task->OnFail([shared, weak](const HttpClientRequest &request, const HttpClientResponse &response, + const HttpClientError &error) { + bool isRequestInStream = false; + auto httpTask = weak.lock(); + if (httpTask != nullptr) { + isRequestInStream = httpTask->IsRequestInStream(); + } + shared->on_fail(request, response, error, isRequestInStream); + }); + task->OnCancel([shared, weak](const HttpClientRequest &request, const HttpClientResponse &response) { + bool isRequestInStream = false; + auto httpTask = weak.lock(); + if (httpTask != nullptr) { + isRequestInStream = httpTask->IsRequestInStream(); + } + shared->on_cancel(request, response, isRequestInStream); + }); +} void OnCallback(const std::shared_ptr &task, rust::Box callback) { + if (task == nullptr) { + return; + } + auto weak = task->weak_from_this(); CallbackWrapper *raw_ptr = callback.into_raw(); auto shared = std::shared_ptr( raw_ptr, [](CallbackWrapper *ptr) { rust::Box::from_raw(ptr); }); - task->OnSuccess([shared](const HttpClientRequest &request, const HttpClientResponse &response) { - shared->on_success(request, response); - }); - task->OnFail([shared](const HttpClientRequest &request, const HttpClientResponse &response, - const HttpClientError &error) { shared->on_fail(request, response, error); }); - task->OnCancel([shared](const HttpClientRequest &request, const HttpClientResponse &response) { - shared->on_cancel(request, response); - }); - auto weak = task->weak_from_this(); + OnRequestStyleCallback(task, shared); task->OnDataReceive([shared, weak](const HttpClientRequest &, const uint8_t *data, size_t size) { auto httpTask = weak.lock(); if (httpTask != nullptr) { @@ -45,6 +78,26 @@ void OnCallback(const std::shared_ptr &task, rust::BoxOnProgress([shared](const HttpClientRequest &, u_long dlTotal, u_long dlNow, u_long ulTotal, u_long ulNow) { shared->on_progress(dlTotal, dlNow, ulTotal, ulNow); }); + task->OnHeadersReceive([shared](const HttpClientRequest &, std::map headers) { + if (shared == nullptr || headers.empty()) { + return; + } + rust::vec ret; + for (auto header : headers) { + if (header.first.empty() || header.second.empty()) { + continue; + } + ret.emplace_back(header.first); + ret.emplace_back(header.second); + } + shared->on_headers_receive(ret); + }); + task->OnHeaderReceive([shared](const HttpClientRequest &, const std::string &header) { + if (shared == nullptr || header.empty()) { + return; + } + shared->on_header_receive(header); + }); }; rust::vec GetHeaders(HttpClientResponse &response) @@ -76,4 +129,111 @@ PerformanceInfoRust GetPerformanceTiming(HttpClientResponse &response) return info; } -} // namespace OHOS::Request \ No newline at end of file +void SetAddressFamily(HttpClientRequest &request, int32_t addressFamily) +{ + switch (addressFamily) { + case ADDRESS_FAMILY_DEFAULT: + request.SetAddressFamily("DEFAULT"); + break; + case ADDRESS_FAMILY_ONLY_V4: + request.SetAddressFamily("ONLY_V4"); + break; + case ADDRESS_FAMILY_ONLY_V6: + request.SetAddressFamily("ONLY_V6"); + break; + default: + break; + } +} + +void SetExtraData(HttpClientRequest &request, const EscapedDataRust& extraData) +{ + EscapedData nativeValue = { + .dataType = extraData.data_type, + .data = std::string(extraData.data.data(), extraData.data.size()), + }; + request.SetExtraData(nativeValue); +} + +void SetExpectDataType(HttpClientRequest &request, int32_t type) +{ + request.SetExpectDataType(static_cast(type)); +} + +void SetClientCert(HttpClientRequest &request, const ClientCert& cert) +{ + HttpClientCert clientCert; + + clientCert.certPath = std::string(cert.cert_path.data(), cert.cert_path.size()); + clientCert.keyPath = std::string(cert.key_path.data(), cert.key_path.size()); + clientCert.keyPassword = std::string(cert.key_password.data(), cert.key_password.size()); + + switch (cert.cert_type) { + case CertType::Pem: + clientCert.certType = "Pem"; + break; + case CertType::Der: + clientCert.certType = "Der"; + break; + case CertType::P12: + clientCert.certType = "P12"; + break; + } + request.SetClientCert(clientCert); +} + +void SetDNSServers(HttpClientRequest &request, const rust::vec& servers) +{ + std::vector dnsServers; + + dnsServers.reserve(servers.size()); + for (const auto& s : servers) { + dnsServers.push_back(std::string(s.data(), s.size())); + } + request.SetDNSServers(dnsServers); +} + +void AddMultiFormData(HttpClientRequest &request, const MultiFormDataRust &item) +{ + HttpMultiFormData nativeValue = { + .name = std::string(item.name.data(), item.name.size()), + .contentType = std::string(item.content_type.data(), item.content_type.size()), + .remoteFileName = std::string(item.remote_file_name.data(), item.remote_file_name.size()), + .data = std::string(item.data.data(), item.data.size()), + .filePath = std::string(item.file_path.data(), item.file_path.size()) + }; + request.AddMultiFormData(nativeValue); +} + +void SetServerAuthentication(HttpClientRequest &request, const ServerAuthentication& server_auth) +{ + HttpServerAuthentication serverAuth; + serverAuth.credential.username.append(server_auth.username.data(), server_auth.username.size()); + serverAuth.credential.password.append(server_auth.password.data(), server_auth.password.size()); + auto authenticationType = + std::string(server_auth.authentication_type.data(), server_auth.authentication_type.size()); + if (authenticationType == "basic") { + serverAuth.authenticationType = HttpAuthenticationType::BASIC; + } else if (authenticationType == "ntlm") { + serverAuth.authenticationType = HttpAuthenticationType::NTLM; + } else if (authenticationType == "digest") { + serverAuth.authenticationType = HttpAuthenticationType::DIGEST; + } + request.SetServerAuthentication(serverAuth); +} + +void SetTLSOptions(HttpClientRequest &request, const TlsConfigRust &tls_options) +{ + TlsOption tlsOption; + tlsOption.tlsVersionMin = static_cast(tls_options.tls_version_min); + tlsOption.tlsVersionMax = static_cast(tls_options.tls_version_max); + for (const auto &element : tls_options.cipher_suites) { + std::string nativeValue(element.data(), element.size()); + auto cipherSuite = GetTlsCipherSuiteFromStandardName(nativeValue); + if (cipherSuite != CipherSuite::INVALID) { + tlsOption.cipherSuite.emplace(cipherSuite); + } + } + request.SetTLSOptions(tlsOption); +} +} // namespace OHOS::Request diff --git a/interfaces/innerkits/rust/netstack_rs/src/request.rs b/interfaces/innerkits/rust/netstack_rs/src/request.rs index dda3f6be7..41cdb66c3 100644 --- a/interfaces/innerkits/rust/netstack_rs/src/request.rs +++ b/interfaces/innerkits/rust/netstack_rs/src/request.rs @@ -12,12 +12,28 @@ // limitations under the License. use cxx::{let_cxx_string, UniquePtr}; +use std::collections::HashMap; use crate::error::HttpClientError; use crate::response::Response; use crate::task::RequestTask; use crate::wrapper; -use crate::wrapper::ffi::{HttpClientRequest, NewHttpClientRequest, SetBody, SetHttpProtocol}; +use crate::wrapper::ffi::{ + HttpClientRequest, + NewHttpClientRequest, + SetBody, + SetHttpProtocol, + SetHttpUsingProxy, + SetAddressFamily, + SetExtraData, + SetExpectDataType, + SetClientCert, + SetDNSServers, + AddMultiFormData, + SetServerAuthentication, + SetTLSOptions, +}; + /// Builder for creating a Request. pub struct Request { inner: UniquePtr, @@ -85,6 +101,105 @@ impl Request { self } + /// Set a proxy for the request. + pub fn using_proxy(&mut self, proxy: i32) -> &mut Self { + SetHttpUsingProxy(self.inner.pin_mut(), proxy); + self + } + + /// Set a proxy for the request. + pub fn max_limit(&mut self, max_limit: u32) -> &mut Self { + self.inner.pin_mut().SetMaxLimit(max_limit); + self + } + + /// Set a ca_path for the request. + pub fn ca_path(&mut self, path: &str) -> &mut Self { + let_cxx_string!(path = path); + self.inner.pin_mut().SetCaPath(&path); + self + } + + /// Set a resume_from for the request. + pub fn resume_from(&mut self, num: i32) -> &mut Self { + self.inner.pin_mut().SetResumeFrom(num as i64); + self + } + + /// Set a resume_to for the request. + pub fn resume_to(&mut self, num: i32) -> &mut Self { + self.inner.pin_mut().SetResumeTo(num as i64); + self + } + + /// Set a address_family for the request. + pub fn address_family(&mut self, family: i32) -> &mut Self { + SetAddressFamily(self.inner.pin_mut(), family); + self + } + + /// Set a extra_data for the request. + pub fn extra_data(&mut self, extra_data: EscapedData) -> &mut Self { + SetExtraData(self.inner.pin_mut(), &extra_data.into()); + self + } + + /// Set a expect_data_type for the request. + pub fn expect_data_type(&mut self, expect_data_type: i32) -> &mut Self { + SetExpectDataType(self.inner.pin_mut(), expect_data_type); + self + } + + /// Set a using_cache for the request. + pub fn using_cache(&mut self, using_cache: bool) -> &mut Self { + self.inner.pin_mut().SetUsingCache(using_cache); + self + } + + /// Set a client_cert for the request. + pub fn client_cert(&mut self, client_cert: ClientCert) -> &mut Self { + SetClientCert(self.inner.pin_mut(), &client_cert.into()); + self + } + + /// Set a dns_over_https for the request. + pub fn dns_over_https(&mut self, dns_over_https: &str) -> &mut Self { + let_cxx_string!(dns_over_https = dns_over_https); + self.inner.pin_mut().SetDNSOverHttps(&dns_over_https); + self + } + + /// Set a dns_servers for the request. + pub fn dns_servers(&mut self, dns_servers: Vec) -> &mut Self { + SetDNSServers(self.inner.pin_mut(), &dns_servers); + self + } + + /// add a multi_form_data to the multi_form_data_list for the request. + pub fn add_multi_form_data(&mut self, multi_form_data: MultiFormData) -> &mut Self { + AddMultiFormData(self.inner.pin_mut(), &multi_form_data.into()); + self + } + + /// Set a remote_validation for the request. + pub fn remote_validation(&mut self, remote_validation: &str) -> &mut Self { + let_cxx_string!(remote_validation = remote_validation); + self.inner.pin_mut().SetRemoteValidation(&remote_validation); + self + } + + /// Set a tls_options for the request. + pub fn tls_options(&mut self, tls_options: TlsConfig) -> &mut Self { + SetTLSOptions(self.inner.pin_mut(), &tls_options.into()); + self + } + + /// Set a server_authentication for the request. + pub fn server_authentication(&mut self, server_authentication: ServerAuthentication) -> &mut Self { + SetServerAuthentication(self.inner.pin_mut(), &server_authentication.into()); + self + } + /// Set a callback for the request. pub fn callback(&mut self, callback: C) -> &mut Self { self.callback = Some(callback); @@ -105,17 +220,21 @@ impl Request { #[allow(unused_variables)] pub trait RequestCallback { /// Called when the request is successful. - fn on_success(&mut self, response: Response) {} + fn on_success(&mut self, response: Response, is_request_in_stream: bool) {} /// Called when the request fails. - fn on_fail(&mut self, response: Response, error: HttpClientError) {} + fn on_fail(&mut self, response: Response, error: HttpClientError, is_request_in_stream: bool) {} /// Called when the request is canceled. - fn on_cancel(&mut self, response: Response) {} + fn on_cancel(&mut self, response: Response, is_request_in_stream: bool) {} /// Called when data is received. fn on_data_receive(&mut self, data: &[u8], task: RequestTask) {} /// Called when progress is made. fn on_progress(&mut self, dl_total: u64, dl_now: u64, ul_total: u64, ul_now: u64) {} /// Called when the task is restarted. fn on_restart(&mut self) {} + /// Called when header is received. + fn on_header_receive(&mut self, header: String) {} + /// Called when headers is received. + fn on_headers_receive(&mut self, headers: HashMap) {} } impl Default for Request { @@ -126,4 +245,98 @@ impl Default for Request { pub fn has_internet_permission() -> bool { wrapper::ffi::HasInternetPermission() -} \ No newline at end of file +} + +pub fn run_cache(capacity: Option) { + match capacity { + Some(value) => { + if value >= 0 { + wrapper::ffi::RunCacheWithSize(value as usize); + } else { + wrapper::ffi::RunCache(); + } + } + None => { + wrapper::ffi::RunCache(); + } + } +} + +pub fn flush_cache() { + wrapper::ffi::FlushCache(); +} + +pub fn delete_cache() { + wrapper::ffi::StopCacheAndDelete(); +} +#[repr(i32)] +pub enum CertType { + Pem, + Der, + P12, +} + +#[repr(C)] +pub struct ClientCert { + pub cert_path: String, + pub cert_type: Option, + pub key_path: String, + pub key_password: Option, +} + +#[derive(Clone, Default)] +#[repr(C)] +pub struct EscapedData { + // Benchmark HttpDataType. StringType is 0, ObjectType is 1, ArrayBuffer is 2. + pub data_type: u32, + pub data: String, +} + +impl EscapedData { + pub fn new() -> Self { + Self { + data_type: 0, + data: String::new(), + } + } +} + +#[repr(C)] +pub struct MultiFormData { + pub name: String, + pub content_type: String, + pub remote_file_name: String, + pub data: String, + pub file_path: String, +} + +#[repr(C)] +pub struct ServerAuthentication { + pub credential: Credential, + pub authentication_type: Option, +} + +#[repr(C)] +pub struct Credential { + pub username: String, + pub password: String, +} + +#[repr(C)] +pub struct TlsConfig { + pub tls_version_min: TlsVersion, + pub tls_version_max: TlsVersion, + pub cipher_suites: Option>, +} + +#[allow(non_camel_case_types)] +#[repr(C)] +pub enum TlsVersion { + TlsV_1_0 = 4, + + TlsV_1_1 = 5, + + TlsV_1_2 = 6, + + TlsV_1_3 = 7, +} diff --git a/interfaces/innerkits/rust/netstack_rs/src/response.rs b/interfaces/innerkits/rust/netstack_rs/src/response.rs index 81a498f3f..d45a02107 100644 --- a/interfaces/innerkits/rust/netstack_rs/src/response.rs +++ b/interfaces/innerkits/rust/netstack_rs/src/response.rs @@ -75,6 +75,12 @@ impl<'a> Response<'a> { } } + pub fn get_expect_data_type(&self) -> HttpDataType { + let response = self.inner.to_response(); + let data_type = HttpDataType::try_from(response.GetExpectDataType()).unwrap_or_default(); + data_type + } + pub(crate) fn from_ffi(inner: &'a HttpClientResponse) -> Self { Self { inner: ResponseInner::Ref(inner), @@ -155,3 +161,24 @@ pub struct PerformanceInfo { pub total_timing: f64, pub redirect_timing: f64, } + +/// http data type +#[derive(Debug, Default)] +pub enum HttpDataType { + StringType = 0, + ObjectType, + ArrayBuffer, + #[default] + None, +} + +impl HttpDataType { + pub fn to_i32(&self) -> i32 { + match self { + HttpDataType::StringType => 0, + HttpDataType::ObjectType => 1, + HttpDataType::ArrayBuffer => 2, + _=> -1, + } + } +} \ No newline at end of file diff --git a/interfaces/innerkits/rust/netstack_rs/src/task.rs b/interfaces/innerkits/rust/netstack_rs/src/task.rs index 1536d6ed8..45104b997 100644 --- a/interfaces/innerkits/rust/netstack_rs/src/task.rs +++ b/interfaces/innerkits/rust/netstack_rs/src/task.rs @@ -126,6 +126,51 @@ impl RequestTask { let ptr = ptr.as_ref().unwrap() as *const HttpClientTask as *mut HttpClientTask; unsafe { Pin::new_unchecked(ptr.as_mut().unwrap()) } } + + pub fn off_data_receive(&mut self) ->bool { + let task = self.inner.lock().unwrap().clone(); + let result = Self::pin_mut(&task).OffDataReceive(); + result + } + + pub fn off_progress(&mut self) ->bool { + let task = self.inner.lock().unwrap().clone(); + let result = Self::pin_mut(&task).OffProgress(); + result + } + + pub fn off_header_receive(&mut self) ->bool { + let task = self.inner.lock().unwrap().clone(); + let result = Self::pin_mut(&task).OffHeaderReceive(); + result + } + + pub fn off_headers_receive(&mut self) ->bool { + let task = self.inner.lock().unwrap().clone(); + let result = Self::pin_mut(&task).OffHeadersReceive(); + result + } + + pub fn set_is_header_once(&mut self, is_once: bool) { + let task = self.inner.lock().unwrap().clone(); + Self::pin_mut(&task).SetIsHeaderOnce(is_once); + } + + pub fn set_is_headers_once(&mut self, is_once: bool) { + let task = self.inner.lock().unwrap().clone(); + Self::pin_mut(&task).SetIsHeadersOnce(is_once); + } + + pub fn set_is_request_in_stream(&mut self, is_request_in_stream: bool) { + let task = self.inner.lock().unwrap().clone(); + Self::pin_mut(&task).SetIsRequestInStream(is_request_in_stream); + } + + pub fn is_request_in_stream(& self) -> bool { + let task = self.inner.lock().unwrap().clone(); + let result = Self::pin_mut(&task).IsRequestInStream(); + result + } } #[cfg(test)] diff --git a/interfaces/innerkits/rust/netstack_rs/src/wrapper.rs b/interfaces/innerkits/rust/netstack_rs/src/wrapper.rs index bda3244aa..6fbb0328e 100644 --- a/interfaces/innerkits/rust/netstack_rs/src/wrapper.rs +++ b/interfaces/innerkits/rust/netstack_rs/src/wrapper.rs @@ -14,14 +14,15 @@ use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, Weak}; +use std::collections::HashMap; use cxx::{let_cxx_string, SharedPtr}; use ffi::{HttpClientRequest, HttpClientTask, NewHttpClientTask, OnCallback}; use netstack_common::debug; use crate::error::{HttpClientError, HttpErrorCode}; -use crate::request::RequestCallback; -use crate::response::{Response, ResponseCode}; +use crate::request::{RequestCallback, CertType, ClientCert, MultiFormData, ServerAuthentication, TlsConfig, TlsVersion, EscapedData}; +use crate::response::{Response, ResponseCode, HttpDataType}; use crate::task::{RequestTask, TaskStatus}; pub struct CallbackWrapper { @@ -48,13 +49,17 @@ impl CallbackWrapper { } impl CallbackWrapper { - fn on_success(&mut self, _request: &HttpClientRequest, response: &ffi::HttpClientResponse) { + fn on_success( + &mut self, + _request: &HttpClientRequest, + response: &ffi::HttpClientResponse, + is_request_in_stream: bool) { debug!("on_success callback is called"); let Some(mut callback) = self.inner.take() else { return; }; let response = Response::from_ffi(response); - callback.on_success(response); + callback.on_success(response, is_request_in_stream); } fn on_fail( @@ -62,6 +67,7 @@ impl CallbackWrapper { _request: &HttpClientRequest, response: &ffi::HttpClientResponse, error: &ffi::HttpClientError, + is_request_in_stream: bool ) { debug!("on_fail callback is called"); let Some(mut callback) = self.inner.take() else { @@ -69,10 +75,15 @@ impl CallbackWrapper { }; let client_error = HttpClientError::from_ffi(error); let response = Response::from_ffi(response); - callback.on_fail(response, client_error); + callback.on_fail(response, client_error, is_request_in_stream); } - fn on_cancel(&mut self, request: &HttpClientRequest, response: &ffi::HttpClientResponse) { + fn on_cancel( + &mut self, + request: &HttpClientRequest, + response: &ffi::HttpClientResponse, + is_request_in_stream: bool + ) { debug!("on_cancel callback is called"); let Some(mut callback) = self.inner.take() else { return; @@ -84,7 +95,7 @@ impl CallbackWrapper { self.reset.store(false, Ordering::SeqCst); } else { let response = Response::from_ffi(response); - callback.on_cancel(response); + callback.on_cancel(response, is_request_in_stream); } } @@ -110,6 +121,37 @@ impl CallbackWrapper { callback.on_progress(dl_total, dl_now, ul_total, ul_now); } + fn on_header_receive( + &mut self, + header: String, + ) { + let Some(callback) = self.inner.as_mut() else { + return; + }; + callback.on_header_receive(header); + } + + fn on_headers_receive( + &mut self, + headers_conversion: Vec, + ) { + let Some(callback) = self.inner.as_mut() else { + return; + }; + let mut ret = HashMap::new(); + let mut headers_iter = headers_conversion.into_iter(); + loop { + if let Some(key) = headers_iter.next() { + if let Some(value) = headers_iter.next() { + ret.insert(key.to_lowercase(), value); + continue; + } + } + break; + } + callback.on_headers_receive(ret); + } + fn create_new_task( &mut self, mut callback: Box, @@ -181,26 +223,165 @@ fn set_range( unsafe impl Send for HttpClientTask {} unsafe impl Sync for HttpClientTask {} +impl From for ffi::ClientCert { + fn from(client_cert: ClientCert) -> Self { + let cert_type = match client_cert.cert_type { + Some(ct) => match ct { + CertType::Pem => ffi::CertType::Pem, + CertType::Der => ffi::CertType::Der, + CertType::P12 => ffi::CertType::P12, + }, + None => ffi::CertType::Pem + }; + + let key_password = match client_cert.key_password { + Some(kp) => kp, + None => "".to_string() + }; + + ffi::ClientCert { + cert_path: client_cert.cert_path, + cert_type, + key_path: client_cert.key_path, + key_password, + } + } +} + +impl From for ffi::EscapedDataRust { + fn from(value: EscapedData) -> Self { + let escaped_type = match value.data_type { + 0 => ffi::HttpDataType::STRING, + 1 => ffi::HttpDataType::OBJECT, + 2 => ffi::HttpDataType::ARRAY_BUFFER, + _ => ffi::HttpDataType::NO_DATA_TYPE, + }; + ffi::EscapedDataRust { + data_type: escaped_type, + data: value.data, + } + } +} + +impl From for ffi::MultiFormDataRust { + fn from(multi_form_data: MultiFormData) -> Self { + ffi::MultiFormDataRust { + name: multi_form_data.name, + content_type: multi_form_data.content_type, + remote_file_name: multi_form_data.remote_file_name, + data: multi_form_data.data, + file_path: multi_form_data.file_path, + } + } +} + +impl From for ffi::ServerAuthentication { + fn from(server_auth: ServerAuthentication) -> Self { + let auth_type = match server_auth.authentication_type { + Some(ty) => ty, + None => "".to_string() + }; + + ffi::ServerAuthentication { + username: server_auth.credential.username, + password: server_auth.credential.password, + authentication_type: auth_type, + } + } +} + +impl From for ffi::TlsConfigRust { + fn from(tls_config: TlsConfig) -> Self { + let min = match tls_config.tls_version_min { + TlsVersion::TlsV_1_0 => ffi::TlsVersionRust::TlsV_1_0, + TlsVersion::TlsV_1_1 => ffi::TlsVersionRust::TlsV_1_1, + TlsVersion::TlsV_1_2 => ffi::TlsVersionRust::TlsV_1_2, + TlsVersion::TlsV_1_3 => ffi::TlsVersionRust::TlsV_1_3 + }; + let max = match tls_config.tls_version_max { + TlsVersion::TlsV_1_0 => ffi::TlsVersionRust::TlsV_1_0, + TlsVersion::TlsV_1_1 => ffi::TlsVersionRust::TlsV_1_1, + TlsVersion::TlsV_1_2 => ffi::TlsVersionRust::TlsV_1_2, + TlsVersion::TlsV_1_3 => ffi::TlsVersionRust::TlsV_1_3 + }; + ffi::TlsConfigRust { + tls_version_min: min, + tls_version_max: max, + cipher_suites: tls_config.cipher_suites.unwrap_or_default(), + } + } +} + #[allow(unused_unsafe)] #[cxx::bridge(namespace = "OHOS::Request")] pub(crate) mod ffi { + + enum CertType { + Pem, + Der, + P12, + } + + struct EscapedDataRust { + data_type: HttpDataType, + data: String, + } + + struct ClientCert { + cert_path: String, + cert_type: CertType, + key_path: String, + key_password: String, + } + + struct MultiFormDataRust { + name: String, + content_type: String, + remote_file_name: String, + data: String, + file_path: String, + } + + struct ServerAuthentication { + username : String, + password: String, + authentication_type: String, + } + + enum TlsVersionRust { + DEFAULT = 0, + TlsV_1_0 = 4, + TlsV_1_1 = 5, + TlsV_1_2 = 6, + TlsV_1_3 = 7, + } + + struct TlsConfigRust { + pub tls_version_min: TlsVersionRust, + pub tls_version_max: TlsVersionRust, + pub cipher_suites: Vec, + } + extern "Rust" { type CallbackWrapper; fn on_success( self: &mut CallbackWrapper, request: &HttpClientRequest, response: &HttpClientResponse, + is_request_in_stream: bool ); fn on_fail( self: &mut CallbackWrapper, request: &HttpClientRequest, response: &HttpClientResponse, error: &HttpClientError, + is_request_in_stream: bool ); fn on_cancel( self: &mut CallbackWrapper, request: &HttpClientRequest, response: &HttpClientResponse, + is_request_in_stream: bool ); unsafe fn on_data_receive( self: &mut CallbackWrapper, @@ -215,6 +396,14 @@ pub(crate) mod ffi { ul_total: u64, ul_now: u64, ); + unsafe fn on_headers_receive( + self: &mut CallbackWrapper, + headers: Vec, + ); + unsafe fn on_header_receive( + self: &mut CallbackWrapper, + header: String, + ); } unsafe extern "C++" { @@ -222,6 +411,13 @@ pub(crate) mod ffi { include!("wrapper.h"); include!("http_client_task.h"); include!("netstack_common_utils.h"); + include!("cache_proxy_ani.h"); + + fn RunCache(); + fn RunCacheWithSize(capacity: usize); + + fn FlushCache(); + fn StopCacheAndDelete(); #[namespace = "OHOS::NetStack::HttpClient"] type TaskStatus; @@ -235,6 +431,9 @@ pub(crate) mod ffi { #[namespace = "OHOS::NetStack::HttpClient"] type HttpErrorCode; + #[namespace = "OHOS::NetStack::HttpClient"] + type HttpDataType; + fn NewHttpClientRequest() -> UniquePtr; fn SetURL(self: Pin<&mut HttpClientRequest>, url: &CxxString); fn SetMethod(self: Pin<&mut HttpClientRequest>, method: &CxxString); @@ -244,6 +443,22 @@ pub(crate) mod ffi { fn SetConnectTimeout(self: Pin<&mut HttpClientRequest>, timeout: u32); unsafe fn SetBody(request: Pin<&mut HttpClientRequest>, data: *const u8, length: usize); fn SetHttpProtocol(request: Pin<&mut HttpClientRequest>, protocol: i32); + fn SetHttpUsingProxy(request: Pin<&mut HttpClientRequest>, proxy: i32); + fn SetMaxLimit(self: Pin<&mut HttpClientRequest>, max_limit: u32); + fn SetCaPath(self: Pin<&mut HttpClientRequest>, path: &CxxString); + fn SetResumeFrom(self: Pin<&mut HttpClientRequest>, resume_from: i64); + fn SetResumeTo(self: Pin<&mut HttpClientRequest>, resume_from: i64); + fn SetAddressFamily(request: Pin<&mut HttpClientRequest>, address_family: i32); + fn SetExtraData(request: Pin<&mut HttpClientRequest>, extra_data: &EscapedDataRust); + fn SetExpectDataType(request: Pin<&mut HttpClientRequest>, expect_data_type: i32); + fn SetUsingCache(self: Pin<&mut HttpClientRequest>, using_cache: bool); + fn SetClientCert(request: Pin<&mut HttpClientRequest>, client_cert: &ClientCert); + fn SetDNSOverHttps(self: Pin<&mut HttpClientRequest>, dns_over_https: &CxxString); + fn SetDNSServers(request: Pin<&mut HttpClientRequest>, dns_servers: &Vec); + fn AddMultiFormData(request: Pin<&mut HttpClientRequest>, data: &MultiFormDataRust); + fn SetRemoteValidation(self: Pin<&mut HttpClientRequest>, remote_validation: &CxxString); + fn SetTLSOptions(request: Pin<&mut HttpClientRequest>, tls_options: &TlsConfigRust); + fn SetServerAuthentication(request: Pin<&mut HttpClientRequest>, server_auth: &ServerAuthentication); #[namespace = "OHOS::NetStack::HttpClient"] type HttpClientTask; @@ -255,6 +470,14 @@ pub(crate) mod ffi { fn GetStatus(self: Pin<&mut HttpClientTask>) -> TaskStatus; fn OnCallback(task: &SharedPtr, callback: Box); fn GetError(self: Pin<&mut HttpClientTask>) -> Pin<&mut HttpClientError>; + fn OffDataReceive(self: Pin<&mut HttpClientTask>) -> bool; + fn OffProgress(self: Pin<&mut HttpClientTask>) -> bool; + fn OffHeaderReceive(self: Pin<&mut HttpClientTask>) -> bool; + fn OffHeadersReceive(self: Pin<&mut HttpClientTask>) -> bool; + fn SetIsHeaderOnce(self: Pin<&mut HttpClientTask>, isOnce: bool); + fn SetIsHeadersOnce(self: Pin<&mut HttpClientTask>, isOnce: bool); + fn SetIsRequestInStream(self: Pin<&mut HttpClientTask>, isRequestInStream: bool); + fn IsRequestInStream(self: Pin<&mut HttpClientTask>) -> bool; #[namespace = "OHOS::NetStack::HttpClient"] type HttpClientResponse; @@ -264,6 +487,7 @@ pub(crate) mod ffi { fn GetCookies(self: &HttpClientResponse) -> &CxxString; fn GetResult(self: &HttpClientResponse) -> &CxxString; fn GetPerformanceTiming(response: Pin<&mut HttpClientResponse>) -> PerformanceInfoRust; + fn GetExpectDataType(self: &HttpClientResponse) -> HttpDataType; #[namespace = "OHOS::NetStack::HttpClient"] type HttpClientError; @@ -361,6 +585,14 @@ pub(crate) mod ffi { HTTP_UNKNOWN_OTHER_ERROR = 2300999, } + #[repr(i32)] + enum HttpDataType { + STRING = 0, + OBJECT, + ARRAY_BUFFER, + NO_DATA_TYPE, + } + struct PerformanceInfoRust { dns_timing: f64, tcp_timing: f64, @@ -487,3 +719,20 @@ impl TryFrom for HttpErrorCode { Ok(ret) } } + +impl TryFrom for HttpDataType { + type Error = ffi::HttpDataType; + fn try_from(value: ffi::HttpDataType) -> Result { + let ret = match value { + ffi::HttpDataType::STRING => HttpDataType::StringType, + ffi::HttpDataType::OBJECT => HttpDataType::ObjectType, + ffi::HttpDataType::ARRAY_BUFFER => HttpDataType::ArrayBuffer, + ffi::HttpDataType::NO_DATA_TYPE => HttpDataType::None, + _ => { + return Err(value); + } + }; + Ok(ret) + } +} + -- Gitee