Dynamically change Retrofit’s base URL and rest version

1. Requirements and premises

base url

The default base url: https://cloud.devwiki.net

Beta URL: https://dev.devwiki.net

Private cloud version url: https://private.devwiki.net

Rest version

  • /rest/v1/
  • /rest/v2/
  • /rest/v3/

Demand point

  1. Cloud host is used for most interfaces, and private host is used for some interfaces
  2. Most interfaces use REST/V3, and some interfaces use V2 and V1.
  3. Each host may have rest v1, V2, and V3 interfaces

2. Implementation idea

Okhttp can add interceptors, which can be used to intercept access before it is initiated. Usually we add headers to interceptors, such as:

class HeaderInterceptor implements Interceptor {

    private static final String ENCODING_GZIP = "gzip";
    private static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8";
    private static final String HEADER_CONTENT_TYPE = "Content-Type";
    private static final String HEADER_ACCEPT_TYPE = "application/json";
    private static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
    private final static String CHARSET = "UTF-8";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originRequest = chain.request();
        Request.Builder newBuilder = originRequest.newBuilder();
        newBuilder.addHeader("Accept", HEADER_ACCEPT_TYPE);
        newBuilder.addHeader("Accept-Charset", CHARSET);
        newBuilder.addHeader("Accept-Encoding", ENCODING_GZIP);
        newBuilder.addHeader("Accept-Language", Locale.getDefault().toString().replace("_"."-"));
        newBuilder.addHeader(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON);
        returnchain.proceed(newBuilder.build()); }}Copy the code

Similarly, we can add a unified UUID or key to all requests to prevent hijacking or authentication. Such as:

Request originRequest = chain.request();
if(paramsMap ! =null) {
    HttpUrl originUrl = originRequest.url();
    HttpUrl.Builder newBuilder = originUrl.newBuilder();
    for (String key : paramsMap.keySet()) {
        newBuilder.addEncodedQueryParameter(key, paramsMap.get(key));
    }
    HttpUrl newUrl = newBuilder.build();
    Request newRequest = originRequest.newBuilder().url(newUrl).build();
    return chain.proceed(newRequest);
}
return chain.proceed(originRequest);
Copy the code

So, we can also replace the host and path in the interceptor, so how to replace?

3. Implementation process

3.1 Define the Host type and REST version

The host type:

interface HostName {
    String CLOUD = "CLOUD";
    String PRIVATE = "PRIVATE";
    String DEV = "DEV";
}

interface HostValue {
    String CLOUD = "https://www.baidu.com";
    String PRIVATE = "https://private.bidu.com";
    String DEV = "https://dev.baidu.com";
}
Copy the code

Rest version:

interface RestVersionCode {
    String EMPTY = "EMPTY";
    String V1 = "V1";
    String V2 = "V2";
    String PRIVATE = "PRIVATE";
}

/** * Path prefix value */
interface RestVersionValue {
    String EMPTY = "";
    String V1 = "rest/v1";
    String V2 = "rest/v2";
    String PRIVATE = "rest/private";
}
Copy the code

Set a default host and REST version, then add a header to the request that you want to change the host and REST version, and change based on the header Settings.

interface BaiduApiService {

    @GET("s")
    Observable<Response<Object>> search(@Query("wd")String wd);

    @GET("s")
    @Headers({UrlConstants.Header.REST_VERSION_V1})
    Observable<Response<Object>> searchChangePath(@Query("wd")String wd);

    @GET("s")
    @Headers({UrlConstants.Header.HOST_DEV})
    Observable<Response<Object>> searchChangeHost(@Query("wd")String wd);

    @Headers({UrlConstants.Header.HOST_PRIVATE, UrlConstants.Header.REST_VERSION_PRIVATE})
    @GET("s")
    Observable<Response<Object>> searchChangeHostPath(@Query("wd")String wd);
}
Copy the code

Optional values of header:

