Skip to content

Commit 9d2d7c0

Browse files
authored
feat: implement cookie persistence in ApiDashboardPanel (#1198)
1 parent 80b68f5 commit 9d2d7c0

File tree

4 files changed

+151
-1
lines changed

4 files changed

+151
-1
lines changed

common-api/src/main/kotlin/com/itangcent/http/HttpCookieStore.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,18 @@ class BasicCookie : MutableCookie {
6262
* Compatible only.Obsolete.
6363
* @return comment
6464
*/
65+
@Deprecated("Obsolete")
6566
override fun getComment(): String? = comment
6667

68+
@Deprecated("Obsolete")
6769
override fun setComment(comment: String?) {
6870
this.comment = comment
6971
}
7072

7173
private var commentURL: String? = null
74+
@Deprecated("Obsolete")
7275
override fun getCommentURL(): String? = commentURL
76+
@Deprecated("Obsolete")
7377
override fun setCommentURL(commentURL: String?) {
7478
this.commentURL = commentURL
7579
}
@@ -112,7 +116,9 @@ class BasicCookie : MutableCookie {
112116
*
113117
* @return the version of the cookie.
114118
*/
119+
@Deprecated("Obsolete")
115120
override fun getVersion(): Int? = version
121+
@Deprecated("Obsolete")
116122
override fun setVersion(version: Int?) {
117123
this.version = version
118124
}
@@ -131,7 +137,9 @@ class BasicCookie : MutableCookie {
131137
}
132138

133139
private var ports: IntArray? = null
140+
@Deprecated("Obsolete")
134141
override fun getPorts(): IntArray? = ports
142+
@Deprecated("Obsolete")
135143
override fun setPorts(ports: IntArray?) {
136144
this.ports = ports
137145
}

idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/dashboard/ApiDashboardService.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.itangcent.common.logger.traceError
2020
import com.itangcent.common.model.*
2121
import com.itangcent.common.spi.Setup
2222
import com.itangcent.common.utils.safe
23+
import com.itangcent.http.CookieStore
2324
import com.itangcent.http.HttpResponse
2425
import com.itangcent.http.RequestUtils
2526
import com.itangcent.http.contentType
@@ -58,6 +59,7 @@ import com.itangcent.intellij.jvm.PsiClassHelper
5859
import com.itangcent.intellij.logger.Logger
5960
import com.itangcent.intellij.psi.PsiClassUtils
6061
import com.itangcent.intellij.util.FileType
62+
import com.itangcent.suv.http.CookiePersistenceHelper
6163
import com.itangcent.suv.http.HttpClientProvider
6264
import org.apache.http.entity.ContentType
6365
import java.util.*
@@ -97,9 +99,15 @@ class ApiDashboardService(private val project: Project) {
9799
@Inject
98100
private lateinit var httpContextCacheHelper: HttpContextCacheHelper
99101

102+
@Inject
103+
private lateinit var cookiePersistenceHelper: CookiePersistenceHelper
104+
100105
lateinit var actionContext: ActionContext
101106
private set
102107

108+
private val cookieStore: CookieStore
109+
get() = httpClientProvider.getHttpClient().cookieStore()
110+
103111
private val requestRawInfoBinderFactory: DbBeanBinderFactory<RequestRawInfo> by lazy {
104112
DbBeanBinderFactory(projectCacheRepository.getOrCreateFile(".api.dashboard.v1.0.db").path) { RequestRawInfo() }
105113
}
@@ -143,7 +151,7 @@ class ApiDashboardService(private val project: Project) {
143151
init {
144152
Setup.load(ApiDashboardService::class.java.classLoader)
145153
createNewActionContext()
146-
154+
147155
// Add project dispose listener
148156
project.messageBus.connect().subscribe(ProjectManager.TOPIC, object : ProjectManagerListener {
149157
override fun projectClosing(project: Project) {
@@ -167,6 +175,9 @@ class ApiDashboardService(private val project: Project) {
167175

168176
actionContext = builder.build()
169177
actionContext.init(this)
178+
actionContext.runAsync {
179+
cookiePersistenceHelper.loadCookiesInto(cookieStore)
180+
}
170181
}
171182

172183
fun ensureActionContextActive() {
@@ -441,6 +452,9 @@ class ApiDashboardService(private val project: Project) {
441452
}
442453
.call()
443454
logger.info("response status code: ${httpResponse.code()}")
455+
actionContext.runAsync {
456+
cookiePersistenceHelper.storeCookiesFrom(cookieStore)
457+
}
444458
return httpResponse
445459
}
446460

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.itangcent.suv.http
2+
3+
import com.google.inject.Inject
4+
import com.google.inject.Singleton
5+
import com.itangcent.common.logger.Log
6+
import com.itangcent.common.logger.traceError
7+
import com.itangcent.common.utils.GsonUtils
8+
import com.itangcent.http.BasicCookie
9+
import com.itangcent.http.CookieStore
10+
import com.itangcent.http.json
11+
import com.itangcent.idea.plugin.api.cache.ProjectCacheRepository
12+
13+
/**
14+
* A utility class that helps persist cookies to file and load them back.
15+
* This class provides methods to save and load cookies from a CookieStore to persistent storage.
16+
*
17+
* @author tangcent
18+
*/
19+
@Singleton
20+
class CookiePersistenceHelper {
21+
22+
@Inject
23+
private lateinit var projectCacheRepository: ProjectCacheRepository
24+
25+
companion object : Log() {
26+
private const val CACHE_FILE = ".cookies.v1.0.json"
27+
}
28+
29+
/**
30+
* Reads cookies from persistent storage and loads them into the provided CookieStore.
31+
* Any expired cookies in storage will be skipped.
32+
*
33+
* @param cookieStore the CookieStore to load cookies into
34+
*/
35+
fun loadCookiesInto(cookieStore: CookieStore) {
36+
try {
37+
val cookieFile = projectCacheRepository.getOrCreateFile(CACHE_FILE)
38+
val cookiesJson = cookieFile.readText()
39+
if (cookiesJson.isBlank()) return
40+
41+
val cookiesList = GsonUtils.fromJson<List<String>>(cookiesJson)
42+
cookiesList?.forEach { cookieJson ->
43+
val cookie = BasicCookie.fromJson(cookieJson)
44+
cookieStore.addCookie(cookie)
45+
}
46+
} catch (e: Exception) {
47+
// If file doesn't exist or there's an error reading it, just log and return
48+
LOG.traceError("cookies is invalid", e)
49+
return
50+
}
51+
}
52+
53+
/**
54+
* Stores all cookies from the provided CookieStore into persistent storage.
55+
* Only non-expired cookies will be saved.
56+
*
57+
* @param cookieStore the CookieStore to save cookies from
58+
*/
59+
fun storeCookiesFrom(cookieStore: CookieStore) {
60+
val cookieFile = projectCacheRepository.getOrCreateFile(CACHE_FILE)
61+
val cookies = cookieStore.cookies()
62+
val cookiesJson = cookies.map { it.json() }
63+
cookieFile.writeText(GsonUtils.toJson(cookiesJson))
64+
}
65+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.itangcent.suv.http
2+
3+
import com.google.inject.Inject
4+
import com.itangcent.http.ApacheHttpClient
5+
import com.itangcent.http.BasicCookie
6+
import com.itangcent.intellij.context.ActionContext
7+
import com.itangcent.mock.AdvancedContextTest
8+
import org.junit.jupiter.api.Assertions.assertEquals
9+
import org.junit.jupiter.api.Test
10+
import org.junit.jupiter.api.assertDoesNotThrow
11+
12+
/**
13+
* Test case of [CookiePersistenceHelper]
14+
*/
15+
class CookiePersistenceHelperTest : AdvancedContextTest() {
16+
17+
@Inject
18+
private lateinit var cookiePersistenceHelper: CookiePersistenceHelper
19+
20+
private val apacheHttpClient = ApacheHttpClient()
21+
22+
override fun afterBind(actionContext: ActionContext) {
23+
actionContext.cache("project_path", tempDir.toString())
24+
}
25+
26+
@Test
27+
fun persistenceCookies() {
28+
val cookieStore = apacheHttpClient.cookieStore()
29+
30+
assertDoesNotThrow {
31+
cookiePersistenceHelper.loadCookiesInto(cookieStore)
32+
}
33+
34+
assertDoesNotThrow {
35+
cookiePersistenceHelper.storeCookiesFrom(cookieStore)
36+
}
37+
38+
// add a cookie
39+
val cookie = BasicCookie().apply {
40+
setName("testCookie")
41+
setValue("testValue")
42+
setDomain("test.com")
43+
setPath("/")
44+
setExpiryDate(System.currentTimeMillis() + 86400000) // expires in 1 day
45+
}
46+
cookieStore.addCookie(cookie)
47+
48+
// store cookies
49+
cookiePersistenceHelper.storeCookiesFrom(cookieStore)
50+
51+
// clear cookies then load cookies
52+
cookieStore.clear()
53+
cookiePersistenceHelper.loadCookiesInto(cookieStore)
54+
55+
// check if cookie is loaded
56+
val readCookie = cookieStore.cookies().first { it.getName() == "testCookie" }
57+
assertEquals(cookie.getName(), readCookie.getName())
58+
assertEquals(cookie.getValue(), readCookie.getValue())
59+
assertEquals(cookie.getDomain(), readCookie.getDomain())
60+
assertEquals(cookie.getPath(), readCookie.getPath())
61+
assertEquals(cookie.getExpiryDate(), readCookie.getExpiryDate())
62+
}
63+
}

0 commit comments

Comments
 (0)