Skip to content

Instantly share code, notes, and snippets.

@swankjesse
Last active May 17, 2024 19:11
Show Gist options
  • Save swankjesse/8571a8207a5815cca1fb to your computer and use it in GitHub Desktop.
Save swankjesse/8571a8207a5815cca1fb to your computer and use it in GitHub Desktop.
This OkHttp application interceptor will replace the destination hostname in the request URL.
import java.io.IOException;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
/** An interceptor that allows runtime changes to the URL hostname. */
public final class HostSelectionInterceptor implements Interceptor {
private volatile String host;
public void setHost(String host) {
this.host = host;
}
@Override public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String host = this.host;
if (host != null) {
HttpUrl newUrl = request.url().newBuilder()
.host(host)
.build();
request = request.newBuilder()
.url(newUrl)
.build();
}
return chain.proceed(request);
}
public static void main(String[] args) throws Exception {
HostSelectionInterceptor interceptor = new HostSelectionInterceptor();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
Request request = new Request.Builder()
.url("http://www.coca-cola.com/robots.txt")
.build();
okhttp3.Call call1 = okHttpClient.newCall(request);
okhttp3.Response response1 = call1.execute();
System.out.println("RESPONSE FROM: " + response1.request().url());
System.out.println(response1.body().string());
interceptor.setHost("www.pepsi.com");
okhttp3.Call call2 = okHttpClient.newCall(request);
okhttp3.Response response2 = call2.execute();
System.out.println("RESPONSE FROM: " + response2.request().url());
System.out.println(response2.body().string());
}
}
@Plastix
Copy link

Plastix commented Apr 18, 2016

Has the OkHttp 3 api changed? I'm currently getting the following error when trying to use this interceptor:

java.lang.IllegalArgumentException: unexpected host: http://localhost:63820/

    at okhttp3.HttpUrl$Builder.host(HttpUrl.java:754)
    at <mypackage>.HostSelectionInterceptor.intercept(HostSelectionInterceptor.java:26)
    at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:187)
    at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:160)
    at okhttp3.RealCall.execute(RealCall.java:57)
    at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
    at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:144)
    at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:125)
    at rx.Observable$2.call(Observable.java:233)
    at rx.Observable$2.call(Observable.java:225)
    at rx.Observable$2.call(Observable.java:233)
    at rx.Observable$2.call(Observable.java:225)
    at rx.Observable$2.call(Observable.java:233)
    at rx.Observable$2.call(Observable.java:225)
    at rx.Observable$2.call(Observable.java:233)
    at rx.Observable$2.call(Observable.java:225)
    at rx.Observable.subscribe(Observable.java:8834)

It appears to me that OkHttp3's HttpUrl builder can't correctly handle parsing the port?

Edit: I fixed my problem by switching the interceptor to parse the URL like so:
HttpUrl newUrl = HttpUrl.parse(host)
@swankjesse is there benefit to using the HttpUrl.Builder as shown in your gist?

@hygorxaraujo
Copy link

@Plastix I was getting this same error when the host string started with http:// or had a trailing slash.
Using the HttpUrl.Builder you can change only the host of the URL while keeping the rest of your request URL.

@mduisenov
Copy link

mduisenov commented Jun 6, 2016

I've got a strange behavior. scheme changes from https to http, but doesn't changes from https to http

public class HostSelectionInterceptor implements Interceptor {

    private volatile String host;
    private volatile String scheme;
    private volatile int port;

    public void setHost(String host) {
        this.host = host;
    }
    public void setScheme(String scheme) {
        this.scheme = scheme;
    }
    public void setPort(int port) {
        this.port = port;
    }

    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        String host = this.host;
        String scheme = this.scheme;
        int port = this.port;
        if(host != null && scheme != null) {
            HttpUrl newUrl = request.url().newBuilder()
                    .scheme(scheme)
                    .host(host)
                    .port(port)
                    .build();
            request = request.newBuilder()
                    .url(newUrl)
                    .build();
        }

        return chain.proceed(request);
    }
}
@Provides
 @Singleton
