@klippa/nativescript-http
在 NativeScript 中执行 HTTP 请求的最佳方式,是核心 HTTP 的直接替换,具有重要的改进和新增功能,如适当的连接池、表单数据支持和证书固定
npm i --save @klippa/nativescript-http

nativescript-http

NPM version Downloads TotalDownloads Build Status

:rocket: 在 NativeScript 中执行 HTTP 请求的最佳方式 :rocket

是核心 HTTP 的直接替换,具有重要的改进和新增功能,如适当的连接池、表单数据支持和证书固定。

功能

  • 无需更改代码即可使用
  • 可以使所有 HTTP 和图像缓存请求通过此插件进行
  • 向下兼容(行为与核心 HTTP 相同)
  • 现代 TLS & SSL 安全功能
  • 共享连接池减少请求延迟
  • 控制并发/连接池
  • 静默恢复常见的连接问题
  • 所有内容都在原生后台线程上运行
  • 透明的 GZIP 以减小响应大小
  • 支持 HTTP/2 和 SPDY
  • 支持在内容属性中直接发布 ArrayBuffer/File/Blob/native 对象(如 java.io.FileNSData.dataWithContentsOfFile
  • 支持多部分表单数据(multipart/form-data)(用于文件上传),支持 ArrayBuffer、File、Blob 和原生对象(如 java.io.FileNSData.dataWithContentsOfFile
  • 可以设置全局用户代理
  • 可以控制 Cookie
  • 可以控制后台图像解析
  • 证书/SSL 固定
  • WebSocket
  • 图像缓存

NativeScript 版本支持

NS 版本 nativescript-http 版本 安装命令 文档
^8.0.0 ^3.0.0 ns plugin add @klippa/nativescript-http@^3.0.0 此页面
^7.0.0 ^2.0.0 ns plugin add @klippa/nativescript-http@^2.0.0 此处
^6.0.0 ^1.0.0 tns plugin add @klippa/nativescript-http@^1.0.0 此处

安装(NS 8)

ns plugin add @klippa/nativescript-http@^3.0.0

使用(NS 8)

自动为此插件的所有 HTTP 调用使用

由于这是一个核心 HTTP 的直接替换,因此我们可以自动在 NativeScript 中使用此插件来执行所有使用 XHR 框架进行 HTTP 调用的 HTTP 调用,这包括

  • 任何为在浏览器中使用而创建的 JavaScript/Angular/Vue 插件
    • Axios
    • Angular HTTPClient
    • vue-resource
  • 任何 NativeScript http 方法
    • request
    • fetch
    • getString, getJSON, getImage, getFile, getBinary
  • NativeScript image-cache
  • 任何内部使用上述方法的 NativeScript 插件

完成此操作的方法非常简单,我们只需导入一个插件并将其添加到 webpack 配置中。

打开文件 webpack.config.js,它可能看起来像这样

const webpack = require("@nativescript/webpack");

module.exports = (env) => {
webpack.init(env);

// Learn how to customize:
// https://docs.nativescript.cn/webpack

return webpack.resolveConfig();
};

导入我们的 webpack 实现,并在 webpack.resolveConfig() 之前添加一行,如下所示

const webpack = require("@nativescript/webpack");
const NativeScriptHTTPPlugin = require("@klippa/nativescript-http/webpack"); // Import NativeScriptHTTPPlugin

module.exports = (env) => {
webpack.init(env);

// Learn how to customize:
// https://docs.nativescript.cn/webpack

webpack.chainWebpack(config => {
config.plugin('NativeScriptHTTPPlugin').use(NativeScriptHTTPPlugin)
});

return webpack.resolveConfig();
};

NativeScriptHTTPPlugin 可以给定一个具有以下属性的对象:replaceHTTP(true/false)和 replaceImageCache(true/false)。这样,您可以控制插件替换的内容。如果您不提供此选项对象,我们将替换两者。选项可以按以下方式传递

webpack.chainWebpack(config => {
config.plugin('NativeScriptHTTPPlugin').use(NativeScriptHTTPPlugin, [
{
replaceHTTP: true,
replaceImageCache: false
}
])
});

注意:如果您这样做,则不需要执行其他集成。

通过添加自检来验证自动集成是否工作

如果您依赖于此插件的新功能,例如处理表单数据或证书固定,并且您想要确保自动集成始终工作,或者您只是想确保安全,您可以将以下自检添加到您的代码中

对于核心 NativeScript / Vue / Angular

import { Http, Dialogs } from "@nativescript/core";

Http.request({
method: "GET",
url: "https://nativescript-http-integration-check.local",
}).then((res) => {
const jsonContent = res.content.toJSON();
if (!jsonContent || !jsonContent.SelfCheck || jsonContent.SelfCheck !== "OK!") {
Dialogs.alert("nativescript-http automatic integration failed! Request to https://nativescript-http-integration-check.local failed");
}
}).catch((e) => {
Dialogs.alert("nativescript-http automatic integration failed! Request to https://nativescript-http-integration-check.local failed");
});

对于 Angular HttpClient

import { Dialogs } from "@nativescript/core";

// Don't forget to inject HttpClient into your component.

// Add the following code in a place where you want to do the self-check in Angular.
this.http.get("https://nativescript-http-integration-check.local", {
responseType: "json",
}).toPromise().then((res) => {
// @ts-ignore
if (!res || !res.SelfCheck || res.SelfCheck !== "OK!") {
Dialogs.alert("nativescript-http automatic integration failed! Request to https://nativescript-http-integration-check.local failed");
}
}).catch((e) => {
Dialogs.alert("nativescript-http automatic integration failed! Request to https://nativescript-http-integration-check.local failed");
});

此插件内部硬编码了 URL https://nativescript-http-integration-check.local 以始终返回相同的结果。

如果请求失败,或内容与预期不符,我们知道出了问题,并将收到一个对话框消息,表明自动集成失败。

代码中的集成

由于这是一个对 核心 HTTP 的直接替换,因此您可以使用与核心 HTTP 相同的方式执行请求,唯一不同的是导入方式

选项和请求输出的格式与核心 HTTP 相同。

import { HttpResponse } from "@nativescript/core";
import { Http } from "@klippa/nativescript-http";

Http.request({
url: "https://httpbin.org/get",
method: "GET"
}).then((response: HttpResponse) => {
// Argument (response) is HttpResponse
}, (e) => {
});

Angular 中的集成

我们还从 nativescript-angular 项目中提供了一个直接替换的 NativeScriptHttpClientModule

为了使 Angular 使用我们的 HTTP 实现,按如下方式导入我们的模块

import { NativeScriptHttpClientModule } from "@klippa/nativescript-http/angular";

@NgModule({
imports: [
NativeScriptHttpClientModule
]

从现在起,您可以使用 Angular 的 HttpClient 服务进行请求,如这里所述。

请注意,此插件会尝试在后台解析您的图像,因此您不需要在 JavaScript 中执行此操作(核心 HTTP 也这样做)。此值无法从 Angular HTTP 客户端访问,只能通过 response.content.toImage(),因此如果您打算下载图像并直接显示它们,建议您直接使用 HTTP 客户端(因此不使用 Angular HTTP 客户端)。

图像缓存

如果您使用 WebPack 插件,则无需执行任何操作即可使用我们的 ImageCache。它与核心的行为相同,因此您无需进行任何更改。

如果您不使用该插件。您可以从 @klippa/nativescript-http 中导入 ImageCache 类。它与核心 ImageCache 具有相同的 API。

关于支持 < Android 5 (SDK 21) 的应用的重要说明

NativeScript 的默认 minSdk 为 17,这是 Android 4.2。我们使用 OkHttp 版本 4,它 不支持 Android 4

如果您不在乎 Android 4 用户

如果您不在乎 Android 4 用户,请编辑文件 App_Resources/Android/app.gradle 并将 minSdk 更改为 21

android {
defaultConfig {
minSdkVersion 21
// ... other config.
}
// ... other config.
}

这会让应用商店知道该应用无法安装在低于 Android 5 的设备上。

如果您在乎 Android 4 用户

幸运的是,OkHttp 有一个名为 okhttp_3.12.x 的特殊支持分支,用于较旧的 Android 版本,并且由于 OkHttp 是二进制安全的,这意味着所有方法都具有相同的签名,我们可以简单地替换版本,并且一切都会正常工作™。

我不介意使用较旧的 OkHttp 版本

如果您不介意每个人使用较旧的 OkHttp 版本,您可以进行以下简单™修复

编辑文件 App_Resources/Android/app.gradle,添加以下行

android {
// ... other config.
configurations.all {
resolutionStrategy.force "com.squareup.okhttp3:okhttp:3.12.+"
}
}

这将强制您的构建使用 OkHttp 的支持版本。

请注意,此 okhttp_3.12.x 分支仅支持到 2020 年 12 月 31 日,并且它只会为严重错误或安全问题提供修复。

这意味着您将无法从版本 4 中获得任何酷炫功能

我想为 Android 5 使用最新版本,为 Android 4 使用版本 3.12

注意:Android 运行时中目前有一个 开放问题,这使得以下配置无法工作

幸运的是,这也是一种可能性,但稍微困难一些,因为您必须拆分您的构建。

编辑文件 App_Resources/Android/app.gradle,添加以下行

android {
// ... other config.
flavorDimensions "api"

productFlavors {
minApi21 {
dimension "api"
minSdkVersion 21
versionNameSuffix "-minApi21"
}

minApi17 {
dimension "api"
minSdkVersion 17
versionNameSuffix "-minApi17"
}
}
}

android.applicationVariants.all { variant ->
if (variant.name.contains("minApi17")) {
variant.getCompileConfiguration().resolutionStrategy.force "com.squareup.okhttp3:okhttp:3.12.+"
variant.getRuntimeConfiguration().resolutionStrategy.force "com.squareup.okhttp3:okhttp:3.12.+"
}

variant.outputs.each { output ->
if (variant.name.contains("minApi17")) {
output.versionCodeOverride = 10000000 + variant.versionCode
} else {
output.versionCodeOverride = 20000000 + variant.versionCode
}
}
}

android 部分是为了创建 2 个产品风味,一个用于 minSdk 17,另一个用于 minSdk 21。

android.applicationVariants 由两部分组成

  1. 确保flavor minApi17使用版本3.12.+的minSdk 17
  2. 确保每个flavor都有自己的构建版本号。它从清单中获取版本,并对minApi17使用(10000000 + manifestVersionCode),对minApi21使用(20000000 + manifestVersionCode)。

当您构建一个发布版本时,这将创建2个APK,一个用于Android 4(app-minApi17-release.apk),另一个用于Android 5(app-minApi21-release.apk)。您还可以将其与ABI拆分结合使用。

当您将这两个APK上传到Playstore时,Google会确保正确的APK被分发到不同的设备。

与其他NativeScript HTTP客户端的比较

插件 Android iOS 后台线程 支持表单数据 适当的连接池 可以替换核心http 证书/SSL固定 WebSocket 图像缓存
@nativescript/core/http :heavy_check_mark: 使用Java HttpURLConnection :heavy_check_mark: 使用NSURLSession :heavy_check_mark :x :x: Android实现不良 - :x :x -
nativescript-background-http :heavy_check_mark: 使用gotev/android-upload-service :heavy_check_mark: 使用NSURLSession :heavy_check_mark: (带服务) :x 未知 :x :x :x :x
nativescript-http-formdata :heavy_check_mark: 使用OkHttp4 :heavy_check_mark: 使用OMGHTTPURLRQ :x :heavy_check_mark :x: OkHttp实现不良 :x :x :x :x
nativescript-okhttp :heavy_check_mark: 使用OkHttp2 :x :x :x :x: OkHttp实现不良 :x :x :x :x
nativescript-https :heavy_check_mark: 使用OkHttp3 :heavy_check_mark: 使用AFNetworking :heavy_check_mark :x :heavy_check_mark: 共享客户端 :white_check_mark: 通过手动替换调用,数据结构(几乎)相同 :heavy_check_mark :x :x
@klippa/nativescript-http :heavy_check_mark: 使用OkHttp4 :heavy_check_mark: 使用NSURLSession :heavy_check_mark :heavy_check_mark :heavy_check_mark: 共享客户端 :heavy_check_mark: 自动和手动 :heavy_check_mark :heavy_check_mark :heavy_check_mark

与NativeScript Core HTTP的实现差异

  • 我们仅在Content-Type以image/开头时尝试将响应解析为Image
  • 我们使用默认的超时时间为60秒进行连接/写入/读取,您可以通过超时选项来更改它
  • 虽然核心HTTP的代码看起来似乎支持FormData,但它只支持键值对,不支持文件,我们通过我们的HTTPFormData类支持它。

API

表单数据

默认情况下,此客户端对FormData对象的行为与核心HTTP相同,这意味着它将简单地将其编码为键=值对,并且不支持Blob/File对象。除非您使用自定义头覆盖它,否则它将作为application/x-www-form-urlencoded发布。

如果您想创建多部分表单数据(multipart/form-data)请求,您可以使用此插件中的HTTPFormData类。您可以创建如下表单数据请求

import { HttpResponse } from "@nativescript/core";
import { Http, HTTPFormData, HTTPFormDataEntry } from "@klippa/nativescript-http";

const form = new HTTPFormData();
form.append("value", "Test");
// You can also append ArrayBuffer/File/Blob/native(such as java.io.File and NSData.dataWithContentsOfFile) objects directly to form here, but please keep in mind that only the File object has the ability to set a filename. And only Blob/File objects have the ability to set a content type.
// Use HTTPFormDataEntry if you want more control.

// formFile data can be a JavaScript ArrayBuffer but also native file objects like java.io.File and NSData.dataWithContentsOfFile.
const formFile = new HTTPFormDataEntry(new java.io.File(fileLocation), "test.png", "image/png");
form.append("file", formFile);

Http.request({
url: "https://httpbin.org/post",
method: "POST",
content: form
}).then((response: HttpResponse) => {
// Argument (response) is HttpResponse
}, (e) => {
});

注意:此功能不与Angular HTTPClient一起使用,因为它尝试将HTTPFormData转换为json。请使用request()方法进行多部分发布。

控制图像解码(仅限Android)

NativeScript HTTP实现始终尝试将响应解码为图像,以确保toImage()快速工作。然而,很多时候您不希望它这样做,因为您没有期望图像。默认情况下,此插件仅在端点返回正确的图像内容类型(ImageParseMethod.CONTENTTYPE)时以此方式工作。使用此方法,您可以控制此行为,使用ImageParseMethod.ALWAYS您将回退到核心HTTP行为,使用ImageParseMethod.NEVER您可以完全禁用它。

注意:仅影响Android,在iOS上,图像解码仅在您使用toImage()时发生。

import { setImageParseMethod, ImageParseMethod } from "@klippa/nativescript-http";

// Add this line where you want to change the image parse mode.
// Options are: NEVER/CONTENTTYPE/ALWAYS.
setImageParseMethod(ImageParseMethod.ALWAYS);

控制cookie

清除所有cookie

import { clearCookies } from "@klippa/nativescript-http";

// Add this line where you want to clear cookies.
clearCookies();

控制并发/连接池限制

注意:仅域名限制对iOS有影响。

import { setConcurrencyLimits } from "@klippa/nativescript-http";

// Add this line where you want to set the concurrency limits.
// First argument is total limit, second per domain.
setConcurrencyLimits(20, 5);

设置全局User Agent

import { setUserAgent } from "@klippa/nativescript-http";

// Add this line where you want to set the user agent.
setUserAgent("MyCoolApp");

WebSocket

注意:证书固定在iOS上的WebSockets不可用。遗憾的是 SocketRocket移除了对此的支持

在此插件中创建WebSocket相当简单

import { newWebsocketConnection } from "@klippa/nativescript-http/websocket";

newWebsocketConnection({
url: "wss://echo.websocket.org",
method: "GET",
}, {
// It's important to wrap callbacks in ngZone for Angular when you do anything binding related.
// If you don'
t do this, Angular won't update the views.
onClosed: (code: number, reason: string) => {
// Invoked when both peers have indicated that no more messages will be transmitted and the connection has been successfully released.
// No further calls to this callback will be made.
console.log("onClosed", code, reason);
},
onFailure: (error) => {
// Invoked when a web socket has been closed due to an error reading from or writing to the network.
// Both outgoing and incoming messages may have been lost. No further calls to this callback will be made.
console.log("onFailure", error);
},
onOpen: () => {
// Invoked when a web socket has been accepted by the remote peer and may begin transmitting messages.
console.log("onOpen");
},
onClosing: (code: number, reason: string) => {
// Invoked when the remote peer has indicated that no more incoming messages will be transmitted.
// This method will not be called on iOS.
console.log("onClosing", code, reason);
},
onMessage: (text: string) => {
// Invoked when a text (type 0x1) message has been received.
console.log("onMessage", text);
},
onBinaryMessage: (data: ArrayBuffer) => {
// Invoked when a binary (type 0x2) message has been received.
console.log("onBinaryMessage", data);
}
}).then((webSocket) => {
// With the webSocket object you can send messages and close the connection.
// Receiving a WebSocket here does not mean the connection worked, you have to check onFailure and onOpen for that.
});

证书固定

在启用之前,请阅读有关证书固定的信息。它可能具有严重的后果。好的文章在这里 这里这里

您可以通过这个Stack Overflow上的问题了解如何获取证书哈希值。

始终提供至少一个备份密钥

为了防止意外将用户锁定在您的应用之外,请确保您至少有一个备份密钥,并且有相应的程序在主要密钥无法使用时切换到备份密钥。例如,如果您将应用锁定到服务器证书的公钥,您应该在安全的地方生成一个备份密钥。如果您锁定到中间CA或根CA,那么您还应该选择一个您愿意在当前CA(或其中间CA)因某些原因无效时切换到的备用CA。

如果您没有备份密钥,您可能会无意中阻止您的应用工作,直到您发布新版本的应用,并且用户更新它。一次这样的事件导致一家银行不得不要求其CA使用已弃用的中间CA发行新证书,以便其用户可以使用该应用,否则应用将面临数周无法使用。

import { certificatePinningAdd, certificatePinningClear } from "@klippa/nativescript-http";

// Add this line where you want to pin the certificate to a specific domain. The second argument are the certificate hashes that you want to pin.
// You can use *.mydomain.com to also use this for direct subdomains, and **.mydomain.com for any subdomain.
// Note: for iOS, *.publicobject.com also behaves as **.publicobject.com due to limitation in TrustKit.
// Note 2: for Android, if you use the older version of OkHttp, the **. prefix does not work.
// Note 3: for iOS, you need to have at least 2 hashes, because Trustkit requires you to have a backup certificate.
certificatePinningAdd("mydomain.com", ["DCU5TkA8n3L8+QM7dyTjfRlxWibigF+1cxMzRhlJV4=", "Lh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=", "Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys="]);

// Use this to clear all certificate pins.
certificatePinningClear();

路线图

  • 缓存控制
  • 允许自签名证书(功能中的工作进度:feature/self-signed)

关于Klippa

Klippa是一家来自荷兰格罗宁根的快速成长公司,成立于2015年,由六位荷兰IT专家共同创立,旨在利用现代技术实现纸质流程的数字化。

我们通过使用机器学习和OCR帮助客户提高其组织的有效性。自2015年以来,我们已为1000多位满意的客户提供Klippa提供的各种软件解决方案。我们的热情是通过智能应用、应付账款软件和OCR数据提取来帮助客户实现纸质流程的数字化。

许可

MIT许可(MIT)