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;
|
||||
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;
|
||||
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];
|
||||
delete pendingRequests[requestId];
|
||||
|
||||
if (result.error) {
|
||||
callbacks.rejectCallback(new Error(result.error.toString()));
|
||||
// if (result.error) {
|
||||
// callbacks.rejectCallback(new Error(result.error.toString()));
|
||||
|
||||
return;
|
||||
}
|
||||
// return;
|
||||
// }
|
||||
|
||||
// read the headers
|
||||
const headers: httpModule.Headers = {};
|
||||
if (result.headers) {
|
||||
const jHeaders = result.headers;
|
||||
const length = jHeaders.size();
|
||||
let pair: org.nativescript.widgets.Async.Http.KeyValuePair;
|
||||
for (let i = 0; i < length; i++) {
|
||||
pair = jHeaders.get(i);
|
||||
addHeader(headers, pair.key, pair.value);
|
||||
}
|
||||
const jHeaders = result.headers();
|
||||
const names = jHeaders.names();
|
||||
Array.from(names.toArray()).forEach((name: string) => {
|
||||
addHeader(headers, name, jHeaders.get(name));
|
||||
});
|
||||
}
|
||||
|
||||
// send response data (for requestId) to network debugger
|
||||
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({
|
||||
content: {
|
||||
raw: result.raw,
|
||||
toArrayBuffer: () => Uint8Array.from(result.raw.toByteArray()).buffer,
|
||||
raw: raw,
|
||||
toArrayBuffer: () => Uint8Array.from(raw.toByteArray()).buffer,
|
||||
toString: (encoding?: HttpResponseEncoding) => {
|
||||
let str: string;
|
||||
if (encoding) {
|
||||
str = decodeResponse(result.raw, encoding);
|
||||
str = decodeResponse(raw, encoding);
|
||||
} else {
|
||||
str = result.responseAsString;
|
||||
str = raw.toString();
|
||||
}
|
||||
if (typeof str === 'string') {
|
||||
return str;
|
||||
@@ -102,9 +112,9 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
||||
toJSON: (encoding?: HttpResponseEncoding) => {
|
||||
let str: string;
|
||||
if (encoding) {
|
||||
str = decodeResponse(result.raw, encoding);
|
||||
str = decodeResponse(raw, encoding);
|
||||
} else {
|
||||
str = result.responseAsString;
|
||||
str = raw.toString();
|
||||
}
|
||||
|
||||
return parseJSON(str);
|
||||
@@ -113,11 +123,46 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
||||
ensureImageSource();
|
||||
|
||||
return new Promise<any>((resolveImage, rejectImage) => {
|
||||
if (result.responseAsImage != null) {
|
||||
resolveImage(new imageSource.ImageSource(result.responseAsImage));
|
||||
} else {
|
||||
// TODO: this should be done in a background thread
|
||||
// currently it's done for every request, even if `toImage` is not called
|
||||
// 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'));
|
||||
}
|
||||
// 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) => {
|
||||
@@ -133,7 +178,7 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
||||
|
||||
const javaFile = new java.io.File(destinationFilePath);
|
||||
stream = new java.io.FileOutputStream(javaFile);
|
||||
stream.write(result.raw.toByteArray());
|
||||
stream.write(raw.toByteArray());
|
||||
|
||||
return file;
|
||||
} catch (exception) {
|
||||
@@ -145,7 +190,7 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
||||
}
|
||||
},
|
||||
},
|
||||
statusCode: result.statusCode,
|
||||
statusCode: result.code(),
|
||||
headers: headers,
|
||||
});
|
||||
}
|
||||
@@ -166,19 +211,34 @@ function buildJavaOptions(options: httpModule.HttpRequestOptions) {
|
||||
const javaOptions = new org.nativescript.widgets.Async.Http.RequestOptions();
|
||||
|
||||
javaOptions.url = options.url;
|
||||
const builder = new okhttp3.Request.Builder().url(options.url);
|
||||
|
||||
if (typeof options.method === 'string') {
|
||||
javaOptions.method = options.method;
|
||||
let contentType: string | null = null;
|
||||
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) {
|
||||
const nativeString = new java.lang.String(options.content.toString());
|
||||
const nativeBytes = nativeString.getBytes('UTF-8');
|
||||
const nativeBuffer = java.nio.ByteBuffer.wrap(nativeBytes);
|
||||
javaOptions.content = nativeBuffer;
|
||||
body = okhttp3.RequestBody.create(nativeBuffer, mediaType);
|
||||
} else if (options.content instanceof ArrayBuffer) {
|
||||
const typedArray = new Uint8Array(options.content as ArrayBuffer);
|
||||
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') {
|
||||
javaOptions.timeout = options.timeout;
|
||||
@@ -187,22 +247,12 @@ function buildJavaOptions(options: httpModule.HttpRequestOptions) {
|
||||
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
|
||||
javaOptions.screenWidth = Screen.mainScreen.widthPixels;
|
||||
javaOptions.screenHeight = Screen.mainScreen.heightPixels;
|
||||
|
||||
return javaOptions;
|
||||
// return javaOptions;
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
const callbacks = {
|
||||
url: options.url,
|
||||
resolveCallback: resolve,
|
||||
rejectCallback: reject,
|
||||
call,
|
||||
};
|
||||
if (options.signal) {
|
||||
(options.signal as any).on('abort', () => {
|
||||
call.cancel();
|
||||
});
|
||||
// ).onabort = () => {
|
||||
// call.cancel();
|
||||
// }
|
||||
}
|
||||
pendingRequests[requestIdCounter] = callbacks;
|
||||
|
||||
ensureCompleteCallback();
|
||||
// ensureCompleteCallback();
|
||||
//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
|
||||
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';
|
||||
if (encoding === HttpResponseEncoding.GBK) {
|
||||
charsetName = 'GBK';
|
||||
|
||||
@@ -36,6 +36,8 @@ export interface HttpRequestOptions {
|
||||
* Gets or sets whether to *not* follow server's redirection responses.
|
||||
*/
|
||||
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="./platforms/ios/typings/objc!MaterialComponents.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" />
|
||||
|
||||
@@ -89,6 +89,12 @@ dependencies {
|
||||
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')
|
||||
|
||||
@@ -100,6 +106,8 @@ dependencies {
|
||||
implementation "androidx.exifinterface:exifinterface:$androidXExifInterfaceVersion"
|
||||
implementation "androidx.appcompat:appcompat:$androidXAppCompatVersion"
|
||||
implementation "androidx.documentfile:documentfile:$androidXDocumentFileVersion"
|
||||
|
||||
implementation "com.squareup.okhttp3:okhttp:$okHttpVersion"
|
||||
}
|
||||
|
||||
task cleanBuildDir (type: Delete) {
|
||||
|
||||
Reference in New Issue
Block a user