import http from '@ohos.net.http' import buffer from '@ohos.buffer'; import { isPlainObject, Emitter, getCurrentMP, } from '@dcloudio/uni-runtime' import { RequestTask as UniRequestTask, RequestOptions as UniRequestOptions, RequestSuccess as UniRequestSuccess, Request } from '../../interface.uts' import { API_REQUEST, RequestApiOptions, RequestApiProtocol, } from '../../protocol.uts' import { getClientCertificate, parseUrl, } from './utils.uts' import { getCookieSync, setCookieSync, } from './cookie.uts' interface IUniRequestEmitter { on: (eventName: string, callback: Function) => void once: (eventName: string, callback: Function) => void off: (eventName: string, callback?: Function | null) => void emit: (eventName: string, ...args: (Object | undefined | null)[]) => void } // copy from uni-app-plus/src/service/api/network/request.ts const cookiesParse = (header: Record) => { let cookiesArr: string[] = [] const handleCookiesArr = (header['Set-Cookie'] || header['set-cookie'] || []) as string[] for (let i = 0; i < handleCookiesArr.length; i++) { if ( handleCookiesArr[i].indexOf('Expires=') !== -1 || handleCookiesArr[i].indexOf('expires=') !== -1 ) { cookiesArr.push(handleCookiesArr[i].replace(',', '')) } else { cookiesArr.push(handleCookiesArr[i]) } } return cookiesArr } interface IRequestTask { abort: Function onHeadersReceived: Function offHeadersReceived: Function } class RequestTask implements UniRequestTask { private _requestTask: IRequestTask constructor(requestTask: IRequestTask) { this._requestTask = requestTask } abort() { this._requestTask.abort() } onHeadersReceived(callback: Function) { this._requestTask.onHeadersReceived(callback) } offHeadersReceived(callback?: Function) { this._requestTask.offHeadersReceived(callback) } } /** * http.request有5MB响应体大小限制 * rcp.Session.fetch无响应体大小限制,但是headers、cookies处理不够完善,另外rcp需要从@hms引用,不是openharmony标准库 * 为突破http.request的限制,现在使用http.requestInStream */ export const request = defineTaskApi, UniRequestSuccess, UniRequestTask>( API_REQUEST, (args: UniRequestOptions, exec: ApiExecutor>) => { let { header, method, data, dataType, timeout, url, responseType } = args header = header || {} as ESObject if (!header!['Cookie'] && !header!['cookie']) { header!['Cookie'] = getCookieSync(url); } let contentType = '' // header const headers = {} as Record const headerRecord = header as Object as Record const headerKeys = Object.keys(headerRecord) for (let i = 0; i < headerKeys.length; i++) { const name = headerKeys[i]; if (name.toLowerCase() === 'content-type') { contentType = headerRecord[name] as string } headers[name.toLowerCase()] = headerRecord[name] } if (!contentType && method === 'POST') { headers['Content-Type'] = 'application/json' contentType = 'application/json' } // url data if (method === 'GET' && data && isPlainObject(data)) { const dataRecord = data as Record const query = Object.keys(dataRecord) .map((key) => { return ( encodeURIComponent(key) + '=' + encodeURIComponent(dataRecord[key] as string | number | boolean) ) }) .join('&') url += query ? (url.indexOf('?') > -1 ? '&' : '?') + query : '' data = null } else if ( method !== 'GET' && contentType && contentType.indexOf('application/json') === 0 && isPlainObject(data) ) { data = JSON.stringify(data) } else if ( method !== 'GET' && contentType && contentType.indexOf('application/x-www-form-urlencoded') === 0 && isPlainObject(data) ) { const dataRecord = data as Record data = Object.keys(dataRecord) .map((key) => { return ( encodeURIComponent(key) + '=' + encodeURIComponent(dataRecord[key] as number | string | boolean) ) }) .join('&') } const httpRequest = http.createHttp() const mp = getCurrentMP() const userAgent = mp.userAgent.fullUserAgent if (userAgent && headers && !headers!['User-Agent'] && !headers!['user-agent']) { headers!['User-Agent'] = userAgent } const emitter = new Emitter() as IUniRequestEmitter const requestTask: IRequestTask = { abort() { emitter.off('headersReceive') httpRequest.destroy() }, onHeadersReceived(callback: Function) { emitter.on('headersReceive', callback) }, offHeadersReceived(callback?: Function) { emitter.off('headersReceive', callback) } } function destroy() { emitter.off('headersReceive') httpRequest.destroy() } mp.on('beforeClose', destroy) let latestHeaders: Object | null = null let lastUrl = url httpRequest.on('headersReceive', (headers: Object) => { const realHeaders = headers as Record const setCookieHeader = realHeaders['set-cookie'] || realHeaders['Set-Cookie'] if (setCookieHeader) { setCookieSync(lastUrl, setCookieHeader as string[]) } latestHeaders = headers const location = realHeaders['location'] || realHeaders['Location'] if (location) { lastUrl = location as string } // TODO headersReceive存在bug,暂不支持回调给用户。注意重定向时会多次触发,但是只需要给用户回调最后一次 // emitter.emit('headersReceive', headers); }) const bufs = [] as buffer.Buffer[] httpRequest.on('dataReceive', (data) => { bufs.push(buffer.from(data)) }) httpRequest.requestInStream( parseUrl(url), { header: headers, method: (method || 'GET').toUpperCase() as http.RequestMethod, // 仅OPTIONS不支持 extraData: data || undefined, // 传空字符串会报错 connectTimeout: timeout ? timeout : undefined, // 不支持仅设置一个timeout readTimeout: timeout ? timeout : undefined, clientCert: getClientCertificate(url) } as http.HttpRequestOptions, (err, statusCode) => { if (err) { /** * TODO abort后此处收到如下错误,待确认是否直接将此错误码转为abort错误 * {"code":2300023,"message":"Failed writing received data to disk/application"} * * reject方法第二个参数可以传errCode,reject(err.message, { errCode: -1 }) */ exec.reject(err.message) } else { const responseData = buffer.concat(bufs) let data: ArrayBuffer | string | object = '' if (responseType === 'arraybuffer') { data = responseData.buffer } else { data = responseData.toString('utf8') if (dataType === 'json') { try { data = JSON.parse(data) } catch (e) { // 与微信保持一致,不抛出异常 } } } const headers = latestHeaders as Record const oldCookies = headers ? (headers['Set-Cookie'] || headers['set-cookie'] || []) as string[] : [] as string[] const cookies = latestHeaders ? cookiesParse(latestHeaders as Record) : [] let newCookies = oldCookies.join(',') if (newCookies) { if (headers['Set-Cookie']) { headers['Set-Cookie'] = newCookies } else { headers['set-cookie'] = newCookies } } exec.resolve({ data, statusCode, header: latestHeaders!, cookies: cookies, } as UniRequestSuccess) } requestTask.offHeadersReceived() httpRequest.destroy() // 调用完毕后必须调用destroy方法 mp.off('beforeClose', destroy) } ) return new RequestTask(requestTask) }, RequestApiProtocol, RequestApiOptions ) as Request