Retrofit2:在 OkHttp 拦截器中修改请求 body

Retrofit2: Modifying request body in OkHttp Interceptor

我在 Android 应用程序中将 Retrofit 2 (2.0.0-beta3) 与 OkHttp 客户端一起使用,到目前为止一切顺利。但目前我正面临 OkHttp 拦截器的问题。我正在与之通信的服务器在请求的 body 中获取访问令牌,因此当我拦截添加身份验证令牌的请求或在 Authenticator 的身份验证方法中需要添加更新的身份验证令牌时,我需要修改 body 为此目的的请求。但看起来我只能在 headers 中添加数据,而不能在正在进行的请求的 body 中添加数据。目前我写的代码如下:

client.interceptors().add(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                if (UserPreferences.ACCESS_TOKEN != null) {
                    // need to add this access token in request body as encoded form field instead of header
                    request = request.newBuilder()
                            .header("access_token", UserPreferences.ACCESS_TOKEN))
                            .method(request.method(), request.body())
                            .build();
                }
                Response response = chain.proceed(request);
                return response;
            }
        });

任何人都可以指出正确的方向,如如何修改请求 body 以添加我的访问令牌(第一次或在令牌刷新后更新)?任何指向正确方向的指针将不胜感激。

我用它来将 post 参数添加到现有参数。

 OkHttpClient client = new OkHttpClient.Builder()
                    .protocols(protocols)
                    .addInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
                            Request request = chain.request();
                            Request.Builder requestBuilder = request.newBuilder();
RequestBody formBody = new FormEncodingBuilder()
            .add("email", "Jurassic@Park.com")
            .add("tel", "90301171XX")
            .build();
                            String postBodyString = Utils.bodyToString(request.body());
                            postBodyString += ((postBodyString.length() > 0) ? "&" : "") +  Utils.bodyToString(formBody);
                            request = requestBuilder
                                    .post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"), postBodyString))
                                    .build();
                            return chain.proceed(request);
                        }
                    })
                    .build();

public static String bodyToString(final RequestBody request){
        try {
            final RequestBody copy = request;
            final Buffer buffer = new Buffer();
            if(copy != null)
                copy.writeTo(buffer);
            else
                return "";
            return buffer.readUtf8();
        }
        catch (final IOException e) {
            return "did not work";
        }
    }

OkHttp3:

RequestBody formBody = new FormBody.Builder()
                .add("email", "Jurassic@Park.com")
                .add("tel", "90301171XX")
                .build();

由于这不能写在@Fabian 上一个答案的评论中,所以我将这个作为单独的答案发布。此答案涉及 "application/json" 和表单数据。

import android.content.Context;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;

import okhttp3.FormBody;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.Buffer;

/**
 * Created by debanjan on 16/4/17.
 */

public class TokenInterceptor implements Interceptor {
    private Context context; //This is here because I needed it for some other cause 

    //private static final String TOKEN_IDENTIFIER = "token_id";
    public TokenInterceptor(Context context) {
        this.context = context;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        RequestBody requestBody = request.body();
        String token = "toku";//whatever or however you get it.
        String subtype = requestBody.contentType().subtype();
        if(subtype.contains("json")){
            requestBody = processApplicationJsonRequestBody(requestBody, token);
        }
        else if(subtype.contains("form")){
            requestBody = processFormDataRequestBody(requestBody, token);
        }
        if(requestBody != null) {
            Request.Builder requestBuilder = request.newBuilder();
            request = requestBuilder
                    .post(requestBody)
                    .build();
        }

        return chain.proceed(request);
    }
    private String bodyToString(final RequestBody request){
        try {
            final RequestBody copy = request;
            final Buffer buffer = new Buffer();
            if(copy != null)
                copy.writeTo(buffer);
            else
                return "";
            return buffer.readUtf8();
        }
        catch (final IOException e) {
            return "did not work";
        }
    }
    private RequestBody processApplicationJsonRequestBody(RequestBody requestBody,String token){
        String customReq = bodyToString(requestBody);
        try {
            JSONObject obj = new JSONObject(customReq);
            obj.put("token", token);
            return RequestBody.create(requestBody.contentType(), obj.toString());
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }
    private RequestBody processFormDataRequestBody(RequestBody requestBody, String token){
        RequestBody formBody = new FormBody.Builder()
                .add("token", token)
                .build();
        String postBodyString = bodyToString(requestBody);
        postBodyString += ((postBodyString.length() > 0) ? "&" : "") +  bodyToString(formBody);
        return RequestBody.create(requestBody.contentType(), postBodyString);
    }

}

我将使用 Dagger 分享我对 @Fabian 的回答的 Kotlin 实现。我想 origin=app 添加到 GET 请求的请求 url 中,并添加到表单编码的正文中 POST 请求

@Provides
@Singleton
fun providesRequestInterceptor() =
        Interceptor {
            val request = it.request()

            it.proceed(when (request.method()) {
                "GET" -> {
                    val url = request.url()
                    request.newBuilder()
                            .url(url.newBuilder()
                                    .addQueryParameter("origin", "app")
                                    .build())
                            .build()
                }
                "POST" -> {
                    val body = request.body()
                    request.newBuilder()
                            .post(RequestBody.create(body?.contentType(),
                                    body.bodyToString() + "&origin=app"))
                            .build()
                }
                else -> request
            })
        }

fun RequestBody?.bodyToString(): String {
    if (this == null) return ""
    val buffer = okio.Buffer()
    writeTo(buffer)
    return buffer.readUtf8()
}

我正在使用这种方式来验证我的令牌

final OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS) //retrofit default 10 seconds
                .writeTimeout(30, TimeUnit.SECONDS) //retrofit default 10 seconds
                .readTimeout(30, TimeUnit.SECONDS) //retrofit default 10 seconds
                .addInterceptor(logging.setLevel(HttpLoggingInterceptor.Level.BODY))
                .addInterceptor(new BasicAuthInterceptor())
                .build();

