提交 50845b46 编写于 作者: DCloud-yyl's avatar DCloud-yyl

开源:uni.downloadFile、uni.request、uni.uploadFile

上级 38059b03
{
"id": "uni-network",
"displayName": "uni-network",
"version": "1.0.0",
"description": "uni-network",
"keywords": [
"uni-network"
],
"repository": "",
"engines": {
"HBuilderX": "^3.6.8"
},
"dcloudext": {
"type": "uts",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "",
"data": "",
"permissions": ""
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"uni-ext-api": {
"uni": {
"request": {
"name": "request",
"app": {
"js": false,
"kotlin": true,
"swift": true
}
},
"uploadFile": {
"name": "uploadFile",
"app": {
"js": false,
"kotlin": true,
"swift": false
}
},
"downloadFile": {
"name": "downloadFile",
"app": {
"js": false,
"kotlin": true,
"swift": false
}
}
}
},
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "u",
"aliyun": "u"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-android": "u",
"app-ios": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
\ No newline at end of file
# uni-network
### 开发文档
[UTS 语法](https://uniapp.dcloud.net.cn/tutorial/syntax-uts.html)
[UTS API插件](https://uniapp.dcloud.net.cn/plugin/uts-plugin.html)
[UTS 组件插件](https://uniapp.dcloud.net.cn/plugin/uts-component.html)
[Hello UTS](https://gitcode.net/dcloud/hello-uts)
\ No newline at end of file
{
"dependencies": [
"com.squareup.okhttp3:okhttp:3.12.12"
],
"minSdkVersion": "19"
}
import { RequestOptions, RequestSuccess, RequestTask, UploadFileOptions, UploadFile, UploadTask, OnProgressUpdateResult, UploadFileSuccess, UploadFileProgressUpdateCallback, DownloadFile, DownloadTask, DownloadFileOptions, OnProgressDownloadResult, DownloadFileProgressUpdateCallback, DownloadFileSuccess } from '../interface'
import { RequestFailImpl, UploadFileFailImpl, DownloadFileFailImpl, getErrcode } from '../unierror';
import { NetworkManager, NetworkRequestListener, NetworkUploadFileListener, NetworkDownloadFileListener } from './network/NetworkManager.uts'
import Pattern from 'java.util.regex.Pattern';
import Locale from 'java.util.Locale';
import TextUtils from 'android.text.TextUtils';
import { StatusCode } from './network/StatusCode.uts'
import Key from 'kotlinx.coroutines.CoroutineExceptionHandler.Key';
import JSONObject from 'com.alibaba.fastjson.JSONObject';
import ArrayList from 'java.util.ArrayList';
import ProgressListener from 'android.os.RecoverySystem.ProgressListener';
import Handler from 'android.os.Handler';
import Looper from 'android.os.Looper';
import UTSAndroid from 'io.dcloud.uts.UTSAndroid';
import Class from 'java.lang.Class';
import Type from 'java.lang.reflect.Type';
let charsetPattern = Pattern.compile('charset=([a-z0-9-]+)')
class RunnableTask extends Runnable {
private callback : () => void | null;
private looper : Looper | null = null;
constructor(looper : Looper | null, callback : () => void) {
super();
this.looper = looper;
this.callback = callback
}
override run() {
this.callback?.()
}
public execute() {
if (this.looper == null) {
this.run();
} else {
new Handler(this.looper!!).post(this);
}
}
}
class RequestNetworkListener<T> extends NetworkRequestListener {
private param : RequestOptions<T> | null = null;
private headers : UTSJSONObject = {};
private looper : Looper | null = null;
private type : Type | null = null;
private clzName : string | null = null;
constructor(param : RequestOptions<T>, type : Type, clzName : string) {
super();
this.param = param;
this.type = type;
this.clzName = clzName;
this.looper = Looper.myLooper();
}
override onStart() : void {
}
override onHeadersReceived(statusCode : number, headers : MutableMap<String, MutableList<String>>) : void {
let simpleHeaders = {};
if (headers != null) {
let it = headers.iterator();
while (it.hasNext()) {
let entry = it.next();
let key = entry.key;
let value = entry.value;
let tmpKey = '_';
if (key == null) {
key = tmpKey;
}
if (value.size == 0) {
continue;
} else if (value.size == 1) {
simpleHeaders[key] = value.get(0);
} else {
simpleHeaders[key] = value.toString();
}
}
}
this.headers = simpleHeaders;
}
override onProgress(progress : number) : void {
}
override onComplete(option : UTSJSONObject) : void {
let kParam = this.param;
let result = {};
if (kParam != null) {
if (option == null || '-1' == option['statusCode']) {
// result['statusText'] = 'ERR_CONNECT_FAILED';
// result['errMsg'] = option['errorMsg'];
if (this.headers != null) {
result['header'] = this.headers;
}
let exception = option['cause']! as Exception;
let cause = exception.cause.toString();
let errMsg = option['errorMsg']! as string;
let errCode = (option['errorCode']! as string).toInt();
if (errMsg.contains("timeout")) {
errCode = 5;
errMsg = "time out";
} else if (cause.contains("Connection refused")) {
errCode = 1000;
errMsg = "server system error";
} else if (cause.contains("Network is unreachable")) {
errCode = 600003;
errMsg = "network interrupted error";
} else if (cause.contains("invalid URL")) {
errCode = 600009;
errMsg = "invalid URL";
} else {
errCode = 602001;
errMsg = "request system error";
}
let failResult = new RequestFailImpl(getErrcode(errCode));
failResult.cause = new Error(cause);
new RunnableTask(this.looper, () => {
if (kParam != null) {
let fail = kParam.fail;
if (fail != null) {
fail(failResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(failResult);
}
}
}).execute();
} else {
result['statusCode'] = option['statusCode'];
if (option['originalData'] == null) {
if ("java.lang.Object".equals(this.clzName, true)) {
let errMsg = option['errorMsg'];
if (errMsg != null) {
let errMsgJson = JSON.parse((option['errorMsg']! as string));
if (errMsgJson != null) {
result['data'] = errMsgJson;
} else {
result['data'] = errMsg;
}
} else {
result['data'] = "error";
}
} else {
let errMsg = option['errorMsg'];
if (errMsg != null) {
let errMsgJson = JSON.parse<T>(errMsg as string, this.type);
if (errMsgJson != null) {
result['data'] = errMsgJson;
} else {
let failResult = new RequestFailImpl(getErrcode(100002));
new RunnableTask(this.looper, () => {
if (kParam != null) {
let fail = kParam.fail;
if (fail != null) {
fail(failResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(failResult);
}
}
}).execute();
return;
}
}
}
} else {
let charset = "";
let headers = this.headers.toJSONObject() as JSONObject;
if (headers != null) {
for (key in headers.keys) {
if (key.equals("Content-Type", true)) {
charset = headers[key] as string;
}
}
}
let strData = this.readAsString(option['originalData'] as ByteArray, charset);
let type = kParam.responseType != null ? kParam.responseType : kParam.dataType;
if (type == null) {
type = charset;
}
if (kParam.method == "HEAD") {
type = "";
}
const data = this.parseData(strData, type);
if (data == null) {
let failResult = new RequestFailImpl(getErrcode(100001));
new RunnableTask(this.looper, () => {
if (kParam != null) {
let fail = kParam.fail;
if (fail != null) {
fail(failResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(failResult);
}
}
}).execute();
return;
}
result['data'] = data;
}
result['statusText'] = StatusCode.getStatus(option['statusCode'] as string);
if (this.headers != null) {
result['header'] = this.headers;
}
let tmp : RequestSuccess<T> = {
data: result['data'] as T,
statusCode: (result['statusCode'] as string).toInt(),
header: result['header']!,
cookies: []
};
new RunnableTask(this.looper, () => {
if (kParam != null) {
let success = kParam.success;
if (success != null) {
success(tmp);
}
let complete = kParam.complete;
if (complete != null) {
complete(tmp);
}
}
}).execute();
}
}
}
private readAsString(byteArray : ByteArray, type : string) : string {
let charsetType = "utf-8";
if (type != null) {
let matcher = charsetPattern.matcher(type.toLowerCase(Locale.ENGLISH));
if (matcher.find()) {
charsetType = matcher.group(1);
}
}
try {
return new String(byteArray, charset(charsetType));
} catch (e : Exception) {
return new String(byteArray);
}
}
private parseData(data : string, type : string) : any | null {
if ("java.lang.Object".equals(this.clzName, true)) {
if (type.contains("json")) {
return JSON.parse(data);
} else if (type == 'jsonp') {
if (TextUtils.isEmpty(data)) {
return {};
}
let start = data.indexOf('(') + 1;
let end = data.indexOf(')');
if (start == 0 || start >= end) {
return {};
}
let tmp = data.substring(start, end);
return JSON.parse(tmp);
} else {
return data;
}
} else {
return JSON.parse<T>(data, this.type);
}
}
}
class UploadNetworkListener implements NetworkUploadFileListener {
private param : UploadFileOptions | null = null;
public progressListeners = new ArrayList<UploadFileProgressUpdateCallback>();
private looper : Looper | null = null;
constructor(param : UploadFileOptions) {
this.param = param;
this.looper = Looper.myLooper();
}
onProgress(progressUpdate : OnProgressUpdateResult) {
if (this.progressListeners.size != 0) {
new RunnableTask(this.looper, () => {
for (let i : Int = 0; i < this.progressListeners.size; i++) {
let listener = this.progressListeners.get(i);
listener(progressUpdate);
}
}).execute();
}
}
onComplete(option : UTSJSONObject) {
let kParam = this.param;
if (kParam != null) {
const errorMsg = option["errorMsg"];
let errCode = (option['statusCode']! as string).toInt();
if (errorMsg != null) {
let failResult = new UploadFileFailImpl(getErrcode(errCode));
new RunnableTask(this.looper, () => {
if (kParam != null) {
let fail = kParam.fail;
if (fail != null) {
fail(failResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(failResult);
}
}
}).execute();
} else {
let kData = option["data"];
let data = "";
if (kData != null) {
data = kData as string;
}
let successResult : UploadFileSuccess = {
data: data,
statusCode: errCode
}
new RunnableTask(this.looper, () => {
if (kParam != null) {
let success = kParam.success;
if (success != null) {
success(successResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(successResult);
}
}
}).execute();
}
}
}
}
class DownloadNetworkListener implements NetworkDownloadFileListener {
private param : DownloadFileOptions | null = null;
public progressListeners = new ArrayList<DownloadFileProgressUpdateCallback>();
private looper : Looper | null = null;
constructor(param : DownloadFileOptions) {
this.param = param;
this.looper = Looper.myLooper();
}
onProgress(progressUpdate : OnProgressDownloadResult) {
if (this.progressListeners.size != 0) {
new RunnableTask(this.looper, () => {
for (let i : Int = 0; i < this.progressListeners.size; i++) {
let listener = this.progressListeners.get(i);
listener(progressUpdate);
}
}).execute();
}
}
onComplete(option : UTSJSONObject) {
let kParam = this.param;
if (kParam != null) {
let statusCode = (option['statusCode']! as string).toInt();
let errMsg = option['errorMsg'];
if (errMsg != null) {
let errCode = (option['errorCode']! as string).toInt();
let failResult = new DownloadFileFailImpl(getErrcode(errCode));
failResult.errMsg = errMsg as string;
let exception = option['cause'];
if(exception != null){
let cause = (exception as Exception).cause.toString();
failResult.cause = new Error(cause);
}
new RunnableTask(this.looper, () => {
if (kParam != null) {
let fail = kParam.fail;
if (fail != null) {
fail(failResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(failResult);
}
}
}).execute();
} else {
let kTempFilePath = option["tempFilePath"];
let tempFilePath = "";
if (kTempFilePath != null) {
tempFilePath = kTempFilePath as string;
}
let successResult : DownloadFileSuccess = {
tempFilePath: tempFilePath,
statusCode: statusCode
}
new RunnableTask(this.looper, () => {
if (kParam != null) {
let success = kParam.success;
if (success != null) {
success(successResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(successResult);
}
}
}).execute();
}
}
}
}
@UTSAndroid.keyword("inline")
@UTSAndroid.keyword('reified')
export function request<T>(options : RequestOptions<T>) : RequestTask | null {
const type = UTSAndroid.getGenericType<T>();
const clzName = UTSAndroid.getGenericClassName<T>();
return NetworkManager.getInstance().request<T>(options, new RequestNetworkListener<T>(options, type, clzName));
}
export const uploadFile : UploadFile = (options : UploadFileOptions) : UploadTask | null => {
return NetworkManager.getInstance().uploadFile(options, new UploadNetworkListener(options));
}
export const downloadFile : DownloadFile = (options : DownloadFileOptions) : DownloadTask | null => {
return NetworkManager.getInstance().downloadFile(options, new DownloadNetworkListener(options));
}
\ No newline at end of file
import { RequestOptions, RequestTask, UploadTask, UploadFileOptions, OnProgressUpdateResult, UploadFileProgressUpdateCallback, OnProgressDownloadResult, DownloadTask, DownloadFileOptions, DownloadFileProgressUpdateCallback } from '../../interface.uts'
import OkHttpClient from 'okhttp3.OkHttpClient';
import Protocol from 'okhttp3.Protocol';
import TimeUnit from 'java.util.concurrent.TimeUnit';
import Collections from 'java.util.Collections';
import { OKDns } from './OKDns.uts';
import Uri from 'android.net.Uri';
import ConnectionPool from 'okhttp3.ConnectionPool';
import Request from 'okhttp3.Request';
import Call from 'okhttp3.Call';
import Response from 'okhttp3.Response';
import Headers from 'okhttp3.Headers';
import Callback from 'okhttp3.Callback';
import ExecutorService from 'java.util.concurrent.ExecutorService';
import Executors from 'java.util.concurrent.Executors';
import RequestBody from 'okhttp3.RequestBody';
import MediaType from 'okhttp3.MediaType';
import Dispatcher from 'okhttp3.Dispatcher';
import IOException from 'java.io.IOException';
import List from 'java.util.List';
import InputStream from 'java.io.InputStream';
import ByteArrayOutputStream from 'java.io.ByteArrayOutputStream';
import BufferedReader from 'java.io.BufferedReader';
import InputStreamReader from 'java.io.InputStreamReader';
import OkHostnameVerifier from 'okhttp3.internal.tls.OkHostnameVerifier';
import { SSLFactoryManager } from './tls/SSLFactoryManager.uts';
import { SSLConfig } from './tls/SSLConfig.uts';
import { CookieInterceptor } from './interceptor/CookieInterceptor.uts'
import UTSAndroid from 'io.dcloud.uts.UTSAndroid';
import MultipartBody from 'okhttp3.MultipartBody';
import JSONObject from 'com.alibaba.fastjson.JSONObject';
import { UploadController } from './upload/UploadController.uts';
import ArrayList from 'java.util.ArrayList';
import { DownloadController } from './download/DownloadController.uts';
class NetworkRequestListener {
public onStart(): void { }
public onProgress(progress: number): void { }
public onComplete(option: UTSJSONObject): void { }
public onHeadersReceived(statusCode: number, headers: MutableMap<String, MutableList<String>>): void { }
}
interface NetworkUploadFileListener {
progressListeners: ArrayList<UploadFileProgressUpdateCallback>;
onProgress(progressUpdate: OnProgressUpdateResult): void;
onComplete(option: UTSJSONObject): void;
}
interface NetworkDownloadFileListener {
progressListeners: ArrayList<DownloadFileProgressUpdateCallback>;
onProgress(progressUpdate: OnProgressDownloadResult): void;
onComplete(option: UTSJSONObject): void;
}
class NetworkRequestTaskImpl implements RequestTask {
private call: Call | null = null;
constructor(call: Call) {
this.call = call;
}
public abort() {
if (this.call != null) {
this.call?.cancel();
}
}
}
class NetworkManager {
private static instance: NetworkManager | null = null;
private static connectPool: ConnectionPool | null = null;
/**
* request的线程池
*/
private requestExecutorService: ExecutorService | null = null;
public static getInstance(): NetworkManager {
if (this.instance == null) {
this.instance = new NetworkManager();
}
return this.instance!;
}
public request<T>(param: RequestOptions<T>, listener: NetworkRequestListener): RequestTask | null {
if (listener != null) {
listener.onStart();
}
let client = this.createRequestClient(param!);
let request = this.createRequest(param!, listener!);
if (request == null) {
return null;
}
let call: Call = client.newCall(request);
call.enqueue(new SimpleCallback(listener));
let task = new NetworkRequestTaskImpl(call);
return task;
}
public createRequestClient<T>(param: RequestOptions<T>): OkHttpClient {
let clientBuilder = OkHttpClient.Builder();
const timeout: Long = param.timeout != null ? param.timeout!.toLong() : 60000;
clientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.callTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.protocols(Collections.singletonList(Protocol.HTTP_1_1));
clientBuilder.addInterceptor(new CookieInterceptor());
if (param.firstIpv4 != null) {
let firstIpv4 = param.firstIpv4!;
if (firstIpv4) {
let okDns = new OKDns();
clientBuilder.dns(okDns);
}
}
// let requestHost = Uri.parse(param.url).getHost();
//todo tls暂时搁置
//todo 把call保存起来, 存储到task里面,等待调用。
// let tlsOption: UTSJSONObject = param.tls;
// if (tlsOption != null) {
// let sslConfig = new SSLConfig();
// sslConfig.setKeystore(tlsOption['keystore']);
// sslConfig.setStorePass(tlsOption['storePass']);
// let caArray:JSONArray = tlsOption['ca'];
// let caParam:Array<string> = null;
// if (caArray != null){
// caParam = caArray.toArray(emptyArray<String>());
// }
// sslConfig.setCa(caParam);
// clientBuilder.sslSocketFactory(SSLFactoryManager.getInstance().getSSLSocketFactory(sslConfig));
// clientBuilder.hostnameVerifier(OkHostnameVerifier.INSTANCE);
// }
if (NetworkManager.connectPool == null) {
NetworkManager.connectPool = new ConnectionPool();
}
clientBuilder.connectionPool(NetworkManager.connectPool);
if (this.requestExecutorService == null) {
this.requestExecutorService = Executors.newFixedThreadPool(10);
}
clientBuilder.dispatcher(new Dispatcher(this.requestExecutorService));
return clientBuilder.build();
}
public createRequest<T>(param: RequestOptions<T>, listener: NetworkRequestListener): Request | null {
let requestBilder = new Request.Builder();
try {
requestBilder.url(param.url);
} catch (e: Exception) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '600009';
option['errorMsg'] = "invalid URL";
option['cause'] = e;
if (listener != null) {
listener.onComplete(option);
}
return null;
}
let ua = UTSAndroid.getWebViewInfo(UTSAndroid.getAppContext()!)["ua"].toString();
requestBilder.header("User-Agent", ua);
if (param.header == null) {
param.header = {}
}
let contentType = "application/x-www-form-urlencoded; charset=UTF-8";
let hasContentType = false;
let headers = param.header!.toJSONObject() as JSONObject;
for (key in headers.keys) {
if (key.equals("Content-Type", true)) {
contentType = "" + headers![key]!;
hasContentType = true;
}
requestBilder.header(key, "" + headers[key]);
}
if (!hasContentType) {
if (!"GET".equals(param.method)) {
contentType = "application/json";
}
}
if ("POST".equals(param.method) || "PUT".equals(param.method) || "PATCH".equals(param.method) || "DELETE".equals(param.method)) {
if (param.data != null && listener != null) {
listener.onProgress(0);
}
let body: string = "";
if (param.data != null) {
if (typeof(param.data) == 'string') {
body = param.data as string;
} else if (param.data instanceof UTSJSONObject){
if(contentType.indexOf("application/x-www-form-urlencoded") == 0){
const data = param.data as UTSJSONObject;
const map:Map<string, any|null> = data.toMap();
const bodyArray = new Array<string>();
map.forEach((value, key)=>{
bodyArray.push(key + "="+ value);
})
body = bodyArray.join("&");
}else{
body = JSON.stringify(param.data);
}
}
}
let requestBody = RequestBody.create(MediaType.parse(contentType), body);
requestBilder.method(param.method, requestBody);
if (listener != null) {
listener.onProgress(100);
}
} else if ("HEAD".equals(param.method)) {
requestBilder.head();
}else if ("OPTIONS".equals(param.method)) {
requestBilder.method(param.method, null);
} else if (param.method == null || "GET".equals(param.method)){
const data = param.data;
if (data != null) {
let json: UTSJSONObject | null = null;
if (typeof(data) == 'string') {
json = JSON.parseObject(data as string);
}else if(data instanceof UTSJSONObject) {
json = data;
}
if (json != null){
let url = param.url;
try {
requestBilder.url(this.stringifyQuery(url, json));
} catch (e: Exception) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '600009';
option['errorMsg'] = "invalid URL";
option['cause'] = e;
if (listener != null) {
listener.onComplete(option);
}
return null;
}
}
}
}
return requestBilder.build();
}
/**
* data拼接到url上
*/
private stringifyQuery(url: string, data: UTSJSONObject) : string{
let newUrl = url;
//http:xxx/xxx?a=b&c=d#123
let str = url.split('#')
let hash = ''
if(str.length > 1){
hash = str[1] //123
}
str = str[0].split('?')
let query = ''
if(str.length > 1){
query = str[1] //a=b&c=d
}
newUrl = str[0] // http:xxx/xxx
const pairs = query.split('&')
const queryMap = new Map<string, string>();
pairs.forEach((item, _)=>{
const temp = item.split('=')
if(temp.length > 1){
queryMap[temp[0]] = temp[1]
}
});
const dataMap: Map<string, any|null> = data.toMap();
dataMap.forEach((value, key)=>{
if (value instanceof UTSJSONObject || value instanceof Array<any|null>){
queryMap[key] = JSON.stringify(value);
}else{
queryMap[key] = "" + value;
}
})
let queryStr = "";
queryMap.forEach((value, key)=>{
queryStr += key + "=" + value + "&"
});
queryStr = queryStr.slice(0, -1);
if(queryStr.length > 0){
newUrl += "?" + queryStr;
}
if(hash.length > 0){
newUrl += "#" + hash;
}
return newUrl;
}
public uploadFile(options: UploadFileOptions, listener: NetworkUploadFileListener): UploadTask | null {
return UploadController.getInstance().uploadFile(options, listener);
}
public downloadFile(options: DownloadFileOptions, listener: NetworkDownloadFileListener): DownloadTask | null {
return DownloadController.getInstance().downloadFile(options, listener);
}
}
class SimpleCallback implements Callback {
private listener?: NetworkRequestListener = null;
constructor(listener: NetworkRequestListener) {
this.listener = listener;
}
override onResponse(call: Call, response: Response): void {
let headers: Headers = response.headers();
let headerMap = headers.toMultimap();
let code = response.code();
let option = {};
option["statusCode"] = code + "";
let pListener = this.listener;
if (pListener != null) {
pListener.onHeadersReceived(code, headerMap);
}
let rawStream = response.body()!.byteStream()!;
if (response.isSuccessful()) {
option['originalData'] = this.readInputStreamAsBytes(rawStream, this.listener);
} else {
option['errorMsg'] = this.readInputStream(rawStream, this.listener);
}
if (pListener != null) {
pListener.onComplete(option);
}
}
override onFailure(call: Call, e: IOException): void {
let pListener = this.listener;
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '602001';
option['errorMsg'] = e.message;
option['cause'] = e;
if (pListener != null) {
pListener.onComplete(option);
}
}
private readInputStreamAsBytes(inputSteam?: InputStream, listener?: NetworkRequestListener): ByteArray | null {
if (inputSteam == null) {
return null;
}
let buffer = new ByteArrayOutputStream();
let readCount = 0;
let data = new ByteArray(2048);
do {
let len = inputSteam.read(data, 0, data.size);
if (len == -1) {
break;
}
buffer.write(data, 0, len);
readCount += len;
if (listener != null) {
listener.onProgress(readCount);
}
} while (true);
buffer.flush();
return buffer.toByteArray();
}
private readInputStream(inputSteam?: InputStream, listener?: NetworkRequestListener): string | null {
if (inputSteam == null) {
return null;
}
let builder = new StringBuilder();
let localBufferedReader = new BufferedReader(new InputStreamReader(inputSteam));
let data = new CharArray(2048);
do {
let len = localBufferedReader.read(data);
if (len == -1) {
break;
}
builder.append(data, 0, len);
if (listener != null) {
listener.onProgress(builder.length);
}
} while (true);
localBufferedReader.close();
return builder.toString();
}
}
export {
NetworkManager,
NetworkRequestListener,
NetworkUploadFileListener,
NetworkDownloadFileListener
}
\ No newline at end of file
import Dns from 'okhttp3.Dns';
import UnknownHostException from 'java.net.UnknownHostException';
import InetAddress from 'java.net.InetAddress';
import Inet4Address from 'java.net.Inet4Address';
export class OKDns implements Dns {
public override lookup(hostName: string): kotlin.collections.MutableList<InetAddress> {
if (hostName == null) {
throw UnknownHostException("hostname == null");
} else {
try {
let inetAddressesList: Array<InetAddress> = [];
let inetAddresses = InetAddress.getAllByName(hostName);
for (inetAddress in inetAddresses) {
if (inetAddress instanceof Inet4Address) {
inetAddressesList.unshift(inetAddress)
} else {
inetAddressesList.push(inetAddress);
}
}
return inetAddressesList;
} catch (e: Exception) {
let unknownHostException = new UnknownHostException("error");
unknownHostException.initCause(e);
throw unknownHostException;
}
}
}
}
class StatusCode {
public static statusCodeMap : Map<string, string> | null = null;
private static initStatusCodeMap() {
let map = new Map<string, string>();
this.statusCodeMap = map;
map.set('100', "Continue");
map.set('101', "Switching Protocol");
map.set('200', "OK");
map.set('201', "Created");
map.set('202', "Accepted");
map.set('203', "Non-Authoritative Information");
map.set('204', "No Content");
map.set('205', "Reset Content");
map.set('206', "Partial Content");
map.set('300', "Multiple Choice");
map.set('301', "Moved Permanently");
map.set('302', "Found");
map.set('303', "See Other");
map.set('304', "Not Modified");
map.set('305', "Use Proxy");
map.set('306', "unused");
map.set('307', "Temporary Redirect");
map.set('308', "Permanent Redirect");
map.set('400', "Bad Request");
map.set('401', "Unauthorized");
map.set('402', "Payment Required");
map.set('403', "Forbidden");
map.set('404', "Not Found");
map.set('405', "Method Not Allowed");
map.set('406', "Not Acceptable");
map.set('407', "Proxy Authentication Required");
map.set('408', "Request Timeout");
map.set('409', "Conflict");
map.set('410', "Gone");
map.set('411', "Length Required");
map.set('412', "Precondition Failed");
map.set('413', "Payload Too Large");
map.set('414', "URI Too Long");
map.set('415', "Unsupported Media Type");
map.set('416', "Requested Range Not Satisfiable");
map.set('417', "Expectation Failed");
map.set('418', "I'm a teapot");
map.set('421', "Misdirected Request");
map.set('426', "Upgrade Required");
map.set('428', "Precondition Required");
map.set('429', "Too Many Requests");
map.set('431', "Request Header Fields Too Large");
map.set('500', "Internal Server Error");
map.set('501', "Not Implemented");
map.set('502', "Bad Gateway");
map.set('503', "Service Unavailable");
map.set('504', "Gateway Timeout");
map.set('505', "HTTP Version Not Supported");
map.set('506', "Variant Also Negotiates");
map.set('507', "Variant Also Negotiates");
map.set('511', "Network Authentication Required");
}
public static getStatus(code : string) : string {
let map = this.statusCodeMap;
if (map == null) {
this.initStatusCodeMap();
}
let tmp = this.statusCodeMap!;
if (!(tmp.has(code))) {
return 'unknown status';
} else {
return tmp.get(code)! as string;
}
}
}
export {
StatusCode
}
import { DownloadFileOptions, DownloadTask, DownloadFileProgressUpdateCallback, OnProgressDownloadResult } from '../../../interface.uts';
import { NetworkDownloadFileListener } from '../NetworkManager.uts'
import OkHttpClient from 'okhttp3.OkHttpClient';
import TimeUnit from 'java.util.concurrent.TimeUnit';
import ExecutorService from 'java.util.concurrent.ExecutorService';
import Executors from 'java.util.concurrent.Executors';
import Dispatcher from 'okhttp3.Dispatcher';
import Callback from 'okhttp3.Callback';
import Response from 'okhttp3.Response';
import Request from 'okhttp3.Request';
import UTSAndroid from 'io.dcloud.uts.UTSAndroid';
import Call from 'okhttp3.Call';
import IOException from 'java.io.IOException';
import ResponseBody from 'okhttp3.ResponseBody';
import File from 'java.io.File';
import BufferedSink from 'okio.BufferedSink';
import BufferedSource from 'okio.BufferedSource';
import Okio from 'okio.Okio';
import TextUtils from 'android.text.TextUtils';
import StringTokenizer from 'java.util.StringTokenizer';
import MimeTypeMap from 'android.webkit.MimeTypeMap';
import URLDecoder from 'java.net.URLDecoder';
import CookieManager from 'android.webkit.CookieManager';
import KotlinArray from 'kotlin.Array';
import Context from 'android.content.Context';
import Environment from 'android.os.Environment';
class NetworkDownloadTaskImpl implements DownloadTask {
private call : Call | null = null;
private listener : NetworkDownloadFileListener | null = null;
constructor(call : Call, listener : NetworkDownloadFileListener) {
this.call = call;
this.listener = listener;
}
public abort() {
if (this.call != null) {
this.call?.cancel();
}
}
public onProgressUpdate(option : DownloadFileProgressUpdateCallback) {
const kListener = this.listener;
if (kListener != null) {
kListener.progressListeners.add(option);
}
}
}
export class DownloadController {
private static instance : DownloadController | null = null
/**
* 上传的线程池
*/
private downloadExecutorService : ExecutorService | null = null;
public static getInstance() : DownloadController {
if (this.instance == null) {
this.instance = new DownloadController();
}
return this.instance!;
}
public downloadFile(options : DownloadFileOptions, listener : NetworkDownloadFileListener) : DownloadTask | null {
const client = this.createDownloadClient(options);
let request = this.createDownloadRequest(options, listener);
if (request == null) {
return null;
}
let call : Call = client.newCall(request);
call.enqueue(new SimpleDownloadCallback(listener, options.filePath ?? ""));
let task = new NetworkDownloadTaskImpl(call, listener);
return task;
}
private createDownloadClient(option : DownloadFileOptions) : OkHttpClient {
let clientBuilder = OkHttpClient.Builder();
const timeout : Long = option.timeout != null ? option.timeout!.toLong() : 120000;
clientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.callTimeout(timeout, TimeUnit.MILLISECONDS);
if (this.downloadExecutorService == null) {
this.downloadExecutorService = Executors.newFixedThreadPool(10);
}
clientBuilder.dispatcher(new Dispatcher(this.downloadExecutorService));
return clientBuilder.build();
}
private createDownloadRequest(options : DownloadFileOptions, listener : NetworkDownloadFileListener) : Request | null {
let requestBilder = new Request.Builder();
try {
requestBilder.url(options.url);
} catch (e : Exception) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = "invalid URL";
option['cause'] = e;
if (listener != null) {
listener.onComplete(option);
}
return null;
}
let ua = UTSAndroid.getWebViewInfo(UTSAndroid.getAppContext()!)["ua"].toString();
requestBilder.header("User-Agent", ua);
const headers = options.header?.toMap();
if (headers != null) {
for (entry in headers) {
const key = entry.key;
const value = entry.value;
if (value != null) {
requestBilder.addHeader(key, "" + value);
} else {
continue;
}
}
}
return requestBilder.build();
}
}
class SimpleDownloadCallback implements Callback {
private downloadFilePath = "/download/";
private listener : NetworkDownloadFileListener | null = null;
private specifyPath = "";
constructor(listener : NetworkDownloadFileListener, specifyPath : string) {
this.listener = listener;
this.specifyPath = specifyPath;
}
override onFailure(call : Call, exception : IOException) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = exception.message;
option['cause'] = exception;
this.listener?.onComplete(option);
}
override onResponse(call : Call, response : Response) {
if (response.isSuccessful()) {
const source = response.body()?.source()
if (source != null) {
this.setCookie(response);
const tempFile = this.getTempFile()
let tempSink : BufferedSink | null = null;
let tempSource : BufferedSource | null = null;
let targetSink : BufferedSink | null = null;
try {
tempSink = Okio.buffer(Okio.sink(tempFile));
let totalBytesRead : Long = 0;
const contentLength = response.body()!!.contentLength();
const bufferSize : Int = 8 * 1024;
const buffer = ByteArray(bufferSize);
do {
let bytesRead = source.read(buffer);
if (bytesRead == -1) {
break;
}
tempSink.write(buffer, 0, bytesRead)
totalBytesRead += bytesRead.toLong();
const progress = (totalBytesRead.toFloat() / contentLength) * 100
let downloadProgressUpdate : OnProgressDownloadResult = {
progress: progress,
totalBytesWritten: totalBytesRead,
totalBytesExpectedToWrite: contentLength
}
this.listener?.onProgress(downloadProgressUpdate);
} while (true)
tempSink.flush()
tempSource = Okio.buffer(Okio.source(tempFile));
let targetFile = this.getFile(response)
targetSink = Okio.buffer(Okio.sink(targetFile));
targetSink.writeAll(tempSource);
targetSink.flush();
let option = {};
option['statusCode'] = response.code() + "";
if (targetFile.exists()) {
option['tempFilePath'] = targetFile.getPath();
}
this.listener?.onComplete(option);
} finally {
tempSink?.close()
targetSink?.close()
tempSource?.close()
tempFile.delete()
}
}
} else {
let option = {};
const code = response.code() + "";
const errorMsg = response.body()?.string();
option['statusCode'] = code;
option['errorCode'] = code;
option['errorMsg'] = errorMsg;
option['cause'] = null;
this.listener?.onComplete(option);
}
}
setCookie(response : Response) {
const setCookie = response.header("Set-Cookie")
if (!TextUtils.isEmpty(setCookie)) {
CookieManager.getInstance().setCookie(response.request().url().toString(), setCookie);
}
}
getTempFile() : File {
return new File(UTSAndroid.getAppContext()!.getExternalCacheDir(), "temp_" + System.currentTimeMillis())
}
getRealPath() : string {
var path = UTSAndroid.getAppTempPath() ?? "";
return path + this.downloadFilePath;
}
getFile(response : Response) : File {
let targetPath = "";
if (this.specifyPath != "") {
const sourcePath = UTSAndroid.convert2AbsFullPath("/");
const sourceFileDir = new File(sourcePath);
if (this.isDescendant(sourceFileDir, new File(this.specifyPath))) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '602001';
option['errorMsg'] = "This path is not supported";
option['cause'] = null;
this.listener?.onComplete(option);
return new File("")
}
const pos = this.specifyPath.lastIndexOf("/")
if (pos == this.specifyPath.length - 1) {
//如果filePath是目录
if (this.isAbsolute(this.specifyPath)) {
targetPath = this.specifyPath;
} else {
targetPath = UTSAndroid.getAppTempPath()!! + "/" + this.specifyPath
}
} else {
let path = "";
if (this.isAbsolute(this.specifyPath)) {
path = this.specifyPath;
} else {
path = UTSAndroid.getAppTempPath()!! + "/" + this.specifyPath;
}
const file = new File(path)
const parentFile = file.getParentFile()
if (parentFile != null) {
if (!parentFile.exists()) {
parentFile.mkdirs()
}
}
if (file.exists() && file.isDirectory()) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '602001';
option['errorMsg'] = "The target file path is already a directory file, and file creation failed.";
option['cause'] = null;
this.listener?.onComplete(option);
}
if (!file.exists()) {
try {
file.createNewFile()
} catch (exception : Exception) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '602001';
option['errorMsg'] = exception.message;
option['cause'] = exception;
this.listener?.onComplete(option);
}
}
return file
}
} else {
targetPath = this.getRealPath();
}
let fileName = "";
let remoteFileName = response.header("content-disposition");
if (!TextUtils.isEmpty(remoteFileName)) {
// form-data; name="file"; filename="xxx.pdf"
const segments : KotlinArray<String | null> | null = this.stringSplit(remoteFileName, ";")
if (segments != null) {
for (let i : Int = 0; i < segments.size; i++) {
const segment = segments[i];
if (segment != null) {
if (segment.contains("filename")) {
const pair = this.stringSplit(segment.trim(), "=") //目前认为只存在一个键值对
if (pair != null) {
let key = pair[0];
let value = pair[1];
if (key != null) {
key = key.replace("\"", "");
}
if (value != null) {
value = value.replace("\"", "");
}
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value) && key!.equals("filename", true)) {
if (value != null) {
fileName = value;
}
}
}
}
}
}
}
}
if (TextUtils.isEmpty(fileName)) {
let path = response.request().url().encodedPath()
let pos = path.lastIndexOf('/')
if (pos >= 0) {
path = path.substring(pos + 1)
if (path.indexOf('.') >= 0) { //存在后缀,则认为成功
if (path.contains("?")) {
path = path.substring(0, path.indexOf("?"))
}
fileName = path
}
}
}
if (TextUtils.isEmpty(fileName)) {
fileName = System.currentTimeMillis().toString()
const contentType = response.header("content-type")
let type = MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType);
if (type != null) {
fileName += "." + type;
}
}
fileName = URLDecoder.decode(fileName, "UTF-8")
fileName = fileName.replace(File.separator.toRegex(), "")
if (fileName.contains("?")) {
fileName = fileName.replace("\\?".toRegex(), "0")
}
if (fileName.length > 80) {
const subFileName : String = fileName.substring(0, 80)
fileName = subFileName + System.currentTimeMillis()
}
targetPath += fileName
if (new File(targetPath).exists()) {
const index = targetPath.lastIndexOf(".");
let tFileName = targetPath;
let tFileType = "";
if (index >= 0) {
tFileName = targetPath.substring(0, index)
tFileType = targetPath.substring(index)
}
var number = 1
while (new File(targetPath).exists()) {
targetPath = tFileName + "(" + number + ")" + tFileType;
number++
}
}
const file = new File(targetPath)
const parentFile = file.getParentFile()
if (parentFile != null) {
if (!parentFile.exists()) {
parentFile.mkdirs()
}
}
if (!file.exists()) {
try {
file.createNewFile()
} catch (exception : Exception) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '602001';
option['errorMsg'] = exception.message;
option['cause'] = exception;
this.listener?.onComplete(option);
}
}
return file
}
isAbsolute(path : string) : boolean {
const context = UTSAndroid.getAppContext()!! as Context;
if (path.startsWith(context.getFilesDir().getParent())) {
return true;
}
const exPath = context.getExternalFilesDir(null)?.getParent();
if (exPath != null && path.startsWith(exPath)) {
return true;
}
return false;
}
/**
* 判断两个文件的上下级关系
*/
isDescendant(parent : File, child : File) : boolean {
//有可能开发者传入的是/sdcard 或者/storage/emulated/ 这样的文件路径, 所以要用软连接的实际文件路径进行对比.
if (child.getCanonicalPath() == parent.getCanonicalPath()) {
return true;
}
let parentFile = child.getParentFile();
if (parentFile == null) {
return false;
}
return this.isDescendant(parent, parentFile);
}
stringSplit(str : String | null, delim : String | null) : KotlinArray<String | null> | null {
if (!TextUtils.isEmpty(str) && !TextUtils.isEmpty(delim)) {
const stringTokenizer = new StringTokenizer(str, delim, false);
const result = arrayOfNulls<String>(stringTokenizer.countTokens())
var index : Int = 0
while (stringTokenizer.hasMoreElements()) {
result[index] = stringTokenizer.nextToken().trim()
index += 1
}
return result
}
return null
}
}
\ No newline at end of file
import Interceptor from 'okhttp3.Interceptor';
import Response from 'okhttp3.Response';
import CookieHandler from 'java.net.CookieHandler';
import TreeMap from 'java.util.TreeMap';
import Headers from 'okhttp3.Headers';
import Request from 'okhttp3.Request';
class CookieInterceptor implements Interceptor {
override intercept(chain : Interceptor.Chain) : Response {
let request = chain.request()
let headerCookie = request.header("cookie")
let uri = request.url().uri()
let cookieHandler = CookieHandler.getDefault()
if (headerCookie == null) {
let requestBuilder = request.newBuilder()
try {
let currentHeaders = this.toMap(request.headers())
let localCookie = cookieHandler.get(uri, currentHeaders)
this.addCookies(requestBuilder, localCookie)
} catch (e : Exception) {
}
request = requestBuilder.build()
}
let response = chain.proceed(request)
try {
cookieHandler.put(uri, this.toMap(response.headers()))
} catch (e : Exception) {
}
return response
}
private toMap(headers : Headers) : MutableMap<String, MutableList<String>> {
let result : MutableMap<String, MutableList<String>> = new TreeMap(String.CASE_INSENSITIVE_ORDER)
let size = headers.size()
for (let i:Int = 0; i < size; i++) {
let name = headers.name(i)
let values = result[name]
if (values == null) {
values = arrayListOf()
result[name] = values
}
values.add(headers.value(i))
}
return result
}
private addCookies(builder : Request.Builder, localCookie : MutableMap<String, MutableList<String>>) : void {
let totalList = mutableListOf<String>()
let flagList = mutableListOf<String>()
for (key in localCookie.keys) {
if (flagList.size == 2) {
break
}
if ("cookie".equals(key, true) || "cookie2".equals(key, true)) {
flagList.add(key)
let cookieList = localCookie[key]
if (!cookieList.isNullOrEmpty()) {
totalList.addAll(cookieList)
}
}
}
let headerStr = new StringBuilder()
for (let str in totalList) {
headerStr.append(str)
headerStr.append("; ")
}
if (headerStr.toString().endsWith("; ")) {
headerStr.deleteRange(headerStr.length - 2, headerStr.length - 1)
}
if (!headerStr.toString().isEmpty()){
builder.addHeader("Cookie", headerStr.toString())
}
}
}
export {
CookieInterceptor
}
\ No newline at end of file
import Arrays from 'java.util.Arrays';
import KotlinArray from 'kotlin.Array'
class SSLConfig {
private keystore ?: string = null;
private storePass ?: string = null;
private ca ?: KotlinArray<String> = null;
public getKeystore() : string | null {
return this.keystore;
}
public setKeystore(ks : string) {
if (ks == null) {
ks = "";
}
this.keystore = ks;
}
public getStorePass() : string | null {
return this.storePass;
}
public setStorePass(sp : string) {
if (sp == null) {
sp = "";
}
this.storePass = sp;
}
public getCa() : KotlinArray<String> | null {
return this.ca;
}
public setCa(ca : KotlinArray<String>) {
if (ca == null) {
ca = emptyArray();
}
this.ca = ca;
}
}
export {
SSLConfig
}
\ No newline at end of file
import SSLSocketFactory from 'javax.net.ssl.SSLSocketFactory';
import { SSLConfig } from './SSLConfig.uts'
import SSLContext from 'javax.net.ssl.SSLContext';
import KeyStore from 'java.security.KeyStore';
import KeyManagerFactory from 'javax.net.ssl.KeyManagerFactory';
import CertificateFactory from 'java.security.cert.CertificateFactory';
import TextUtils from 'android.text.TextUtils';
class SSLFactoryManager {
private static instance?: SSLFactoryManager = null;
private cacheSSLFactory: Map<SSLConfig, SSLSocketFactory> = new Map<SSLConfig, SSLSocketFactory>();
public static getInstance(): SSLFactoryManager {
if (this.instance == null) {
this.instance = SSLFactoryManager();
}
return this.instance!;
}
public getSSLSocketFactory(sslConfig: SSLConfig): SSLSocketFactory | null {
if (sslConfig == null) {
return null;
}
if (this.cacheSSLFactory.has(sslConfig)){
let sslFactory = this.cacheSSLFactory.get(sslConfig);
if (sslConfig != null){
return sslFactory;
}
}
try{
let sslContext = SSLContext.getInstance('TLS');
let keyStore = KeyStore.getInstance('PKCS12');
let keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
if (!TextUtils.isEmpty(sslConfig.getKeystore()) && !TextUtils.isEmpty(sslConfig.getStorePass())){
//todo 1. 这里需要解析keystore
// 2. 如果是文件需要转换一下路径,然后读出来。
// resolve : 原生层会提供bundleurl和运行模式的接口。
}else{
keyManagerFactory = null;
}
let certificateFactory = CertificateFactory.getInstance('X.509');
let caKeyStore = KeyStore.getInstance('PKCS12');
}catch(e : Exception){
}
return null;
}
}
export {
SSLFactoryManager
}
\ No newline at end of file
import RequestBody from 'okhttp3.RequestBody';
import MediaType from 'okhttp3.MediaType';
import InputStream from 'java.io.InputStream';
import BufferedSink from 'okio.BufferedSink';
import Source from 'okio.Source';
import Okio from 'okio.Okio';
import Util from 'okhttp3.internal.Util';
export class InputStreamRequestBody extends RequestBody {
private mediaType : MediaType | null = null;
private length : Long = -1;
private inputStream : InputStream | null = null;
constructor(mediaType : MediaType, length : Long, inputStream : InputStream) {
super()
this.mediaType = mediaType;
this.length = length;
this.inputStream = inputStream;
}
override contentLength() : Long {
return this.length;
}
override contentType() : MediaType {
const type = this.mediaType;
if (type == null) {
return MediaType.parse("application/octet-stream")!;
} else {
return type;
}
}
override writeTo(sink : BufferedSink) {
let source : Source | null = null;
try {
source = Okio.source(this.inputStream);
sink.writeAll(source);
} catch (e) {
}
Util.closeQuietly(source);
}
}
\ No newline at end of file
import RequestBody from 'okhttp3.RequestBody';
import MediaType from 'okhttp3.MediaType';
import BufferedSink from 'okio.BufferedSink';
import ForwardingSink from 'okio.ForwardingSink';
import Sink from 'okio.Sink';
import Buffer from 'okio.Buffer';
import Okio from 'okio.Okio';
export interface UploadProgressListener {
onProgress(bytesWritten : number, contentLength : number) : void;
}
class CountingSink extends ForwardingSink {
private listener : UploadProgressListener | null = null;
private bytesWritten : number = 0;
private total : number = 0;
constructor(sink : Sink, total : number, listener : UploadProgressListener) {
super(sink)
this.listener = listener;
this.total = total;
}
override write(source : Buffer, byteCount : Long) {
super.write(source, byteCount);
this.bytesWritten += byteCount;
this.listener?.onProgress(this.bytesWritten, this.total);
}
}
export class ProgressRequestBody extends RequestBody {
private requestBody : RequestBody | null = null;
private listener : UploadProgressListener | null = null;
constructor(requestBody : RequestBody, listener : UploadProgressListener) {
super();
this.requestBody = requestBody;
this.listener = listener;
}
override contentLength() : Long {
return this.requestBody?.contentLength() ?? 0;
}
override contentType() : MediaType {
const body = this.requestBody;
if (body == null) {
return MediaType.parse("application/octet-stream")!;
} else {
return body.contentType()!;
}
}
override writeTo(sink : BufferedSink) {
const countingSink = new CountingSink(sink, this.contentLength(), this.listener!);
const bufferedSink = Okio.buffer(countingSink);
this.requestBody?.writeTo(bufferedSink);
bufferedSink.flush();
}
}
\ No newline at end of file
import { UploadFileOptions, UploadTask, UploadFileProgressUpdateCallback, UploadFileOptionFiles, OnProgressUpdateResult } from '../../../interface.uts';
import { NetworkUploadFileListener } from '../NetworkManager.uts'
import OkHttpClient from 'okhttp3.OkHttpClient';
import TimeUnit from 'java.util.concurrent.TimeUnit';
import ExecutorService from 'java.util.concurrent.ExecutorService';
import Executors from 'java.util.concurrent.Executors';
import RequestBody from 'okhttp3.RequestBody';
import MediaType from 'okhttp3.MediaType';
import UTSAndroid from 'io.dcloud.uts.UTSAndroid';
import MultipartBody from 'okhttp3.MultipartBody';
import Call from 'okhttp3.Call';
import Dispatcher from 'okhttp3.Dispatcher';
import Request from 'okhttp3.Request';
import MimeTypeMap from 'android.webkit.MimeTypeMap';
import TextUtils from 'android.text.TextUtils';
import File from 'java.io.File';
import Uri from 'android.net.Uri';
import InputStream from 'java.io.InputStream';
import MediaStore from 'android.provider.MediaStore';
import FileInputStream from 'java.io.FileInputStream';
import { InputStreamRequestBody } from './InputStreamRequestBody.uts';
import { UploadProgressListener, ProgressRequestBody } from './ProgressRequestBody.uts'
import Callback from 'okhttp3.Callback';
import Response from 'okhttp3.Response';
import IOException from 'java.io.IOException';
import Retention from 'java.lang.annotation.Retention';
import URI from 'java.net.URI';
import Build from 'android.os.Build';
import Environment from 'android.os.Environment';
import UUID from 'java.util.UUID';
class FileInformation {
public inputStream : InputStream | null = null;
public size : Long = -1;
public mime : string | null = null;
public name : string | null = null;
}
class NetworkUploadTaskImpl implements UploadTask {
private call : Call | null = null;
private listener : NetworkUploadFileListener | null = null;
constructor(call : Call, listener : NetworkUploadFileListener) {
this.call = call;
this.listener = listener;
}
public abort() {
if (this.call != null) {
this.call?.cancel();
}
}
public onProgressUpdate(option : UploadFileProgressUpdateCallback) {
const kListener = this.listener;
if (kListener != null) {
kListener.progressListeners.add(option);
}
}
}
class NetworkUploadProgressListener implements UploadProgressListener {
private listener : NetworkUploadFileListener | null = null;
constructor(listener : NetworkUploadFileListener) {
this.listener = listener;
}
onProgress(bytesWritten : number, contentLength : number) {
const progress = (bytesWritten.toFloat() / contentLength) * 100
const progressUpdate : OnProgressUpdateResult = {
progress: progress.toInt(),
totalBytesSent: bytesWritten,
totalBytesExpectedToSend: contentLength
}
this.listener?.onProgress(progressUpdate);
}
}
class UploadController {
private static instance : UploadController | null = null
/**
* 上传的线程池
*/
private uploadExecutorService : ExecutorService | null = null;
public static getInstance() : UploadController {
if (this.instance == null) {
this.instance = new UploadController();
}
return this.instance!;
}
public uploadFile(options : UploadFileOptions, listener : NetworkUploadFileListener) : UploadTask | null {
const client = this.createUploadClient(options);
let request = this.createUploadRequest(options, listener);
if (request == null) {
return null;
}
let call : Call = client.newCall(request);
call.enqueue(new SimpleUploadCallback(listener));
let task = new NetworkUploadTaskImpl(call, listener);
return task;
}
private createUploadClient(option : UploadFileOptions) : OkHttpClient {
let clientBuilder = OkHttpClient.Builder();
const timeout : Long = option.timeout != null ? option.timeout!.toLong() : 120000;
clientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.callTimeout(timeout, TimeUnit.MILLISECONDS);
if (this.uploadExecutorService == null) {
this.uploadExecutorService = Executors.newFixedThreadPool(10);
}
clientBuilder.dispatcher(new Dispatcher(this.uploadExecutorService));
return clientBuilder.build();
}
private createUploadRequest(options : UploadFileOptions, listener : NetworkUploadFileListener) : Request | null {
let requestBilder = new Request.Builder();
try {
requestBilder.url(options.url);
} catch (e : Exception) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = "invalid URL";
option['cause'] = "invalid URL";
if (listener != null) {
listener.onComplete(option);
}
return null;
}
let multiPartBody = (new MultipartBody.Builder("----" + UUID.randomUUID().toString())).setType(MultipartBody.FORM);
const formData = options.formData?.toMap();
if (formData != null) {
for (entry in formData) {
const key = entry.key;
const value = entry.value;
if (value != null) {
multiPartBody.addFormDataPart(key, "" + value);
} else {
continue;
}
}
}
const tempFiles = options.files;
if (tempFiles != null && tempFiles!.length > 0) {
const files : UploadFileOptionFiles[] = tempFiles;
for (let i = 0; i < files.length; i++) {
const file = files[i];
const path = file.uri;
const fileInformation = this.getFileInformation(path)
const name = file.name ?? "file";
const inputStream = fileInformation?.inputStream;
if (fileInformation != null && inputStream != null) {
let requestBody = new InputStreamRequestBody(MediaType.parse(fileInformation.mime ?? "*/*")!, fileInformation.size, inputStream);
multiPartBody.addFormDataPart(name, fileInformation.name, requestBody);
} else {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = "Illegal file";
option['cause'] = "Illegal file";
if (listener != null) {
listener.onComplete(option);
}
return null;
}
}
} else {
const filePath = options.filePath;
if (filePath == null) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = "filePath is null";
option['cause'] = "filePath is null";
if (listener != null) {
listener.onComplete(option);
}
return null;
}
const fileInformation = this.getFileInformation(filePath);
const name = options.name ?? "file";
const inputStream = fileInformation?.inputStream;
if (fileInformation != null && inputStream != null) {
let requestBody = new InputStreamRequestBody(MediaType.parse(fileInformation.mime ?? "*/*")!, fileInformation.size, inputStream);
multiPartBody.addFormDataPart(name, fileInformation.name, requestBody);
} else {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = "Illegal file";
option['cause'] = "Illegal file";
if (listener != null) {
listener.onComplete(option);
}
return null;
}
}
let ua = UTSAndroid.getWebViewInfo(UTSAndroid.getAppContext()!)["ua"].toString();
requestBilder.header("User-Agent", ua);
const headers = options.header?.toMap();
if (headers != null) {
for (entry in headers) {
const key = entry.key;
const value = entry.value;
if (value != null) {
requestBilder.addHeader(key, "" + value);
} else {
continue;
}
}
}
requestBilder.post(new ProgressRequestBody(multiPartBody.build(), new NetworkUploadProgressListener(listener)));
return requestBilder.build();
}
/**
* 获取文件信息对象
*/
private getFileInformation(uri : string) : FileInformation | null {
let result : FileInformation | null = null;
if (uri.startsWith("content://")) {
const contentUri = Uri.parse(uri);
const context = UTSAndroid.getAppContext();
let cursor = context!.getContentResolver().query(contentUri, null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
let fileInformation = new FileInformation();
fileInformation.inputStream = context.getContentResolver().openInputStream(contentUri);
fileInformation.size = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media.SIZE)).toLong();
fileInformation.name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
fileInformation.mime = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE))
result = fileInformation;
cursor.close()
}
} else {
if (!uri.startsWith("file://")) {
// 如果不是file://开头的,就说明是相对路径。
uri = UTSAndroid.convert2AbsFullPath(uri)
} else {
uri = uri.substring("file://".length)
}
let file = new File(uri);
let fileInputStream = new FileInputStream(file);
let size = file.length();
let name = file.getName();
let mime = this.getMimeType(name);
let fileInformation = new FileInformation();
fileInformation.inputStream = fileInputStream;
fileInformation.size = size;
fileInformation.name = name;
fileInformation.mime = mime;
result = fileInformation;
}
return result;
}
private checkPrivatePath(path : string) : boolean {
if (Build.VERSION.SDK_INT > 29 && Environment.isExternalStorageManager()) {
return true;
}
if (path.startsWith("file://")) {
path = path.replace("file://", "");
}
const context = UTSAndroid.getAppContext()!;
let cache = context.getExternalCacheDir();
let sPrivateExternalDir = ""
if (cache == null) {
sPrivateExternalDir = Environment.getExternalStorageDirectory().getPath() + "/Android/data/" + context.getPackageName();
} else {
sPrivateExternalDir = cache.getParent();
}
const sPrivateDir = context.getFilesDir().getParent();
if (sPrivateExternalDir.startsWith("/") && !path.startsWith("/")) {
path = "/" + path;
}
if ((path.contains(sPrivateDir) || path.contains(sPrivateExternalDir))//表示应用私有路径
|| this.isAssetFile(path) //表示apk的assets路径文件
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.Q//表示当前手机属于可正常访问路径系统
) {
//文件路径在私有路径下或手机系统版符合非分区存储逻辑
return true;
}
return false;
}
private isAssetFile(filePath : string) : boolean {
let isAsset = false;
if (filePath.startsWith("apps/")) {
isAsset = true;
} else if (filePath.startsWith("/android_asset/") || filePath.startsWith("android_asset/")) {
isAsset = true;
}
return isAsset;
}
/**
* 获取文件mime
*/
private getMimeType(filename : string) : string {
let map = MimeTypeMap.getSingleton()
var extType = MimeTypeMap.getFileExtensionFromUrl(filename)
if (extType == null && filename.lastIndexOf(".") >= 0) {
extType = filename.substring(filename.lastIndexOf(".") + 1)
}
let ret = map.getMimeTypeFromExtension(extType);
if (TextUtils.isEmpty(ret)) {
if (TextUtils.isEmpty(extType)) {
ret = "*/*"
} else {
ret = "application/" + extType
}
}
return ret!;
}
}
class SimpleUploadCallback implements Callback {
private listener : NetworkUploadFileListener | null = null;
constructor(listener : NetworkUploadFileListener) {
this.listener = listener;
}
override onFailure(call : Call, exception : IOException) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = exception.message;
option['cause'] = exception.message;
this.listener?.onComplete(option);
}
override onResponse(call : Call, response : Response) {
const result = {};
result["statusCode"] = response.code() + "";
result["data"] = response.body()?.string();
this.listener?.onComplete(result);
}
}
export {
UploadController
}
\ No newline at end of file
//
// CodingHandler.swift
//
// Created by TaoHebin on 2023/3/6.
//
import Foundation
func cleanUTF8(data:Data?) -> Data? {
if let data = data {
let cd = iconv_open("UTF-8", "UTF-8")
var one = 1
let argPointer = withUnsafeMutablePointer(to: &one) {$0}
iconvctl(cd, ICONV_SET_DISCARD_ILSEQ, argPointer)
var inbytesleft:Int = data.count
var outbytesleft:Int = data.count
var inbuf = data.withUnsafeBytes { (buffer : UnsafeRawBufferPointer) in
let src:UnsafeMutablePointer<CChar>? = UnsafeMutablePointer<CChar>.allocate(capacity:buffer.count)
memset(src, 0, buffer.count)
memcpy(src, buffer.baseAddress, buffer.count)
return src
}
let outbuf:UnsafeMutablePointer<CChar>? = UnsafeMutablePointer<CChar>.allocate(capacity:data.count)
var outPtr = outbuf
if iconv(cd, &inbuf, &inbytesleft, &outPtr, &outbytesleft) == -1{
return nil
}
var result : Data? = nil
if let outbuf = outbuf{
result = Data(bytes: outbuf, count: data.count)
}
iconv_close(cd)
outbuf?.deallocate()
return result
}else{
return nil
}
}
import { Request, RequestOptions, RequestSuccess, RequestFail, RequestTask } from './interface';
import { NetworkManager, NetworkRequestListener } from './network/NetworkManager.uts'
import { Data, HTTPURLResponse, NSError, NSNumber , ComparisonResult } from 'Foundation';
import { StatusCode } from './network/StatusCode.uts';
class SimpleNetworkListener extends NetworkRequestListener {
private param : RequestOptions | null = null;
private headers : Map<string, any> | null = null;
private received : number = 0;
private data : Data = new Data();
constructor(param : RequestOptions) {
this.param = param;
super();
}
public override onStart() : void {
}
public override onHeadersReceived(statusCode : number, headers : Map<string, any>) : void {
this.headers = headers;
}
public override onDataReceived(data : Data) : void {
this.received += data.count;
this.data.append(data);
}
public override onFinished(response : HTTPURLResponse) : void {
try {
let headers = response.allHeaderFields as Map<string, any>;
let kParam = this.param;
let result = {};
result['statusCode'] = response.statusCode;
result['statusText'] = StatusCode.getStatus(new String(response.statusCode));
if (headers != null) {
result['header'] = headers;
}
let strData = this.readStringFromData(this.data, response.textEncodingName);
let type = kParam?.responseType != null ? kParam?.responseType : kParam?.dataType;
if (type == null && headers != null) {
for (entry in headers){
let key = entry.key;
if (key.caseInsensitiveCompare("Content-Type") == ComparisonResult.orderedSame) {
type = headers[key] as string;
}
}
}
result['data'] = this.parseData(this.data, strData, type);
let tmp : RequestSuccess = {
data: result['data']!,
statusCode: (new NSNumber(value = response.statusCode)),
header: result['header'] ?? "",
cookies: []
};
let success = kParam?.success;
let complete = kParam?.complete;
success?.(tmp);
complete?.(tmp);
} catch (e) {
}
}
public override onFail(error : NSError) : void {
let kParam = this.param;
let result = {};
let code = (error as NSError).code;
// result['data'] = error.localizedDescription + "(" + new String(code) + ")";
// result['errorMsg'] = error.localizedDescription + "(" + new String(code) + ")";
// result['statusText'] = StatusCode.getStatus(new String(code) + "");
// if (this.headers != null) {
// result['header'] = this.headers;
// }
let errCode = code;
let errMsg = error.localizedDescription;
if (code == -1001) {
errCode = 5;
} else if (code == -1004) {
errCode = 1000;
} else if (code == -1009) {
errCode = 600003;
} else if (code == -1000 || code == -1002 || code == -1003) {
errMsg = 'ERR_INVALID_REQUEST';
}
// let failResult : RequestFail = {
// errSubject: "uni-request",
// errMsg: errMsg,
// errCode: errCode,
// };
let failResult : RequestFail = new UniError("uni-request", Number.from(errCode), errMsg);
let fail = kParam?.fail;
let complete = kParam?.complete;
fail?.(failResult);
complete?.(failResult);
}
private readStringFromData(data : Data, type : string | null) : string | null {
let result : string | null = null;
let finalType = type;
if (finalType == null || finalType!.length == 0) {
finalType = "utf-8";
}
let cfEncoding = CFStringConvertIANACharSetNameToEncoding(finalType as CFString);
if (cfEncoding != kCFStringEncodingInvalidId) {
let stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
let encode = new String.Encoding(rawValue = stringEncoding);
result = new String(data = data, encoding = encode);
}
return result;
}
private parseData(data : Data | null, dataStr : string | null, type : string | null) : any | null {
if (type != null && type!.contains("json")) {
if (dataStr == null || dataStr!.length == 0) {
return {};
}
return this.parseJson(dataStr!);
} else if (type == 'jsonp') {
if (dataStr == null || dataStr!.length == 0) {
return {};
}
let start = dataStr!.indexOf('(');
let end = dataStr!.indexOf(')');
if (start == 0 || start >= end) {
return {};
}
start += 1;
let tmp = dataStr!.slice(start, end);
return this.parseJson(tmp);
} else {
//dataStr如果解码失败是空的时候,还需要继续尝试解码。极端情况,服务器不是utf8的,所以字符解码会出现乱码,所以特殊处理一下非utf8的字符。
if (data == null) {
return data;
}
let currentStr : string | null = dataStr;
//todo 等uts支持swift文件混编的时候,再进行处理。
// if (currentStr == null) {
// let data = cleanUTF8(data);
// if (data != null) {
// currentStr = new String(data = data, encoding = String.Encoding.utf8);
// }
// }
if (currentStr == null) {
currentStr = new String(data = data!, encoding = String.Encoding.ascii);
}
return currentStr;
}
}
private parseJson(str : string) : any | null{
return JSON.parse(str);
}
}
export const request : Request = (param : RequestOptions) : RequestTask | null => {
return NetworkManager.getInstance().request(param, new SimpleNetworkListener(param));
}
\ No newline at end of file
export type Request = (param: RequestOptions) => RequestTask | null;
/**
* 网络请求参数
*/
export type RequestOptions = {
/**
* 开发者服务器接口地址
*/
url: string,
/**
* 请求的参数 Object|String类型
* @type {RequestDataOptions}
* @defaultValue null
*/
data?: any | null,
/**
* 设置请求的 header,header 中不能设置 Referer
* @defaultValue null
*/
header?: UTSJSONObject,
/**
* 请求类型 默认值GET
* GET|POST|PUT|DELETE|HEAD|OPTIONS
* @type {RequestMethod}
* @defaultValue "GET"
*/
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"| null,
/**
* 超时时间,单位 ms
* @defaultValue 60000
*/
timeout?: number | null,
/**
* 如果设为 json,会对返回的数据进行一次 JSON.parse,非 json 不会进行 JSON.parse
* @defaultValue "json"
* @deprecated 不支持
* @autodoc false
*/
dataType?: string | null,
/**
* 设置响应的数据类型。
*
* @deprecated 不支持
* @autodoc false
*/
responseType?: string | null,
/**
* 验证 ssl 证书
* @deprecated 不支持
* @autodoc false
*/
sslVerify?: boolean | null,
/**
* 跨域请求时是否携带凭证(cookies)
*
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "x"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
*/
withCredentials?: boolean | null,
/**
* DNS解析时优先使用ipv4
* @defaultValue false
*/
firstIpv4?: boolean | null,
/**
* 网络请求成功回调。
* @defaultValue null
*/
success?: RequestSuccessCallback | null,
/**
* 网络请求失败回调。
* @defaultValue null
*/
fail?: RequestFailCallback | null,
/**
* 网络请求完成回调,成功或者失败都会调用。
* @defaultValue null
*/
complete?: RequestCompleteCallback | null
}
export type RequestSuccess = {
/**
* 开发者服务器返回的数据
* @type {RequestDataOptions}
*/
data: any | null,
/**
* 开发者服务器返回的 HTTP 状态码
*/
statusCode: number,
/**
* 开发者服务器返回的 HTTP Response Header
*/
header: any,
/**
* 开发者服务器返回的 cookies,格式为字符串数组
*/
cookies: Array<string>
}
export type RequestFail = UniError;
export type RequestSuccessCallback = (option: RequestSuccess) => void;
export type RequestFailCallback = (option: RequestFail) => void;
export type RequestCompleteCallback = (option: any) => void;
export interface RequestTask {
/**
* abort()
* @description
* 中断网络请求。
* @param {void}
* @return {void}
* @tutorial https://uniapp.dcloud.net.cn/api/request/request.html#request
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
* @example
```typescript
var requestTask = uni.request({
url: 'http://192.168.12.106:8080/postHalo', //仅为示例,并非真实接口地址。
complete: ()=> {}
});
requestTask.abort();
```
*/
abort(): void
}
//===============================上传==================================
export type UploadFile = (options: UploadFileOptions) => UploadTask | null;
export type UploadFileOptionFiles = {
/**
* multipart 提交时,表单的项目名,默认为 file,如果 name 不填或填的值相同,可能导致服务端读取文件时只能读取到一个文件。
*/
name: string | null,
/**
* 要上传文件资源的路径
*/
uri: string,
/**
* 要上传的文件对象,仅H5(2.6.15+)支持
*
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "x"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
*/
file: any | null
};
export type UploadFileSuccess = {
/**
* 开发者服务器返回的数据
*/
data: string,
/**
* 开发者服务器返回的 HTTP 状态码
*/
statusCode: number
};
export type UploadFileSuccessCallback = (result: UploadFileSuccess) => void;
export type UploadFileFail = UniError;
export type UploadFileFailCallback = (result: UploadFileFail) => void;
export type UploadFileCompleteCallback = (result: any) => void;
export type UploadFileOptions = {
/**
* 开发者服务器 url
*/
url: string,
/**
* 要上传文件资源的路径
*/
filePath?: string | null,
/**
* 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
*/
name?: string | null,
/**
* 需要上传的文件列表。
*/
files?: (UploadFileOptionFiles[]) | null,
/**
* HTTP 请求 Header, header 中不能设置 Referer
*/
header?: UTSJSONObject | null,
/**
* HTTP 请求中其他额外的 form data
*/
formData?: UTSJSONObject | null,
/**
* 超时时间,单位 ms
*/
timeout?: number | null,
/**
* 成功返回的回调函数
*/
success?: UploadFileSuccessCallback | null,
/**
* 失败的回调函数
*/
fail?: UploadFileFailCallback | null,
/**
* 结束的回调函数(调用成功、失败都会执行)
*/
complete?: UploadFileCompleteCallback | null
};
export type OnProgressUpdateResult = {
/**
* 上传进度百分比
*/
progress: number,
/**
* 已经上传的数据长度,单位 Bytes
*/
totalBytesSent: number,
/**
* 预期需要上传的数据总长度,单位 Bytes
*/
totalBytesExpectedToSend: number
};
export type UploadFileProgressUpdateCallback = (result: OnProgressUpdateResult) => void
export interface UploadTask {
/**
* abort()
* @description
* 中断上传任务。
* @param {void}
* @return {void}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#uploadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
var uploadTask = uni.uploadFile({
url: 'http://192.168.12.106:8080/uploadFile', //仅为示例,并非真实接口地址。
complete: ()=> {}
});
uploadTask.abort();
```
*/
abort(): void,
/**
* onProgressUpdate()
* @description
* 监听上传进度变化。
* @param {UploadFileProgressUpdateCallback} callback
* @return {void}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#uploadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
uploadTask.onProgressUpdate((res) => {
console.log('上传进度' + res.progress);
console.log('已经上传的数据长度' + res.totalBytesSent);
console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
});
```
*/
onProgressUpdate(callback: UploadFileProgressUpdateCallback): void,
};
//===============================下载==================================
export type DownloadFile = (options: DownloadFileOptions) => DownloadTask | null;
export type DownloadFileSuccess = {
/**
* 临时文件路径,下载后的文件会存储到一个临时文件
*/
tempFilePath: string,
/**
* 开发者服务器返回的 HTTP 状态码
*/
statusCode: number
};
export type DownloadFileSuccessCallback = (result: DownloadFileSuccess) => void;
export type DownloadFileFail = UniError;
export type DownloadFileFailCallback = (result: DownloadFileFail) => void;
export type DownloadFileComplete = any;
export type DownloadFileCompleteCallback = (result: DownloadFileComplete) => void;
export type DownloadFileOptions = {
/**
* 下载资源的 url
*/
url: string,
/**
* HTTP 请求 Header,header 中不能设置 Referer
*/
header?: UTSJSONObject | null,
/**
* 超时时间,单位 ms
*/
timeout?: number | null,
/**
* 下载成功后以 tempFilePath 的形式传给页面,res = {tempFilePath: '文件的临时路径'}
*/
success?: DownloadFileSuccessCallback | null,
/**
* 失败的回调函数
*/
fail?: DownloadFileFailCallback | null,
/**
* 结束的回调函数(调用成功、失败都会执行)
*/
complete?: DownloadFileCompleteCallback | null
};
export type OnProgressDownloadResult = {
/**
* 下载进度百分比
*/
progress: number,
/**
* 已经下载的数据长度,单位 Bytes
*/
totalBytesWritten: number,
/**
* 预期需要下载的数据总长度,单位 Bytes
*/
totalBytesExpectedToWrite: number
};
export type DownloadFileProgressUpdateCallback = (result: OnProgressDownloadResult) => void;
export interface DownloadTask {
/**
* abort()
* @description
* 中断下载任务。
* @param {void}
* @return {void}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#downloadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
var downloadTask = uni.downloadFile({
url: 'https://www.example.com/file/test', //仅为示例,并非真实接口地址。
complete: ()=> {}
});
downloadTask.abort();
```
*/
abort(): void,
/**
* onProgressUpdate()
* @description
* 监听下载进度变化。
* @param {DownloadFileProgressUpdateCallback} callback
* @return {void}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#downloadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
downloadTask.onProgressUpdate((res) => {
console.log('下载进度' + res.progress);
console.log('已经下载的数据长度' + res.totalBytesWritten);
console.log('预期需要下载的数据总长度' + res.totalBytesExpectedToWrite);
});
```
*/
onProgressUpdate(callback: DownloadFileProgressUpdateCallback): void,
};
export interface Uni {
/**
* Request()
* @description
* 发起网络请求。
* @param {RequestOptions} options
* @return {RequestTask | null}
* @tutorial https://uniapp.dcloud.net.cn/api/request/request.html
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
* @example
```typescript
uni.request({
url: "http://192.168.12.106:8080/postHalo",
dataType: "json",
responseType: "json",
method: "POST",
data: {
platform: "ios",
},
// header: {
// "Content-Type": "application/json",
// },
timeout: 6000,
sslVerify: false,
withCredentials: false,
firstIpv4: false,
success(res) {
console.log("success :", res.data);
},
fail(e) {
console.log(e);
},
complete(res) {
console.log("complete :", res);
},
});
```
*/
request: Request,
/**
* UploadFile()
* @description
* 将本地资源上传到开发者服务器。
* @param {UploadFileOptions} options
* @return {UploadTask | null}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#uploadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
uni.uploadFile({
url: 'http://192.168.12.106:8080/uploadFile', //仅为示例,非真实的接口地址
filePath: "/static/logo.png",
name: 'file',
formData: {
'user': 'test'
},
success: (uploadFileRes) => {
console.log(uploadFileRes.data);
}
});
```
*/
uploadFile: UploadFile,
/**
* DownloadFile()
* @description
* 下载文件资源到本地,客户端直接发起一个 HTTP GET 请求,返回文件的本地临时路径。
* @param {DownloadFileOptions} options
* @return {DownloadTask | null}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#downloadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
uni.downloadFile({
url: "http://192.168.12.106:8080/downloadfile",
success(e) {
console.log("success111 :", e);
}
});
```
*/
downloadFile: DownloadFile
}
import { RequestOptions, RequestTask } from '../interface.uts'
import { UTSiOS } from "DCloudUTSFoundation";
import { URLSessionDataDelegate, URL, CharacterSet, URLSession, URLSessionConfiguration, OperationQueue, URLSessionTask, URLResponse, URLSessionDataTask, URLAuthenticationChallengeSender, URLAuthenticationChallenge, URLCredential, URLSessionTaskMetrics, Data, HTTPURLResponse, NSError, URLRequest, ComparisonResult } from 'Foundation';
class NetworkRequestListener {
public onStart() : void { }
public onHeadersReceived(statusCode : number, headers : Map<string, any>) : void { }
public onDataReceived(data : Data) : void { }
public onFinished(response : HTTPURLResponse) : void { }
public onFail(error : NSError) : void { }
}
class NetworkRequestTaskImpl implements RequestTask {
private task : URLSessionDataTask | null = null;
constructor(task : URLSessionDataTask | null) {
this.task = task;
super();
}
public abort() {
this.task?.cancel()
}
}
class NetworkManager implements URLSessionDataDelegate {
private static instance : NetworkManager | null = null;
private session : URLSession | null = null;
private taskMap : Map<URLSessionTask, NetworkRequestListener> = new Map<URLSessionDataTask, NetworkRequestListener>();
public static getInstance() : NetworkManager {
if (this.instance == null) {
this.instance = new NetworkManager();
}
return this.instance!;
}
public request(param : RequestOptions, listener : NetworkRequestListener) : RequestTask | null {
let request = this.createRequest(param);
if (request == null) {
let error = new NSError(domain = "invalid URL", code = 600009);
listener.onFail(error);
return null;
}
if (this.session == null) {
let urlSessionConfig = URLSessionConfiguration.default;
this.session = new URLSession(configuration = urlSessionConfig, delegate = this, delegateQueue = OperationQueue.main);
}
let task = this.session?.dataTask(with = request!);
task?.resume();
if (task != null) {
this.taskMap.set(task!, listener);
}
let requestTask = new NetworkRequestTaskImpl(task);
return requestTask;
}
public createRequest(param : RequestOptions) : URLRequest | null {
let url = new URL(string = param.url);
if (url == null) {
return null
}
let timeout = param.timeout == null ? 60000 : param.timeout;
let timeoutInterval = new Double(timeout!) / 1000;
let request = new URLRequest(url = url!, cachePolicy = URLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval = timeoutInterval);
request.httpShouldHandleCookies = true;
let method = param.method;
if (method == null || method!.trimmingCharacters(in = CharacterSet.whitespacesAndNewlines).count == 0) {
method = "GET";
}
request.httpMethod = method!;
let ua = UTSiOS.getUserAgent();
request.setValue(ua, forHTTPHeaderField = "User-Agent");
if (param.header == null) {
param.header = {}
}
let headers = param.header?.toMap() as Map<string, any>;
let hasContentType = false;
if (headers != null) {
for (entry in headers) {
let key = entry.key;
if (key.caseInsensitiveCompare("Content-Type") == ComparisonResult.orderedSame) {
hasContentType = true;
}
if (typeof(entry.value) == 'string') {
request.setValue((entry.value) as string, forHTTPHeaderField = key);
}
}
}
if (!hasContentType) {
if ("GET" != param.method) {
request.setValue("application/json", forHTTPHeaderField = "Content-Type");
}
}
if (param.data != null) {
let body : Data | null = null;
if (typeof(param.data) == 'string') {
body = (param.data as string).data(using = String.Encoding.utf8);
} else {
body = JSON.stringify(param.data)?.data(using = String.Encoding.utf8);
}
if (body == null) {
return null;
}
request.httpBody = body;
}
return request;
}
//mark --- URLSessionDataDelegate
urlSession(session : URLSession, @argumentLabel("") task : URLSessionTask, @argumentLabel("didSendBodyData") bytesSent : Int64, @argumentLabel("") totalBytesSent : Int64, @argumentLabel("") totalBytesExpectedToSend : Int64) {
//todo 原生的onDataSent貌似没实现 ,考虑删掉这个回调。
}
urlSession(session : URLSession, @argumentLabel("") dataTask : URLSessionDataTask, @argumentLabel("didReceive") response : URLResponse, @argumentLabel("") @escaping completionHandler : (dis : URLSession.ResponseDisposition) => void) {
// response开始的时候的header回调
let listener = this.taskMap.get(dataTask);
if (listener != null) {
let httpResponse : HTTPURLResponse = response as HTTPURLResponse;
let statusCode = new NSNumber(value = httpResponse.statusCode);
listener?.onHeadersReceived(statusCode, httpResponse.allHeaderFields as Map<string, any>);
}
completionHandler(URLSession.ResponseDisposition.allow);
}
urlSession(session : URLSession, @argumentLabel("") dataTask : URLSessionDataTask, @argumentLabel("didReceive") data : Data) {
let listener = this.taskMap.get(dataTask);
listener?.onDataReceived(data);
}
urlSession(session : URLSession, @argumentLabel("") task : URLSessionTask, @argumentLabel("didCompleteWithError") error : NSError | null) {
let listener = this.taskMap.get(task);
if (error != null) {
listener?.onFail(error as NSError);
} else {
listener?.onFinished(task.response as HTTPURLResponse);
}
this.taskMap.delete(task);
}
//todo 暂时证书验证先不实现。
// urlSession( session: URLSession, @argumentLabel("didReceive") challenge: URLAuthenticationChallenge, @escaping completionHandler:(dis:URLSession.AuthChallengeDisposition, credentiual:URLCredential)=>void) {
// console.log("didReceivechallenge");
// }
}
export {
NetworkManager,
NetworkRequestListener
}
\ No newline at end of file
class StatusCode {
private static statusCodeMap : Map<string, string> | null = null;
private static initStatusCodeMap() {
let map = new Map<string, string>();
map.set('100', "Continue");
map.set('101', "Switching Protocol");
map.set('200', "OK");
map.set('201', "Created");
map.set('202', "Accepted");
map.set('203', "Non-Authoritative Information");
map.set('204', "No Content");
map.set('205', "Reset Content");
map.set('206', "Partial Content");
map.set('300', "Multiple Choice");
map.set('301', "Moved Permanently");
map.set('302', "Found");
map.set('303', "See Other");
map.set('304', "Not Modified");
map.set('305', "Use Proxy");
map.set('306', "unused");
map.set('307', "Temporary Redirect");
map.set('308', "Permanent Redirect");
map.set('400', "Bad Request");
map.set('401', "Unauthorized");
map.set('402', "Payment Required");
map.set('403', "Forbidden");
map.set('404', "Not Found");
map.set('405', "Method Not Allowed");
map.set('406', "Not Acceptable");
map.set('407', "Proxy Authentication Required");
map.set('408', "Request Timeout");
map.set('409', "Conflict");
map.set('410', "Gone");
map.set('411', "Length Required");
map.set('412', "Precondition Failed");
map.set('413', "Payload Too Large");
map.set('414', "URI Too Long");
map.set('415', "Unsupported Media Type");
map.set('416', "Requested Range Not Satisfiable");
map.set('417', "Expectation Failed");
map.set('418', "I'm a teapot");
map.set('421', "Misdirected Request");
map.set('426', "Upgrade Required");
map.set('428', "Precondition Required");
map.set('429', "Too Many Requests");
map.set('431', "Request Header Fields Too Large");
map.set('500', "Internal Server Error");
map.set('501', "Not Implemented");
map.set('502', "Bad Gateway");
map.set('503', "Service Unavailable");
map.set('504', "Gateway Timeout");
map.set('505', "HTTP Version Not Supported");
map.set('506', "Variant Also Negotiates");
map.set('507', "Variant Also Negotiates");
map.set('511', "Network Authentication Required");
this.statusCodeMap = map;
}
public static getStatus(code : string) : string {
let map = this.statusCodeMap;
if (map == null) {
this.initStatusCodeMap();
}
let tmp = this.statusCodeMap!;
if (!(tmp.has(code))) {
return 'unknown status';
} else {
return tmp.get(code)! as string;
}
}
}
export {
StatusCode
}
export type RequestDataOptions =
/**
* @description json对象
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
*/
Object
/**
* @description 字符串
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
*/
| String
/**
* @description ArrayBuffer
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "x",
* "uniVer": "√",
* "unixVer": "x"
* },
* "ios": {
* "osVer": "x",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
*/
| ArrayBuffer;
export type RequestMethod =
/**
* @description GET
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
*/
GET
/**
* @description POST
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
*/
| POST
/**
* @description PUT
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
*/
| PUT
/**
* @description DELETE
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
*/
| DELETE
/**
* @description CONNECT
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
*/
| CONNECT
/**
* @description HEAD
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
*/
| HEAD
/**
* @description OPTIONS
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
*/
| OPTIONS
/**
* @description TRACE
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
*/
| TRACE;
export type Request<T> = (param: RequestOptions<T>) => RequestTask | null;
/**
* 网络请求参数
*/
export type RequestOptions<T> = {
/**
* 开发者服务器接口地址
*/
url: string,
/**
* 请求的参数 UTSJSONObject|string类型
* @type {RequestDataOptions}
* @defaultValue null
*/
data?: any | null,
/**
* 设置请求的 header,header 中不能设置 Referer
* @defaultValue null
*/
header?: UTSJSONObject | null,
/**
* 请求方法
* 如果设置的值不在取值范围内,会以GET方法进行请求。
* @type {RequestMethod}
* @defaultValue "GET"
*/
method?: RequestMethod | null,
/**
* 超时时间,单位 ms
* @defaultValue 60000
*/
timeout?: number | null,
/**
* 如果设为 json,会对返回的数据进行一次 JSON.parse,非 json 不会进行 JSON.parse
* @defaultValue "json"
* @deprecated 不支持
* @autodoc false
*/
dataType?: string | null,
/**
* 设置响应的数据类型。
*
* @deprecated 不支持
* @autodoc false
*/
responseType?: string | null,
/**
* 验证 ssl 证书
*
* @deprecated 不支持
* @autodoc false
*/
sslVerify?: boolean | null,
/**
* 跨域请求时是否携带凭证(cookies)
*
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "x"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
*
*/
withCredentials?: boolean | null,
/**
* DNS解析时优先使用ipv4
* @defaultValue false
*/
firstIpv4?: boolean | null,
/**
* 网络请求成功回调。
* @defaultValue null
*/
success?: RequestSuccessCallback<T> | null,
/**
* 网络请求失败回调。
* @defaultValue null
*/
fail?: RequestFailCallback | null,
/**
* 网络请求完成回调,成功或者失败都会调用。
* @defaultValue null
*/
complete?: RequestCompleteCallback | null
}
export type RequestSuccess<T> = {
/**
* 开发者服务器返回的数据
* @type {RequestDataOptions}
*/
data: T | null,
/**
* 开发者服务器返回的 HTTP 状态码
*/
statusCode: number,
/**
* 开发者服务器返回的 HTTP Response Header
*/
header: any,
/**
* 开发者服务器返回的 cookies,格式为字符串数组
*/
cookies: Array<string>
}
/**
* 请求方法
* - GET GET方法请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据。
* - POST POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。
* - PUT PUT方法用有效载荷请求替换目标资源的所有当前表示。
* - PATCH PATCH方法用于对资源应用部分修改。
* - DELETE DELETE方法删除指定的资源。
* - HEAD HEAD方法请求一个与GET请求的响应相同的响应,但没有响应体。
* - OPTIONS OPTIONS 方法用于描述目标资源的通信选项。
*/
export type RequestMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
/**
* 错误码
* - 5 接口超时
* - 1000 服务端系统错误
* - 100001 json数据解析错误
* - 100002 错误信息json解析失败
* - 600000 未知的网络错误
* - 600003 网络中断
* - 600009 URL格式不合法
* - 600010 请求的 data 序列化失败
* - 602001 request系统错误
*/
export type RequestErrorCode = 5 | 1000 | 100001 | 100002 | 600000 | 600003 | 600009 | 600010 | 602001;
/**
* 网络请求失败的错误回调参数
*/
export interface RequestFail extends IUniError{
errCode: RequestErrorCode
};
export type RequestSuccessCallback<T> = (option: RequestSuccess<T>) => void;
export type RequestFailCallback = (option: RequestFail) => void;
export type RequestCompleteCallback = (option: any) => void;
export interface RequestTask {
/**
* abort()
* @description
* 中断网络请求。
* @param {void}
* @return {void}
* @tutorial https://uniapp.dcloud.net.cn/api/request/request.html#request
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
* @example
```typescript
var requestTask = uni.request({
url: 'http://192.168.12.106:8080/postHalo', //仅为示例,并非真实接口地址。
complete: ()=> {}
});
requestTask.abort();
```
*/
abort(): void
}
//===============================上传==================================
export type UploadFile = (options: UploadFileOptions) => UploadTask | null;
export type UploadFileOptionFiles = {
/**
* multipart 提交时,表单的项目名,默认为 file,如果 name 不填或填的值相同,可能导致服务端读取文件时只能读取到一个文件。
* @defaultValue "file"
*/
name: string | null,
/**
* 要上传文件资源的路径
*/
uri: string,
/**
* 要上传的文件对象
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "x"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
*/
file: any | null
};
export type UploadFileSuccess = {
/**
* 开发者服务器返回的数据
*/
data: string,
/**
* 开发者服务器返回的 HTTP 状态码
*/
statusCode: number
};
export type UploadFileSuccessCallback = (result: UploadFileSuccess) => void;
/**
* 上传文件失败的错误回调参数
*/
export interface UploadFileFail extends IUniError{
errCode: RequestErrorCode
};
export type UploadFileFailCallback = (result: UploadFileFail) => void;
export type UploadFileCompleteCallback = (result: any) => void;
export type UploadFileOptions = {
/**
* 开发者服务器 url
*/
url: string,
/**
* 要上传文件资源的路径
* @defaultValue null
*/
filePath?: string | null,
/**
* 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
* @defaultValue null
*/
name?: string | null,
/**
* 需要上传的文件列表。
* @defaultValue null
*/
files?: (UploadFileOptionFiles[]) | null,
/**
* HTTP 请求 Header, header 中不能设置 Referer
* @defaultValue null
*/
header?: UTSJSONObject | null,
/**
* HTTP 请求中其他额外的 form data
* @defaultValue null
*/
formData?: UTSJSONObject | null,
/**
* 超时时间,单位 ms
* @defaultValue 120000
*/
timeout?: number | null,
/**
* 成功返回的回调函数
* @defaultValue null
*/
success?: UploadFileSuccessCallback | null,
/**
* 失败的回调函数
* @defaultValue null
*/
fail?: UploadFileFailCallback | null,
/**
* 结束的回调函数(调用成功、失败都会执行)
* @defaultValue null
*/
complete?: UploadFileCompleteCallback | null
};
export type OnProgressUpdateResult = {
/**
* 上传进度百分比
*/
progress: number,
/**
* 已经上传的数据长度,单位 Bytes
*/
totalBytesSent: number,
/**
* 预期需要上传的数据总长度,单位 Bytes
*/
totalBytesExpectedToSend: number
};
export type UploadFileProgressUpdateCallback = (result: OnProgressUpdateResult) => void
export interface UploadTask {
/**
* abort()
* @description
* 中断上传任务。
* @param {void}
* @return {void}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#uploadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
var uploadTask = uni.uploadFile({
url: 'http://192.168.12.106:8080/uploadFile', //仅为示例,并非真实接口地址。
complete: ()=> {}
});
uploadTask.abort();
```
*/
abort(): void,
/**
* onProgressUpdate()
* @description
* 监听上传进度变化。
* @param {UploadFileProgressUpdateCallback} callback
* @return {void}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#uploadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
uploadTask.onProgressUpdate((res) => {
console.log('上传进度' + res.progress);
console.log('已经上传的数据长度' + res.totalBytesSent);
console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
});
```
*/
onProgressUpdate(callback: UploadFileProgressUpdateCallback): void,
};
//===============================下载==================================
export type DownloadFile = (options: DownloadFileOptions) => DownloadTask | null;
export type DownloadFileSuccess = {
/**
* 临时文件路径,下载后的文件会存储到一个临时文件
*/
tempFilePath: string,
/**
* 开发者服务器返回的 HTTP 状态码
*/
statusCode: number
};
export type DownloadFileSuccessCallback = (result: DownloadFileSuccess) => void;
/**
* 下载文件失败的错误回调参数
*/
export interface DownloadFileFail extends IUniError{
errCode: RequestErrorCode
};
export type DownloadFileFailCallback = (result: DownloadFileFail) => void;
export type DownloadFileComplete = any;
export type DownloadFileCompleteCallback = (result: DownloadFileComplete) => void;
export type DownloadFileOptions = {
/**
* 下载资源的 url
*/
url: string,
/**
* HTTP 请求 Header,header 中不能设置 Referer
* @defaultValue null
*/
header?: UTSJSONObject | null,
/**
* 指定文件下载路径
* 支持相对路径与绝对路径,例:
* `/imgs/pic.png`、`/storage/emulated/0/Android/data/io.dcloud.HBuilder/apps/HBuilder/temp/imgs/pic.png`
* 并且支持指定下载目录,例:
* `/imgs/`
* @defaultValue null
*/
filePath?: string | null,
/**
* 超时时间,单位 ms
* @defaultValue 120000
*/
timeout?: number | null,
/**
* 下载成功后以 tempFilePath 的形式传给页面,res = {tempFilePath: '文件的临时路径'}
* @defaultValue null
*/
success?: DownloadFileSuccessCallback | null,
/**
* 失败的回调函数
* @defaultValue null
*/
fail?: DownloadFileFailCallback | null,
/**
* 结束的回调函数(调用成功、失败都会执行)
* @defaultValue null
*/
complete?: DownloadFileCompleteCallback | null
};
export type OnProgressDownloadResult = {
/**
* 下载进度百分比
*/
progress: number,
/**
* 已经下载的数据长度,单位 Bytes
*/
totalBytesWritten: number,
/**
* 预期需要下载的数据总长度,单位 Bytes
*/
totalBytesExpectedToWrite: number
};
export type DownloadFileProgressUpdateCallback = (result: OnProgressDownloadResult) => void;
export interface DownloadTask {
/**
* abort()
* @description
* 中断下载任务。
* @param {void}
* @return {void}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#downloadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
var downloadTask = uni.downloadFile({
url: 'https://www.example.com/file/test', //仅为示例,并非真实接口地址。
complete: ()=> {}
});
downloadTask.abort();
```
*/
abort(): void,
/**
* onProgressUpdate()
* @description
* 监听下载进度变化。
* @param {DownloadFileProgressUpdateCallback} callback
* @return {void}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#downloadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
downloadTask.onProgressUpdate((res) => {
console.log('下载进度' + res.progress);
console.log('已经下载的数据长度' + res.totalBytesWritten);
console.log('预期需要下载的数据总长度' + res.totalBytesExpectedToWrite);
});
```
*/
onProgressUpdate(callback: DownloadFileProgressUpdateCallback): void,
};
export interface Uni {
/**
* Request()
* @description
* 发起网络请求。
* @param {RequestOptions} options
* @return {RequestTask | null}
* @tutorial https://uniapp.dcloud.net.cn/api/request/request.html
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "3.9+"
* }
* }
* }
* @example
```typescript
uni.request({
url: "http://192.168.12.106:8080/postHalo",
dataType: "json",
responseType: "json",
method: "POST",
data: {
platform: "ios",
},
// header: {
// "Content-Type": "application/json",
// },
timeout: 6000,
sslVerify: false,
withCredentials: false,
firstIpv4: false,
success(res) {
console.log("success :", res.data);
},
fail(e) {
console.log(e);
},
complete(res) {
console.log("complete :", res);
},
});
```
*/
request<T>(param: RequestOptions<T>): RequestTask | null;
/**
* UploadFile()
* @description
* 将本地资源上传到开发者服务器。
* @param {UploadFileOptions} options
* @return {UploadTask | null}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#uploadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
uni.uploadFile({
url: 'http://192.168.12.106:8080/uploadFile', //仅为示例,非真实的接口地址
filePath: "/static/logo.png",
name: 'file',
formData: {
'user': 'test'
},
success: (uploadFileRes) => {
console.log(uploadFileRes.data);
}
});
```
*/
uploadFile(options: UploadFileOptions): UploadTask | null;
/**
* DownloadFile()
* @description
* 下载文件资源到本地,客户端直接发起一个 HTTP GET 请求,返回文件的本地临时路径。
* @param {DownloadFileOptions} options
* @return {DownloadTask | null}
* @tutorial https://uniapp.dcloud.net.cn/api/request/network-file.html#downloadfile
* @uniPlatform {
* "app": {
* "android": {
* "osVer": "4.4",
* "uniVer": "√",
* "unixVer": "3.9+"
* },
* "ios": {
* "osVer": "9.0",
* "uniVer": "√",
* "unixVer": "x"
* }
* }
* }
* @example
```typescript
uni.downloadFile({
url: "http://192.168.12.106:8080/downloadfile",
success(e) {
console.log("success111 :", e);
}
});
```
*/
downloadFile(options: DownloadFileOptions): DownloadTask | null;
}
import { RequestErrorCode, RequestFail, UploadFileFail, DownloadFileFail } from "./interface.uts"
/**
* 错误主题
*/
export const UniErrorSubject = 'uni-request';
/**
* 错误码
* @UniError
*/
export const UniErrors : Map<RequestErrorCode, string> = new Map([
/**
* 接口超时
*/
[5, 'time out'],
/**
* 服务端系统错误
*/
[1000, 'server system error'],
/**
* json数据解析错误
*/
[100001, 'invalid json'],
/**
* 错误信息json解析失败
*/
[100002, 'error message invalid json'],
/**
* 未知的网络错误
*/
[600000, 'unknown network error'],
/**
* 网络中断
*/
[600003, 'network interrupted error'],
/**
* URL 格式不合法
*/
[600009, 'invalid URL'],
/**
* 请求的 data 序列化失败
*/
[600010, 'invalid request data'],
/**
* request系统错误
*/
[602001, 'request system error']
]);
export function getErrcode(errCode : number) : RequestErrorCode {
const res = UniErrors[errCode];
return res == null ? 602001 : errCode;
}
export class RequestFailImpl extends UniError implements RequestFail {
constructor(errCode : RequestErrorCode) {
super();
this.errSubject = UniErrorSubject;
this.errCode = errCode;
this.errMsg = UniErrors[errCode] ?? "";
}
}
export class UploadFileFailImpl extends UniError implements UploadFileFail {
constructor(errCode : RequestErrorCode) {
super();
this.errSubject = "uni-uploadFile";
this.errCode = errCode;
this.errMsg = UniErrors[errCode] ?? "";
}
}
export class DownloadFileFailImpl extends UniError implements DownloadFileFail {
constructor(errCode : RequestErrorCode) {
super();
this.errSubject = "uni-downloadFile";
this.errCode = errCode;
this.errMsg = UniErrors[errCode] ?? "";
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册