Этап 1: единый OkHttpClient ч.2 — 401 централизован через interceptor
- UnauthorizedInterceptor (401→UnauthorizedException) в NetworkFactory, флаг throwOnUnauthorized (по умолчанию true). - Удалены 75 ручных проверок if(code==401) throw в 16 репозиториях. - AuthVerifier (login-верификация) исключён: throwOnUnauthorized=false — 401 = 'неверный пароль' со своим сообщением, не session-expired. - Побочно: 401 теперь ловится единообразно ВЕЗДЕ (включая пути, что раньше забывали проверку). - +3 MockWebServer-теста (401 бросает; 200/403 проходят) — всего 18 тестов. Проверено: 18 тестов зелёные + assembleRelease BUILD SUCCESSFUL, APK 53 МБ.
This commit is contained in:
@@ -189,7 +189,6 @@ object CalDavClient {
|
||||
.put(ics.toRequestBody("text/calendar; charset=utf-8".toMediaType()))
|
||||
.build()
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
|
||||
error("CalDAV create event HTTP ${response.code}")
|
||||
}
|
||||
@@ -244,7 +243,6 @@ object CalDavClient {
|
||||
.delete()
|
||||
.build()
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 204 && response.code != 404) {
|
||||
error("CalDAV delete event HTTP ${response.code}")
|
||||
}
|
||||
@@ -278,7 +276,6 @@ object CalDavClient {
|
||||
builder.header("If-None-Match", "*")
|
||||
}
|
||||
client.newCall(builder.build()).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
|
||||
error("CalDAV put event HTTP ${response.code}")
|
||||
}
|
||||
@@ -314,7 +311,6 @@ object CalDavClient {
|
||||
.method("MKCALENDAR", body.toRequestBody("application/xml; charset=utf-8".toMediaType()))
|
||||
.build()
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
|
||||
error("CalDAV create calendar HTTP ${response.code}")
|
||||
}
|
||||
@@ -347,7 +343,6 @@ object CalDavClient {
|
||||
.method("MKCOL", body.toRequestBody("application/xml; charset=utf-8".toMediaType()))
|
||||
.build()
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
|
||||
error("CalDAV subscribe calendar HTTP ${response.code}")
|
||||
}
|
||||
@@ -405,7 +400,6 @@ object CalDavClient {
|
||||
.header("Destination", destinationHref)
|
||||
.build()
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
|
||||
error("CalDAV move HTTP ${response.code}")
|
||||
}
|
||||
@@ -644,7 +638,6 @@ object CalDavClient {
|
||||
.put(ics.toRequestBody("text/calendar; charset=utf-8".toMediaType()))
|
||||
.build()
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
|
||||
error("CalDAV create task HTTP ${response.code}")
|
||||
}
|
||||
@@ -691,7 +684,6 @@ object CalDavClient {
|
||||
.put(ics.toRequestBody("text/calendar; charset=utf-8".toMediaType()))
|
||||
.build()
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 204) {
|
||||
error("CalDAV update task HTTP ${response.code}")
|
||||
}
|
||||
@@ -705,7 +697,6 @@ object CalDavClient {
|
||||
.delete()
|
||||
.build()
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 204 && response.code != 404) {
|
||||
error("CalDAV delete task HTTP ${response.code}")
|
||||
}
|
||||
@@ -765,7 +756,6 @@ object CalDavClient {
|
||||
|
||||
private fun execute(client: OkHttpClient, req: Request): String {
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
val code = response.code
|
||||
if (code !in 200..299 && code != 207) {
|
||||
error("CalDAV error HTTP $code")
|
||||
|
||||
@@ -90,7 +90,6 @@ object CardDavClient {
|
||||
.put(vcard.toRequestBody("text/vcard; charset=utf-8".toMediaType()))
|
||||
.build()
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
|
||||
error("Не удалось создать контакт (HTTP ${response.code})")
|
||||
}
|
||||
@@ -163,7 +162,6 @@ object CardDavClient {
|
||||
.method("PROPFIND", body.toRequestBody("application/xml; charset=utf-8".toMediaType()))
|
||||
.build()
|
||||
client.newCall(req).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
val code = response.code
|
||||
if (code !in 200..299 && code != 207) {
|
||||
error("CardDAV error HTTP $code")
|
||||
|
||||
@@ -27,9 +27,6 @@ object DavClient {
|
||||
.method("MKCOL", null)
|
||||
.build()
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.code == 401) {
|
||||
throw UnauthorizedException()
|
||||
}
|
||||
if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
|
||||
error("DAV MKCOL HTTP ${response.code}")
|
||||
}
|
||||
@@ -46,9 +43,6 @@ object DavClient {
|
||||
.put(body)
|
||||
.build()
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.code == 401) {
|
||||
throw UnauthorizedException()
|
||||
}
|
||||
if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
|
||||
error("DAV upload HTTP ${response.code}")
|
||||
}
|
||||
@@ -76,9 +70,6 @@ object DavClient {
|
||||
.method("PROPFIND", body.toRequestBody("application/xml; charset=utf-8".toMediaType()))
|
||||
.build()
|
||||
return client.newCall(request).execute().use { response ->
|
||||
if (response.code == 401) {
|
||||
throw UnauthorizedException()
|
||||
}
|
||||
val code = response.code
|
||||
if (code !in 200..299 && code != 207) {
|
||||
error("DAV error HTTP $code")
|
||||
@@ -182,7 +173,6 @@ object DavClient {
|
||||
.delete()
|
||||
.build()
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 204) {
|
||||
error("DAV DELETE HTTP ${response.code}")
|
||||
}
|
||||
@@ -197,7 +187,6 @@ object DavClient {
|
||||
.header("Overwrite", "T")
|
||||
.build()
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 201 && response.code != 204) {
|
||||
error("DAV MOVE HTTP ${response.code}")
|
||||
}
|
||||
@@ -221,7 +210,6 @@ object DavClient {
|
||||
.method("PROPPATCH", body.toRequestBody("application/xml; charset=utf-8".toMediaType()))
|
||||
.build()
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (response.code !in 200..299 && response.code != 207) {
|
||||
error("DAV PROPPATCH HTTP ${response.code}")
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@ import java.util.concurrent.TimeUnit
|
||||
* - авторизованные варианты — через `base.newBuilder()` (шарят пул/диспатчер/кэш);
|
||||
* - экземпляры кэшируются по кредам+таймаутам → переиспользуются, соединения живут.
|
||||
*
|
||||
* Публичный API НЕ менялся — все вызывающие (`newAuthedClient*`) работают как прежде.
|
||||
* NB: 401→UnauthorizedException пока проверяется в репозиториях вручную; централизация
|
||||
* через interceptor — отдельный шаг (меняет семантику исключений).
|
||||
* Публичный API совместим — вызывающие (`newAuthedClient*`) работают как прежде.
|
||||
* 401→UnauthorizedException централизован через interceptor (`throwOnUnauthorized`, по умолч.
|
||||
* true) — ручные проверки в репозиториях убраны. Login-пути (AuthVerifier), где 401 = «неверный
|
||||
* пароль» со своим сообщением, передают `throwOnUnauthorized = false`.
|
||||
*/
|
||||
object NetworkFactory {
|
||||
@Volatile
|
||||
@@ -38,8 +39,9 @@ object NetworkFactory {
|
||||
trustAllCerts: Boolean = false,
|
||||
callTimeoutSeconds: Long = 30,
|
||||
readTimeoutSeconds: Long = 30,
|
||||
throwOnUnauthorized: Boolean = true,
|
||||
): OkHttpClient {
|
||||
val key = "$username|$appPassword|$trustAllCerts|$callTimeoutSeconds|$readTimeoutSeconds"
|
||||
val key = "$username|$appPassword|$trustAllCerts|$callTimeoutSeconds|$readTimeoutSeconds|$throwOnUnauthorized"
|
||||
return clients.getOrPut(key) {
|
||||
base.newBuilder() // общий пул/диспатчер/кэш базового клиента
|
||||
.callTimeout(callTimeoutSeconds, TimeUnit.SECONDS)
|
||||
@@ -47,6 +49,7 @@ object NetworkFactory {
|
||||
.readTimeout(readTimeoutSeconds, TimeUnit.SECONDS)
|
||||
.applyUnsafeSslIfNeeded(trustAllCerts)
|
||||
.addInterceptor(BasicAuthInterceptor(username, appPassword))
|
||||
.apply { if (throwOnUnauthorized) addInterceptor(UnauthorizedInterceptor) }
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ class NotificationsRepository {
|
||||
.header("Accept", "application/json")
|
||||
.build()
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (!response.isSuccessful || response.body == null) {
|
||||
error("Уведомления HTTP ${response.code}")
|
||||
}
|
||||
@@ -86,7 +85,6 @@ class NotificationsRepository {
|
||||
.header("Accept", "application/json")
|
||||
.build()
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.code == 401) throw UnauthorizedException()
|
||||
if (!response.isSuccessful) {
|
||||
error("Уведомления HTTP ${response.code}")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package ru.forbion.f7cloud.core.network
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* Единая точка обработки 401 для авторизованных запросов: бросает [UnauthorizedException]
|
||||
* (session expired → logout). Заменяет ~70 ручных `if (code == 401) throw ...` по репозиториям.
|
||||
*
|
||||
* Подключается в [NetworkFactory] при `throwOnUnauthorized = true` (по умолчанию). Login-пути
|
||||
* (проверка пароля при входе) НЕ используют этот interceptor — там 401 = «неверный пароль».
|
||||
*/
|
||||
object UnauthorizedInterceptor : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val response = chain.proceed(chain.request())
|
||||
if (response.code == 401) {
|
||||
response.close() // не течём телом — дальше по цепочке оно не читается
|
||||
throw UnauthorizedException()
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package ru.forbion.f7cloud.core.network
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
/** Централизованная обработка 401: interceptor бросает UnauthorizedException вместо ручных проверок. */
|
||||
class UnauthorizedInterceptorTest {
|
||||
private lateinit var server: MockWebServer
|
||||
|
||||
@Before fun setUp() { server = MockWebServer().apply { start() } }
|
||||
@After fun tearDown() { server.shutdown() }
|
||||
|
||||
private fun call(client: OkHttpClient) =
|
||||
client.newCall(Request.Builder().url(server.url("/x")).build()).execute()
|
||||
|
||||
@Test(expected = UnauthorizedException::class)
|
||||
fun `401 бросает UnauthorizedException`() {
|
||||
server.enqueue(MockResponse().setResponseCode(401))
|
||||
val client = OkHttpClient.Builder().addInterceptor(UnauthorizedInterceptor).build()
|
||||
call(client)
|
||||
}
|
||||
|
||||
@Test fun `не-401 проходит как обычно`() {
|
||||
server.enqueue(MockResponse().setResponseCode(200).setBody("ok"))
|
||||
val client = OkHttpClient.Builder().addInterceptor(UnauthorizedInterceptor).build()
|
||||
call(client).use { assertEquals(200, it.code) }
|
||||
}
|
||||
|
||||
@Test fun `403 не трактуется как Unauthorized`() {
|
||||
server.enqueue(MockResponse().setResponseCode(403))
|
||||
val client = OkHttpClient.Builder().addInterceptor(UnauthorizedInterceptor).build()
|
||||
call(client).use { assertEquals(403, it.code) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user