From 39727f87f916092300ea7cdb6b6b4aceade8667a Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 19 May 2026 00:04:43 +0800 Subject: [PATCH] fix: preserve webbricks params for startup ui requests --- .../kotlin/com/bricks/mp/core/BricksHttp.kt | 40 ++++++++++++------- test/sageclient/README.md | 2 +- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/shared/src/commonMain/kotlin/com/bricks/mp/core/BricksHttp.kt b/shared/src/commonMain/kotlin/com/bricks/mp/core/BricksHttp.kt index c4758bb..6dd0066 100644 --- a/shared/src/commonMain/kotlin/com/bricks/mp/core/BricksHttp.kt +++ b/shared/src/commonMain/kotlin/com/bricks/mp/core/BricksHttp.kt @@ -87,8 +87,8 @@ class BricksHttp(private val context: BricksContext? = null) { authToken: String = "" ): JsonObject { val requestParams = params.withBackendContextIfNeeded(url) - val response = client.post(url) { - appendQueryParameters(requestParams) + val requestUrl = url.withQueryParameters(requestParams) + val response = client.post(requestUrl) { contentType(ContentType.Application.Json) setBody(body) if (authToken.isNotEmpty()) { @@ -96,7 +96,7 @@ class BricksHttp(private val context: BricksContext? = null) { } } val text = response.bodyAsText() - response.throwIfHttpError(text, url) + response.throwIfHttpError(text, requestUrl) return parseJsonObjectOrError(text) } @@ -109,11 +109,11 @@ class BricksHttp(private val context: BricksContext? = null) { authToken: String = "" ): String { val requestParams = emptyMap().withBackendContextIfNeeded(url) + val requestUrl = url.withQueryParameters(requestParams) val formBody = form.entries.joinToString("&") { (k, v) -> "${k.encodeURLParameter()}=${v.encodeURLParameter()}" } - val response = client.post(url) { - appendQueryParameters(requestParams) + val response = client.post(requestUrl) { contentType(ContentType.Application.FormUrlEncoded) setBody(formBody) if (authToken.isNotEmpty()) { @@ -121,7 +121,7 @@ class BricksHttp(private val context: BricksContext? = null) { } } val text = response.bodyAsText() - response.throwIfHttpError(text, url) + response.throwIfHttpError(text, requestUrl) return text } @@ -134,14 +134,14 @@ class BricksHttp(private val context: BricksContext? = null) { authToken: String = "" ): String { val requestParams = params.withBackendContextIfNeeded(url) - val response = client.get(url) { - appendQueryParameters(requestParams) + val requestUrl = url.withQueryParameters(requestParams) + val response = client.get(requestUrl) { if (authToken.isNotEmpty()) { header(HttpHeaders.Authorization, "Bearer $authToken") } } val text = response.bodyAsText() - response.throwIfHttpError(text, url) + response.throwIfHttpError(text, requestUrl) return text } @@ -175,13 +175,23 @@ class BricksHttp(private val context: BricksContext? = null) { private fun Map.withBackendContextIfNeeded(url: String): Map = if (url.isWebBricksBackendResource()) withWebBricksRequestContext(this, requestContext) else this - private fun HttpRequestBuilder.appendQueryParameters(params: Map) { - url { - params.forEach { (k, v) -> - parameters.remove(k) - parameters.append(k, v) - } + private fun String.withQueryParameters(params: Map): String { + if (params.isEmpty()) return this + val fragmentIndex = indexOf('#') + val baseAndQuery = if (fragmentIndex >= 0) substring(0, fragmentIndex) else this + val fragment = if (fragmentIndex >= 0) substring(fragmentIndex) else "" + val path = baseAndQuery.substringBefore('?') + val existingQuery = baseAndQuery.substringAfter('?', missingDelimiterValue = "") + val encodedOverrideKeys = params.keys.map { it.encodeURLParameter() }.toSet() + val preservedQuery = existingQuery + .split('&') + .filter { it.isNotBlank() } + .filter { entry -> entry.substringBefore('=').substringBefore('&') !in encodedOverrideKeys } + val appendedQuery = params.entries.map { (key, value) -> + "${key.encodeURLParameter()}=${value.encodeURLParameter()}" } + val query = (preservedQuery + appendedQuery).joinToString("&") + return if (query.isBlank()) "$path$fragment" else "$path?$query$fragment" } private suspend fun HttpResponse.throwIfHttpError(body: String, requestUrl: String) { diff --git a/test/sageclient/README.md b/test/sageclient/README.md index bce148c..04dad1e 100644 --- a/test/sageclient/README.md +++ b/test/sageclient/README.md @@ -57,7 +57,7 @@ The packaged macOS app also accepts the URL as its first argument: open build/compose/binaries/main/app/sageclient.app --args https://ai.atvoe.com/center.ui ``` -If the argument is an absolute `http://` or `https://` URL, `sageclient` uses the URL origin as `baseUrl` and loads the path/query part as the initial Bricks UI. If the argument is a relative path such as `/center.ui`, it uses `sage.baseUrl` and loads that path. +If the argument is an absolute `http://` or `https://` URL, `sageclient` uses the URL origin as `baseUrl` and loads the path/query part as the initial Bricks UI. If the argument is a relative path such as `/center.ui`, it uses `sage.baseUrl` and loads that path. The actual HTTP request is still issued through `BricksHttp`, so startup URLs ending in `.ui` / `.dspy` are requested with `_webbricks_=1`, `_width`, `_height`, `_is_mobile` and `_lang`; they must not be fetched as raw HTML/template pages. Optional properties: