Retrofitをproxyに対応させるには
kotlinでサーバサイドアプリを作っていて、そこからインターネット上のサイトにretrofitでデータを取りに行く必要がありました。
在宅で仕事していると、最近の光回線はproxyなしのダイレクトなので、proxyのことは、全く考慮にはなく、out of 眼中でした。
社内のproxyの中にあるネットワークに持って行くと・・・あれ?繋がらない?
Windowsの環境変数も試してみましたが、retrofit、okHttp3は環境変数は見てくれないようで、独自に対応が必要です。
proxyを設定する。
retrofitでproxyに対応させるには、okHttp3のクライアントでproxyに対応させる必要があります。
val proxyHost = "192.168.10.1"
val proxyPort = 8080
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.addInterceptor(logging)
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(proxyHost, proxyPort))))
.build()
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
return Retrofit.Builder()
.baseUrl("http://localhost/")
.client(client)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
.create(MyInterface::class.java)
ミソはこの部分です。
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(proxyHost, proxyPort))))
これでめでたく、proxyを通して外のインターネットサイトと通信できます。
あれ?ローカルなLAN内も、proxyに飛んじゃう?
ところが、これでめでたしめでたしとはなりませんでした。
同一LAN内のマシンにもretrofitでアクセスが必要で、それも含めて全部がproxyに向かってしまうようになってしまいます。
Windowsのproxyの設定とかでよくある、proxy除外ホストのリストですね。これはどうやって実現したらいいのでしょうか?
java.net.ProxySelector を使う
ProxySelectorを使ってretrofitでアクセスするサイトによって、proxyを通すのか、ダイレクトなのかを判定してやります。ProxySelectorを継承してクラスを1個作ります。
class CustomProxySelector(
private val httpProxyHost: String,
private val httpProxyPort: Int,
private val noProxy: String) : ProxySelector() {
private val logger = LoggerFactory.getLogger(CustomProxySelector::class.java)
override fun select(uri: URI?): MutableList<Proxy> {
val noProxyList = noProxy.split(",").toList()
logger.debug("uri.host = ${uri?.host}")
logger.debug("noProxyList = {}", noProxyList)
val proxyList = mutableListOf<Proxy>()
if (httpProxyHost.isBlank()) {
// proxy未設定
logger.debug("proxy not set")
proxyList.add(Proxy.NO_PROXY)
} else if (uri != null && noProxyList.contains(uri.host)) {
// proxy除外ホスト
logger.debug("no proxy: ${uri.host}")
proxyList.add(Proxy.NO_PROXY)
} else {
// proxyを通してアクセス
logger.debug("to proxy: ${uri?.host}")
proxyList.add(Proxy(Proxy.Type.HTTP, InetSocketAddress(httpProxyHost, httpProxyPort)))
}
return proxyList
}
override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {
throw UnsupportedOperationException("Not support, yet")
}
}
このクラスのコンストラクタの引数は3つを想定しています。
- proxyのhost(String)
- proxyのポート(Int)
- proxy除外ホスト、カンマ区切りで複数(String)
このクラスの中の処理は
- proxyのhostが未設定の場合は全部、ダイレクトです。
- アクセスするURIのホストがproxy除外ホストの中にあれば、ダイレクト
- アクセスするURIのホストがproxy除外ホストの中になけば、proxyアクセス
となります。これをopkHttp3のクライアントに渡してやります
val proxyHost = "192.168.10.1"
val proxyPort = 8080
val noProxy = "localhost"
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.addInterceptor(logging)
.proxySelector(CustomProxySelector(proxyHost, proxyPort, noProxy))
.build()
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
return Retrofit.Builder()
.baseUrl("http://localhost/")
.client(client)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
.create(MyInterface::class.java)
ミソはこの部分です。
.proxySelector(CustomProxySelector(proxyHost, proxyPort, noProxy))
これで同一LAN内のホスト、proxyを通してインターネット上のホスト、両方にアクセスできます。