这里我通过 BasicAuthInterceptor 发送令牌

public class MyServiceInterceptor implements Interceptor {

private String HEADER_NAME="Authorization";
private String OBJECT_NAME="Bearer";
private String SPACE="  ";
@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    Request.Builder requestBuilder = request.newBuilder();

    String token= PreferenceManager.getInstance().getString(PreferenceManager.TOKEN);
        if (token != null) { {
            requestBuilder.addHeader(HEADER_NAME, OBJECT_NAME+SPACE+ token);
        }
}

    return chain.proceed(requestBuilder.build());

} }

private static class NetworkInterceptor implements Interceptor {

@Override
public Response intercept(Chain chain) throws IOException {
    
    Request request = chain.request();
    RequestBody oldBody = request.body(); //retrieve the current request body
    Buffer buffer = new Buffer();
    oldBody.writeTo(buffer);
    String strOldBody = buffer.readUtf8(); // String representation of the current request body
    buffer.clear();
    buffer.close();

    MediaType mediaType = MediaType.parse("application/json; charset=UTF-8");
    String strNewBody = enDecService.encryptBody(strOldBody); // Your encryption/ modification logic 
    RequestBody body = RequestBody.create(mediaType, strNewBody); // New request body with the encrypted/modified string of the current request body

    request = request.newBuilder()
            .header("Content-Type", "application/json")
            .header("Content-Length", String.valueOf(body.contentLength()))
            .header("Authorization", "Bearer " + "your token")
            .method(request.method(), body).build();


    long t1 = System.nanoTime();
    Log.d(TAG, String.format("Sending request %s on %s", request.url(), request.headers()));

    Response response = chain.proceed(request); // sending req. to server. current req. body is a encrypted string.
    int maxAge = 6000; // read from cache for 6000 seconds even if there is internet connection
    response.header("Cache-Control", "public, max-age=" + maxAge);
    response = response.newBuilder().removeHeader("Pragma").build();


    long t2 = System.nanoTime();
    Log.d(TAG, String.format("Received response for %s in %.1fms  %s", response.request().url(), (t2 - t1) / 1e6d, response.toString()));

    try {
        String s = response.body().string(); // retrieve string representation of encrypted response assuming your response is encrypted.
        ResponseBody responseBody = ResponseBody.create(mediaType, enDecService.decryptBody(s)); // decrypt the encrypted response or make other modifications.yor decryption/modifications logic goes here.
        response = response.newBuilder().body(responseBody).build(); // build a new response with the decrypted response body.
    } catch (JOSEException e) {

    } catch (ParseException e) {

    }
    return response;
}

}

您可以通过以下方法编辑请求体,传递请求和参数进行编辑。

private fun editBody(request: Request, parameter: String): RequestBody {
           
  val oldBody = request.body //retrieve the current request body
  val buffer =  Buffer()
  oldBody?.writeTo(buffer)

  val strOldBody = buffer.readUtf8() // String representation of the current request body
  buffer.clear()
  buffer.close()

  val strNewBody = JSONObject(strOldBody).put("parameter", parameter).toString()
  return strNewBody.toRequestBody(request.body?.contentType()) // New request body with the encrypted/modified string of the current request body

}

现在您可以使用更新后的请求正文再次请求

override fun intercept(chain: Interceptor.Chain): Response {
      val request: Request = chain.request()
      return chain.proceed(requestWithUpdatedParameter(request, "parameter"))
    
}
    
private fun requestWithUpdatedParameter(req: Request, parameter: String): Request {
                val newRequest: Request
                val body = editBody(req, parameter)
                newRequest = req.newBuilder().method(req.method, body).build()
                return newRequest
}