Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Show comment
Hide comment
@Plastix

Plastix 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?

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

This comment has been minimized.

Show comment
Hide comment
@hygorxaraujo

hygorxaraujo May 17, 2016

@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.

hygorxaraujo commented May 17, 2016

@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.

@mdzht

This comment has been minimized.

Show comment
Hide comment
@mdzht

mdzht 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

mdzht 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

This comment has been minimized.

Show comment
Hide comment
@cxzhang2

cxzhang2 Jun 25, 2016

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.

cxzhang2 commented Jun 25, 2016

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

This comment has been minimized.

Show comment
Hide comment
@GlobeTechGit

GlobeTechGit Jul 22, 2016

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(); }

GlobeTechGit commented Jul 22, 2016

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

This comment has been minimized.

Show comment
Hide comment
@joshafeinberg

joshafeinberg Sep 26, 2016

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

joshafeinberg commented Sep 26, 2016

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

@crm416

This comment has been minimized.

Show comment
Hide comment
@crm416

crm416 Oct 23, 2016

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.

crm416 commented Oct 23, 2016

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

This comment has been minimized.

Show comment
Hide comment
@TheLester

TheLester Mar 2, 2017

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

TheLester commented Mar 2, 2017

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

@timrijckaert

This comment has been minimized.

Show comment
Hide comment
@timrijckaert

timrijckaert Apr 4, 2017

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)
    }
}

timrijckaert commented Apr 4, 2017

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

This comment has been minimized.

Show comment
Hide comment
@JessYanCoding

JessYanCoding Jul 27, 2017

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

JessYanCoding commented Jul 27, 2017

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

This comment has been minimized.

Show comment
Hide comment
@rohankandwal

rohankandwal Feb 22, 2018

@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.

rohankandwal commented Feb 22, 2018

@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

This comment has been minimized.

Show comment
Hide comment
@thsaravana

thsaravana Feb 23, 2018

@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) ?

thsaravana commented Feb 23, 2018

@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

This comment has been minimized.

Show comment
Hide comment
@JessYanCoding

JessYanCoding Mar 24, 2018

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

JessYanCoding commented Mar 24, 2018

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

@iBotasky

This comment has been minimized.

Show comment
Hide comment
@iBotasky

iBotasky 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?

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

This comment has been minimized.

Show comment
Hide comment
@JessYanCoding

JessYanCoding commented Jul 12, 2018

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

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