preface

Recently, I’ve been learning to use Retrofit and trying to introduce it into an existing project. Content-type: Content-type: Content-type: Content-type: Content-type: Content-type: Content-type: Content-type: Content-type

    APP_FORM_URLENCODED("application/x-www-form-urlencoded"),
    APP_JSON("application/json"),
    APP_OCTET_STREAM("application/octet-stream"),
    MULTIPART_FORM_DATA("multipart/form-data"),
    TEXT_HTML("text/html"),
    TEXT_PLAIN("text/plain"),
Copy the code

In real projects, the final request parameters usually include the default parameters (Token, Api version, App version, etc.) and the normal request parameters. There are many ways to add default parameters to the first content-type (post-form) on the Web. On my current project, most requests go post-JSON except for file uploads. Instead of discussing the pros and cons of the two, let’s talk about how you can gracefully add default parameters when the content-type is application/ JSON.

The traditional way:

Let’s first recall the two approaches to post-JSON

public interface Apis {
    
    @POST("user/login") Observable<Entity<User>> login(@Body RequestBody body); // Construct a RequestBody object @post ()"user/login") Observable<Entity<User>> login(@Body LoginInfo loginInfo); // Construct an entity object}Copy the code

In the second method, you need to create a different Model for each different requested object. This is too cumbersome, so select the first method and construct the RequestBody object directly:

Retrofit mRetrofit = new Retrofit.Builder() .baseUrl(HttpConfig.BASE_URL) AddConverterFactory (GsonConverterFactory. The create ()) / / add gson converter AddCallAdapterFactory (RxJava2CallAdapterFactory. The create ()) / / add rxjava converter. The client (new OkHttpClient. Builder (). The build ()) .build(); Apis mAPIFunction = mRetrofit.create(Apis.class); Map<String, Object> params = new LinkedHashMap<>(); params.put("name"."Daniel Wu");
    params.put("request"."123456");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), JsonHelper.toJSONString(params));
mAPIFunction.login(RequestBody.create(requestBody))
Copy the code

After the packet is captured, the request body is as follows:

And here’s what I want:

RequestBody

   public static RequestBody getRequestBody(HashMap<String, Object> hashMap) {
        Map<String, Object> params = new LinkedHashMap<>();
        params.put("auth", getBaseParams());
        params.put("request".hashMap);
        return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), JsonHelper.toJSONString(params));
    }
Copy the code

That’s perfectly fine, but it’s not elegant enough, so let’s talk about one that I’ve come up with

Interceptor mode:

The first content-type is defined as a default parameter. The first content-type is defined as a default parameter. The first content-type is defined as a default parameter.

 @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        if (request.method().equals("POST")) {
       if (request.body() instanceof FormBody) {
            FormBody.Builder bodyBuilder = new FormBody.Builder();
            FormBody formBody = (FormBody) request.body();
            // Add the original argument to the new constructor, (no direct add, so new new)
            for (int i = 0; i < formBody.size(); i++) {
                bodyBuilder.addEncoded(formBody.encodedName(i), formBody.encodedValue(i));
            }
            formBody = bodyBuilder
                    .addEncoded("clienttype"."1")
                    .addEncoded("imei"."imei")
                    .addEncoded("version"."VersionName")
                    .addEncoded("timestamp", String.valueOf(System.currentTimeMillis()))
                    .build();

            request = request.newBuilder().post(formBody).build();
        }
         return chain.proceed(request);
       }
Copy the code

So up here, we’ve got the request object, and we’ve got the requestBody object, and we’re going to determine if it’s a FormBody object, and if it is, we’re going to take the key pair, add the default key pair and construct a new FormBody object, Finally, a new request object is constructed from the original request object, and the new formBody object is inserted into it, and the interceptor returns. FormBody object is content-type for application/x-www-form-urlencoded,Retrofit will generate an object for us, it is a subclass of RequestBody; When content-type is Application/JSON, RequestBody(or more precisely, anonymous subclasses) is generated. So all we have to do is override the RequestBody, record the request, pull it out and add it to the interceptor and process it.

public class PostJsonBody extends RequestBody {

    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    private static final Charset charset = Util.UTF_8;

    private String content;

    public PostJsonBody(@NonNull String content) {
        this.content = content;
    }

    public String getContent(a) {
        return content;
    }

    @Nullable
    @Override
    public MediaType contentType(a) {
        return JSON;
    }

    @Override
    public void writeTo(@NonNull BufferedSink sink) throws IOException {
        byte[] bytes = content.getBytes(charset);
        if (bytes == null) throw new NullPointerException("content == null");
        Util.checkOffsetAndCount(bytes.length, 0, bytes.length);
        sink.write(bytes, 0, bytes.length);
    }

    public static RequestBody create(@NonNull String content) {
        return newPostJsonBody(content); }}Copy the code

Take the raw JSON data from the interceptor and add the new default parameters:


    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Request originalRequest = chain.request();
        Request.Builder builder = originalRequest.newBuilder();
        if (originalRequest.method().equals("POST")) {
            RequestBody requestBody = originalRequest.body();
            if (requestBody instanceofPostJsonBody) { String content = ((PostJsonBody) requestBody).getContent(); HashMap<String, Object> hashMap = JsonHelper.fromJson(content, HashMap.class); builder.post(RequestBodyFactory.getRequestBody(hashMap)); }}return chain.proceed(builder.build());
    }
Copy the code

So on the outside we can add default parameters globally by just changing one line of code:

RequestBody requestBody =
 RequestBody.create(MediaType.parse("application/json; charset=utf-8"),JsonHelper.toJSONString(params));
Copy the code

Replace with:

RequestBody requestBody = PostJsonBody.create( JsonHelper.toJSONString(params));
Copy the code

Half-baked ideas:

To review the post-form usage, let’s change the previous login to the post-form definition as follows:

@formurlencoded // @formurlencoded will automatically request the parameter 'content @post ("user/login")
    Observable<Entity<User>> login(@Field("account") String name, @Field("password") String password);
Copy the code

Is the API definition much clearer than post-JSON? We don’t need to construct a HashMap or requestBody when we call the interface. The parameters defined in the request method correspond to the original data we get from the input box.

   mAPIFunction.login("Daniel Wu"."123456");
Copy the code

However, as mentioned above, the request body of the post-form can only contain key and value pairs, and cannot describe more complex objects. If the interceptor is added with default parameters, it can only be mixed with normal parameters:

public class AddQueryParameterInterceptor implements Interceptor {

    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Request originalRequest = chain.request();
        Request.Builder builder = originalRequest.newBuilder();
        if (originalRequest.method().equals("POST")) {
            RequestBody requestBody = originalRequest.body();
            if (requestBody instanceof PostJsonBody) {
                String content = ((PostJsonBody) requestBody).getContent();
                HashMap<String, Object> hashMap = JsonHelper.fromJson(content, HashMap.class);
                builder.post(RequestBodyFactory.getRequestBody(hashMap));
            } else if (requestBody instanceof FormBody) {
                FormBody formBody = (FormBody) requestBody;
                LinkedHashMap<String, Object> hashMap = new LinkedHashMap<>();
                for (int i = 0; i < formBody.size(); i++) { hashMap.put(formBody.encodedName(i), formBody.encodedValue(i)); } builder.post(RequestBodyFactory.getRequestBody(hashMap)); }}returnchain.proceed(builder.build()); }}Copy the code

If you have a lot of get or post-form requests in your project and want to change them to post-JSON, this is an easy way to do it without touching the upper layer.