Итак, у меня возникла проблема, которая выглядит так, будто она вышла прямо из Сумеречной зоны.
Проблема
Мне нужно попасть в конечную точку REST API изБэкэнд, дело в том, что для достижения этой конечной точки мне нужно пройти через VPN.В противном случае хост недоступен.На рабочем столе все работает нормально, я открываю Postman, нажимаю конечную точку GET и получаю ответ.Однако, когда я пытаюсь подключиться к той же конечной точке через устройство Android, Retrofit генерирует исключение UnknownHostException.
Context
URL-адрес конечной точки выглядит примерно так: https://api.something.something.net/
.Я использую инъекцию зависимостей в Dagger, поэтому у меня есть NetworkModule
, который выглядит следующим образом:
...
NetworkModule("https://api.something.something.net/")
...
@Module
class NetworkModule(
private val baseHost: String
) {
...
@Provides
@Named("authInterceptor")
fun providesAuthInterceptor(
@Named("authToken") authToken: String
): Interceptor {
return Interceptor { chain ->
var request = chain.request()
request = request.newBuilder()
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer $authToken")
.build()
val response = chain.proceed(request)
}
}
...
@Provides
@Singleton
fun provideOkHttpClient(
curlInterceptor: CurlInterceptor,
@Named("authInterceptor") authInterceptor: Interceptor
): OkHttpClient {
val builder = OkHttpClient.Builder()
builder.addInterceptor(authInterceptor)
builder.addInterceptor(curlInterceptor)
return builder.build()
}
...
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient, gson: Gson): Retrofit {
return Retrofit.Builder()
.baseUrl(baseHost)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build()
}
}
Затем у меня есть несколько репозиториев, которые делают запрос через Retrofit:
class MyApiRepositoryImpl(
val myRetrofitApi: MyRetrofitApi,
val uiScheduler: Scheduler,
val backgroundScheduler: Scheduler
) : MyApiRepository {
override fun getSomethingFromTheApi(): Observable<DataResource<List<ApiSomethingResponse>>> {
return myRetrofitApi.getResponseFromEndpoint()
.map {
if (it.isSuccessful) {
DataResource(it.body()?.list!!, ResourceType.NETWORK_SUCCESS)
} else {
throw RuntimeException("Network request failed code ${it.code()}")
}
}
.subscribeOn(backgroundScheduler)
.observeOn(uiScheduler)
.toObservable()
}
}
И это интерфейс API Retrofit:
interface MyRetrofitApi {
@GET("/v1/something/")
fun getResponseFromEndpoint(): Single<Response<ApiSomethingResponse>>
}
Итак, когда я вызываю этот метод репозитория из моего Interactor / UseCases, он прыгает прямо через onError и показывает UnknownHostException.
То, что я пробовал до сих пор
- Я переключил Retrofit на Volley, а затем на Ion, просто чтобы быть уверенным, что это не было связано с остальным клиентом.Я получил одно и то же исключение во всех случаях:
java.net.UnknownHostException: Unable to resolve host "api.something.something.net": No address associated with hostname
com.android.volley.NoConnectionError: java.net.UnknownHostException: Unable to resolve host "api.something.something.net": No address associated with hostname
Я перепробовал все возможные конфигурации с Retrofit и OkHttpClient:
На OkHttpClient я попытался установить followSslRedirects
в true и false.followRedirects
на истину и ложь.Установите hostnameVerifier
, чтобы разрешить пропуск любого имени хоста.Установите SSLSocketFactory, чтобы разрешить прохождение любых неподписанных сертификатов.
В моем манифесте я установил для android:networkSecurityConfig
значение:
https://github.com/commonsguy/cwac-netsecurity/issues/5
Я тестировал приложение на своем Android-устройстве (Android Nougat), на эмуляторах с Nougat, Marshmellow и Oreo, а также на эмуляторе Genymotion с Nougat.
Я пытался попасть в произвольную общедоступную конечную точку (https://jsonplaceholder.typicode.com/todos/1
), и она отлично работала.Так что это не проблема с подключением к Интернету.
В моем манифесте установлены три разрешения:
android.permission.INTERNET
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_WIFI_STATE
Это очень странно, потому что у меня есть набор Interceptor для преобразования всех запросов в запросы cURL, я скопировал и вставил тот же запрос, который не выполняется, в Postman и работает отлично.
- На моем ноутбуке яиспользуя Cisco AnyConnect, на моем устройстве Android я использую приложение Cisco AnyConnect и AFAIK на эмуляторах и Genymotion, оно должно использовать ту же сеть, что и хост-компьютер.
Есть несколько веб-сайтов, которыевидны только через VPN и я вижу их на устройствах и на эмуляторах.Но URL-адрес конечной точки по-прежнему недоступен из приложения.
Пара странных вещей
Да, это становится страннее.Если я подключаюсь к конечной точке через браузер Chrome на своем устройстве или в эмуляторе, меня перенаправляют на страницу входа в систему, потому что я не отправляю токен авторизации.Теперь, если я проверю сетевые ответы через chrome: // inspect, я смогу получить IP-адрес хоста.Теперь, если я изменяю базовый URL-адрес NetworkModule по IP и добавляю эту строку в Interceptor авторизации:
@Provides
@Named("authInterceptor")
fun providesAuthInterceptor(
@Named("authToken") authToken: String
): Interceptor {
return Interceptor { chain ->
var request = chain.request()
request = request.newBuilder()
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer $authToken")
.build()
val response = chain.proceed(request)
response.newBuilder().code(HttpURLConnection.HTTP_SEE_OTHER).build() // <--- This one
}
}
Тогда я получаю:
11-13 13:56:01.084 4867-4867/com.something.myapp D/GetSomethingUseCase: javax.net.ssl.SSLPeerUnverifiedException: Hostname <BASE IP> not verified:
certificate: sha256/someShaString
DN: CN=something.server.net,OU=Title,O=Company Something\, LLC,L=Some City,ST=SomeState,C=SomeCountryCode
subjectAltNames: [someServer1.com, someServer2.com, someServer3.com, someServer4.com, andSoOn.com]
Я неконечно, если это не связано или это действительно один шаг вперед, чтобы исправить это.
Любой совет или совет приветствуется.
Спасибо,