Retrofit provideRetrofitHttps(final OkHttpClient okHttpClient){
        return new Retrofit.Builder()
                .baseUrl("https://bombi-bom:776/")
                .callFactory(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
private static okhttp3.OkHttpClient createApiClient(HostSelectionInterceptor hostSelectionInterceptor) {
        try {
            OkHttpClient okHttpClient = new okhttp3.OkHttpClient();
            return okHttpClient.newBuilder().sslSocketFactory(createBadSslSocketFactory())
                    .hostnameVerifier(new HostnameVerifier() {
                        @Override
                        public boolean verify(String hostname, SSLSession session) {
                            return true;
                        }
                    })
                    .addInterceptor(hostSelectionInterceptor)
                    .build();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
 hostSelectionInterceptor.setScheme("http://");
 hostSelectionInterceptor.setHost("bombi-bom");
 hostSelectionInterceptor.setPort(80);

it works, my url changes from: https://bombi-bom:776/ to http://bombi-bom:80/

but when I'm trying to get it back

hostSelectionInterceptor.setScheme("https://");
  hostSelectionInterceptor.setHost("bombi-bom");
  hostSelectionInterceptor.setPort(776);

I get this url: http://bombi-bom:80

@cxzhang2
Copy link

Don't suppose anyone could give an example of the race condition that volatile host here prevents? Would like to understand the concurrency wizardry that's going on.

@GlobeTechGit
Copy link

It did not like ASP.NET web application URLs such as http://domain.com/MyWebApp, so I had to change setHost slightly:

public void setHost(String host) { // Store the actual host as "domain.com" this.host = HttpUrl.parse(host).host(); }

@joshafeinberg
Copy link

Has something changed with the okhttp3 API? When I try to change the host I get this:

network interceptor must retain the same host and port

@charliermarsh
Copy link

My guess is that this has to be used with addInterceptor rather than addNetworkInterceptor. As @joshafeinberg points out, if you try to change the host or port with an Interceptor added via addNetworkInterceptor, you get an error (with okhttp3 at least). I've had success altering those fields with addInterceptor, though.

@TheLester
Copy link

How this work with retrofit2? Seems like its just replaces whole request url

@timrijckaert
Copy link

This seems to be working for me.
Thanks!

class ChangeableBaseUrlInterceptor : Interceptor {
    @Volatile private var host: HttpUrl? = null

    fun setHost(url: String) {
        this.host = HttpUrl.parse(url)
    }

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val newRequest = host?.let {
            val newUrl = chain.request().url().newBuilder()
                    .scheme(it.scheme())
                    .host(it.url().toURI().host)
                    .port(it.port())
                    .build()

            return@let chain.request().newBuilder()
                    .url(newUrl)
                    .build()
        }

        return chain.proceed(newRequest)
    }
}

@JessYanCoding
Copy link

This solution will affect @get (full path) @post (full path) and @url so that they can not work,try my solution: https://github.com/JessYanCoding/RetrofitUrlManager

@rohankandwal
Copy link

@JessYanCoding was using this class until I ran into same issue you mentioned. My @url's base url got replaced with intercepted host. Thanks for creating your library.

@thsaravana
Copy link

@JessYanCoding Could you elaborate on why this solution won't work? The whole objective is to intercept the request and change the host alone to whatever we want. So how is this affecting @get(full path) ?

@JessYanCoding
Copy link

JessYanCoding commented Mar 24, 2018

@thsaravana You try, @get(full path) will be replaced with intercepted host

@iBotasky
Copy link

iBotasky commented Apr 6, 2018

@JessYanCoding
I request two api with different baseUrl at the same time, one of the request's baseUrl is wrong.

mHost.setHost(Urls.DOUBAN_FILM_URL_HOST)
        mRetrofit.create(FilmsApi::class.java)
                .getInTheaters()
                .flatMap { filmsData -> Observable.fromIterable(filmsData.films) }
                .compose(TransformScheduler.applyNewThreadScheduler())
                .subscribe(
                        { film ->
                            val filmEntity = FilmEntity(text = film.title, comment = film.originalTitle, date = Date())

                            Log.e("TAG", "put success " + filmEntity.text)
                        },
                        { e ->
                            Log.e("TAG", e.message)
                        })

        mHost.setHost(Urls.GANK_GIRLS_URL_HOST)
        mRetrofit.create(GirlsApi::class.java)
                .accessGirls(1)
                .compose(TransformScheduler.applyNewThreadScheduler())
                .subscribe({ girl ->
                    Log.e("TAG", "size " + girl.results.size)
                })

The two baseurl are: http://gank.io/ and https://api.douban.com/
And occur this:
D/OkHttp: <-- 404 Not Found http://gank.io/v2/movie/in_theaters (308ms)
This is wrong because the base url is https://api.douban.com/ but not http://gank.io/
I think it course by concurrency with the Volatile , how solve it?

@JessYanCoding
Copy link

@iBotasky Because the network request is asynchronous, try https://github.com/JessYanCoding/RetrofitUrlManager

@uni-cstar
Copy link

I have a good implementation plan, which will be easier to use and solve the problem more thoroughly. Welcome everyone to try https://github.com/uni-cstar/oknet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment