美文网首页Android developing tips
网络请求时关于cookie或token失效的解决方案

网络请求时关于cookie或token失效的解决方案

作者: turbof | 来源:发表于2017-01-09 14:22 被阅读1640次

    当一次网络请求(比如说请求购物车的数据,这时是需要验证用户身份的标识的,例如cookie或者token)
    想到的三种方法:

    1.最开始没用rxjava之前就是用的这种,但是感觉实在累赘。当token失效后重新请求登录接口,当登录成功后通知原先的Activity重新加载数据。这样需要对每个接口都进行token是否失效的判断。

    2.使用Intercept(参考这篇文章,但是Okhttpclien3.0删除了ErrorHandler)onErrorResumeNext操作符实现app与服务器间token机制
    http://blog.csdn.net/johnny901114/article/details/51533586
    在intercept方法中拿到返回的json字符串,然后判断token是否失效,如果失效,那么重新登录,但是这儿需要注意的是因为需要继续往下传递请求,登录接口的请求必须是同步的!(ps:后来朋友想了另一个办法,在intercept中抛出异常,这儿就需要用到第三种方法了)

    3.使用retryWhen操作符
    (关于retryWhen这篇博客讲的非常好http://www.jianshu.com/p/023a5f60e6d0
    最开始我的理解有问题。我的代码是这样的。
    <pre>ApiClient.getInstance().getLiveApi().getFollow(cookie)
    .flatMap(new Func1<FollowLive, Observable<FollowLive>>() {
    @Override
    public Observable<FollowLive > call(FollowLive followLive) {
    if (myCourse.status == 205) {
    return Observable.error(new Exception("kkkk"));
    }
    return Observable.just(followLive);
    }
    })
    .retryWhen(new RetryWithDelay(3, 1000))
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<FollowLive >() {
    @Override
    public void call(FollowLive response) {
    fillData(response);
    }
    }, new Action1<Throwable>() {
    @Override
    public void call(Throwable throwable) {
    Log.i("===========k", throwable.toString());
    }
    });</pre>
    然后我在retryWhen中进行了重新登录获取到了最新的cookie,结果显示没有获取到正确的数据,我猜,难道retryWhen只重试了flatMap?当然不是,我抓包得到的结果是,重新进行了网络请求,但是并没有使用新的cookie,为什么呢,cookies作为一个成员变量,他的值变化了啊!
    然后我写了个just的例子测试了下!
    <pre> str = "aaa";
    Observable.just(str).map(new Func1<String, String>() {
    @Override
    public String call(String s) {
    Log.i("====", "s == " + s);
    if ("aaa".equals(s)) throw new RuntimeException(s);
    return s + "123";
    }
    }).retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
    @Override
    public Observable<?> call(Observable<? extends Throwable> observable) {
    return observable.zipWith(Observable.range(1, 4), new Func2<Throwable, Integer, Integer>() {
    @Override
    public Integer call(Throwable throwable, Integer i) {
    str = "ggg";
    return i;
    }
    }).flatMap(new Func1<Integer, Observable<? extends Long>>() {
    @Override
    public Observable<? extends Long> call(Integer retryCount) {
    return Observable.timer(1, TimeUnit.SECONDS);
    }
    });
    }
    }).subscribe(new Action1<String>() {
    @Override
    public void call(String s) {
    Log.i("====k", "s = " + s);
    }
    }, new Action1<Throwable>() {
    @Override
    public void call(Throwable throwable) {
    Log.i("====", "throwable = " + throwable.getMessage());
    }
    });</pre>
    结果是
    <pre>aaa
    aaa
    aaa
    ...
    </pre>
    what?
    为啥啊?为什么后面不打印ggg呢?
    看这里吧。
    关于retryWhen的issue
    https://github.com/ReactiveX/RxJava/issues/4840
    也就说retryWhen每次重试的都是Source Observable!而Observable.just(str)已经创建完成,每次传递给map操作符的都是创建时候用的那个str,网络请求的那个类似,当下次重试的时候使用的是已经创建好的Observable(而这个Observable创建的时候使用的是空的cookie)为了保证使用最新的cookie,使用defer操作符,原理类似于fromCallable.
    当然也可以这样写:

     Observable.just(null).flatMap(new Func1<Object, Observable<FollowLive>>() {
                @Override
                public Observable<FollowLive> call(Object o) {
                    return ApiClient.getInstance().getLiveApi().getFollow(cookie);
                }
            })
    

    再来说第二条提到的那个抛出异常的方法

    具体实现就是Intercept和RetryWhen结合,在Intercept中进行token是否失效的判断,如果token失效那么就直接抛出异常,然后在retryWhen中进行重新登录,并给token设置最新的值。这样就避免了同步请求的问题。不过需要注意:

    1.因为重新登录是异步请求,所以需要对retryWhen中的重试进行限制,即重新请求原先接口需要延迟(用timer操作符)

    2.对重新登录次数进行限制
    3.最好自定义抛出的异常,这样方便在Subscriber的onError方法或者retryWhen中进行判断是否是token失效,万一后续还有其他问题需要在Intercept中处理呢。

    4.最大的弊端是对所有的接口都进行了token是否失效的判断(因为Intercept会是全局的),所以在Intercept中对token是否失效那儿的判断可以自行处理,比如说用户是否登录?这样的判断。

    2017.1.16更新:使用BlockingObservable阻塞操作符可以避免了这种状况:
    比如说登录接口返回较慢(慢的超过了retryCount*retryDelayMillis),那么会一直重试登录接口和follow数据接口,直到超过重试次数,如果这时候仍然没有获取到数据,才会抛出异常,具体的看代码。

    其他参考:https://lorentzos.com/improving-ux-with-rxjava-4440a13b157f#.e4b4absxs
    pps:这是我现在使用的一套方法,有什么不对的地方希望大家可以留言改进,谢谢!
    参考代码:https://github.com/xturbofan/TokenDemo

    相关文章

      网友评论

      • PsyMe:有空把代码加缩近吧!
      • 有兴不虚昧:第二种方法不行吗
        turbof:@有兴不虚昧 DemoInterceptor 中有的,request中包含了请求的所有东西,在那儿请求登录接口然后用最新拿到的token然后组装新的request继续执行请求就可以了。不过我没用那种方法,如果你需要的话我发你好了。
        有兴不虚昧:@turbof 我看他的那个代码,我看不懂,不明白他哪里那么写的时候怎么可以获取到原来要请求的数据
        turbof:可以用,朋友的项目中就是那么写的。只是开始我自己对网络请求的同步和异步有点误解,现在看来下一个接口也必须是登录成功之后才能重复原先的请求。
      • 50c8dc99c5fa:你好,请问下,如果获取到 token 之后不执行之前的请求了可能是什么情况呢?
        50c8dc99c5fa:@turbof 嗯,是的...后来改了其他的代码可以用了, 只能先用着慢慢找感觉了.谢谢回复哈...
        turbof:用defer操作符包裹了之后仍然不重新请求吗?
      • 追风骚年:我想知道重试的时候也会出错,如果重试拿到一个token有问题,就陷入了死循环
        turbof:@追风骚年 有请求次数限制啊,代码里限制了最多3次的。
        追风骚年:请问该怎么解决

      本文标题:网络请求时关于cookie或token失效的解决方案

      本文链接:https://www.haomeiwen.com/subject/edchbttx.html