react native 使用fetch进行网络请求(https 问题SSLHandshake,解决办法)
- 使用实例
- 怎样进行封装
- 常见问题(超时设置、无效的ssl证书、…)
- fetch原理讲解
使用实例
1.使用get方式进行网络请求,例如:
fetch('http://nero-zou.com/test',{
method: 'GET'
}).then(function(response) {
//获取数据,数据处理
}).catch(function(err) {
//错误处理
});
2.使用post方式进行网络请求,例如:
let param = {user:'xxx',phone:'xxxxxx'};
fetch(url,{
method: 'post',body: JSON.stringify(param)
}).then(function(response) {
//获取数据,数据处理
});
3.其它写法,例如:
try {
fetch(url,{
method: 'post',body: JSON.stringify(param)
}).then(function(response) {
//获取数据,数据处理
});
} catch(e) {
//捕获异常消息
}
4.带header 或其它参数
fetch(url,{ method: 'POST',headers: { 'Accept': 'application/json','Content-Type': 'application/json',},body: JSON.stringify({ firstParam: 'yourValue',secondParam: 'yourOtherValue',})
})
怎样进行封装
基本上要求是写个基础的函数,其它在进行网络请求时都调用这个函数。即使以后不使用fetch 直接将这个基础函数修改,不用修改其它的地方就可以实现。
基础函数的模型一般是这样的
function sendNetRequest(...props) {
this.url = props.shift(1);
this.options = props.shift(1);
return fetch(this.url,Object.assign({},this.options))
.then((response) =>return response.json());
}
封装各个接口
//封装login 接口
function postLogin(userName,password) {
let loginParam= {user:userName,password:password};
var loginApiPort = "mlogin";//login 对应的接口
//下面的baseURL=https://XXXXX.com
return sendNetRequest(`${baseURL}/${loginApiPort}`,{
method: 'post',body: JSON.stringify(loginParam),headers: {
'Content-Type': 'application/x-www-form-urlencoded',},});
}
//...其它一系列接口的封装
调用实例
try {
postLogin(user,password)
.then((response) => { //获取数据,数据处理 }) } catch(e) { //捕获异常消息 }
这样就大功告成了,下面是遇到的常见问题
常见问题
1.请求时,出现异常
将header里面的Content-Type 设置为’application/x-www-form-urlencoded’,如果还是报错问server 端参数是什么格式 ,然后设置Content-Type的值即可.
2.响应时,出现异常
上述封装容易出现的问题在 response.json() 这一句中,如果response==null就会抛出异常,建议先判断response是否为null,如果为null 再进行特殊处理。
3.fetch设置超时的时间
fetch 本身目前没有设置超时时间的属性,只能机制来进行设置。fetch函数在各个平台的实现,如果你看到源代码的话肯定会觉得能设置超时而且很容易,但是它封装的时候并没有把 这个作为一个属性来设置.因此只能结合promise 机制使用setTimeout 来设置超时的时间。
4.https,如果server 端是无效证书 来进行https 请求的时候出现的错误
SSLHandshake: Received fatal alert: certificate_expired
或者是
SSLHandshake: Remote host closed connection during handshake
…
(1).在android 暂时无解 ,只能用http,因为它不能改变库里面的函数。如果非要支持https ,只能将你的工程目录 + node_modules/react-native/android/com/facebook/react/react-native/0.36.0/react-native-0.36.0-sources.jar!/com/facebook/react/modules/network/OkHttpClientProvider.java,
其他rn版本的文件目录可以推测,总的来说是修改reactnative的network库里面的OkHttpClientProvider.java 这个文件。
OkHttpClientProvider.java 中找到下述代码
return new OkHttpClient.Builder()
.connectTimeout(0,TimeUnit.MILLISECONDS)
.readTimeout(0,TimeUnit.MILLISECONDS)
.writeTimeout(0,TimeUnit.MILLISECONDS)
.cookieJar(new ReactCookieJarContainer())
.build();
改为:
return new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory())
.hostnameVerifier(new HostnameVerifier() {
@H_502_253@@Override
public boolean verify(String hostname,SSLSession session) {
return true; //忽略所有的认证,直接返回了true
}
})
.connectTimeout(0,TimeUnit.MILLISECONDS)
.readTimeout(0,TimeUnit.MILLISECONDS)
.writeTimeout(0,TimeUnit.MILLISECONDS)
.cookieJar(new ReactCookieJarContainer())
.build();
2.iOS 端 ,用xcode 打开 ios 目录下的工程,找到 infor.plist,并添加属性
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
肯定还是报错 ,找到React native 的networking 库,在库里面添加 NSURLSession delegate 函数处理ssl 证书认证的相关代码,设置为都为pass,你可以根据是否是debug模式 来选择是不是pass ,如果是release 版建议不要这样做。
如这个函数
//根据你自己的逻辑处理这个函数,加点判断千万别直接pass,有安全隐患,如果都pass了还不如用http省的麻烦。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,NSURLCredential * _Nullable credential))completionHandler;
5.忘了,貌似是上传图片时出现的问题,以后再补充
原理讲解,实现fetch实现在各平台的关键代码
react native 的网络操作常用的是 fetch,经了解fetch 函数的实现在iOS 和android 平台上各自都是使用RCTNetworking 实现的。
上述第4个问题的解决就是通过看RCTNetworking源代码来解决的。
android上的关键代码分析:
以下是实现的RCTNetworking ,仔细看是用OkHttpClient来实现的。
@H_502_253@@ReactModule(name = "RCTNetworking",supportsWebWorkers = true)
public final class NetworkingModule extends ReactContextBaseJavaModule {
private static final String CONTENT_ENCODING_HEADER_NAME = "content-encoding";
private static final String CONTENT_TYPE_HEADER_NAME = "content-type";
private static final String REQUEST_BODY_KEY_STRING = "string";
private static final String REQUEST_BODY_KEY_URI = "uri";
private static final String REQUEST_BODY_KEY_FORMDATA = "formData";
private static final String USER_AGENT_HEADER_NAME = "user-agent";
private static final int CHUNK_TIMEOUT_NS = 100 * 1000000; // 100ms
private static final int MAX_CHUNK_SIZE_BETWEEN_FLUSHES = 8 * 1024; // 8K
private final OkHttpClient mClient;//其实内部是使用的OkHttpClient
private final ForwardingCookieHandler mCookieHandler;
private final @Nullable String mDefaultUserAgent;
private final CookieJarContainer mCookieJarContainer;
private final Set<Integer> mRequestIds;
private boolean mShuttingDown;
/* package */ NetworkingModule(
ReactApplicationContext reactContext,@H_502_253@@Nullable String defaultUserAgent,OkHttpClient client,@H_502_253@@Nullable List<NetworkInterceptorCreator> networkInterceptorCreators) {
super(reactContext);
if (networkInterceptorCreators != null) {
OkHttpClient.Builder clientBuilder = client.newBuilder();
for (NetworkInterceptorCreator networkInterceptorCreator : networkInterceptorCreators) {
clientBuilder.addNetworkInterceptor(networkInterceptorCreator.create());
}
client = clientBuilder.build();
}
mClient = client;
OkHttpClientProvider.replaceOkHttpClient(client);
mCookieHandler = new ForwardingCookieHandler(reactContext);
mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
mShuttingDown = false;
mDefaultUserAgent = defaultUserAgent;
mRequestIds = new HashSet<>();
}
OkHttpClientProvider编写单例类来管理网络请求
public class OkHttpClientProvider {
// Centralized OkHttpClient for all networking requests.
private static @Nullable OkHttpClient sClient;
public static OkHttpClient getOkHttpClient() {
if (sClient == null) {
//sClient = createClient();
sClient=createClient_initNetworkConfig();
}
return sClient;
}
// okhttp3 OkHttpClient is immutable
// This allows app to init an OkHttpClient with custom settings.
public static void replaceOkHttpClient(OkHttpClient client) {
sClient = client;
}
private static OkHttpClient createClient() {
// No timeouts by default
return new OkHttpClient.Builder()
.connectTimeout(0,TimeUnit.MILLISECONDS)
.cookieJar(new ReactCookieJarContainer())
.build();
}
通过上述代码你可以发现超时可以设置,但是fetch 没有封装该属性。
ios 端关键代码说明
RCTNetInfo 类实现的功能:查询是否联网 是否是WiFi 。
RCTNetworking类实现的功能:跟AFNetworking的实现差不多,你可以仔细研究一下。
暂时略。
最后提供一个完整的实例(下载)
1.新建项目:
执行命令react-native init ZXJNetDemo
2. 编写代码
新建文件BaseServiceApiNet.js:
const baseURL = "https://api.app.net";
function fetchAction(...props) {
this.url = props.shift(1);
this.options = props.shift(1);
return fetch(this.url,this.options))
.then((response) =>response.json());
}
export default {
getTest() {
var apiPort = "stream/0/posts/stream/global";
return fetchAction(`${baseURL}/${apiPort}`,{
method: 'get',headers: {
'Content-Type': 'application/x-www-form-urlencoded',});
}
};
文件index.ios.js
import React,{ Component } from 'react';
import {
AppRegistry,StyleSheet,Text,View,ActivityIndicator
} from 'react-native';
import BaseServiceApiNet from './BaseServiceApiNet';
export default class ZXJNetDemo extends Component {
constructor(props){
super(props);
this.state ={
isLoading:false,resultJson:null
};
}
sendTestRequest(){
if(this.state.isLoading==true){
return;
}
this.setState({
resultJson:null,isLoading:true
});
try {
BaseServiceApiNet.getTest()
.then((response) => { let data = response.Meta; this.setState({ resultJson:data==null?null:JSON.stringify(data),isLoading:false }); console.log("返回数据:"+JSON.stringify(data)); }) } catch(e) { alert(e); this.setState({ isLoading:false }); } } render() { return ( <View style={styles.container}> <ActivityIndicator animating={this.state.isLoading} /> <Text style={styles.welcome} onPress={this.sendTestRequest.bind(this)}> 测试网络 </Text> <Text style={styles.instructions}> {this.state.resultJson} </Text> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1,justifyContent: 'center',alignItems: 'center',backgroundColor: '#F5FCFF',welcome: { fontSize: 20,textAlign: 'center',margin: 10,instructions: { textAlign: 'center',color: '#333333',marginBottom: 5,}); AppRegistry.registerComponent('ZXJNetDemo',() => ZXJNetDemo);