mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
wip: switch core http to okhttp
This commit is contained in:
29
apps/toolbox/src/pages/http.ts
Normal file
29
apps/toolbox/src/pages/http.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Page, EventData, Application, File, Folder, knownFolders, path, getFileAccess, Utils, Http } from '@nativescript/core';
|
||||||
|
import { AbortController } from '@nativescript/core/abortcontroller';
|
||||||
|
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
export function navigatingTo(args: EventData) {
|
||||||
|
page = <Page>args.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function makeRequest(args) {
|
||||||
|
try {
|
||||||
|
// const result = await fetch('https://httpbin.org/get');
|
||||||
|
const controller = new AbortController();
|
||||||
|
console.log('getting json with okhttp!');
|
||||||
|
// const result = await Http.getJSON('https://httpbin.org/get')
|
||||||
|
setTimeout(() => {
|
||||||
|
controller.abort();
|
||||||
|
}, 0);
|
||||||
|
const result = await Http.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://httpbin.org/get',
|
||||||
|
signal: controller.signal as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(result);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
apps/toolbox/src/pages/http.xml
Normal file
6
apps/toolbox/src/pages/http.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
|
||||||
|
|
||||||
|
<StackLayout>
|
||||||
|
<Button text="Make request" tap="makeRequest" />
|
||||||
|
</StackLayout>
|
||||||
|
</Page>
|
||||||
@@ -22,7 +22,13 @@ function parseJSON(source: string): any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let requestIdCounter = 0;
|
let requestIdCounter = 0;
|
||||||
const pendingRequests = {};
|
interface Call {
|
||||||
|
url: string;
|
||||||
|
resolveCallback: (value: httpModule.HttpResponse | PromiseLike<httpModule.HttpResponse>) => void;
|
||||||
|
rejectCallback: (reason?: any) => void;
|
||||||
|
call: okhttp3.Call;
|
||||||
|
}
|
||||||
|
const pendingRequests: Record<number, Call> = {};
|
||||||
|
|
||||||
let imageSource: typeof imageSourceModule;
|
let imageSource: typeof imageSourceModule;
|
||||||
function ensureImageSource() {
|
function ensureImageSource() {
|
||||||
@@ -55,43 +61,47 @@ function ensureCompleteCallback() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRequestComplete(requestId: number, result: org.nativescript.widgets.Async.Http.RequestResult) {
|
function onRequestComplete(requestId: number, call: okhttp3.Call, result: okhttp3.Response) {
|
||||||
const callbacks = pendingRequests[requestId];
|
const callbacks = pendingRequests[requestId];
|
||||||
delete pendingRequests[requestId];
|
delete pendingRequests[requestId];
|
||||||
|
|
||||||
if (result.error) {
|
// if (result.error) {
|
||||||
callbacks.rejectCallback(new Error(result.error.toString()));
|
// callbacks.rejectCallback(new Error(result.error.toString()));
|
||||||
|
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// read the headers
|
// read the headers
|
||||||
const headers: httpModule.Headers = {};
|
const headers: httpModule.Headers = {};
|
||||||
if (result.headers) {
|
if (result.headers) {
|
||||||
const jHeaders = result.headers;
|
const jHeaders = result.headers();
|
||||||
const length = jHeaders.size();
|
const names = jHeaders.names();
|
||||||
let pair: org.nativescript.widgets.Async.Http.KeyValuePair;
|
Array.from(names.toArray()).forEach((name: string) => {
|
||||||
for (let i = 0; i < length; i++) {
|
addHeader(headers, name, jHeaders.get(name));
|
||||||
pair = jHeaders.get(i);
|
});
|
||||||
addHeader(headers, pair.key, pair.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// send response data (for requestId) to network debugger
|
// send response data (for requestId) to network debugger
|
||||||
if (global.__inspector && global.__inspector.isConnected) {
|
if (global.__inspector && global.__inspector.isConnected) {
|
||||||
NetworkAgent.responseReceived(requestId, result, headers);
|
// TODO: adapt network agent to support okHttp results
|
||||||
|
// NetworkAgent.responseReceived(requestId, result, headers);
|
||||||
}
|
}
|
||||||
|
// TODO: process the full result.body in java and in a background thread
|
||||||
|
const bytes = result.body().bytes();
|
||||||
|
const raw = new java.io.ByteArrayOutputStream(bytes.length);
|
||||||
|
raw.write(bytes, 0, bytes.length);
|
||||||
|
result.body().close();
|
||||||
|
|
||||||
callbacks.resolveCallback({
|
callbacks.resolveCallback({
|
||||||
content: {
|
content: {
|
||||||
raw: result.raw,
|
raw: raw,
|
||||||
toArrayBuffer: () => Uint8Array.from(result.raw.toByteArray()).buffer,
|
toArrayBuffer: () => Uint8Array.from(raw.toByteArray()).buffer,
|
||||||
toString: (encoding?: HttpResponseEncoding) => {
|
toString: (encoding?: HttpResponseEncoding) => {
|
||||||
let str: string;
|
let str: string;
|
||||||
if (encoding) {
|
if (encoding) {
|
||||||
str = decodeResponse(result.raw, encoding);
|
str = decodeResponse(raw, encoding);
|
||||||
} else {
|
} else {
|
||||||
str = result.responseAsString;
|
str = raw.toString();
|
||||||
}
|
}
|
||||||
if (typeof str === 'string') {
|
if (typeof str === 'string') {
|
||||||
return str;
|
return str;
|
||||||
@@ -102,9 +112,9 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
|||||||
toJSON: (encoding?: HttpResponseEncoding) => {
|
toJSON: (encoding?: HttpResponseEncoding) => {
|
||||||
let str: string;
|
let str: string;
|
||||||
if (encoding) {
|
if (encoding) {
|
||||||
str = decodeResponse(result.raw, encoding);
|
str = decodeResponse(raw, encoding);
|
||||||
} else {
|
} else {
|
||||||
str = result.responseAsString;
|
str = raw.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseJSON(str);
|
return parseJSON(str);
|
||||||
@@ -113,11 +123,46 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
|||||||
ensureImageSource();
|
ensureImageSource();
|
||||||
|
|
||||||
return new Promise<any>((resolveImage, rejectImage) => {
|
return new Promise<any>((resolveImage, rejectImage) => {
|
||||||
if (result.responseAsImage != null) {
|
// TODO: this should be done in a background thread
|
||||||
resolveImage(new imageSource.ImageSource(result.responseAsImage));
|
// currently it's done for every request, even if `toImage` is not called
|
||||||
} else {
|
// so ideally we should do it lazily
|
||||||
|
try {
|
||||||
|
const bitmapOptions = new android.graphics.BitmapFactory.Options();
|
||||||
|
bitmapOptions.inJustDecodeBounds = true;
|
||||||
|
let nativeImage: android.graphics.Bitmap = null;
|
||||||
|
android.graphics.BitmapFactory.decodeByteArray(raw.buf, null, raw.size(), bitmapOptions);
|
||||||
|
if (bitmapOptions.outWidth > 0 && bitmapOptions.outHeight > 0) {
|
||||||
|
let scale = 1;
|
||||||
|
const height = bitmapOptions.outHeight;
|
||||||
|
const width = bitmapOptions.outWidth;
|
||||||
|
|
||||||
|
// if ((options.screenWidth > 0 && bitmapOptions.outWidth > options.screenWidth) ||
|
||||||
|
// (options.screenHeight > 0 && bitmapOptions.outHeight > options.screenHeight)) {
|
||||||
|
// final int halfHeight = height / 2;
|
||||||
|
// final int halfWidth = width / 2;
|
||||||
|
|
||||||
|
// // scale down the image since it is larger than the
|
||||||
|
// // screen resolution
|
||||||
|
// while ((halfWidth / scale) > options.screenWidth && (halfHeight / scale) > options.screenHeight) {
|
||||||
|
// scale *= 2;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
bitmapOptions.inJustDecodeBounds = false;
|
||||||
|
bitmapOptions.inSampleSize = scale;
|
||||||
|
nativeImage = android.graphics.BitmapFactory.decodeByteArray(raw.buf, null, raw.size(), bitmapOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveImage(new imageSource.ImageSource(nativeImage));
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.stack);
|
||||||
rejectImage(new Error('Response content may not be converted to an Image'));
|
rejectImage(new Error('Response content may not be converted to an Image'));
|
||||||
}
|
}
|
||||||
|
// if (result.responseAsImage != null) {
|
||||||
|
// resolveImage(new imageSource.ImageSource(result.responseAsImage));
|
||||||
|
// } else {
|
||||||
|
// rejectImage(new Error('Response content may not be converted to an Image'));
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
toFile: (destinationFilePath: string) => {
|
toFile: (destinationFilePath: string) => {
|
||||||
@@ -133,7 +178,7 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
|||||||
|
|
||||||
const javaFile = new java.io.File(destinationFilePath);
|
const javaFile = new java.io.File(destinationFilePath);
|
||||||
stream = new java.io.FileOutputStream(javaFile);
|
stream = new java.io.FileOutputStream(javaFile);
|
||||||
stream.write(result.raw.toByteArray());
|
stream.write(raw.toByteArray());
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
@@ -145,7 +190,7 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
statusCode: result.statusCode,
|
statusCode: result.code(),
|
||||||
headers: headers,
|
headers: headers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -166,19 +211,34 @@ function buildJavaOptions(options: httpModule.HttpRequestOptions) {
|
|||||||
const javaOptions = new org.nativescript.widgets.Async.Http.RequestOptions();
|
const javaOptions = new org.nativescript.widgets.Async.Http.RequestOptions();
|
||||||
|
|
||||||
javaOptions.url = options.url;
|
javaOptions.url = options.url;
|
||||||
|
const builder = new okhttp3.Request.Builder().url(options.url);
|
||||||
|
|
||||||
if (typeof options.method === 'string') {
|
let contentType: string | null = null;
|
||||||
javaOptions.method = options.method;
|
if (options.headers) {
|
||||||
|
for (const key in options.headers) {
|
||||||
|
if (key.toLowerCase() === 'content-type') {
|
||||||
|
contentType = options.headers[key];
|
||||||
}
|
}
|
||||||
|
builder.addHeader(key, `${options.headers[key]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mediaType = contentType ? okhttp3.MediaType.parse(contentType) : null;
|
||||||
|
|
||||||
|
let body: okhttp3.RequestBody | null = null;
|
||||||
if (typeof options.content === 'string' || options.content instanceof FormData) {
|
if (typeof options.content === 'string' || options.content instanceof FormData) {
|
||||||
const nativeString = new java.lang.String(options.content.toString());
|
const nativeString = new java.lang.String(options.content.toString());
|
||||||
const nativeBytes = nativeString.getBytes('UTF-8');
|
const nativeBytes = nativeString.getBytes('UTF-8');
|
||||||
const nativeBuffer = java.nio.ByteBuffer.wrap(nativeBytes);
|
const nativeBuffer = java.nio.ByteBuffer.wrap(nativeBytes);
|
||||||
javaOptions.content = nativeBuffer;
|
body = okhttp3.RequestBody.create(nativeBuffer, mediaType);
|
||||||
} else if (options.content instanceof ArrayBuffer) {
|
} else if (options.content instanceof ArrayBuffer) {
|
||||||
const typedArray = new Uint8Array(options.content as ArrayBuffer);
|
const typedArray = new Uint8Array(options.content as ArrayBuffer);
|
||||||
const nativeBuffer = java.nio.ByteBuffer.wrap(Array.from(typedArray));
|
const nativeBuffer = java.nio.ByteBuffer.wrap(Array.from(typedArray));
|
||||||
javaOptions.content = nativeBuffer;
|
body = okhttp3.RequestBody.create(nativeBuffer, mediaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.method === 'string') {
|
||||||
|
builder.method(options.method, body);
|
||||||
|
javaOptions.method = options.method;
|
||||||
}
|
}
|
||||||
if (typeof options.timeout === 'number') {
|
if (typeof options.timeout === 'number') {
|
||||||
javaOptions.timeout = options.timeout;
|
javaOptions.timeout = options.timeout;
|
||||||
@@ -187,22 +247,12 @@ function buildJavaOptions(options: httpModule.HttpRequestOptions) {
|
|||||||
javaOptions.dontFollowRedirects = options.dontFollowRedirects;
|
javaOptions.dontFollowRedirects = options.dontFollowRedirects;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.headers) {
|
|
||||||
const arrayList = new java.util.ArrayList<org.nativescript.widgets.Async.Http.KeyValuePair>();
|
|
||||||
const pair = org.nativescript.widgets.Async.Http.KeyValuePair;
|
|
||||||
|
|
||||||
for (const key in options.headers) {
|
|
||||||
arrayList.add(new pair(key, options.headers[key] + ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
javaOptions.headers = arrayList;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pass the maximum available image size to the request options in case we need a bitmap conversion
|
// pass the maximum available image size to the request options in case we need a bitmap conversion
|
||||||
javaOptions.screenWidth = Screen.mainScreen.widthPixels;
|
javaOptions.screenWidth = Screen.mainScreen.widthPixels;
|
||||||
javaOptions.screenHeight = Screen.mainScreen.heightPixels;
|
javaOptions.screenHeight = Screen.mainScreen.heightPixels;
|
||||||
|
|
||||||
return javaOptions;
|
// return javaOptions;
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function request(options: httpModule.HttpRequestOptions): Promise<httpModule.HttpResponse> {
|
export function request(options: httpModule.HttpRequestOptions): Promise<httpModule.HttpResponse> {
|
||||||
@@ -220,18 +270,49 @@ export function request(options: httpModule.HttpRequestOptions): Promise<httpMod
|
|||||||
if (global.__inspector && global.__inspector.isConnected) {
|
if (global.__inspector && global.__inspector.isConnected) {
|
||||||
NetworkAgent.requestWillBeSent(requestIdCounter, options);
|
NetworkAgent.requestWillBeSent(requestIdCounter, options);
|
||||||
}
|
}
|
||||||
|
const clientBuilder = new okhttp3.OkHttpClient.Builder();
|
||||||
|
clientBuilder.followRedirects(!options.dontFollowRedirects);
|
||||||
|
if (options.timeout) {
|
||||||
|
// TODO: which one should we use?
|
||||||
|
// clientBuilder.callTimeout(options.timeout, java.util.concurrent.TimeUnit.MILLISECONDS);
|
||||||
|
// clientBuilder.readTimeout(options.timeout, java.util.concurrent.TimeUnit.MILLISECONDS);
|
||||||
|
clientBuilder.connectTimeout(options.timeout, java.util.concurrent.TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
const client = clientBuilder.build();
|
||||||
|
|
||||||
|
const call = client.newCall(javaOptions);
|
||||||
|
const requestId = requestIdCounter;
|
||||||
|
call.enqueue(
|
||||||
|
new okhttp3.Callback({
|
||||||
|
onFailure(param0, param1) {
|
||||||
|
onRequestError(param1.getLocalizedMessage(), requestId);
|
||||||
|
},
|
||||||
|
onResponse(param0, param1) {
|
||||||
|
onRequestComplete(requestId, param0, param1);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// remember the callbacks so that we can use them when the CompleteCallback is called
|
// remember the callbacks so that we can use them when the CompleteCallback is called
|
||||||
const callbacks = {
|
const callbacks = {
|
||||||
url: options.url,
|
url: options.url,
|
||||||
resolveCallback: resolve,
|
resolveCallback: resolve,
|
||||||
rejectCallback: reject,
|
rejectCallback: reject,
|
||||||
|
call,
|
||||||
};
|
};
|
||||||
|
if (options.signal) {
|
||||||
|
(options.signal as any).on('abort', () => {
|
||||||
|
call.cancel();
|
||||||
|
});
|
||||||
|
// ).onabort = () => {
|
||||||
|
// call.cancel();
|
||||||
|
// }
|
||||||
|
}
|
||||||
pendingRequests[requestIdCounter] = callbacks;
|
pendingRequests[requestIdCounter] = callbacks;
|
||||||
|
|
||||||
ensureCompleteCallback();
|
// ensureCompleteCallback();
|
||||||
//make the actual async call
|
//make the actual async call
|
||||||
org.nativescript.widgets.Async.Http.MakeRequest(javaOptions, completeCallback, new java.lang.Integer(requestIdCounter));
|
// org.nativescript.widgets.Async.Http.MakeRequest(javaOptions, completeCallback, new java.lang.Integer(requestIdCounter));
|
||||||
|
|
||||||
// increment the id counter
|
// increment the id counter
|
||||||
requestIdCounter++;
|
requestIdCounter++;
|
||||||
@@ -241,7 +322,7 @@ export function request(options: httpModule.HttpRequestOptions): Promise<httpMod
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeResponse(raw: any, encoding?: HttpResponseEncoding) {
|
function decodeResponse(raw: java.io.ByteArrayOutputStream, encoding?: HttpResponseEncoding) {
|
||||||
let charsetName = 'UTF-8';
|
let charsetName = 'UTF-8';
|
||||||
if (encoding === HttpResponseEncoding.GBK) {
|
if (encoding === HttpResponseEncoding.GBK) {
|
||||||
charsetName = 'GBK';
|
charsetName = 'GBK';
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ export interface HttpRequestOptions {
|
|||||||
* Gets or sets whether to *not* follow server's redirection responses.
|
* Gets or sets whether to *not* follow server's redirection responses.
|
||||||
*/
|
*/
|
||||||
dontFollowRedirects?: boolean;
|
dontFollowRedirects?: boolean;
|
||||||
|
|
||||||
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
5946
packages/core/platforms/android/typings/okhttp.d.ts
vendored
Normal file
5946
packages/core/platforms/android/typings/okhttp.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
packages/core/references.d.ts
vendored
1
packages/core/references.d.ts
vendored
@@ -5,4 +5,5 @@
|
|||||||
/// <reference path="../types-android/src/lib/android-29.d.ts" />
|
/// <reference path="../types-android/src/lib/android-29.d.ts" />
|
||||||
/// <reference path="./platforms/ios/typings/objc!MaterialComponents.d.ts" />
|
/// <reference path="./platforms/ios/typings/objc!MaterialComponents.d.ts" />
|
||||||
/// <reference path="./platforms/ios/typings/objc!NativeScriptUtils.d.ts" />
|
/// <reference path="./platforms/ios/typings/objc!NativeScriptUtils.d.ts" />
|
||||||
|
/// <reference path="./platforms/android/typings/okhttp.d.ts" />
|
||||||
/// <reference path="./global-types.d.ts" />
|
/// <reference path="./global-types.d.ts" />
|
||||||
|
|||||||
@@ -89,6 +89,12 @@ dependencies {
|
|||||||
outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.documentfile:documentfile:$androidXDocumentFileVersion"
|
outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.documentfile:documentfile:$androidXDocumentFileVersion"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def okHttpVersion = "4.10.0"
|
||||||
|
if (project.hasProperty("okHttp")) {
|
||||||
|
okHttpVersion = okHttp
|
||||||
|
println "\t + using okHttp library com.squareup.okhttp3:okhttp:$okHttpVersion"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
|
|
||||||
@@ -100,6 +106,8 @@ dependencies {
|
|||||||
implementation "androidx.exifinterface:exifinterface:$androidXExifInterfaceVersion"
|
implementation "androidx.exifinterface:exifinterface:$androidXExifInterfaceVersion"
|
||||||
implementation "androidx.appcompat:appcompat:$androidXAppCompatVersion"
|
implementation "androidx.appcompat:appcompat:$androidXAppCompatVersion"
|
||||||
implementation "androidx.documentfile:documentfile:$androidXDocumentFileVersion"
|
implementation "androidx.documentfile:documentfile:$androidXDocumentFileVersion"
|
||||||
|
|
||||||
|
implementation "com.squareup.okhttp3:okhttp:$okHttpVersion"
|
||||||
}
|
}
|
||||||
|
|
||||||
task cleanBuildDir (type: Delete) {
|
task cleanBuildDir (type: Delete) {
|
||||||
|
|||||||
Reference in New Issue
Block a user