You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RxJava2中400响应处理及异常处理问题求助

解决OkHttp拦截器中RxJava2令牌刷新的异常处理及400响应问题

我来帮你捋清楚这个问题——在OkHttp拦截器里用RxJava处理令牌刷新确实容易踩坑,尤其是异步逻辑和异常处理的衔接,咱们一步步来修正和完善。

一、先修正拦截器里的RxJava执行逻辑

你当前的代码直接调用subscribe(),这会导致令牌刷新在异步线程执行,但OkHttp的拦截器是同步阻塞的,也就是说拦截器会直接返回响应,而刷新令牌的逻辑还在后台跑,这肯定达不到你想要的“刷新后重试请求”的效果。

解决办法是用RxJava的阻塞型操作符,比如blockingSingle(),让拦截器等待刷新令牌的结果完成后再继续执行。同时,要把异常处理从doOnError转移到更可靠的错误处理链里,因为doOnError只是做副作用(比如打日志),不能改变流的走向。

修改后的核心代码大概是这样:

// 在拦截器的intercept方法中,当捕获到401响应时
try {
    TokenResponse tokenResponse = tokenRepository.refreshToken(authStateManager.current.refreshToken!!)
            .blockingSingle();
    
    Log.d(TAG, "Obtained new access token: " + tokenResponse.toString());
    authStateManager.updateAfterTokenResponse(TokenResponse.jsonDeserialize(tokenResponse.toString()), null);
    
    // 用新令牌重新构建请求并发起
    Request newRequest = chain.request().newBuilder()
            .header("Authorization", "Bearer " + tokenResponse.accessToken)
            .build();
    return chain.proceed(newRequest);
} catch (Exception e) {
    Log.w(TAG, "Could not obtain new access token.", e);
    authStateManager.signOut();
    // 这里可以返回一个自定义的错误响应,或者抛出异常让上层处理
    return new Response.Builder()
            .code(401)
            .message("Token refresh failed")
            .request(chain.request())
            .protocol(Protocol.HTTP_1_1)
            .body(ResponseBody.create(null, ""))
            .build();
}

二、异常处理的正确姿势

你之前用doOnError来处理刷新失败的情况,虽然能打日志和登出,但如果刷新过程中抛出异常,subscribe()的错误回调会触发,但拦截器已经无法感知这个错误了(因为异步)。用blockingSingle()配合try-catch可以同步捕获所有异常,确保拦截器能正确处理失败场景。

另外,如果不想用try-catch,也可以用RxJava的错误处理操作符,比如onErrorReturn或者onErrorResumeNext:

TokenResponse tokenResponse = tokenRepository.refreshToken(authStateManager.current.refreshToken!!)
        .onErrorResumeNext(throwable -> {
            Log.w(TAG, "Could not obtain new access token.", throwable);
            authStateManager.signOut();
            // 抛出异常让blockingSingle捕获,或者返回一个无效的TokenResponse
            return Single.error(throwable);
        })
        .blockingSingle();

三、如何处理400响应

400响应分两种场景,要区别对待:

1. 刷新令牌的请求返回400

这种情况通常意味着你的refreshToken已经失效(过期、被吊销、格式错误),这时候再重试刷新也没用,应该直接执行登出操作,并且不要再把原请求重试了。你可以在拦截器里判断当前请求是不是刷新令牌的请求,如果是且返回400,直接触发登出:

Response originalResponse = chain.proceed(request);
if (originalResponse.code() == 400 && isRefreshTokenRequest(request)) {
    authStateManager.signOut();
    // 返回错误响应给上层
    return originalResponse.newBuilder()
            .message("Refresh token invalid")
            .build();
}

2. 普通API请求返回400

400是“Bad Request”,通常是参数错误、请求格式不对等问题,和认证无关,所以不需要触发令牌刷新逻辑,直接把原响应返回给上层即可,让业务层去处理参数错误的情况。

四、额外优化:避免并发刷新令牌

如果多个请求同时触发401,你的当前代码会多次调用refreshToken,这会造成不必要的重复请求。可以用一个原子布尔变量来标记是否正在刷新:

private AtomicBoolean isRefreshing = new AtomicBoolean(false);

// 在拦截器中
if (originalResponse.code() == 401) {
    if (isRefreshing.compareAndSet(false, true)) {
        // 只有第一个请求会执行刷新
        try {
            // 执行刷新逻辑...
        } finally {
            isRefreshing.set(false);
        }
    } else {
        // 其他请求等待刷新完成后,用新令牌重试
        // 这里可以加个短暂等待,或者直接返回401让上层重试
        return new Response.Builder()
                .code(401)
                .message("Token refreshing in progress")
                .request(chain.request())
                .protocol(Protocol.HTTP_1_1)
                .body(ResponseBody.create(null, ""))
                .build();
    }
}

这样就能保证同一时间只有一个刷新令牌的请求在执行。

内容的提问来源于stack exchange,提问作者Smajl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:39:57