react native 使用fetch进行网络请求(https),解决SSLHandshake问题,以及怎样进行二次封装

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() {
                @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来实现的。

@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,@Nullable String defaultUserAgent,OkHttpClient client,@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);

3.运行结果:

相关文章

react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接...
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc ...