From b5e846fe3c2e08e33996c9f07d27c88d07d51d84 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Tue, 19 Apr 2022 19:09:09 +0200 Subject: [PATCH 01/35] Build and Call controller actions using URL from _publicApi --- .../kuzzle/sdk/controllers/AuthController.kt | 6 +- .../exceptions/KuzzleExceptionCode.kt | 8 +- .../exceptions/MissingActionException.kt | 5 + .../exceptions/MissingControllerException.kt | 5 + .../exceptions/MissingURLParamException.kt | 8 + .../exceptions/URLNotFoundException.kt | 5 + .../sdk/coreClasses/http/HttpRequest.kt | 10 + .../io/kuzzle/sdk/coreClasses/http/Route.kt | 168 ++++++++++++++ .../kuzzle/sdk/events/LogoutAttemptEvent.kt | 3 + .../kotlin/io/kuzzle/sdk/protocol/Http.kt | 210 +++++++++++++++++- 10 files changed, 417 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingActionException.kt create mode 100644 src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt create mode 100644 src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt create mode 100644 src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt create mode 100644 src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt create mode 100644 src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt create mode 100644 src/main/kotlin/io/kuzzle/sdk/events/LogoutAttemptEvent.kt diff --git a/src/main/kotlin/io/kuzzle/sdk/controllers/AuthController.kt b/src/main/kotlin/io/kuzzle/sdk/controllers/AuthController.kt index 985c849e..6287ecda 100644 --- a/src/main/kotlin/io/kuzzle/sdk/controllers/AuthController.kt +++ b/src/main/kotlin/io/kuzzle/sdk/controllers/AuthController.kt @@ -6,6 +6,7 @@ import io.kuzzle.sdk.coreClasses.lang.Lang import io.kuzzle.sdk.coreClasses.maps.KuzzleMap import io.kuzzle.sdk.coreClasses.responses.Response import io.kuzzle.sdk.events.LoginAttemptEvent +import io.kuzzle.sdk.events.LogoutAttemptEvent import java.util.concurrent.CompletableFuture class AuthController(kuzzle: Kuzzle) : BaseController(kuzzle) { @@ -164,7 +165,10 @@ class AuthController(kuzzle: Kuzzle) : BaseController(kuzzle) { put("controller", "auth") put("action", "logout") } - return kuzzle.query(query) + return kuzzle.query(query).thenApplyAsync { response -> + kuzzle.protocol.trigger(LogoutAttemptEvent()) + response + } } @JvmOverloads diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/KuzzleExceptionCode.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/KuzzleExceptionCode.kt index 959efa62..1ecf4786 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/KuzzleExceptionCode.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/KuzzleExceptionCode.kt @@ -1,7 +1,13 @@ package io.kuzzle.sdk.coreClasses.exceptions enum class KuzzleExceptionCode { - MISSING_REQUESTID(0, "Missing field requestId"), MISSING_QUERY(400, "You must provide a query"), NOT_CONNECTED(500, "Not connected."), CONNECTION_LOST(500, "Connection lost"), WRONG_VOLATILE_TYPE( + MISSING_REQUESTID(0, "Missing field requestId"), + MISSING_CONTROLLER(0, "Missing field controller"), + MISSING_ACTION(0, "Missing field action"), + MISSING_QUERY(400, "You must provide a query"), + NOT_CONNECTED(500, "Not connected."), + CONNECTION_LOST(500, "Connection lost"), + WRONG_VOLATILE_TYPE( 400, "Volatile data must be a Map" ); diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingActionException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingActionException.kt new file mode 100644 index 00000000..26b1704c --- /dev/null +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingActionException.kt @@ -0,0 +1,5 @@ +package io.kuzzle.sdk.coreClasses.exceptions + +open class MissingActionException : KuzzleException { + constructor() : super(KuzzleExceptionCode.MISSING_ACTION) +} diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt new file mode 100644 index 00000000..994bc917 --- /dev/null +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt @@ -0,0 +1,5 @@ +package io.kuzzle.sdk.coreClasses.exceptions + +open class MissingControllerException : KuzzleException { + constructor() : super(KuzzleExceptionCode.MISSING_CONTROLLER) +} diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt new file mode 100644 index 00000000..7aae2e3b --- /dev/null +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt @@ -0,0 +1,8 @@ +package io.kuzzle.sdk.coreClasses.exceptions + +/** + * Thrown when attempting to interact with the network while not connected. + */ +class MissingURLParamException : KuzzleException { + constructor(templateParam: String, baseURL: String) : super("Missing URL Param ${templateParam} in ${baseURL}", 0) +} diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt new file mode 100644 index 00000000..23c20ad7 --- /dev/null +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt @@ -0,0 +1,5 @@ +package io.kuzzle.sdk.coreClasses.exceptions + +class URLNotFoundException : KuzzleException { + constructor(controller: String, action: String) : super("No URL found for \"$controller:$action\".", 400) +} diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt new file mode 100644 index 00000000..1aa44e58 --- /dev/null +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt @@ -0,0 +1,10 @@ +package io.kuzzle.sdk.coreClasses.http + +import io.kuzzle.sdk.coreClasses.maps.KuzzleMap + +data class HttpRequest( + val verb: String, + val url: String, + val body: String, + val headers: KuzzleMap, +) \ No newline at end of file diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt new file mode 100644 index 00000000..0fbd656f --- /dev/null +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -0,0 +1,168 @@ +package io.kuzzle.sdk.coreClasses.http + +import io.kuzzle.sdk.coreClasses.exceptions.MissingURLParamException +import io.kuzzle.sdk.coreClasses.json.JsonSerializer +import io.kuzzle.sdk.coreClasses.maps.KuzzleMap +import java.net.URLEncoder + +enum class PartType { + STATIC, + TEMPLATE +} + +private class RoutePart { + val type: PartType + val value: String + + constructor(type: PartType, value: String) { + this.type = type + this.value = value + } +} + +class Route { + private val partType: PartType + private val routeParts: ArrayList + private var staticURL: String = "" + private var baseURL: String + private var verb: String + + private constructor(verb: String, baseUrl: String, routeParts: ArrayList) { + this.verb = verb.uppercase() + this.baseURL = baseUrl + this.routeParts = routeParts + val hasOnePartTemplated = routeParts.indexOfFirst { it.type == PartType.TEMPLATE } > -1 + this.partType = if (hasOnePartTemplated) PartType.TEMPLATE else PartType.STATIC + if (! hasOnePartTemplated) { + staticURL = routeParts.joinToString("/") + } + } + + /** + * Given map containing the properties of the request, the buildRequest method + * will create an instance of HttpRequest which contains the information needed to make + * the request later on. + * + * The method has to: + * - Generate the URL using the properties of the request if there are part of the URL that are templated + * - Generate the query string + * - Generate the list of headers + */ + fun buildRequest(request: KuzzleMap): HttpRequest { + val headers = request.optMap("headers", KuzzleMap()) + + val queryArgs = KuzzleMap() + for (key: String? in request.keys) { + // Skip if a key or value is null + if (key == null || request[key] == null) { + continue + } + + when (key) { + "jwt" -> headers["Authorization"] = "Bearer ${request["jwt"]}" + "volatile" -> { + headers["x-kuzzle-volatile"] = request["volatile"] + } + "headers" -> continue + "body" -> { + if (verb == "GET") { + queryArgs.putAll(request.optMap("body", KuzzleMap())) + } + } + else -> { + queryArgs[key] = request[key] + } + } + } + + /** + * Build the query string + */ + val queryString: String = queryArgs.keys.filterNotNull().joinToString("&") { + val encodedKey = URLEncoder.encode(it, "utf-8") + + if (queryArgs.isArrayList(it)) { + val value = queryArgs.getArrayList(it)!!.joinToString(",") + "${encodedKey}=${URLEncoder.encode(value, "utf-8")}" + } else if (queryArgs.optBoolean(it, false) == true) { + encodedKey + } else { + val value = if (queryArgs.isMap(it)) JsonSerializer.serialize(queryArgs.getMap(it)!!) else queryArgs[it].toString() + "${encodedKey}=${URLEncoder.encode(value, "utf-8")}" + } + } + + + /** + * If the partType is STATIC it means that there is no template in the url + * So we can use the staticURL instead of building the URL + */ + if (partType == PartType.STATIC) { + return HttpRequest( + verb, + if (queryArgs.isEmpty()) staticURL else "$staticURL?$queryString", + if (request["body"] != null) JsonSerializer.serialize(request["body"]!!) else "", + headers + ) + } + + /** + * Each STATIC part will be appended to the string builder without further processing, + * TEMPLATE parts however will be used to find their corresponding value in the request map + * and the retrieve value will be put in the URL + */ + val urlBuilder = StringBuilder() + for (routePart: RoutePart in routeParts) { + if (routePart.type == PartType.STATIC) { + urlBuilder.append(routePart.value) + } else { + if (! request.containsKey(routePart.value) || request[routePart.value] == null) { + throw MissingURLParamException(routePart.value, baseURL) + } + val serializedValue = request[routePart.value]!!.toString() + urlBuilder.append(URLEncoder.encode(serializedValue, "utf-8")) + } + } + return HttpRequest( + verb, + if (queryArgs.isEmpty()) urlBuilder.toString() else "$urlBuilder?$queryString", + if (request["body"] != null) JsonSerializer.serialize(request["body"]!!) else "", + headers + ) + } + + companion object { + /** + * Parse Kuzzle url with the format /:index/:collection + * Split each section of the url and tag them either as STATIC or TEMPLATE + * Static parts are static as the name suggest, they can be combined without further processing + * Template parts however need to be replaced with the appropriate value + * Each sections starting with : is considered to be a template part + */ + fun parse(verb: String, url: String): Route { + val routeParts = ArrayList() + + // Sanitize path in case of double / + val parts = url.split('/').filter { it.isNotEmpty() } + + routeParts.add(RoutePart(PartType.STATIC, "/")) + + for (part: String in parts) { + if (part.startsWith(":")) { + routeParts.add(RoutePart(PartType.TEMPLATE, part.substring(1))) + } else { + routeParts.add(RoutePart(PartType.STATIC, part)) + } + // Add static / to separate each parts + routeParts.add(RoutePart(PartType.STATIC, "/")) + } + + // Remove trailing / if there was none in the url + if (! url.endsWith("/")) { + routeParts.removeLast() + } + + return Route(verb, url, routeParts) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/kuzzle/sdk/events/LogoutAttemptEvent.kt b/src/main/kotlin/io/kuzzle/sdk/events/LogoutAttemptEvent.kt new file mode 100644 index 00000000..c5951acf --- /dev/null +++ b/src/main/kotlin/io/kuzzle/sdk/events/LogoutAttemptEvent.kt @@ -0,0 +1,3 @@ +package io.kuzzle.sdk.events + +class LogoutAttemptEvent diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index b9dc570f..721611cc 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -4,17 +4,25 @@ import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.request.* import io.ktor.client.statement.* +import io.ktor.http.* +import io.kuzzle.sdk.coreClasses.exceptions.* +import io.kuzzle.sdk.coreClasses.http.HttpRequest +import io.kuzzle.sdk.coreClasses.http.Route import io.kuzzle.sdk.coreClasses.json.JsonSerializer -import io.kuzzle.sdk.events.MessageReceivedEvent -import io.kuzzle.sdk.events.NetworkStateChangeEvent -import io.kuzzle.sdk.events.RequestErrorEvent +import io.kuzzle.sdk.coreClasses.maps.KuzzleMap +import io.kuzzle.sdk.events.* import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import java.util.concurrent.CompletableFuture +data class ControllerActionRoute(val verb: String, val path: String) + open class Http : AbstractProtocol { override var state: ProtocolState = ProtocolState.CLOSE private var uri: String + private var useBuiltRoutes = false + private val routes = HashMap() + private var buildOnFirstTry = false @JvmOverloads constructor( @@ -23,12 +31,114 @@ open class Http : AbstractProtocol { isSsl: Boolean = false ) { if (!isSsl) { - this.uri = "http://$host:$port/_query" + this.uri = "http://$host:$port" } else { - this.uri = "https://$host:$port/_query" + this.uri = "https://$host:$port" + } + super.addListener(::onLoginAttempt) + } + + private fun onLoginAttempt(event: LoginAttemptEvent) { + if (! buildOnFirstTry && event.success) { + tryToBuildRoutes() + } + } + + private fun onLogoutAttempt(event: LogoutAttemptEvent) { + if (! buildOnFirstTry) { + useBuiltRoutes = false + routes.clear() + } + } + + private fun findBestRoute(controller: String, action: String, httpRoutes: List): ControllerActionRoute { + if (httpRoutes.size == 1) { + return httpRoutes[0] + } + + if (controller.lowercase() == "document" && action.lowercase() == "search") { + return httpRoutes.find { + it.verb == "POST" + } ?: httpRoutes[0] + } + + var length = httpRoutes[0].path.length + var selectedRoute = httpRoutes[0] + var shortestRoute = httpRoutes[0] + var sameLength = true + for (route: ControllerActionRoute in httpRoutes) { + if (route.path.length < length) { + length = route.path.length + shortestRoute = route + sameLength = false + } + + if (route.verb.uppercase() == "GET") { + selectedRoute = route + } + } + return if (sameLength) selectedRoute else shortestRoute + } + + private fun buildRoutes(routes: Map) { + for (controllerEntry in routes) { + for (actionEntry in controllerEntry.value as Map) { + var map = actionEntry.value as Map + + if (! map.containsKey("controller") || ! map.containsKey("action") || ! map.containsKey("http")) { + continue + } + + var controller = map["controller"] as String + var action = map["action"] as String + + var httpRoutes = map["http"] as List> + + if (httpRoutes.isNotEmpty()) { + val route = findBestRoute(controller, action, httpRoutes.map { + ControllerActionRoute(it["verb"] as String, it["path"] as String) + }) + + this.routes["$controller:$action"] = Route.parse(route.verb, route.path) + } + } } } + private fun tryToBuildRoutes() { + val wait = CompletableFuture() + + GlobalScope.launch { + var client = HttpClient() { + expectSuccess = false + } + try { + var response: HttpResponse = client.get("$uri/_publicApi") { + this.header("content-type", "application/json") + this.body = "{}" + } + + if (response.status.value != 200) { + wait.complete(null) + return@launch + } + + val responseJson = JsonSerializer.deserialize(response.receive()) as Map + + buildRoutes(responseJson["result"] as Map) + useBuiltRoutes = true + + } catch (e: Exception) { + e.printStackTrace() + } finally { + client.close() + wait.complete(null) + } + } + + wait.get() + } + override fun connect() { if (this.state != ProtocolState.CLOSE) { return @@ -41,7 +151,7 @@ open class Http : AbstractProtocol { expectSuccess = false } try { - var response: HttpResponse = client.post(uri) { + var response: HttpResponse = client.post("$uri/_query") { this.header("content-type", "application/json") this.body = "{}" } @@ -58,6 +168,10 @@ open class Http : AbstractProtocol { } wait.get() + tryToBuildRoutes() + if (useBuiltRoutes) { + buildOnFirstTry = true // Built on first try + } this.state = ProtocolState.OPEN trigger(NetworkStateChangeEvent(ProtocolState.OPEN)) @@ -72,19 +186,30 @@ open class Http : AbstractProtocol { trigger(NetworkStateChangeEvent(ProtocolState.CLOSE)) } - override fun send(payload: Map) { + /** + * Make a request to Kuzzle using the _query endpoint + */ + private fun query(payload: Map) { GlobalScope.launch { // Launch HTTP Request inside a coroutine to be non-blocking var client = HttpClient() { expectSuccess = false } try { - var response: HttpResponse = client.post(uri) { + var response: HttpResponse = client.post("$uri/_query") { + if (payload["headers"] != null && payload["headers"] is Map<*, *>) { + for (entry in payload["headers"] as Map) { + if (entry.key == null || entry.value == null) { + continue + } + this.header(entry.key.toString(), JsonSerializer.serialize(entry.value!!)) + } + } this.header("content-type", "application/json") if (payload["jwt"] != null) { this.header("Authorization", "Bearer ${payload["jwt"]}") } if (payload["volatile"] != null) { - this.header("Volatile", "${payload["volatile"]}") + this.header("x-kuzzle-volatile", JsonSerializer.serialize(payload["volatile"]!!)) } this.body = JsonSerializer.serialize(payload) } @@ -97,4 +222,71 @@ open class Http : AbstractProtocol { } } } + + private fun queryActionEndpoint(payload: Map, requestInfo: io.kuzzle.sdk.coreClasses.http.HttpRequest) { + GlobalScope.launch { // Launch HTTP Request inside a coroutine to be non-blocking + var client = HttpClient() { + expectSuccess = false + } + try { + var response: HttpResponse = client.request("$uri${requestInfo.url}") { + this.method = HttpMethod.parse(requestInfo.verb) + for (entry in requestInfo.headers) { + if (entry.key == null || entry.value == null) { + continue + } + + this.header(entry.key.toString(), JsonSerializer.serialize(entry.value!!)) + } + this.header("content-type", "application/json") + this.body = requestInfo.body + } + // trigger messageReceived + super.trigger(MessageReceivedEvent(response.receive(), payload["requestId"] as String?)) + } catch (e: Exception) { + super.trigger(RequestErrorEvent(e, payload["requestId"] as String?)) + } finally { + client.close() + } + } + } + + + override fun send(payload: Map) { + if (! payload.containsKey("controller") || payload["controller"] !is String) { + super.trigger(RequestErrorEvent(MissingControllerException(), payload["requestId"] as String?)) + return + } + + if (! payload.containsKey("action") || payload["action"] !is String) { + super.trigger(RequestErrorEvent(MissingActionException(), payload["requestId"] as String?)) + return + } + + if (! useBuiltRoutes) { + query(payload) + return + } + + val controller = payload["controller"] as String + val action = payload["action"] as String + + val route = this.routes["$controller:$action"] + + if (route == null) { + super.trigger(RequestErrorEvent(URLNotFoundException(controller, action), payload["requestId"] as String?)) + return + } + + try { + val requestInfo = route.buildRequest(KuzzleMap.from(payload)) + queryActionEndpoint(payload, requestInfo) + } catch (e: MissingURLParamException) { + /** + * Fallback if we could not find a matching route with the given parameters + * Try to make the request using directly using /_query endpoint + */ + query(payload) + } + } } From e7b8a5ddf5f14b0062cc6bb35a2277bee9070468 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 09:36:12 +0200 Subject: [PATCH 02/35] use built HTTP Endpoint --- src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 721611cc..8253cf5d 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -22,7 +22,6 @@ open class Http : AbstractProtocol { private var uri: String private var useBuiltRoutes = false private val routes = HashMap() - private var buildOnFirstTry = false @JvmOverloads constructor( @@ -39,18 +38,11 @@ open class Http : AbstractProtocol { } private fun onLoginAttempt(event: LoginAttemptEvent) { - if (! buildOnFirstTry && event.success) { + if (! useBuiltRoutes && event.success) { tryToBuildRoutes() } } - private fun onLogoutAttempt(event: LogoutAttemptEvent) { - if (! buildOnFirstTry) { - useBuiltRoutes = false - routes.clear() - } - } - private fun findBestRoute(controller: String, action: String, httpRoutes: List): ControllerActionRoute { if (httpRoutes.size == 1) { return httpRoutes[0] @@ -169,9 +161,6 @@ open class Http : AbstractProtocol { wait.get() tryToBuildRoutes() - if (useBuiltRoutes) { - buildOnFirstTry = true // Built on first try - } this.state = ProtocolState.OPEN trigger(NetworkStateChangeEvent(ProtocolState.OPEN)) From cb1aa32bddfa105cd57f1280c3d21714f4d327ac Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 09:46:50 +0200 Subject: [PATCH 03/35] lint --- .../exceptions/MissingURLParamException.kt | 2 +- .../sdk/coreClasses/http/HttpRequest.kt | 10 ++++---- .../io/kuzzle/sdk/coreClasses/http/Route.kt | 25 +++++++++---------- .../kotlin/io/kuzzle/sdk/protocol/Http.kt | 14 +++++------ 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt index 7aae2e3b..ae8a5171 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt @@ -4,5 +4,5 @@ package io.kuzzle.sdk.coreClasses.exceptions * Thrown when attempting to interact with the network while not connected. */ class MissingURLParamException : KuzzleException { - constructor(templateParam: String, baseURL: String) : super("Missing URL Param ${templateParam} in ${baseURL}", 0) + constructor(templateParam: String, baseURL: String) : super("Missing URL Param $templateParam in $baseURL", 0) } diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt index 1aa44e58..8504f25a 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt @@ -3,8 +3,8 @@ package io.kuzzle.sdk.coreClasses.http import io.kuzzle.sdk.coreClasses.maps.KuzzleMap data class HttpRequest( - val verb: String, - val url: String, - val body: String, - val headers: KuzzleMap, -) \ No newline at end of file + val verb: String, + val url: String, + val body: KuzzleMap, + val headers: KuzzleMap, +) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 0fbd656f..c56ee76d 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -83,27 +83,26 @@ class Route { if (queryArgs.isArrayList(it)) { val value = queryArgs.getArrayList(it)!!.joinToString(",") - "${encodedKey}=${URLEncoder.encode(value, "utf-8")}" + "$encodedKey=${URLEncoder.encode(value, "utf-8")}" } else if (queryArgs.optBoolean(it, false) == true) { encodedKey } else { val value = if (queryArgs.isMap(it)) JsonSerializer.serialize(queryArgs.getMap(it)!!) else queryArgs[it].toString() - "${encodedKey}=${URLEncoder.encode(value, "utf-8")}" + "$encodedKey=${URLEncoder.encode(value, "utf-8")}" } } - /** * If the partType is STATIC it means that there is no template in the url * So we can use the staticURL instead of building the URL */ if (partType == PartType.STATIC) { return HttpRequest( - verb, - if (queryArgs.isEmpty()) staticURL else "$staticURL?$queryString", - if (request["body"] != null) JsonSerializer.serialize(request["body"]!!) else "", - headers - ) + verb, + if (queryArgs.isEmpty()) staticURL else "$staticURL?$queryString", + request.optMap("body", KuzzleMap()), + headers + ) } /** @@ -124,10 +123,10 @@ class Route { } } return HttpRequest( - verb, - if (queryArgs.isEmpty()) urlBuilder.toString() else "$urlBuilder?$queryString", - if (request["body"] != null) JsonSerializer.serialize(request["body"]!!) else "", - headers + verb, + if (queryArgs.isEmpty()) urlBuilder.toString() else "$urlBuilder?$queryString", + request.optMap("body", KuzzleMap()), + headers ) } @@ -165,4 +164,4 @@ class Route { return Route(verb, url, routeParts) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 8253cf5d..304ffbc3 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -75,16 +75,16 @@ open class Http : AbstractProtocol { private fun buildRoutes(routes: Map) { for (controllerEntry in routes) { for (actionEntry in controllerEntry.value as Map) { - var map = actionEntry.value as Map + val map = actionEntry.value as Map if (! map.containsKey("controller") || ! map.containsKey("action") || ! map.containsKey("http")) { continue } - var controller = map["controller"] as String - var action = map["action"] as String + val controller = map["controller"] as String + val action = map["action"] as String - var httpRoutes = map["http"] as List> + val httpRoutes = map["http"] as List> if (httpRoutes.isNotEmpty()) { val route = findBestRoute(controller, action, httpRoutes.map { @@ -212,7 +212,7 @@ open class Http : AbstractProtocol { } } - private fun queryActionEndpoint(payload: Map, requestInfo: io.kuzzle.sdk.coreClasses.http.HttpRequest) { + private fun queryHTTPEndpoint(payload: Map, requestInfo: io.kuzzle.sdk.coreClasses.http.HttpRequest) { GlobalScope.launch { // Launch HTTP Request inside a coroutine to be non-blocking var client = HttpClient() { expectSuccess = false @@ -228,7 +228,7 @@ open class Http : AbstractProtocol { this.header(entry.key.toString(), JsonSerializer.serialize(entry.value!!)) } this.header("content-type", "application/json") - this.body = requestInfo.body + this.body = JsonSerializer.serialize(requestInfo.body) } // trigger messageReceived super.trigger(MessageReceivedEvent(response.receive(), payload["requestId"] as String?)) @@ -269,7 +269,7 @@ open class Http : AbstractProtocol { try { val requestInfo = route.buildRequest(KuzzleMap.from(payload)) - queryActionEndpoint(payload, requestInfo) + queryHTTPEndpoint(payload, requestInfo) } catch (e: MissingURLParamException) { /** * Fallback if we could not find a matching route with the given parameters From 971f50eff610db7d91f2a5a9344fde219e599178 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 10:49:21 +0200 Subject: [PATCH 04/35] fix route builder --- .../io/kuzzle/sdk/coreClasses/http/Route.kt | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index c56ee76d..71e3911f 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -20,6 +20,15 @@ private class RoutePart { } } +val PayloadProperties = setOf( + "controller", + "action", + "index", + "collection", + "meta", + "volatile", +) + class Route { private val partType: PartType private val routeParts: ArrayList @@ -34,7 +43,9 @@ class Route { val hasOnePartTemplated = routeParts.indexOfFirst { it.type == PartType.TEMPLATE } > -1 this.partType = if (hasOnePartTemplated) PartType.TEMPLATE else PartType.STATIC if (! hasOnePartTemplated) { - staticURL = routeParts.joinToString("/") + staticURL = routeParts.joinToString("") { + it.value + } } } @@ -70,7 +81,9 @@ class Route { } } else -> { - queryArgs[key] = request[key] + if (! PayloadProperties.contains(key)) { + queryArgs[key] = request[key] + } } } } From 53fe55a114c51686e4d04fb72ea14a1064d85860 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 10:59:11 +0200 Subject: [PATCH 05/35] lint --- .../kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt | 4 ++-- src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 71e3911f..a0d5901f 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -113,7 +113,7 @@ class Route { return HttpRequest( verb, if (queryArgs.isEmpty()) staticURL else "$staticURL?$queryString", - request.optMap("body", KuzzleMap()), + request.optMap("body", KuzzleMap()), headers ) } @@ -138,7 +138,7 @@ class Route { return HttpRequest( verb, if (queryArgs.isEmpty()) urlBuilder.toString() else "$urlBuilder?$queryString", - request.optMap("body", KuzzleMap()), + request.optMap("body", KuzzleMap()), headers ) } diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 304ffbc3..643f6dcb 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -87,9 +87,12 @@ open class Http : AbstractProtocol { val httpRoutes = map["http"] as List> if (httpRoutes.isNotEmpty()) { - val route = findBestRoute(controller, action, httpRoutes.map { - ControllerActionRoute(it["verb"] as String, it["path"] as String) - }) + val route = findBestRoute( + controller, + action, + httpRoutes.map { + ControllerActionRoute(it["verb"] as String, it["path"] as String) + }) this.routes["$controller:$action"] = Route.parse(route.verb, route.path) } @@ -119,9 +122,8 @@ open class Http : AbstractProtocol { buildRoutes(responseJson["result"] as Map) useBuiltRoutes = true - } catch (e: Exception) { - e.printStackTrace() + } finally { client.close() wait.complete(null) @@ -240,7 +242,6 @@ open class Http : AbstractProtocol { } } - override fun send(payload: Map) { if (! payload.containsKey("controller") || payload["controller"] !is String) { super.trigger(RequestErrorEvent(MissingControllerException(), payload["requestId"] as String?)) From ec77f116c17e10ae46b1cae8c16c12723b525727 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 12:08:52 +0200 Subject: [PATCH 06/35] enhance error messages, use proper serialization for header --- src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt | 1 + .../coreClasses/exceptions/ApiErrorException.kt | 8 ++++++-- .../io/kuzzle/sdk/coreClasses/http/Route.kt | 2 +- .../io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt | 6 ++++++ .../coreClasses/serializer/StringSerializer.kt | 15 +++++++++++++++ src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt | 8 ++++---- 6 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/io/kuzzle/sdk/coreClasses/serializer/StringSerializer.kt diff --git a/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt b/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt index 5d809cf8..8a7965f8 100644 --- a/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt +++ b/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt @@ -90,6 +90,7 @@ open class Kuzzle { } queries[requestId]?.completeExceptionally(ApiErrorException(response)) + queries.remove(requestId) protocol.trigger(TokenExpiredEvent()) } diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/ApiErrorException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/ApiErrorException.kt index c0efe32e..3369799a 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/ApiErrorException.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/ApiErrorException.kt @@ -22,6 +22,10 @@ class ApiErrorException : KuzzleException { var id: String? = null private set + override fun toString(): String { + return "ApiErrorException \"$id\": $message\n$stack" + } + /** * Initializes a new instance of the ApiErrorException * @@ -30,8 +34,8 @@ class ApiErrorException : KuzzleException { constructor(response: Response) : super(response.error?.message, response.status) { if (response.error != null) { - this.stack = response.error?.stack - this.id = response.error?.id + this.stack = response.error!!.stack + this.id = response.error!!.id } } } diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index a0d5901f..bcf1b460 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -70,7 +70,7 @@ class Route { } when (key) { - "jwt" -> headers["Authorization"] = "Bearer ${request["jwt"]}" + "jwt" -> headers["authorization"] = "Bearer ${request["jwt"]}" "volatile" -> { headers["x-kuzzle-volatile"] = request["volatile"] } diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt index 57a4f0b0..52cdb894 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt @@ -1,5 +1,7 @@ package io.kuzzle.sdk.coreClasses.maps +import io.kuzzle.sdk.coreClasses.json.JsonSerializer + /** * KuzzleMap is a Class that extends Map to be ThreadSafe and that * has the purpose of giving a wrapper on top of Map to easily @@ -222,6 +224,10 @@ class KuzzleMap : HashMap { return if (isMap(key)) from(super.get(key) as Map) else from(def) } + override fun toString(): String { + return JsonSerializer.serialize(this) + } + companion object { /** * serialVersionUID diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/serializer/StringSerializer.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/serializer/StringSerializer.kt new file mode 100644 index 00000000..57924f2e --- /dev/null +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/serializer/StringSerializer.kt @@ -0,0 +1,15 @@ +package io.kuzzle.sdk.coreClasses.serializer + +import io.kuzzle.sdk.coreClasses.json.JsonSerializer + +object StringSerializer { + fun serialize(obj: Any): String { + if (obj == null) { + return "" + } + if (obj is Map<*, *> || obj is List<*>) { + return JsonSerializer.serialize(obj) + } + return obj.toString() + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 643f6dcb..5911dfe8 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -6,10 +6,10 @@ import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.kuzzle.sdk.coreClasses.exceptions.* -import io.kuzzle.sdk.coreClasses.http.HttpRequest import io.kuzzle.sdk.coreClasses.http.Route import io.kuzzle.sdk.coreClasses.json.JsonSerializer import io.kuzzle.sdk.coreClasses.maps.KuzzleMap +import io.kuzzle.sdk.coreClasses.serializer.StringSerializer import io.kuzzle.sdk.events.* import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -192,7 +192,7 @@ open class Http : AbstractProtocol { if (entry.key == null || entry.value == null) { continue } - this.header(entry.key.toString(), JsonSerializer.serialize(entry.value!!)) + this.header(entry.key.toString(), StringSerializer.serialize(entry.value!!)) } } this.header("content-type", "application/json") @@ -200,7 +200,7 @@ open class Http : AbstractProtocol { this.header("Authorization", "Bearer ${payload["jwt"]}") } if (payload["volatile"] != null) { - this.header("x-kuzzle-volatile", JsonSerializer.serialize(payload["volatile"]!!)) + this.header("x-kuzzle-volatile", StringSerializer.serialize(payload["volatile"]!!)) } this.body = JsonSerializer.serialize(payload) } @@ -227,7 +227,7 @@ open class Http : AbstractProtocol { continue } - this.header(entry.key.toString(), JsonSerializer.serialize(entry.value!!)) + this.header(entry.key.toString(), StringSerializer.serialize(entry.value!!)) } this.header("content-type", "application/json") this.body = JsonSerializer.serialize(requestInfo.body) From 1a87c45267dad1876c453f17c43c69a54374824b Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 12:10:38 +0200 Subject: [PATCH 07/35] lint --- src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 5911dfe8..42d1f65d 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -93,7 +93,6 @@ open class Http : AbstractProtocol { httpRoutes.map { ControllerActionRoute(it["verb"] as String, it["path"] as String) }) - this.routes["$controller:$action"] = Route.parse(route.verb, route.path) } } @@ -123,7 +122,7 @@ open class Http : AbstractProtocol { buildRoutes(responseJson["result"] as Map) useBuiltRoutes = true } catch (e: Exception) { - + // Do nothing } finally { client.close() wait.complete(null) From 2c9788af37796060b4a8caa51cb9b4f9fd242583 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 12:18:13 +0200 Subject: [PATCH 08/35] lint --- .../io/kuzzle/sdk/coreClasses/serializer/StringSerializer.kt | 2 +- src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/serializer/StringSerializer.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/serializer/StringSerializer.kt index 57924f2e..5f9f4a39 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/serializer/StringSerializer.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/serializer/StringSerializer.kt @@ -12,4 +12,4 @@ object StringSerializer { } return obj.toString() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 42d1f65d..a3ed4562 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -92,7 +92,8 @@ open class Http : AbstractProtocol { action, httpRoutes.map { ControllerActionRoute(it["verb"] as String, it["path"] as String) - }) + } + ) this.routes["$controller:$action"] = Route.parse(route.verb, route.path) } } From 206cd3534ce75f2f9bc5f88234c1c317527dcf63 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 15:28:45 +0200 Subject: [PATCH 09/35] remove toString --- .../io/kuzzle/sdk/coreClasses/exceptions/ApiErrorException.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/ApiErrorException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/ApiErrorException.kt index 3369799a..4fc8a45f 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/ApiErrorException.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/ApiErrorException.kt @@ -22,10 +22,6 @@ class ApiErrorException : KuzzleException { var id: String? = null private set - override fun toString(): String { - return "ApiErrorException \"$id\": $message\n$stack" - } - /** * Initializes a new instance of the ApiErrorException * From 6306341c34cc3bbf31d041f004f9baf80a1b1c83 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 16:19:53 +0200 Subject: [PATCH 10/35] remove toString --- src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt index 52cdb894..abf10f0b 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt @@ -224,10 +224,6 @@ class KuzzleMap : HashMap { return if (isMap(key)) from(super.get(key) as Map) else from(def) } - override fun toString(): String { - return JsonSerializer.serialize(this) - } - companion object { /** * serialVersionUID From ed1e26de484cbce0d1274944c1464b16a71f2c3f Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 16:25:43 +0200 Subject: [PATCH 11/35] remove unused import --- src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt index abf10f0b..57a4f0b0 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/maps/KuzzleMap.kt @@ -1,7 +1,5 @@ package io.kuzzle.sdk.coreClasses.maps -import io.kuzzle.sdk.coreClasses.json.JsonSerializer - /** * KuzzleMap is a Class that extends Map to be ThreadSafe and that * has the purpose of giving a wrapper on top of Map to easily From ab72a5d5ec0cee96e92e5c052a5865eb9394a4e9 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 17:09:17 +0200 Subject: [PATCH 12/35] add custom headers --- src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index bcf1b460..5768c9f6 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -1,5 +1,6 @@ package io.kuzzle.sdk.coreClasses.http +import io.kuzzle.sdk.Kuzzle import io.kuzzle.sdk.coreClasses.exceptions.MissingURLParamException import io.kuzzle.sdk.coreClasses.json.JsonSerializer import io.kuzzle.sdk.coreClasses.maps.KuzzleMap @@ -74,7 +75,7 @@ class Route { "volatile" -> { headers["x-kuzzle-volatile"] = request["volatile"] } - "headers" -> continue + "headers" -> headers.putAll(request.optMap("headers", KuzzleMap())) "body" -> { if (verb == "GET") { queryArgs.putAll(request.optMap("body", KuzzleMap())) From d8470a26957e0551de673f1578ef9cd663594495 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 17:37:50 +0200 Subject: [PATCH 13/35] LINT --- src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 5768c9f6..4f182f9d 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -1,6 +1,5 @@ package io.kuzzle.sdk.coreClasses.http -import io.kuzzle.sdk.Kuzzle import io.kuzzle.sdk.coreClasses.exceptions.MissingURLParamException import io.kuzzle.sdk.coreClasses.json.JsonSerializer import io.kuzzle.sdk.coreClasses.maps.KuzzleMap From 4b0a1b1766c53640ff2e7c1931562576bbac5e75 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 17:45:41 +0200 Subject: [PATCH 14/35] add index and collection in querystring --- src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 4f182f9d..57f208f8 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -23,8 +23,6 @@ private class RoutePart { val PayloadProperties = setOf( "controller", "action", - "index", - "collection", "meta", "volatile", ) From e4aa3d51f7aa52367924b5418db6abd2ca14fefd Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 20 Apr 2022 18:00:06 +0200 Subject: [PATCH 15/35] add comments to explain what has been done in the code --- .../kotlin/io/kuzzle/sdk/protocol/Http.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index a3ed4562..9820e10c 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -38,16 +38,31 @@ open class Http : AbstractProtocol { } private fun onLoginAttempt(event: LoginAttemptEvent) { + /** + * Once the user is logged in we try to fetch and build the routes + * if not already built. + */ if (! useBuiltRoutes && event.success) { tryToBuildRoutes() } } + /** + * Try to find the best route for a given action of a controller + * + * With same URL size, we prefer the GET route + * with different URL sizes, we keep the shortest because URL params + * will be in the query string + */ private fun findBestRoute(controller: String, action: String, httpRoutes: List): ControllerActionRoute { if (httpRoutes.size == 1) { return httpRoutes[0] } + /** + * Search route can also be accessed with GET, + * but to provide a query for the search we need to use the POST method + */ if (controller.lowercase() == "document" && action.lowercase() == "search") { return httpRoutes.find { it.verb == "POST" @@ -72,6 +87,10 @@ open class Http : AbstractProtocol { return if (sameLength) selectedRoute else shortestRoute } + /** + * Given a map of routes from _publicApi + * construct the best routes for each controller's action + */ private fun buildRoutes(routes: Map) { for (controllerEntry in routes) { for (actionEntry in controllerEntry.value as Map) { @@ -100,6 +119,10 @@ open class Http : AbstractProtocol { } } + /** + * Try to fetch and build the routes + * if it fails it does not switch the flag "useBuiltRoutes" + */ private fun tryToBuildRoutes() { val wait = CompletableFuture() @@ -214,6 +237,9 @@ open class Http : AbstractProtocol { } } + /** + * Make a request to Kuzzle using the appropriate HTTP Endpoint for a given controller's action + */ private fun queryHTTPEndpoint(payload: Map, requestInfo: io.kuzzle.sdk.coreClasses.http.HttpRequest) { GlobalScope.launch { // Launch HTTP Request inside a coroutine to be non-blocking var client = HttpClient() { @@ -253,6 +279,10 @@ open class Http : AbstractProtocol { return } + /** + * If no route has been built we try to send the request to Kuzzle using + * the /_query endpoint + */ if (! useBuiltRoutes) { query(payload) return From 153b25f3e933b48dddd809d3c20059fdfb7e0c7e Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 14:50:27 +0200 Subject: [PATCH 16/35] add headers to response object and RequestPayload --- .../kuzzle/sdk/coreClasses/RequestPayload.kt | 7 ++++++- .../sdk/coreClasses/responses/Response.kt | 21 ++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/RequestPayload.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/RequestPayload.kt index 6369af83..e1a6a03a 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/RequestPayload.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/RequestPayload.kt @@ -23,7 +23,8 @@ data class RequestPayload( var volatile: Any? = null, var body: Any? = null, var requestId: String? = null, - var other: Map? = null + var other: Map? = null, + var headers: Map? = null, ) { fun toMap(): Map { val map = HashMap() @@ -64,6 +65,10 @@ data class RequestPayload( map["requestId"] = requestId } + if (headers != null) { + map["headers"] = headers + } + if (other != null) { map.putAll(other!!) } diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/responses/Response.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/responses/Response.kt index ab0012b8..c6e77657 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/responses/Response.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/responses/Response.kt @@ -5,7 +5,7 @@ import io.kuzzle.sdk.coreClasses.maps.KuzzleMap import io.kuzzle.sdk.coreClasses.maps.Serializable class Response : Serializable { - var mapResponse: Map? = null + var mapResponse: KuzzleMap? = null private set var room: String? = null @@ -80,14 +80,19 @@ class Response : Serializable { */ var type: String? = null + /** + * Response headers + */ + var headers: Map? = null + override fun fromMap(map: Map?) { if (map == null) return - mapResponse = map - val kuzzleMap = KuzzleMap(map) + mapResponse = kuzzleMap + room = kuzzleMap.getString("room") - result = kuzzleMap.get("result") + result = kuzzleMap["result"] error = null if (kuzzleMap.isMap("error")) { error = ErrorResponse() @@ -97,20 +102,21 @@ class Response : Serializable { if (requestId == null) { throw Exception(KuzzleExceptionCode.MISSING_REQUESTID.message) } - status = (kuzzleMap.optNumber("status", 0) as com.google.gson.internal.LazilyParsedNumber).toInt() + status = kuzzleMap.optNumber("status", 0)!!.toInt() controller = kuzzleMap.getString("controller") action = kuzzleMap.getString("action") index = kuzzleMap.getString("index") collection = kuzzleMap.getString("collection") - Volatile = kuzzleMap.optMap("volatile", HashMap()) + Volatile = kuzzleMap.optMap("volatile", KuzzleMap()) protocol = kuzzleMap.getString("protocol") scope = kuzzleMap.getString("scope") state = kuzzleMap.getString("state") timestamp = when (kuzzleMap.getNumber("timestamp")) { null -> null - else -> (kuzzleMap.getNumber("timestamp") as com.google.gson.internal.LazilyParsedNumber).toLong() + else -> kuzzleMap.getNumber("timestamp")!!.toLong() } type = kuzzleMap.getString("type") + headers = kuzzleMap.getMap("headers") } override fun toMap(): Map { @@ -131,6 +137,7 @@ class Response : Serializable { map.put("status", status) timestamp?.let { map.put("timestamp", it) } type?.let { map.put("type", it) } + headers?.let { map.put("headers", it) } return map } From b6115ea3bbbd8e3647da94824774955bc3b65a73 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 14:59:24 +0200 Subject: [PATCH 17/35] add documentation for headers field in HTTP Request --- doc/1/core-classes/kuzzle/query/index.md | 59 +++++++++++++----------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/doc/1/core-classes/kuzzle/query/index.md b/doc/1/core-classes/kuzzle/query/index.md index b5a9385f..23c2e512 100644 --- a/doc/1/core-classes/kuzzle/query/index.md +++ b/doc/1/core-classes/kuzzle/query/index.md @@ -46,15 +46,15 @@ public CompletableFuture query(
-| Argument | Type | Description | -| --------- | ----------------- | ---------------------- | -| `query` |
Map | API request    |
+| Argument | Type                     | Description |
+| -------- | ------------------------ | ----------- |
+| `query`  | 
Map | API request |
 
 
 
-| Argument  | Type              | Description            |
-| --------- | ----------------- | ---------------------- |
-| `query` | 
Map
or String
or RawJson
or RequestPayload
or Object
| API request | +| Argument | Type | Description | +| -------- | ------------------------------------------------------------------------------------------- | ----------- | +| `query` |
Map
or String
or RawJson
or RequestPayload
or Object
| API request | ::: info Calling query with a `String` or `RawJson` makes no differences, and will be interpreted as raw json strings. @@ -74,15 +74,17 @@ This will avoid the deserialization + reserialization slowdown All properties necessary for the Kuzzle API can be added in the query object. The following properties are the most common. -| Property | Type | Description | -| ------------ | ----------------- | ---------------------------------------- | -| `controller` |
String
| Controller name (mandatory) | -| `action` |
String
| Action name (mandatory) | +| Property | Type | Description | +| ------------ | --------------------------------------------------------- | ---------------------------------------- | +| `controller` |
String
| Controller name (mandatory) | +| `action` |
String
| Action name (mandatory) | | `body` |
Map
or RawJson
or Object
| Query body for this action | -| `index` |
String
| Index name for this action | -| `collection` |
String
| Collection name for this action | -| `_id` |
String
| id for this action | +| `index` |
String
| Index name for this action | +| `collection` |
String
| Collection name for this action | +| `_id` |
String
| id for this action | | `volatile` |
Map
or RawJson
or Object
| Additional information to send to Kuzzle | +| `headers` |
Map
| Optionnal headers to send (HTTP Only) | + ## Returns @@ -112,15 +114,15 @@ fun query(query: Any): CompletableFuture
-| Argument | Type | Description | -| --------- | ----------------- | ---------------------- | -| `query` |
Map | API request    |
+| Argument | Type                    | Description |
+| -------- | ----------------------- | ----------- |
+| `query`  | 
Map | API request |
 
 
 
-| Argument  | Type              | Description            |
-| --------- | ----------------- | ---------------------- |
-| `query` | 
Map
or RawJson
or String
or RequestPayload
or Any
| API request | +| Argument | Type | Description | +| -------- | --------------------------------------------------------------------------------------- | ----------- | +| `query` |
Map
or RawJson
or String
or RequestPayload
or Any
| API request | ::: info Calling query with a `String` or `RawJson` makes no differences, and will be interpreted as raw json strings. @@ -140,15 +142,16 @@ This will avoid the deserialization + reserialization slowdown All properties necessary for the Kuzzle API can be added in the query object. The following properties are the most common. -| Property | Type | Description | -| ------------ | ----------------- | ---------------------------------------- | -| `controller` |
String
| Controller name (mandatory) | -| `action` |
String
| Action name (mandatory) | -| `body` |
Map
or RawJson
or Any
| Query body for this action | -| `index` |
String
| Index name for this action | -| `collection` |
String
| Collection name for this action | -| `_id` |
String
| id for this action | -| `volatile` |
Map
or RawJson
or Any
| Additional information to send to Kuzzle | +| Property | Type | Description | +| ------------ | ---------------------------------------------------- | ---------------------------------------- | +| `controller` |
String
| Controller name (mandatory) | +| `action` |
String
| Action name (mandatory) | +| `body` |
Map
or RawJson
or Any
| Query body for this action | +| `index` |
String
| Index name for this action | +| `collection` |
String
| Collection name for this action | +| `_id` |
String
| id for this action | +| `volatile` |
Map
or RawJson
or Any
| Additional information to send to Kuzzle | +| `headers` |
Map
| Optionnal headers to send (HTTP Only) | ## Returns From 242fc0cc65f5ca2e6ece8994a67ea3e71611dbad Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 15:01:34 +0200 Subject: [PATCH 18/35] add headers field in response --- .../response/introduction/index.md | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/doc/1/core-classes/response/introduction/index.md b/doc/1/core-classes/response/introduction/index.md index 14a2d399..afc1b69c 100644 --- a/doc/1/core-classes/response/introduction/index.md +++ b/doc/1/core-classes/response/introduction/index.md @@ -12,19 +12,20 @@ order: 0 ## Properties -| Property | Type | Description | -|--- |--- |--- | -| `action` |
String
| Executed Kuzzle API controller's action | -| `collection` |
String
| Impacted collection | -| `controller` |
String
| Executed Kuzzle API controller | -| `error` |
[ErrorResponse](/sdk/jvm/1/core-classes/error-response)
| Error object (null if the request finished successfully) | -| `index` |
String
| Impacted index | -| `protocol` |
String
| Network protocol at the origin of the real-time notification | -| `requestId` |
String
| Request unique identifier | -| `result` |
Object
| Response payload (depends on the executed API action) | -| `room` |
String
| Room identifier (realtime only) | -| `scope` |
String
| Document scope ("in" or "out", realtime only) | -| `state` |
String
| Document state (realtime only) | -| `status` |
int
| Response status, following HTTP status codes | -| `timestamp` |
Long
| Notification timestamp (UTC) | -| `volatile` |
Map
| Volatile data | +| Property | Type | Description | +| ------------ | ------------------------------------------------------------------ | ------------------------------------------------------------ | +| `action` |
String
| Executed Kuzzle API controller's action | +| `collection` |
String
| Impacted collection | +| `controller` |
String
| Executed Kuzzle API controller | +| `error` |
[ErrorResponse](/sdk/jvm/1/core-classes/error-response)
| Error object (null if the request finished successfully) | +| `headers` |
Map
| Headers returned in the response | +| `index` |
String
| Impacted index | +| `protocol` |
String
| Network protocol at the origin of the real-time notification | +| `requestId` |
String
| Request unique identifier | +| `result` |
Object
| Response payload (depends on the executed API action) | +| `room` |
String
| Room identifier (realtime only) | +| `scope` |
String
| Document scope ("in" or "out", realtime only) | +| `state` |
String
| Document state (realtime only) | +| `status` |
int
| Response status, following HTTP status codes | +| `timestamp` |
Long
| Notification timestamp (UTC) | +| `volatile` |
Map
| Volatile data | From 7c33d836ba2f176345e64890d29911d19de337ce Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 15:02:23 +0200 Subject: [PATCH 19/35] ensure RawJson is properly stringified --- src/main/kotlin/io/kuzzle/sdk/coreClasses/json/RawJson.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/json/RawJson.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/json/RawJson.kt index 842532e3..b346ea73 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/json/RawJson.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/json/RawJson.kt @@ -1,3 +1,7 @@ package io.kuzzle.sdk.coreClasses.json -data class RawJson(val rawJson: String) +data class RawJson(val rawJson: String) { + override fun toString(): String { + return rawJson + } +} From 3cb8d818c4544b63ac61a152db460c24c15f8879 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 17:19:33 +0200 Subject: [PATCH 20/35] remove weird behaviour copied from SDK-JS --- .../sdk/coreClasses/http/HttpRequest.kt | 2 +- .../io/kuzzle/sdk/coreClasses/http/Route.kt | 27 ++++++++----------- .../kotlin/io/kuzzle/sdk/protocol/Http.kt | 8 +----- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt index 8504f25a..edeb88a2 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt @@ -5,6 +5,6 @@ import io.kuzzle.sdk.coreClasses.maps.KuzzleMap data class HttpRequest( val verb: String, val url: String, - val body: KuzzleMap, + val body: KuzzleMap?, val headers: KuzzleMap, ) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 57f208f8..5d1d28e4 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -25,6 +25,7 @@ val PayloadProperties = setOf( "action", "meta", "volatile", + "body", ) class Route { @@ -69,15 +70,9 @@ class Route { when (key) { "jwt" -> headers["authorization"] = "Bearer ${request["jwt"]}" - "volatile" -> { - headers["x-kuzzle-volatile"] = request["volatile"] - } + "volatile" -> headers["x-kuzzle-volatile"] = request["volatile"] + "requestId" -> headers["x-kuzzle-request-id"] = request["requestId"] "headers" -> headers.putAll(request.optMap("headers", KuzzleMap())) - "body" -> { - if (verb == "GET") { - queryArgs.putAll(request.optMap("body", KuzzleMap())) - } - } else -> { if (! PayloadProperties.contains(key)) { queryArgs[key] = request[key] @@ -89,16 +84,16 @@ class Route { /** * Build the query string */ - val queryString: String = queryArgs.keys.filterNotNull().joinToString("&") { + val queryString: String = queryArgs.keys.filter { + it != null && ! queryArgs.isArrayList(it) && ! queryArgs.isMap(it) + } + .joinToString("&") { val encodedKey = URLEncoder.encode(it, "utf-8") - if (queryArgs.isArrayList(it)) { - val value = queryArgs.getArrayList(it)!!.joinToString(",") - "$encodedKey=${URLEncoder.encode(value, "utf-8")}" - } else if (queryArgs.optBoolean(it, false) == true) { + if (queryArgs.optBoolean(it!!, false) == true) { encodedKey } else { - val value = if (queryArgs.isMap(it)) JsonSerializer.serialize(queryArgs.getMap(it)!!) else queryArgs[it].toString() + val value = queryArgs[it].toString() "$encodedKey=${URLEncoder.encode(value, "utf-8")}" } } @@ -111,7 +106,7 @@ class Route { return HttpRequest( verb, if (queryArgs.isEmpty()) staticURL else "$staticURL?$queryString", - request.optMap("body", KuzzleMap()), + if (verb != "GET") request.optMap("body", KuzzleMap()) else null, headers ) } @@ -136,7 +131,7 @@ class Route { return HttpRequest( verb, if (queryArgs.isEmpty()) urlBuilder.toString() else "$urlBuilder?$queryString", - request.optMap("body", KuzzleMap()), + if (verb != "GET") request.optMap("body", KuzzleMap()) else null, headers ) } diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 9820e10c..9505d257 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -219,12 +219,6 @@ open class Http : AbstractProtocol { } } this.header("content-type", "application/json") - if (payload["jwt"] != null) { - this.header("Authorization", "Bearer ${payload["jwt"]}") - } - if (payload["volatile"] != null) { - this.header("x-kuzzle-volatile", StringSerializer.serialize(payload["volatile"]!!)) - } this.body = JsonSerializer.serialize(payload) } // trigger messageReceived @@ -256,7 +250,7 @@ open class Http : AbstractProtocol { this.header(entry.key.toString(), StringSerializer.serialize(entry.value!!)) } this.header("content-type", "application/json") - this.body = JsonSerializer.serialize(requestInfo.body) + this.body = if (requestInfo.body != null) JsonSerializer.serialize(requestInfo.body) else "" } // trigger messageReceived super.trigger(MessageReceivedEvent(response.receive(), payload["requestId"] as String?)) From 749413778d436917e3d18d4865208720f9034568 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 17:22:12 +0200 Subject: [PATCH 21/35] add other payload properties to skip --- src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 5d1d28e4..101d1ad6 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -25,7 +25,10 @@ val PayloadProperties = setOf( "action", "meta", "volatile", + "jwt", + "requestId", "body", + "headers", ) class Route { From 0aec27646dabe16a2678058719334f908a9c232f Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 17:25:08 +0200 Subject: [PATCH 22/35] lint --- .../io/kuzzle/sdk/coreClasses/http/Route.kt | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 101d1ad6..82f7c84b 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -87,19 +87,20 @@ class Route { /** * Build the query string */ - val queryString: String = queryArgs.keys.filter { - it != null && ! queryArgs.isArrayList(it) && ! queryArgs.isMap(it) - } - .joinToString("&") { - val encodedKey = URLEncoder.encode(it, "utf-8") + val queryString: String = queryArgs.keys + .filter { + it != null && ! queryArgs.isArrayList(it) && ! queryArgs.isMap(it) + } + .joinToString("&") { + val encodedKey = URLEncoder.encode(it, "utf-8") - if (queryArgs.optBoolean(it!!, false) == true) { - encodedKey - } else { - val value = queryArgs[it].toString() - "$encodedKey=${URLEncoder.encode(value, "utf-8")}" + if (queryArgs.optBoolean(it!!, false) == true) { + encodedKey + } else { + val value = queryArgs[it].toString() + "$encodedKey=${URLEncoder.encode(value, "utf-8")}" + } } - } /** * If the partType is STATIC it means that there is no template in the url From 101715a7a5e18382c225c36eabc64180a2e7c61a Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 17:26:42 +0200 Subject: [PATCH 23/35] remove unused import --- src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 82f7c84b..95eab777 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -1,7 +1,6 @@ package io.kuzzle.sdk.coreClasses.http import io.kuzzle.sdk.coreClasses.exceptions.MissingURLParamException -import io.kuzzle.sdk.coreClasses.json.JsonSerializer import io.kuzzle.sdk.coreClasses.maps.KuzzleMap import java.net.URLEncoder From 371271987b6335ec3ec3bb26644738e8cc76f27f Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 18:01:49 +0200 Subject: [PATCH 24/35] add default headers --- src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 9505d257..46abe407 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -219,6 +219,15 @@ open class Http : AbstractProtocol { } } this.header("content-type", "application/json") + if (payload["jwt"] != null) { + this.header("Authorization", "Bearer ${payload["jwt"]}") + } + if (payload["volatile"] != null) { + this.header("x-kuzzle-volatile", StringSerializer.serialize(payload["volatile"]!!)) + } + if (payload["requestId"] != null) { + this.header("x-kuzzle-request-id", payload["requestId"].toString()) + } this.body = JsonSerializer.serialize(payload) } // trigger messageReceived From 05af4e1bf267931583c952d4a5d53b5cede9179c Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 18:19:14 +0200 Subject: [PATCH 25/35] fix body properties not transfered --- src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt | 5 +++++ src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 95eab777..d4000fd5 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -75,6 +75,11 @@ class Route { "volatile" -> headers["x-kuzzle-volatile"] = request["volatile"] "requestId" -> headers["x-kuzzle-request-id"] = request["requestId"] "headers" -> headers.putAll(request.optMap("headers", KuzzleMap())) + "body" -> { + if (verb == "GET") { + queryArgs.putAll(request.optMap("body", KuzzleMap())) + } + } else -> { if (! PayloadProperties.contains(key)) { queryArgs[key] = request[key] diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 46abe407..afe4c3f2 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -220,7 +220,7 @@ open class Http : AbstractProtocol { } this.header("content-type", "application/json") if (payload["jwt"] != null) { - this.header("Authorization", "Bearer ${payload["jwt"]}") + this.header("authorization", "Bearer ${payload["jwt"]}") } if (payload["volatile"] != null) { this.header("x-kuzzle-volatile", StringSerializer.serialize(payload["volatile"]!!)) From 366642d9550187c9fb2dcc7ed41efb20ecc1dcc5 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 20:00:23 +0200 Subject: [PATCH 26/35] rollback --- src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt | 6 ++-- .../io/kuzzle/sdk/coreClasses/http/Route.kt | 9 ++++-- .../kuzzle/sdk/protocol/AbstractProtocol.kt | 3 +- .../kotlin/io/kuzzle/sdk/protocol/Http.kt | 28 +++++++++++++------ .../io/kuzzle/sdk/protocol/WebSocket.kt | 3 +- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt b/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt index 8a7965f8..dcfa59c1 100644 --- a/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt +++ b/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt @@ -56,9 +56,9 @@ open class Kuzzle { } private fun onRequestError(event: RequestErrorEvent) { - if (event.requestId != null && queries[event.requestId] != null) { - queries[event.requestId]?.completeExceptionally(event.exception) - queries.remove(event.requestId) + if (event.requestId != null && queries[event.requestId!!] != null) { + queries[event.requestId!!]?.completeExceptionally(event.exception) + queries.remove(event.requestId!!) } } diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 57f208f8..92446db3 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -25,6 +25,10 @@ val PayloadProperties = setOf( "action", "meta", "volatile", + "jwt", + "requestId", + "volatile", + "body" ) class Route { @@ -69,9 +73,8 @@ class Route { when (key) { "jwt" -> headers["authorization"] = "Bearer ${request["jwt"]}" - "volatile" -> { - headers["x-kuzzle-volatile"] = request["volatile"] - } + "volatile" -> headers["x-kuzzle-volatile"] = request["volatile"] + "requestId" -> headers["x-kuzzle-request-id"] = request["requestId"] "headers" -> headers.putAll(request.optMap("headers", KuzzleMap())) "body" -> { if (verb == "GET") { diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/AbstractProtocol.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/AbstractProtocol.kt index 8df92630..29f3afaf 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/AbstractProtocol.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/AbstractProtocol.kt @@ -1,10 +1,11 @@ package io.kuzzle.sdk.protocol +import io.kuzzle.sdk.coreClasses.maps.KuzzleMap import io.kuzzle.sdk.events.EventManager abstract class AbstractProtocol : EventManager() { abstract var state: ProtocolState abstract fun connect() abstract fun disconnect() - abstract fun send(payload: Map) + abstract fun send(payload: KuzzleMap) } diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 9820e10c..12eda5c3 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -63,7 +63,7 @@ open class Http : AbstractProtocol { * Search route can also be accessed with GET, * but to provide a query for the search we need to use the POST method */ - if (controller.lowercase() == "document" && action.lowercase() == "search") { + if (controller.toLowerCase() == "document" && action.toLowerCase() == "search") { return httpRoutes.find { it.verb == "POST" } ?: httpRoutes[0] @@ -80,7 +80,7 @@ open class Http : AbstractProtocol { sameLength = false } - if (route.verb.uppercase() == "GET") { + if (route.verb.toUpperCase() == "GET") { selectedRoute = route } } @@ -219,12 +219,6 @@ open class Http : AbstractProtocol { } } this.header("content-type", "application/json") - if (payload["jwt"] != null) { - this.header("Authorization", "Bearer ${payload["jwt"]}") - } - if (payload["volatile"] != null) { - this.header("x-kuzzle-volatile", StringSerializer.serialize(payload["volatile"]!!)) - } this.body = JsonSerializer.serialize(payload) } // trigger messageReceived @@ -268,7 +262,7 @@ open class Http : AbstractProtocol { } } - override fun send(payload: Map) { + override fun send(payload: KuzzleMap) { if (! payload.containsKey("controller") || payload["controller"] !is String) { super.trigger(RequestErrorEvent(MissingControllerException(), payload["requestId"] as String?)) return @@ -279,6 +273,22 @@ open class Http : AbstractProtocol { return } + if (payload["headers"] == null) { + payload["headers"] = KuzzleMap() + } + + var headers = payload.getMap("headers")!! + + if (payload["jwt"] != null) { + headers.put("Authorization", "Bearer ${payload["jwt"]}") + } + if (payload["volatile"] != null) { + headers.put("x-kuzzle-volatile", StringSerializer.serialize(payload["volatile"]!!)) + } + if (payload["requestId"] != null) { + headers.put("x-kuzzle-request-id", payload["requestId"].toString()) + } + /** * If no route has been built we try to send the request to Kuzzle using * the /_query endpoint diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/WebSocket.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/WebSocket.kt index b81cfa76..3b543fc9 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/WebSocket.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/WebSocket.kt @@ -13,6 +13,7 @@ import io.ktor.http.cio.websocket.close import io.ktor.http.cio.websocket.readBytes import io.ktor.http.cio.websocket.readText import io.kuzzle.sdk.coreClasses.json.JsonSerializer +import io.kuzzle.sdk.coreClasses.maps.KuzzleMap import io.kuzzle.sdk.events.MessageReceivedEvent import io.kuzzle.sdk.events.NetworkStateChangeEvent import kotlinx.coroutines.GlobalScope @@ -221,7 +222,7 @@ open class WebSocket : AbstractProtocol { } } - override fun send(payload: Map) { + override fun send(payload: KuzzleMap) { queue.add(JsonSerializer.serialize(payload)) } } From e3bc10b0249dd2441b7a6f0e4700e68399244d8c Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Mon, 25 Apr 2022 20:03:58 +0200 Subject: [PATCH 27/35] fix tests --- src/test/kotlin/io/kuzzle/sdk/protocolTest/WebSocketTests.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/io/kuzzle/sdk/protocolTest/WebSocketTests.kt b/src/test/kotlin/io/kuzzle/sdk/protocolTest/WebSocketTests.kt index c3475472..ab89e502 100644 --- a/src/test/kotlin/io/kuzzle/sdk/protocolTest/WebSocketTests.kt +++ b/src/test/kotlin/io/kuzzle/sdk/protocolTest/WebSocketTests.kt @@ -8,6 +8,7 @@ import io.ktor.client.features.json.JsonFeature import io.ktor.http.ContentType import io.ktor.http.fullPath import io.ktor.http.headersOf +import io.kuzzle.sdk.coreClasses.maps.KuzzleMap import io.kuzzle.sdk.protocol.ProtocolState import io.kuzzle.sdk.protocol.WebSocket import org.junit.Assert.assertEquals @@ -62,7 +63,7 @@ class WebSocketTests { @Test fun sendTest() { - val query = HashMap().apply { + val query = KuzzleMap().apply { put("controller", "server") put("action", "now") } From ddd5752b192d046773dc2a32d63162985adf2115 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Tue, 26 Apr 2022 09:41:29 +0200 Subject: [PATCH 28/35] apply requested changes --- .../exceptions/MissingActionException.kt | 4 +- .../exceptions/MissingControllerException.kt | 4 +- .../exceptions/MissingURLParamException.kt | 4 +- .../exceptions/URLNotFoundException.kt | 4 +- .../io/kuzzle/sdk/coreClasses/http/Route.kt | 5 --- .../kotlin/io/kuzzle/sdk/protocol/Http.kt | 38 +++++++------------ 6 files changed, 17 insertions(+), 42 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingActionException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingActionException.kt index 26b1704c..7f1d734b 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingActionException.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingActionException.kt @@ -1,5 +1,3 @@ package io.kuzzle.sdk.coreClasses.exceptions -open class MissingActionException : KuzzleException { - constructor() : super(KuzzleExceptionCode.MISSING_ACTION) -} +open class MissingActionException : KuzzleException(KuzzleExceptionCode.MISSING_ACTION) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt index 994bc917..98417de5 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt @@ -1,5 +1,3 @@ package io.kuzzle.sdk.coreClasses.exceptions -open class MissingControllerException : KuzzleException { - constructor() : super(KuzzleExceptionCode.MISSING_CONTROLLER) -} +open class MissingControllerException : KuzzleException(KuzzleExceptionCode.MISSING_CONTROLLER) \ No newline at end of file diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt index ae8a5171..d8a2b789 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt @@ -3,6 +3,4 @@ package io.kuzzle.sdk.coreClasses.exceptions /** * Thrown when attempting to interact with the network while not connected. */ -class MissingURLParamException : KuzzleException { - constructor(templateParam: String, baseURL: String) : super("Missing URL Param $templateParam in $baseURL", 0) -} +class MissingURLParamException(templateParam: String, baseURL: String) : KuzzleException("Missing URL Param $templateParam in $baseURL", 0) \ No newline at end of file diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt index 23c20ad7..8fa127ec 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt @@ -1,5 +1,3 @@ package io.kuzzle.sdk.coreClasses.exceptions -class URLNotFoundException : KuzzleException { - constructor(controller: String, action: String) : super("No URL found for \"$controller:$action\".", 400) -} +class URLNotFoundException(controller: String, action: String) : KuzzleException("No URL found for \"$controller:$action\".", 400) \ No newline at end of file diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 2fe2a3b9..e762e520 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -26,13 +26,8 @@ val PayloadProperties = setOf( "volatile", "jwt", "requestId", -<<<<<<< HEAD - "body", - "headers", -======= "volatile", "body" ->>>>>>> e3bc10b0249dd2441b7a6f0e4700e68399244d8c ) class Route { diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 68920f9e..5c5d659b 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -29,10 +29,10 @@ open class Http : AbstractProtocol { port: Int = 7512, isSsl: Boolean = false ) { - if (!isSsl) { - this.uri = "http://$host:$port" - } else { + if (isSsl) { this.uri = "https://$host:$port" + } else { + this.uri = "http://$host:$port" } super.addListener(::onLoginAttempt) } @@ -127,7 +127,7 @@ open class Http : AbstractProtocol { val wait = CompletableFuture() GlobalScope.launch { - var client = HttpClient() { + val client = HttpClient() { expectSuccess = false } try { @@ -164,11 +164,11 @@ open class Http : AbstractProtocol { val wait = CompletableFuture() // if state is NOT open, request /_query to see if we have the proper rights to make request using _query GlobalScope.launch { - var client = HttpClient() { + val client = HttpClient() { expectSuccess = false } try { - var response: HttpResponse = client.post("$uri/_query") { + val response: HttpResponse = client.post("$uri/_query") { this.header("content-type", "application/json") this.body = "{}" } @@ -205,11 +205,11 @@ open class Http : AbstractProtocol { */ private fun query(payload: Map) { GlobalScope.launch { // Launch HTTP Request inside a coroutine to be non-blocking - var client = HttpClient() { + val client = HttpClient() { expectSuccess = false } try { - var response: HttpResponse = client.post("$uri/_query") { + val response: HttpResponse = client.post("$uri/_query") { if (payload["headers"] != null && payload["headers"] is Map<*, *>) { for (entry in payload["headers"] as Map) { if (entry.key == null || entry.value == null) { @@ -219,18 +219,6 @@ open class Http : AbstractProtocol { } } this.header("content-type", "application/json") -<<<<<<< HEAD - if (payload["jwt"] != null) { - this.header("authorization", "Bearer ${payload["jwt"]}") - } - if (payload["volatile"] != null) { - this.header("x-kuzzle-volatile", StringSerializer.serialize(payload["volatile"]!!)) - } - if (payload["requestId"] != null) { - this.header("x-kuzzle-request-id", payload["requestId"].toString()) - } -======= ->>>>>>> e3bc10b0249dd2441b7a6f0e4700e68399244d8c this.body = JsonSerializer.serialize(payload) } // trigger messageReceived @@ -248,11 +236,11 @@ open class Http : AbstractProtocol { */ private fun queryHTTPEndpoint(payload: Map, requestInfo: io.kuzzle.sdk.coreClasses.http.HttpRequest) { GlobalScope.launch { // Launch HTTP Request inside a coroutine to be non-blocking - var client = HttpClient() { + val client = HttpClient() { expectSuccess = false } try { - var response: HttpResponse = client.request("$uri${requestInfo.url}") { + val response: HttpResponse = client.request("$uri${requestInfo.url}") { this.method = HttpMethod.parse(requestInfo.verb) for (entry in requestInfo.headers) { if (entry.key == null || entry.value == null) { @@ -292,13 +280,13 @@ open class Http : AbstractProtocol { var headers = payload.getMap("headers")!! if (payload["jwt"] != null) { - headers.put("Authorization", "Bearer ${payload["jwt"]}") + headers["Authorization"] = "Bearer ${payload["jwt"]}" } if (payload["volatile"] != null) { - headers.put("x-kuzzle-volatile", StringSerializer.serialize(payload["volatile"]!!)) + headers["x-kuzzle-volatile"] = StringSerializer.serialize(payload["volatile"]!!) } if (payload["requestId"] != null) { - headers.put("x-kuzzle-request-id", payload["requestId"].toString()) + headers["x-kuzzle-request-id"] = payload["requestId"].toString() } /** From 3a5611b6498fe90d8107e283af33809a089ef01c Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Tue, 26 Apr 2022 11:42:16 +0200 Subject: [PATCH 29/35] lint --- .../sdk/coreClasses/exceptions/MissingControllerException.kt | 2 +- .../sdk/coreClasses/exceptions/MissingURLParamException.kt | 2 +- .../kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt index 98417de5..ee7e6eae 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingControllerException.kt @@ -1,3 +1,3 @@ package io.kuzzle.sdk.coreClasses.exceptions -open class MissingControllerException : KuzzleException(KuzzleExceptionCode.MISSING_CONTROLLER) \ No newline at end of file +open class MissingControllerException : KuzzleException(KuzzleExceptionCode.MISSING_CONTROLLER) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt index d8a2b789..c2e94e49 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/MissingURLParamException.kt @@ -3,4 +3,4 @@ package io.kuzzle.sdk.coreClasses.exceptions /** * Thrown when attempting to interact with the network while not connected. */ -class MissingURLParamException(templateParam: String, baseURL: String) : KuzzleException("Missing URL Param $templateParam in $baseURL", 0) \ No newline at end of file +class MissingURLParamException(templateParam: String, baseURL: String) : KuzzleException("Missing URL Param $templateParam in $baseURL", 0) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt index 8fa127ec..eea85a30 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/URLNotFoundException.kt @@ -1,3 +1,3 @@ package io.kuzzle.sdk.coreClasses.exceptions -class URLNotFoundException(controller: String, action: String) : KuzzleException("No URL found for \"$controller:$action\".", 400) \ No newline at end of file +class URLNotFoundException(controller: String, action: String) : KuzzleException("No URL found for \"$controller:$action\".", 400) From b840f3dbf0726e2d44004f1632ffc10dcc71ec7f Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Tue, 26 Apr 2022 11:43:11 +0200 Subject: [PATCH 30/35] fix query string --- .../kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index e762e520..a3ea5cee 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -2,6 +2,7 @@ package io.kuzzle.sdk.coreClasses.http import io.kuzzle.sdk.coreClasses.exceptions.MissingURLParamException import io.kuzzle.sdk.coreClasses.maps.KuzzleMap +import io.kuzzle.sdk.coreClasses.serializer.StringSerializer import java.net.URLEncoder enum class PartType { @@ -91,17 +92,16 @@ class Route { /** * Build the query string */ - val queryString: String = queryArgs.keys - .filter { - it != null && ! queryArgs.isArrayList(it) && ! queryArgs.isMap(it) + val queryString: String = queryArgs.filter { + it.key != null && it.value != null } - .joinToString("&") { + .keys.joinToString("&") { val encodedKey = URLEncoder.encode(it, "utf-8") if (queryArgs.optBoolean(it!!, false) == true) { encodedKey } else { - val value = queryArgs[it].toString() + val value = StringSerializer.serialize(queryArgs[it!!].toString()) "$encodedKey=${URLEncoder.encode(value, "utf-8")}" } } From 569b9f15f8f8d4827cdee48f072cc5e67bc16178 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Tue, 26 Apr 2022 13:08:10 +0200 Subject: [PATCH 31/35] add better error when failed to parse JSON response --- src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt | 21 +++++++++++++++---- .../sdk/coreClasses/exceptions/InvalidJSON.kt | 3 +++ .../io/kuzzle/sdk/coreClasses/http/Route.kt | 2 +- .../kotlin/io/kuzzle/sdk/protocol/Http.kt | 6 +++++- 4 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/InvalidJSON.kt diff --git a/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt b/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt index dcfa59c1..6b0524d0 100644 --- a/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt +++ b/src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt @@ -9,6 +9,7 @@ import io.kuzzle.sdk.controllers.RealtimeController import io.kuzzle.sdk.controllers.ServerController import io.kuzzle.sdk.coreClasses.RequestPayload import io.kuzzle.sdk.coreClasses.exceptions.ApiErrorException +import io.kuzzle.sdk.coreClasses.exceptions.InvalidJSON import io.kuzzle.sdk.coreClasses.exceptions.KuzzleExceptionCode import io.kuzzle.sdk.coreClasses.exceptions.NotConnectedException import io.kuzzle.sdk.coreClasses.json.JsonSerializer @@ -50,9 +51,9 @@ open class Kuzzle { collectionController = CollectionController(this) bulkController = BulkController(this) // @TODO Create enums for events - protocol.addListener(::onMessageReceived) - protocol.addListener(::onNetworkStateChange) - protocol.addListener(::onRequestError) + protocol.addListener(::onMessageReceived) + protocol.addListener(::onNetworkStateChange) + protocol.addListener(::onRequestError) } private fun onRequestError(event: RequestErrorEvent) { @@ -64,8 +65,20 @@ open class Kuzzle { private fun onMessageReceived(event: MessageReceivedEvent) { val message = event.message + val jsonObject: Map + try { + jsonObject = JsonSerializer.deserialize(message) as Map + } catch (e: Exception) { + if (event.requestId != null) { + queries[event.requestId]?.completeExceptionally(InvalidJSON(event.message ?: "null")) + queries.remove(event.requestId) + } else { + protocol.trigger(UnhandledResponseEvent(message)) + } + return + } val response = Response().apply { - fromMap(JsonSerializer.deserialize(message) as Map) + fromMap(jsonObject) } val requestId = event.requestId ?: response.room ?: response.requestId diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/InvalidJSON.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/InvalidJSON.kt new file mode 100644 index 00000000..962515d8 --- /dev/null +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/exceptions/InvalidJSON.kt @@ -0,0 +1,3 @@ +package io.kuzzle.sdk.coreClasses.exceptions + +class InvalidJSON(json: String) : KuzzleException("Invalid JSON \"$json\".", 0) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index a3ea5cee..cc0331a2 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -101,7 +101,7 @@ class Route { if (queryArgs.optBoolean(it!!, false) == true) { encodedKey } else { - val value = StringSerializer.serialize(queryArgs[it!!].toString()) + val value = StringSerializer.serialize(queryArgs[it]!!) "$encodedKey=${URLEncoder.encode(value, "utf-8")}" } } diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 5c5d659b..2c8f5927 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -34,7 +34,7 @@ open class Http : AbstractProtocol { } else { this.uri = "http://$host:$port" } - super.addListener(::onLoginAttempt) + super.addListener(::onLoginAttempt) } private fun onLoginAttempt(event: LoginAttemptEvent) { @@ -235,6 +235,10 @@ open class Http : AbstractProtocol { * Make a request to Kuzzle using the appropriate HTTP Endpoint for a given controller's action */ private fun queryHTTPEndpoint(payload: Map, requestInfo: io.kuzzle.sdk.coreClasses.http.HttpRequest) { + println(requestInfo.verb) + println(requestInfo.url) + println(requestInfo.body) + println(requestInfo.headers) GlobalScope.launch { // Launch HTTP Request inside a coroutine to be non-blocking val client = HttpClient() { expectSuccess = false From 7d4c70c9a665c3614534e24e52eeafa01e48d42d Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Tue, 26 Apr 2022 13:11:44 +0200 Subject: [PATCH 32/35] remove debug print --- src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt index 2c8f5927..e4156b7f 100644 --- a/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt +++ b/src/main/kotlin/io/kuzzle/sdk/protocol/Http.kt @@ -235,10 +235,6 @@ open class Http : AbstractProtocol { * Make a request to Kuzzle using the appropriate HTTP Endpoint for a given controller's action */ private fun queryHTTPEndpoint(payload: Map, requestInfo: io.kuzzle.sdk.coreClasses.http.HttpRequest) { - println(requestInfo.verb) - println(requestInfo.url) - println(requestInfo.body) - println(requestInfo.headers) GlobalScope.launch { // Launch HTTP Request inside a coroutine to be non-blocking val client = HttpClient() { expectSuccess = false From 84c3e5366750e16a3147598f8df020006ff67556 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Tue, 26 Apr 2022 13:13:12 +0200 Subject: [PATCH 33/35] lint --- src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index cc0331a2..114a3071 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -93,9 +93,8 @@ class Route { * Build the query string */ val queryString: String = queryArgs.filter { - it.key != null && it.value != null - } - .keys.joinToString("&") { + it.key != null && it.value != null + }.keys.joinToString("&") { val encodedKey = URLEncoder.encode(it, "utf-8") if (queryArgs.optBoolean(it!!, false) == true) { From 109d78baf72e640188c06408d729bf99f03d1efe Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Tue, 26 Apr 2022 13:31:14 +0200 Subject: [PATCH 34/35] lint --- .../kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt index 114a3071..404f203d 100644 --- a/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt +++ b/src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt @@ -95,15 +95,15 @@ class Route { val queryString: String = queryArgs.filter { it.key != null && it.value != null }.keys.joinToString("&") { - val encodedKey = URLEncoder.encode(it, "utf-8") + val encodedKey = URLEncoder.encode(it, "utf-8") - if (queryArgs.optBoolean(it!!, false) == true) { - encodedKey - } else { - val value = StringSerializer.serialize(queryArgs[it]!!) - "$encodedKey=${URLEncoder.encode(value, "utf-8")}" - } + if (queryArgs.optBoolean(it!!, false) == true) { + encodedKey + } else { + val value = StringSerializer.serialize(queryArgs[it]!!) + "$encodedKey=${URLEncoder.encode(value, "utf-8")}" } + } /** * If the partType is STATIC it means that there is no template in the url From aeedba39123768b505310fb9bf334fab18aa8255 Mon Sep 17 00:00:00 2001 From: Shiranuit Date: Wed, 27 Apr 2022 15:20:11 +0200 Subject: [PATCH 35/35] update compatibility matrix --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index db15c996..5e8b3b5b 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,11 @@ You can then find the jars file in build/libs/ | **< 1.2.4** | **1.5.2** | **6.1** | **JDK-8** | | **>= 1.2.4** | **1.6.8** | **7.4** | **JDK-11** | +| SDK Version | Kuzzle Version | +| :----------: | :------------: | +| **< 1.2.4** | **2.x.x** | +| **>= 1.2.4** | **Websocket: 2.x.x**
**HTTP: >=2.18.1** | + ### Maven ```xml