interface Header {
    String SPLIT_COLON = ":";
    String HOST = "HostName";
    String HOST_CLOUD = HOST + SPLIT_COLON + HostName.CLOUD;
    String HOST_PRIVATE = HOST + SPLIT_COLON + HostName.PRIVATE;
    String HOST_DEV = HOST + SPLIT_COLON + HostName.DEV;
    String REST_VERSION = "RestVersion";
    String REST_VERSION_V1 = REST_VERSION + SPLIT_COLON + RestVersionCode.V1;
    String REST_VERSION_V2 = REST_VERSION + SPLIT_COLON + RestVersionCode.V2;
    String REST_VERSION_PRIVATE = REST_VERSION + SPLIT_COLON + RestVersionCode.PRIVATE;
    String REST_VERSION_EMPTY = REST_VERSION + SPLIT_COLON + RestVersionCode.EMPTY;
}
Copy the code

And then parsing:

class RequestInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originRequest = chain.request();
        HttpUrl originUrl = originRequest.url();
        HttpUrl.Builder newBuilder;

        String hostType = originRequest.header(UrlConstants.Header.HOST);
        System.out.println("hostType:" + hostType);
        if(hostType ! =null && hostType.length() > 0) {
            String hostValue = UrlManager.getInstance().getHost(hostType);
            HttpUrl temp = HttpUrl.parse(hostValue);
            if (temp == null) {
                throw new IllegalArgumentException(hostType + "Host address is invalid :" + hostValue);
            }
            newBuilder = temp.newBuilder();
        } else {
            newBuilder = new HttpUrl.Builder()
                    .scheme(originUrl.scheme())
                    .host(originUrl.host())
                    .port(originUrl.port());
        }
        String restVersion = originRequest.header(UrlConstants.Header.REST_VERSION);
        System.out.println("restVersion:" + restVersion);
        if (restVersion == null) {
            restVersion = UrlConstants.RestVersionCode.V2;
        }
        String restValue = UrlManager.getInstance().getRest(restVersion);
        if (restValue.contains("/")) {
            String[] paths = restValue.split("/");
            for(String path : paths) { newBuilder.addEncodedPathSegment(path); }}else {
            newBuilder.addEncodedPathSegment(restValue);
        }
        for (int i = 0; i < originUrl.pathSegments().size(); i++) {
            newBuilder.addEncodedPathSegment(originUrl.encodedPathSegments().get(i));
        }

        newBuilder.encodedPassword(originUrl.encodedPassword())
                .encodedUsername(originUrl.encodedUsername())
                .encodedQuery(originUrl.encodedQuery())
                .encodedFragment(originUrl.encodedFragment());

        HttpUrl newUrl = newBuilder.build();
        System.out.println("newUrl:" + newUrl.toString());
        Request newRequest = originRequest.newBuilder().url(newUrl).build();
        returnchain.proceed(newRequest); }}Copy the code

To be able to set hosts dynamically, we need a map to store the host type and value.

private Map<String, String> hostMap;
private Map<String, String> restMap;

private UrlManager(a) {
    hostMap = new HashMap<>(16);
    for (UrlConstants.Host host : UrlConstants.Host.values()) {
        hostMap.put(host.getName(), host.getValue());
    }
    restMap = new HashMap<>();
    for(UrlConstants.Rest rest : UrlConstants.Rest.values()) { restMap.put(rest.getVersion(), rest.getValue()); }}// Update the value of host
