Skip to content

Commit ca5dcd4

Browse files
committed
Server endpoint matching custom paths
1 parent e951602 commit ca5dcd4

File tree

7 files changed

+74
-11
lines changed

7 files changed

+74
-11
lines changed

bridge-settings/src/main/java/com/penumbraos/bridge_settings/SettingsProvider.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.penumbraos.bridge_settings
22

3+
import android.os.IBinder
34
import android.util.Log
45
import com.penumbraos.bridge.ISettingsProvider
56
import com.penumbraos.bridge.callback.IHttpEndpointCallback
@@ -343,6 +344,12 @@ class SettingsProvider(private val settingsRegistry: SettingsRegistry) : ISettin
343344
Log.e(TAG, "Cannot register HTTP endpoint - web server not initialized")
344345
false
345346
} else {
347+
callback.asBinder().linkToDeath(object : IBinder.DeathRecipient {
348+
override fun binderDied() {
349+
server.unregisterEndpoint(providerId, path, method)
350+
}
351+
}, 0)
352+
346353
val success = server.registerEndpoint(
347354
providerId,
348355
path,

bridge-settings/src/main/java/com/penumbraos/bridge_settings/SettingsWebServer.kt

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -280,13 +280,24 @@ class SettingsWebServer(
280280
}
281281

282282
intercept(ApplicationCallPipeline.Call) {
283-
val fullPath =
284-
call.request.uri.substringBefore('?') // Remove query params from path
283+
val fullPath = call.request.uri.substringBefore('?')
285284
val method = call.request.httpMethod.value
286-
val endpointKey = "${method}:$fullPath"
285+
286+
var matchedEndpoint: RegisteredEndpoint? = null
287+
var pathParams: Map<String, String>? = null
288+
289+
for (endpoint in registeredEndpoints.values) {
290+
if (endpoint.method.equals(method, ignoreCase = true)) {
291+
val match = endpoint.matchesPath(fullPath)
292+
if (match != null) {
293+
matchedEndpoint = endpoint
294+
pathParams = match
295+
break
296+
}
297+
}
298+
}
287299

288-
val endpoint = registeredEndpoints[endpointKey]
289-
if (endpoint != null) {
300+
if (matchedEndpoint != null && pathParams != null) {
290301
try {
291302
val headers = call.request.headers.toMap()
292303
.mapValues { it.value.firstOrNull() ?: "" }
@@ -303,10 +314,11 @@ class SettingsWebServer(
303314
method = method,
304315
headers = headers,
305316
queryParams = queryParams,
317+
pathParams = pathParams,
306318
body = body
307319
)
308320

309-
val response = endpoint.callback.handle(request)
321+
val response = matchedEndpoint.callback.handle(request)
310322

311323
response.headers.forEach { (key, value) ->
312324
call.response.headers.append(key, value)
@@ -320,7 +332,7 @@ class SettingsWebServer(
320332
)
321333
return@intercept finish()
322334
} catch (e: Exception) {
323-
Log.e(TAG, "Error handling dynamic endpoint $endpointKey", e)
335+
Log.e(TAG, "Error handling dynamic endpoint ${matchedEndpoint.path}", e)
324336
call.respond(
325337
HttpStatusCode.InternalServerError,
326338
mapOf("error" to "Internal server error")
@@ -335,7 +347,7 @@ class SettingsWebServer(
335347
webSocket("/ws/settings") {
336348
handleWebSocketConnection(this)
337349
}
338-
350+
339351
// REST API endpoints
340352
get("/api/settings") {
341353
try {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.penumbraos.bridge_settings.server
2+
3+
object PathParser {
4+
fun matchPath(pattern: String, actualPath: String): Map<String, String>? {
5+
val patternSegments = pattern.split('/').filter { it.isNotEmpty() }
6+
val actualSegments = actualPath.split('/').filter { it.isNotEmpty() }
7+
8+
if (patternSegments.size != actualSegments.size) {
9+
return null
10+
}
11+
12+
val pathParams = mutableMapOf<String, String>()
13+
14+
for (i in patternSegments.indices) {
15+
val patternSegment = patternSegments[i]
16+
val actualSegment = actualSegments[i]
17+
18+
if (patternSegment.startsWith('{') && patternSegment.endsWith('}')) {
19+
val variableName = patternSegment.substring(1, patternSegment.length - 1)
20+
pathParams[variableName] = actualSegment
21+
} else if (patternSegment != actualSegment) {
22+
return null
23+
}
24+
}
25+
26+
return pathParams
27+
}
28+
}

bridge-settings/src/main/java/com/penumbraos/bridge_settings/server/types.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ data class EndpointRequest(
1111
val method: String,
1212
val headers: Map<String, String>,
1313
val queryParams: Map<String, String>,
14+
val pathParams: Map<String, String>,
1415
val body: String?
1516
)
1617

@@ -35,7 +36,11 @@ data class RegisteredEndpoint(
3536
val method: String,
3637
val callback: EndpointCallback,
3738
val providerId: String
38-
)
39+
) {
40+
fun matchesPath(requestPath: String): Map<String, String>? {
41+
return PathParser.matchPath(this.path, requestPath)
42+
}
43+
}
3944

4045
class AidlEndpointCallback(
4146
private val aidlCallback: IHttpEndpointCallback
@@ -64,6 +69,7 @@ class AidlEndpointCallback(
6469
aidlCallback.onHttpRequest(
6570
request.path,
6671
request.method,
72+
request.pathParams,
6773
request.headers,
6874
request.queryParams,
6975
request.body,

bridge-shared/aidl/com/penumbraos/bridge/callback/IHttpEndpointCallback.aidl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ package com.penumbraos.bridge.callback;
33
import com.penumbraos.bridge.callback.IHttpResponseCallback;
44

55
interface IHttpEndpointCallback {
6-
void onHttpRequest(String path, String method, in Map headers, in Map queryParams, String body, IHttpResponseCallback responseCallback);
6+
void onHttpRequest(String path, String method, in Map pathParams, in Map headers, in Map queryParams, String body, IHttpResponseCallback responseCallback);
77
}

sdk/src/main/java/com/penumbraos/sdk/api/SettingsClient.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ class SettingsClient(private val settingsProvider: ISettingsProvider) {
182182
override fun onHttpRequest(
183183
path: String,
184184
method: String,
185+
pathParams: MutableMap<Any?, Any?>,
185186
headers: MutableMap<Any?, Any?>?,
186187
queryParams: MutableMap<Any?, Any?>?,
187188
body: String?,
@@ -193,7 +194,15 @@ class SettingsClient(private val settingsProvider: ISettingsProvider) {
193194
val queryMap = queryParams?.mapKeys { it.key.toString() }
194195
?.mapValues { it.value.toString() } ?: emptyMap()
195196

196-
val request = HttpRequest(path, method, headerMap, queryMap, body)
197+
val request =
198+
HttpRequest(
199+
path,
200+
method,
201+
pathParams as Map<String, String>,
202+
headerMap,
203+
queryMap,
204+
body
205+
)
197206

198207
scope.launch {
199208
try {

sdk/src/main/java/com/penumbraos/sdk/api/types/settingsTypes.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.penumbraos.sdk.api.types
33
data class HttpRequest(
44
val path: String,
55
val method: String,
6+
val pathParams: Map<String, String>,
67
val headers: Map<String, String>,
78
val queryParams: Map<String, String>,
89
val body: String?

0 commit comments

Comments
 (0)