public void setHost(String name, String value) {
    if (hostMap.containsKey(name)) {
        HttpUrl httpUrl = HttpUrl.parse(value);
        if (httpUrl == null) {
            throw new IllegalArgumentException("Host to store" + name + "Value :"
                    + value + "Illegal!);
        }
        hostMap.put(name, value);
    } else {
        throw new NoSuchElementException("No Host name defined :" + name + ", please first" +
                "Net. Devwiki. Manager. UrlConstants. Host defined in!"); }}// Get the value based on host
public String getHost(String name) {
    if(! hostMap.containsKey(name)) {throw new NoSuchElementException("No Host name defined :" + name + ", please first" +
                "Net. Devwiki. Manager. UrlConstants. Host defined in!");
    }
    return hostMap.get(name);
}
Copy the code

This allows you to dynamically replace the host and REST versions.

4. Test run

Test code:

private static void testRequest(a) {
    BaiduRest rest = new BaiduRest();

    testDefault(rest);

    testChangeHost(rest);

    testChangePath(rest);

    testChangeHostPath(rest);
}
Copy the code

Test results:

OstType: null restVersion: null newUrl:https://www.baidu.com/rest/v2/s?wd=123 September 7, 2018 okhttp3 11:36:58 morning. Internal. The platform. The platform the log information: - > GET https://www.baidu.com/rest/v2/s?wd=123 HTTP / 1.1 September 7, 2018 okhttp3 11:36:58 morning. Internal. The platform. The platform the log information: < - 302 Found https://www.baidu.com/rest/v2/s?wd=123 (83 ms, 154 - byte (body) on September 7, 2018 okhttp3 11:36:58 morning. Internal. The platform. The platform the log information: --> GET http://www.baidu.com/s?wd=123&tn=SE_PSStatistics_p1d9m0nf HTTP /1.1 September 07, 2018 okhttp3 11:36:58 morning. Internal. The platform. The platform the log information: <-- 200 OK http://www.baidu.com/s?wd=123&tn=SE_PSStatistics_p1d9m0nf (46ms, Unknown - length body) hostType: DEV restVersion: null newUrl:https://dev.baidu.com/rest/v2/s?wd=123 September 7, 2018 okhttp3 11:36:58 morning. Internal. The platform. The platform the log information: - > GET https://dev.baidu.com/rest/v2/s?wd=123 HTTP / 1.1 September 7, 2018 okhttp3 11:36:59 morning. Internal. The platform. The platform the log information: < - 302 Found https://dev.baidu.com/rest/v2/s?wd=123 (154 ms, 154 - byte (body) on September 7, 2018 okhttp3 11:36:59 morning. Internal. The platform. The platform the log information: - > GET http://developer.baidu.com/error.html HTTP / 1.1 September 7, 2018 okhttp3 11:36:59 morning. Internal. The platform. The platform the log information: < - 301 version Permanently http://developer.baidu.com/error.html (18 ms, 73 - byte body) on September 7, 2018 okhttp3 11:36:59 morning. Internal. The platform. The platform the log information: - > GET https://developer.baidu.com/error.html HTTP / 1.1 hostType: null restVersion: V1 newUrl:https://www.baidu.com/rest/v1/s?wd=123 September 7, 2018 okhttp3 11:36:59 morning. Internal. Platform. The platform the log information: < https://developer.baidu.com/error.html - 200 OK (157 ms, unknown - length body) on September 7, 2018 okhttp3 11:36:59 morning. Internal. The platform. The platform the log information: - > GET https://www.baidu.com/rest/v1/s?wd=123 HTTP / 1.1 September 7, 2018 okhttp3 11:36:59 morning. Internal. The platform. The platform the log information: < - 302 Found https://www.baidu.com/rest/v1/s?wd=123 (46 ms, 154 - byte body) on September 7, 2018 okhttp3 11:36:59 morning. Internal. The platform. The platform the log information: --> GET http://www.baidu.com/s?wd=123&tn=SE_PSStatistics_p1d9m0nf HTTP /1.1 September 07, 2018 okhttp3 11:36:59 morning. Internal. The platform. The platform the log information: <-- 200 OK http://www.baidu.com/s?wd=123&tn=SE_PSStatistics_p1d9m0nf (54ms, unknown-length body) hostType:PRIVATE restVersion:PRIVATE newUrl:https://private.bidu.com/rest/private/s?wd=123Copy the code

The host and REST changes are made according to the Settings.

5. Project code

Project code address: dev-wiki /OkHttpDemo

For more articles, visit my blog: DevWiki